diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e25acf..6006c99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,19 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools) set(TS_FILES Notepad--_en_GB.ts) set(PROJECT_SOURCES - main.cpp - mainwindow.cpp - mainwindow.h - mainwindow.ui - ${TS_FILES} + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui + document.cpp + document.h + languages/cppsyntaxhighlighter.cpp + languages/cppsyntaxhighlighter.h + languages/pythonsyntaxhighlighter.cpp + languages/pythonsyntaxhighlighter.h + languages/languagemanager.cpp + languages/languagemanager.h + ${TS_FILES} ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) @@ -44,9 +52,9 @@ else() else() add_executable(Notepad-- ${PROJECT_SOURCES} - codeeditor.h codeeditor.cpp - document.h document.cpp - cppsyntaxhighlighter.h cppsyntaxhighlighter.cpp + codeeditor.h + codeeditor.cpp + languages/pythonsyntaxhighlighter.h languages/pythonsyntaxhighlighter.cpp ) endif() diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user index 75db15f..14bb7a7 100644 --- a/CMakeLists.txt.user +++ b/CMakeLists.txt.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -103,13 +103,13 @@ false -DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} -DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} -DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} -DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} 0 /data/Code/Qt/Notepad--/build/Desktop-Debug @@ -160,13 +160,13 @@ false -DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} -DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} -DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} -DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} /data/Code/Qt/Notepad--/build/Desktop-Release @@ -216,13 +216,13 @@ false -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} -DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} -DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} -DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} /data/Code/Qt/Notepad--/build/Desktop-RelWithDebInfo @@ -270,13 +270,13 @@ false -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} -DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} -DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} -DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} 0 /data/Code/Qt/Notepad--/build/Desktop-Profile @@ -325,13 +325,13 @@ false -DCMAKE_BUILD_TYPE:STRING=MinSizeRel +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} -DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} -DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} -DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} /data/Code/Qt/Notepad--/build/Desktop-MinSizeRel diff --git a/CodeEditor.cpp b/CodeEditor.cpp deleted file mode 100644 index 37ac40d..0000000 --- a/CodeEditor.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "CodeEditor.h" -#include -#include - -CodeEditor::CodeEditor(QWidget *parent) - : QPlainTextEdit(parent), lineNumberArea(new LineNumberArea(this)) -{ - // Set text color to black - QPalette p = this->palette(); - p.setColor(QPalette::Text, Qt::black); - this->setPalette(p); - - // Set background color to white - this->setStyleSheet("QPlainTextEdit { background-color: white; }"); - - // Set a monospaced font - QFont font; - font.setFamily("Sans Serif"); // Monospaced font - font.setFixedPitch(true); - font.setPointSize(12); // Adjust size if necessary - this->setFont(font); - - connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth); - connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea); - connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine); - - updateLineNumberAreaWidth(0); - highlightCurrentLine(); -} - -int CodeEditor::lineNumberAreaWidth() -{ - int digits = 1; - int max = qMax(1, blockCount()); - while (max >= 10) { - max /= 10; - ++digits; - } - - // Ensure there's enough space for numbers - int space = 5 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; - - return space; -} - -void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */) -{ - setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); -} - -void CodeEditor::updateLineNumberArea(const QRect &rect, int dy) -{ - if (dy) - lineNumberArea->scroll(0, dy); - else - lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); - - if (rect.contains(viewport()->rect())) - updateLineNumberAreaWidth(0); -} - -void CodeEditor::resizeEvent(QResizeEvent *e) -{ - QPlainTextEdit::resizeEvent(e); - - QRect cr = contentsRect(); - lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); -} - -void CodeEditor::highlightCurrentLine() -{ - QList extraSelections; - - if (!isReadOnly()) { - QTextEdit::ExtraSelection selection; - - QColor lineColor = QColor(Qt::yellow).lighter(160); - - selection.format.setBackground(lineColor); - selection.format.setProperty(QTextFormat::FullWidthSelection, true); - selection.cursor = textCursor(); - selection.cursor.clearSelection(); - extraSelections.append(selection); - } - - setExtraSelections(extraSelections); -} - -void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) -{ - QPainter painter(lineNumberArea); - painter.fillRect(event->rect(), Qt::lightGray); - - QTextBlock block = firstVisibleBlock(); - int blockNumber = block.blockNumber(); - int top = static_cast(blockBoundingGeometry(block).translated(contentOffset()).top()); - int bottom = top + static_cast(blockBoundingRect(block).height()); - - // Get the current line number - int currentLineNumber = textCursor().blockNumber(); - - // Loop through each block and draw line numbers - while (block.isValid() && top <= event->rect().bottom()) { - if (block.isVisible() && bottom >= event->rect().top()) { - QString number = QString::number(blockNumber + 1); - - // Check if this is the current line - if (blockNumber == currentLineNumber) { - // Set bold font for the current line number - QFont boldFont = painter.font(); - boldFont.setBold(true); - painter.setFont(boldFont); - painter.setPen(Qt::blue); // Optional: change color for current line number - } else { - // Use normal font for other line numbers - painter.setFont(QFont()); - painter.setPen(Qt::black); - } - - // Draw the line number - painter.drawText(-5, top, lineNumberArea->width(), fontMetrics().height(), - Qt::AlignRight | Qt::AlignVCenter, number); - - // Check if the block has wrapped lines - QTextLayout *layout = block.layout(); - for (int i = 0; i < layout->lineCount(); ++i) { - QTextLine line = layout->lineAt(i); - int lineTop = top + static_cast(line.y()); - int lineBottom = lineTop + static_cast(line.height()); - - // If the line is within the visible area and it's not the first line, draw a return symbol - if (i > 0 && lineTop >= event->rect().top() && lineBottom <= event->rect().bottom()) { - painter.setPen(Qt::black); // Set color to black for the return symbol - painter.drawText(-5, lineTop, lineNumberArea->width(), fontMetrics().height(), - Qt::AlignRight | Qt::AlignVCenter, "↩"); - } - } - } - - block = block.next(); - top = bottom; - bottom = top + static_cast(blockBoundingRect(block).height()); - ++blockNumber; - } -} diff --git a/CodeEditor.h b/CodeEditor.h deleted file mode 100644 index c1735d6..0000000 --- a/CodeEditor.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef CODEEDITOR_H -#define CODEEDITOR_H - -#include - -class QPaintEvent; -class QResizeEvent; -class QSize; -class QWidget; - -class LineNumberArea; - -class CodeEditor : public QPlainTextEdit -{ - Q_OBJECT - -public: - CodeEditor(QWidget *parent = nullptr); - - void lineNumberAreaPaintEvent(QPaintEvent *event); - int lineNumberAreaWidth(); - -protected: - void resizeEvent(QResizeEvent *event) override; - -private slots: - void updateLineNumberAreaWidth(int newBlockCount); - void highlightCurrentLine(); - void updateLineNumberArea(const QRect &rect, int dy); - -private: - QWidget *lineNumberArea; -}; - -class LineNumberArea : public QWidget -{ -public: - LineNumberArea(CodeEditor *editor) : QWidget(editor), codeEditor(editor) {} - - QSize sizeHint() const override - { - return QSize(codeEditor->lineNumberAreaWidth(), 0); - } - -protected: - void paintEvent(QPaintEvent *event) override - { - codeEditor->lineNumberAreaPaintEvent(event); - } - -private: - CodeEditor *codeEditor; -}; - -#endif // CODEEDITOR_H diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0852004 --- /dev/null +++ b/LICENSE @@ -0,0 +1,174 @@ + GNU LESSER GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + You may use, distribute and copy the Qt Toolkit under the terms of + GNU Lesser General Public License version 3, which is displayed below. + This license makes reference to the version 3 of the GNU General + Public License, which you can find in the LICENSE file. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright © 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies of this +licensedocument, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + + As used herein, “this License” refers to version 3 of the GNU Lesser +General Public License, and the “GNU GPL” refers to version 3 of the +GNU General Public License. + + “The Library” refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An “Application” is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A “Combined Work” is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the “Linked +Version”. + + The “Minimal Corresponding Source” for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The “Corresponding Application Code” for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this + license document. + +4. Combined Works. + + You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. + + e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +5. Combined Libraries. + + You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of + it is a work based on the Library, and explaining where to find + the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License “or any later version” applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, +you may choose any version of the GNU Lesser General Public License +ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the Library. diff --git a/README.md b/README.md index f156221..85d252b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Notepad-- +![gui](assets/lgplv3.png) + A simple light text editor. Notepadqq was my choice editor. Unfortunately it doesn't maintain anymore. I tried to help but there was old PRs and plenty of unanswered issues. The project was a mix of C++/Qt/Javascript/HTML,CSS,Python... It is a while I didn't code in C++ and my knowledge of Qt is a little. All that said this is a **toy** project, at least for a while. The -- (minus minus) indicates that this is a minimalistic project. I will design GUI just like Notepadqq so there is no new GUI learning. For now I just release for Linux which has Qt6 installed (dynamic compile). diff --git a/assets/lgplv3.png b/assets/lgplv3.png new file mode 100644 index 0000000..012f011 Binary files /dev/null and b/assets/lgplv3.png differ diff --git a/cppsyntaxhighlighter.cpp b/cppsyntaxhighlighter.cpp deleted file mode 100644 index ee63123..0000000 --- a/cppsyntaxhighlighter.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "cppsyntaxhighlighter.h" - -CppSyntaxHighlighter::CppSyntaxHighlighter(QTextDocument *parent) - : QSyntaxHighlighter(parent) { - - // Keywords - QStringList keywordPatterns = { - "\\bchar\\b", "\\bclass\\b", "\\bconst\\b", "\\bdouble\\b", "\\benum\\b", - "\\bexplicit\\b", "\\bfriend\\b", "\\binline\\b", "\\bint\\b", "\\blong\\b", - "\\bnamespace\\b", "\\boperator\\b", "\\bprivate\\b", "\\bprotected\\b", "\\bpublic\\b", - "\\bshort\\b", "\\bsignals\\b", "\\bsigned\\b", "\\bslots\\b", "\\bstatic\\b", - "\\bstruct\\b", "\\btemplate\\b", "\\btypedef\\b", "\\btypename\\b", "\\bunion\\b", - "\\bunsigned\\b", "\\bvirtual\\b", "\\bvoid\\b", "\\bvolatile\\b" - }; - - keywordFormat.setForeground(Qt::blue); - for (const QString &pattern : keywordPatterns) { - HighlightingRule rule; - rule.pattern = QRegularExpression(pattern); - rule.format = keywordFormat; - highlightingRules.append(rule); - } - - // Class names - classFormat.setFontWeight(QFont::Bold); - classFormat.setForeground(Qt::darkMagenta); - HighlightingRule classRule; - classRule.pattern = QRegularExpression("\\bQ[A-Za-z]+\\b"); - classRule.format = classFormat; - highlightingRules.append(classRule); - - // Single line comments - singleLineCommentFormat.setForeground(Qt::darkGreen); - HighlightingRule singleLineCommentRule; - singleLineCommentRule.pattern = QRegularExpression("//[^\n]*"); - singleLineCommentRule.format = singleLineCommentFormat; - highlightingRules.append(singleLineCommentRule); - - // Multi-line comments - multiLineCommentFormat.setForeground(Qt::darkGreen); - commentStartExpression = QRegularExpression("/\\*"); - commentEndExpression = QRegularExpression("\\*/"); - - // Quotation marks - quotationFormat.setForeground(Qt::darkRed); - HighlightingRule quotationRule; - quotationRule.pattern = QRegularExpression("\".*\""); - quotationRule.format = quotationFormat; - highlightingRules.append(quotationRule); - - // Function names - functionFormat.setFontItalic(true); - functionFormat.setForeground(Qt::blue); - HighlightingRule functionRule; - functionRule.pattern = QRegularExpression("\\b[A-Za-z0-9_]+(?=\\()"); - functionRule.format = functionFormat; - highlightingRules.append(functionRule); -} - -void CppSyntaxHighlighter::highlightBlock(const QString &text) { - for (const HighlightingRule &rule : qAsConst(highlightingRules)) { - QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); - while (matchIterator.hasNext()) { - QRegularExpressionMatch match = matchIterator.next(); - setFormat(match.capturedStart(), match.capturedLength(), rule.format); - } - } - - setCurrentBlockState(0); - - int startIndex = 0; - if (previousBlockState() != 1) - startIndex = text.indexOf(commentStartExpression); - - while (startIndex >= 0) { - QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); - int endIndex = match.capturedStart(); - int commentLength = 0; - if (endIndex == -1) { - setCurrentBlockState(1); - commentLength = text.length() - startIndex; - } else { - commentLength = endIndex - startIndex + match.capturedLength(); - } - setFormat(startIndex, commentLength, multiLineCommentFormat); - startIndex = text.indexOf(commentStartExpression, startIndex + commentLength); - } -} diff --git a/document.cpp b/document.cpp index 6185e6e..c89c10c 100644 --- a/document.cpp +++ b/document.cpp @@ -1,18 +1,14 @@ #include "document.h" #include "codeeditor.h" -#include "cppsyntaxhighlighter.h" -#include -#include +#include "languages/languagemanager.h" +#include +#include #include #include -#include -#include +#include #include +#include #include -#include -#include -#include -#include Document::Document(const QString &filePath, QWidget *parent) : QWidget(parent), m_filePath(filePath), m_fileSize(0), m_startPointer(0), m_endPointer(0), syntaxHighlighter(nullptr) { @@ -34,14 +30,40 @@ Document::Document(const QString &filePath, QWidget *parent) m_currentText.clear(); } +void Document::setFilePath(const QString &path) { + m_filePath = path; + m_fileExtension = QFileInfo(path).suffix(); + qDebug() << "File opened with extension:" << m_fileExtension; + QString language = LanguageManager::getLanguageFromExtension(m_fileExtension); + qDebug() << "Detected language:" << language; + applySyntaxHighlighter(language); +} + QString Document::filePath() const { return m_filePath; } -void Document::setFilePath(const QString &path) { - m_filePath = path; - m_fileExtension = QFileInfo(path).suffix(); - applySyntaxHighlighter(); +QString Document::getLanguage() const { + return m_language; +} + +void Document::applySyntaxHighlighter(const QString &language) { + if (syntaxHighlighter) { + delete syntaxHighlighter; + syntaxHighlighter = nullptr; + } + + m_language = language; // Set the current language based on the argument + + // Use LanguageManager to create the appropriate highlighter based on the language directly + syntaxHighlighter = LanguageManager::createHighlighterForExtension(language, editor->document()); + + if (syntaxHighlighter) { + syntaxHighlighter->rehighlight(); // Rehighlight the current text in the editor + qDebug() << "Applied syntax highlighter for language:" << language; + } else { + qDebug() << "No syntax highlighter found for language:" << language; + } } void Document::openFile(const QString &filePath) { @@ -54,24 +76,24 @@ void Document::openFile(const QString &filePath) { setFilePath(filePath); m_fileSize = m_file.size(); - m_startPointer = 0; - m_endPointer = qMin(m_fileSize, m_startPointer + 1024 * 1024); // Load first 1MB - loadContent(); + // Load the entire file content for simplicity + QByteArray content = m_file.readAll(); + editor->setPlainText(QString::fromUtf8(content)); + editor->moveCursor(QTextCursor::Start); // Calculate the original hash of the file asynchronously QtConcurrent::run([this]() { m_originalHash = calculateMD5Stream(&m_file); + qDebug() << "Original MD5 calculated:" << m_originalHash.toHex(); }); -} -void Document::loadContent() { - if (!m_file.isOpen()) return; + m_file.close(); +} - m_file.seek(m_startPointer); - QByteArray content = m_file.read(m_endPointer - m_startPointer); - editor->setPlainText(QString::fromUtf8(content)); - editor->moveCursor(QTextCursor::Start); +void Document::trackChanges() { + // Basic change tracking (full document, for simplicity) + m_currentText = editor->toPlainText(); } void Document::updatePointers() { @@ -94,9 +116,33 @@ void Document::updatePointers() { } } -void Document::trackChanges() { - m_currentText = editor->toPlainText(); - m_changedSegments[m_startPointer] = m_currentText; +void Document::loadContent() { + if (!m_file.isOpen()) { + qDebug() << "File is not open, cannot load content."; + return; + } + + // Calculate the size of the portion to read + qint64 bytesToRead = m_endPointer - m_startPointer; + + // Ensure the reading is within bounds + if (m_startPointer < 0 || m_startPointer >= m_fileSize || bytesToRead <= 0) { + qDebug() << "Invalid pointers or no data to read."; + return; + } + + m_file.seek(m_startPointer); // Seek to the start pointer + + QByteArray content = m_file.read(bytesToRead); + if (content.isEmpty()) { + qDebug() << "No content read from the file."; + return; + } + + editor->setPlainText(QString::fromUtf8(content)); // Set content to the editor + editor->moveCursor(QTextCursor::Start); // Move cursor to the start of the document + + qDebug() << "File content loaded successfully. Start pointer:" << m_startPointer << ", End pointer:" << m_endPointer; } void Document::saveFile() { @@ -111,46 +157,55 @@ void Document::saveFile() { return; } - // Use QTextStream for writing QTextStream out(&file); - out << editor->toPlainText(); // Write the entire content of the editor to the file - - // After saving, calculate the new hash asynchronously - QtConcurrent::run([this]() { - m_originalHash = calculateMD5Stream(&m_file); - }); + out << editor->toPlainText(); file.close(); + + // Recalculate and store the MD5 hash of the saved file + m_originalHash = calculateMD5Stream(&file); + + qDebug() << "File saved. New MD5:" << m_originalHash.toHex(); } void Document::saveFileAs(const QString &newFilePath) { - if (newFilePath.isEmpty()) return; + if (newFilePath.isEmpty()) { + return; // User canceled the Save As dialog, do nothing + } m_filePath = newFilePath; saveFile(); } QByteArray Document::calculateMD5Stream(QFile *file) { QCryptographicHash hash(QCryptographicHash::Md5); - file->seek(0); + if (!file->isOpen()) { + if (!file->open(QIODevice::ReadOnly)) { + qDebug() << "Failed to open file for MD5 calculation:" << file->errorString(); + return QByteArray(); + } + } - QByteArray buffer; + file->seek(0); // Ensure we start from the beginning of the file + + // Read the entire file content and update the hash while (!file->atEnd()) { - buffer = file->read(1024 * 1024); // Read in chunks + QByteArray buffer = file->read(1024 * 1024); // Read in chunks (1MB here) hash.addData(buffer); } - return hash.result(); + file->close(); // Close the file if it was opened here + return hash.result(); // Return the calculated MD5 hash } QByteArray Document::calculateModifiedMD5() { - QString currentContent = editor->toPlainText(); - QCryptographicHash hash(QCryptographicHash::Md5); - hash.addData(currentContent.toUtf8()); - return hash.result(); + QFile file(m_filePath); + return calculateMD5Stream(&file); } bool Document::closeDocument() { QByteArray currentHash = calculateModifiedMD5(); + qDebug() << "Original MD5:" << m_originalHash.toHex(); + qDebug() << "Current MD5:" << currentHash.toHex(); if (currentHash != m_originalHash) { QMessageBox::StandardButton reply; @@ -162,22 +217,53 @@ bool Document::closeDocument() { saveFile(); return true; } else if (reply == QMessageBox::Discard) { - return true; // Allow closing without saving + return true; } else { - return false; // Cancel closing + return false; } } - return true; // No unsaved changes, allow closing + return true; } -void Document::applySyntaxHighlighter() { - if (syntaxHighlighter) { - delete syntaxHighlighter; - syntaxHighlighter = nullptr; +void Document::goToLineNumberInEditor() { + bool ok; + int lineNumber = QInputDialog::getInt(this, tr("Go to Line"), + tr("Line number:"), 1, 1, INT_MAX, 1, &ok); + + if (ok) { + QTextCursor cursor = editor->textCursor(); + cursor.movePosition(QTextCursor::Start); // Move to the start of the editor + + for (int i = 1; i < lineNumber; ++i) { + cursor.movePosition(QTextCursor::Down); + + if (cursor.atEnd()) { + QMessageBox::warning(this, tr("Line Number Out of Bounds"), + tr("The specified line number is outside the bounds of the file.")); + return; + } + } + + editor->setTextCursor(cursor); + editor->ensureCursorVisible(); } +} + +void Document::goToLineNumberInText(QWidget* parent) { + bool ok; + int lineNumber = QInputDialog::getInt(parent, tr("Go to Line"), + tr("Line number:"), 1, 1, INT_MAX, 1, &ok); + if (ok) { + QTextCursor cursor = editor->textCursor(); + cursor.movePosition(QTextCursor::Start); + + int currentLineNumber = 0; + while (currentLineNumber < lineNumber - 1 && !cursor.atEnd()) { + cursor.movePosition(QTextCursor::NextBlock); + ++currentLineNumber; + } - if (m_fileExtension == "cpp" || m_fileExtension == "cxx") { - syntaxHighlighter = new CppSyntaxHighlighter(editor->document()); + editor->setTextCursor(cursor); } } diff --git a/document.h b/document.h index f9a5019..d0601b7 100644 --- a/document.h +++ b/document.h @@ -5,9 +5,10 @@ #include #include #include +#include +#include "languages/languagemanager.h" -class CodeEditor; // Forward declaration -class CppSyntaxHighlighter; // Forward declaration +class CodeEditor; class Document : public QWidget { Q_OBJECT @@ -16,15 +17,18 @@ class Document : public QWidget { Document(const QString &filePath, QWidget *parent = nullptr); QString filePath() const; void setFilePath(const QString &path); + QString getLanguage() const; void openFile(const QString &filePath); void saveFile(); void saveFileAs(const QString &newFilePath); bool closeDocument(); - void applySyntaxHighlighter(); + void goToLineNumberInText(QWidget* parent); + void goToLineNumberInEditor(); + void applySyntaxHighlighter(const QString &language); signals: - void saveError(const QString &error); // Signal for save errors - void saveCompleted(); // Signal for successful save + void saveError(const QString &error); + void saveCompleted(); private: void loadContent(); @@ -37,13 +41,14 @@ class Document : public QWidget { QString m_fileExtension; QFile m_file; CodeEditor *editor; - CppSyntaxHighlighter *syntaxHighlighter; + QSyntaxHighlighter *syntaxHighlighter; qint64 m_fileSize; qint64 m_startPointer; qint64 m_endPointer; QByteArray m_originalHash; - QMap m_changedSegments; // Store changed segments - QString m_currentText; // Store current text in the editor + QMap m_changedSegments; + QString m_currentText; + QString m_language; }; #endif // DOCUMENT_H diff --git a/examples/1GBfile.sh b/examples/1GBfile.sh index 2e5a13f..69bf450 100755 --- a/examples/1GBfile.sh +++ b/examples/1GBfile.sh @@ -1,15 +1,7 @@ #!/usr/bin/env bash -file="1GB.txt" +output_file="1GB.txt" -# Define the size in bytes (1 GB) -size=$((1 * 1024 * 1024 * 1024)) - -# Generate the random file -< /dev/urandom tr -dc 'a-zA-Z0-9 ' | head -c "$size" > "$file" +base64 /dev/urandom | head -c 1073741824 > "$output_file" echo "Done!" - - - - diff --git a/examples/Notepad--1.cpp b/examples/Hello World.cpp similarity index 59% rename from examples/Notepad--1.cpp rename to examples/Hello World.cpp index 7cff662..7867d62 100644 --- a/examples/Notepad--1.cpp +++ b/examples/Hello World.cpp @@ -1,6 +1,6 @@ #include int main() { - std::cout << "Hello World1!"; + std::cout << "Hello World!"; return 0; } diff --git a/examples/Hello World.py b/examples/Hello World.py new file mode 100644 index 0000000..f301245 --- /dev/null +++ b/examples/Hello World.py @@ -0,0 +1 @@ +print("Hello World!") diff --git a/examples/Hello World.txt b/examples/Hello World.txt new file mode 100644 index 0000000..557db03 --- /dev/null +++ b/examples/Hello World.txt @@ -0,0 +1 @@ +Hello World diff --git a/examples/Hello World1.txt b/examples/Hello World1.txt deleted file mode 100644 index d111384..0000000 --- a/examples/Hello World1.txt +++ /dev/null @@ -1 +0,0 @@ -Hello World1 diff --git a/examples/Hello World2.txt b/examples/Hello World2.txt deleted file mode 100644 index b2b6f00..0000000 --- a/examples/Hello World2.txt +++ /dev/null @@ -1 +0,0 @@ -Hello World2 diff --git a/examples/Hello World3.txt b/examples/Hello World3.txt deleted file mode 100644 index f64861f..0000000 --- a/examples/Hello World3.txt +++ /dev/null @@ -1 +0,0 @@ -Hello World3 diff --git a/examples/Notepad--2.cpp b/examples/Notepad--2.cpp deleted file mode 100644 index 3dcafa5..0000000 --- a/examples/Notepad--2.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() { - std::cout << "Hello World2!"; - return 0; -} diff --git a/examples/Notepad--3.cpp b/examples/Notepad--3.cpp deleted file mode 100644 index 8de1be3..0000000 --- a/examples/Notepad--3.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() { - std::cout << "Hello World3!"; - return 0; -} diff --git a/examples/file-generator.sh b/examples/file-generator.sh new file mode 100755 index 0000000..8694bf4 --- /dev/null +++ b/examples/file-generator.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +file="100MB.txt" + +# Define the size in bytes (10 MB) +size=$((100 * 1024 * 1024)) + +# Pre-generate random data and then split it into lines +< /dev/urandom tr -dc 'a-zA-Z0-9 ' | head -c "$size" | awk -v min=40 -v max=400 ' +{ + while (length($0) > 0) { + len = int(min + rand() * (max - min + 1)) + print substr($0, 1, len) + $0 = substr($0, len + 1) + } +} +' > "$file" + +echo "Done! The file $file has been created." diff --git a/languages/cppsyntaxhighlighter.cpp b/languages/cppsyntaxhighlighter.cpp new file mode 100644 index 0000000..9938676 --- /dev/null +++ b/languages/cppsyntaxhighlighter.cpp @@ -0,0 +1,117 @@ +#include "cppsyntaxhighlighter.h" + +CppSyntaxHighlighter::CppSyntaxHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) { + + // Define keyword formats + keywordFormat.setForeground(Qt::blue); + keywordFormat.setFontWeight(QFont::Bold); + + typeFormat.setForeground(Qt::darkMagenta); + typeFormat.setFontWeight(QFont::Bold); + + preprocessorFormat.setForeground(Qt::darkYellow); + preprocessorFormat.setFontWeight(QFont::Bold); + + // List of C++ keywords + QStringList keywordPatterns = { + "\\balignas\\b", "\\balignof\\b", "\\band\\b", "\\band_eq\\b", "\\basm\\b", "\\batomic_cancel\\b", + "\\batomic_commit\\b", "\\batomic_noexcept\\b", "\\bauto\\b", "\\bbreak\\b", "\\bcase\\b", "\\bcatch\\b", + "\\bchar\\b", "\\bchar8_t\\b", "\\bchar16_t\\b", "\\bchar32_t\\b", "\\bclass\\b", "\\bcompl\\b", + "\\bconcept\\b", "\\bconst\\b", "\\bconsteval\\b", "\\bconstexpr\\b", "\\bconstinit\\b", "\\bconst_cast\\b", + "\\bcontinue\\b", "\\bco_await\\b", "\\bco_return\\b", "\\bco_yield\\b", "\\bdecltype\\b", "\\bdefault\\b", + "\\bdelete\\b", "\\bdo\\b", "\\bdouble\\b", "\\bdynamic_cast\\b", "\\belse\\b", "\\benum\\b", "\\bexplicit\\b", + "\\bexport\\b", "\\bextern\\b", "\\bfalse\\b", "\\bfloat\\b", "\\bfor\\b", "\\bfriend\\b", "\\bgoto\\b", + "\\bif\\b", "\\binline\\b", "\\bint\\b", "\\blong\\b", "\\bmutable\\b", "\\bnamespace\\b", "\\bnew\\b", + "\\bnoexcept\\b", "\\bnot\\b", "\\bnot_eq\\b", "\\bnullptr\\b", "\\boperator\\b", "\\bor\\b", "\\bor_eq\\b", + "\\bprivate\\b", "\\bprotected\\b", "\\bpublic\\b", "\\bregister\\b", "\\breinterpret_cast\\b", "\\brequires\\b", + "\\breturn\\b", "\\bshort\\b", "\\bsigned\\b", "\\bsizeof\\b", "\\bstatic\\b", "\\bstatic_assert\\b", + "\\bstatic_cast\\b", "\\bstruct\\b", "\\bswitch\\b", "\\bsynchronized\\b", "\\btemplate\\b", "\\bthis\\b", + "\\bthread_local\\b", "\\bthrow\\b", "\\btrue\\b", "\\btry\\b", "\\btypedef\\b", "\\btypeid\\b", + "\\btypename\\b", "\\bunion\\b", "\\bunsigned\\b", "\\busing\\b", "\\bvirtual\\b", "\\bvoid\\b", "\\bvolatile\\b", + "\\bwchar_t\\b", "\\bwhile\\b", "\\bxor\\b", "\\bxor_eq\\b" + }; + + for (const QString &pattern : keywordPatterns) { + HighlightingRule rule; + rule.pattern = QRegularExpression(pattern); + rule.format = keywordFormat; + highlightingRules.append(rule); + } + + // List of C++ types + QStringList typePatterns = { + "\\bbool\\b", "\\bchar\\b", "\\bchar16_t\\b", "\\bchar32_t\\b", "\\bdouble\\b", "\\bfloat\\b", + "\\bint\\b", "\\blong\\b", "\\bshort\\b", "\\bsigned\\b", "\\bunsigned\\b", "\\bvoid\\b", "\\bwchar_t\\b" + }; + + for (const QString &pattern : typePatterns) { + HighlightingRule rule; + rule.pattern = QRegularExpression(pattern); + rule.format = typeFormat; + highlightingRules.append(rule); + } + + // Preprocessor directives + HighlightingRule preprocessorRule; + preprocessorRule.pattern = QRegularExpression("^#\\s*[a-zA-Z_]+"); + preprocessorRule.format = preprocessorFormat; + highlightingRules.append(preprocessorRule); + + // Single line comments + singleLineCommentFormat.setForeground(Qt::darkGreen); + HighlightingRule singleLineCommentRule; + singleLineCommentRule.pattern = QRegularExpression("//[^\n]*"); + singleLineCommentRule.format = singleLineCommentFormat; + highlightingRules.append(singleLineCommentRule); + + // Multi-line comments + multiLineCommentFormat.setForeground(Qt::darkGreen); + commentStartExpression = QRegularExpression("/\\*"); + commentEndExpression = QRegularExpression("\\*/"); + + // Quotation marks + quotationFormat.setForeground(Qt::darkRed); + HighlightingRule quotationRule; + quotationRule.pattern = QRegularExpression("\".*\""); + quotationRule.format = quotationFormat; + highlightingRules.append(quotationRule); + + // Function names + functionFormat.setFontItalic(true); + functionFormat.setForeground(Qt::blue); + HighlightingRule functionRule; + functionRule.pattern = QRegularExpression("\\b[A-Za-z_][A-Za-z0-9_]*(?=\\()"); + functionRule.format = functionFormat; + highlightingRules.append(functionRule); +} + +void CppSyntaxHighlighter::highlightBlock(const QString &text) { + for (const HighlightingRule &rule : qAsConst(highlightingRules)) { + QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.format); + } + } + + setCurrentBlockState(0); + + int startIndex = 0; + if (previousBlockState() != 1) + startIndex = text.indexOf(commentStartExpression); + + while (startIndex >= 0) { + QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); + int endIndex = match.capturedStart(); + int commentLength = 0; + if (endIndex == -1) { + setCurrentBlockState(1); + commentLength = text.length() - startIndex; + } else { + commentLength = endIndex - startIndex + match.capturedLength(); + } + setFormat(startIndex, commentLength, multiLineCommentFormat); + startIndex = text.indexOf(commentStartExpression, startIndex + commentLength); + } +} diff --git a/cppsyntaxhighlighter.h b/languages/cppsyntaxhighlighter.h similarity index 92% rename from cppsyntaxhighlighter.h rename to languages/cppsyntaxhighlighter.h index d07a5d6..1d052ed 100644 --- a/cppsyntaxhighlighter.h +++ b/languages/cppsyntaxhighlighter.h @@ -22,7 +22,8 @@ class CppSyntaxHighlighter : public QSyntaxHighlighter { QVector highlightingRules; QTextCharFormat keywordFormat; - QTextCharFormat classFormat; + QTextCharFormat typeFormat; + QTextCharFormat preprocessorFormat; QTextCharFormat singleLineCommentFormat; QTextCharFormat multiLineCommentFormat; QTextCharFormat quotationFormat; diff --git a/languages/languagemanager.cpp b/languages/languagemanager.cpp new file mode 100644 index 0000000..32bb164 --- /dev/null +++ b/languages/languagemanager.cpp @@ -0,0 +1,24 @@ +#include "languagemanager.h" +#include "cppsyntaxhighlighter.h" +#include "pythonsyntaxhighlighter.h" + +QSyntaxHighlighter* LanguageManager::createHighlighterForExtension(const QString &identifier, QTextDocument *document) { + if (identifier == "C++" || identifier == "cpp" || identifier == "cxx" || identifier == "h" || identifier == "hpp") { + return new CppSyntaxHighlighter(document); + } else if (identifier == "Python" || identifier == "py") { + return new PythonSyntaxHighlighter(document); + } + + // Add more language mappings here as needed + + return nullptr; // Return nullptr if no matching highlighter is found +} + +QString LanguageManager::getLanguageFromExtension(const QString &extension) { + if (extension == "cpp" || extension == "cxx" || extension == "h" || extension == "hpp") { + return "C++"; + } else if (extension == "py") { + return "Python"; + } + return "Unknown"; +} diff --git a/languages/languagemanager.h b/languages/languagemanager.h new file mode 100644 index 0000000..8a1ab85 --- /dev/null +++ b/languages/languagemanager.h @@ -0,0 +1,14 @@ +#ifndef LANGUAGEMANAGER_H +#define LANGUAGEMANAGER_H + +#include +#include +#include + +class LanguageManager { +public: + static QSyntaxHighlighter* createHighlighterForExtension(const QString &extension, QTextDocument *document); + static QString getLanguageFromExtension(const QString &extension); +}; + +#endif // LANGUAGEMANAGER_H diff --git a/languages/pythonsyntaxhighlighter.cpp b/languages/pythonsyntaxhighlighter.cpp new file mode 100644 index 0000000..7737152 --- /dev/null +++ b/languages/pythonsyntaxhighlighter.cpp @@ -0,0 +1,121 @@ +#include "pythonsyntaxhighlighter.h" + +PythonSyntaxHighlighter::PythonSyntaxHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) { + + // Define the formats + keywordFormat.setForeground(Qt::blue); + keywordFormat.setFontWeight(QFont::Bold); + + classFormat.setFontWeight(QFont::Bold); + classFormat.setForeground(Qt::darkMagenta); + + singleLineCommentFormat.setForeground(Qt::darkGreen); + + quotationFormat.setForeground(Qt::darkRed); + + functionFormat.setFontItalic(true); + functionFormat.setForeground(Qt::blue); + + // Define the keyword lists + keywords = QStringList() << "and" << "as" << "assert" << "break" << "class" << "continue" + << "def" << "del" << "elif" << "else" << "except" << "False" + << "finally" << "for" << "from" << "global" << "if" << "import" + << "in" << "is" << "lambda" << "None" << "nonlocal" << "not" + << "or" << "pass" << "raise" << "return" << "True" << "try" + << "while" << "with" << "yield"; + + builtins = QStringList() << "abs" << "dict" << "help" << "min" << "setattr" + << "all" << "dir" << "hex" << "next" << "slice" + << "any" << "divmod" << "id" << "object" << "sorted" + << "ascii" << "enumerate" << "input" << "oct" << "staticmethod" + << "bin" << "eval" << "int" << "open" << "str" + << "bool" << "exec" << "isinstance" << "ord" << "sum" + << "bytearray" << "filter" << "issubclass" << "pow" << "super" + << "bytes" << "float" << "iter" << "print" << "tuple" + << "callable" << "format" << "len" << "property" << "type" + << "chr" << "frozenset" << "list" << "range" << "vars" + << "classmethod" << "getattr" << "locals" << "repr" << "zip" + << "compile" << "globals" << "map" << "reversed" << "__import__" + << "complex" << "hasattr" << "max" << "round" << "delattr" + << "hash" << "memoryview" << "set"; + + operators = QStringList() << "=" << "==" << "!=" << "<" << "<=" << ">" << ">=" << "+" << "-" + << "*" << "/" << "//" << "%" << "**" << "<<" << ">>" << "&" + << "|" << "^" << "~"; + + braces = QStringList() << "{" << "}" << "(" << ")" << "[" << "]"; +} + +void PythonSyntaxHighlighter::highlightBlock(const QString &text) { + // Highlight keywords + for (const QString &keyword : keywords) { + int index = text.indexOf(keyword); + while (index >= 0) { + int length = keyword.length(); + setFormat(index, length, keywordFormat); + index = text.indexOf(keyword, index + length); + } + } + + // Highlight built-ins + for (const QString &builtin : builtins) { + int index = text.indexOf(builtin); + while (index >= 0) { + int length = builtin.length(); + setFormat(index, length, functionFormat); + index = text.indexOf(builtin, index + length); + } + } + + // Highlight comments + int commentIndex = text.indexOf("#"); + if (commentIndex >= 0) { + setFormat(commentIndex, text.length() - commentIndex, singleLineCommentFormat); + } + + // Highlight strings + int startIndex = 0; + while (startIndex >= 0) { + int singleQuoteIndex = text.indexOf("'", startIndex); + int doubleQuoteIndex = text.indexOf("\"", startIndex); + if (singleQuoteIndex >= 0 && (singleQuoteIndex < doubleQuoteIndex || doubleQuoteIndex < 0)) { + highlightMultiLineString(text, "'", singleQuoteIndex); + startIndex = singleQuoteIndex + 1; + } else if (doubleQuoteIndex >= 0) { + highlightMultiLineString(text, "\"", doubleQuoteIndex); + startIndex = doubleQuoteIndex + 1; + } else { + break; + } + } + + // Highlight operators and braces + for (const QString &op : operators) { + int index = text.indexOf(op); + while (index >= 0) { + setFormat(index, op.length(), keywordFormat); + index = text.indexOf(op, index + op.length()); + } + } + + for (const QString &brace : braces) { + int index = text.indexOf(brace); + while (index >= 0) { + setFormat(index, 1, keywordFormat); + index = text.indexOf(brace, index + 1); + } + } +} + +void PythonSyntaxHighlighter::highlightMultiLineString(const QString &text, const QString &delimiter, int startIndex) { + int endIndex = text.indexOf(delimiter, startIndex + 1); + int length; + if (endIndex == -1) { + setCurrentBlockState(1); + length = text.length() - startIndex; + } else { + length = endIndex - startIndex + 1; + } + setFormat(startIndex, length, quotationFormat); +} diff --git a/languages/pythonsyntaxhighlighter.h b/languages/pythonsyntaxhighlighter.h new file mode 100644 index 0000000..47d6f9b --- /dev/null +++ b/languages/pythonsyntaxhighlighter.h @@ -0,0 +1,32 @@ +#ifndef PYTHONSYNTAXHIGHLIGHTER_H +#define PYTHONSYNTAXHIGHLIGHTER_H + +#include +#include +#include + +class PythonSyntaxHighlighter : public QSyntaxHighlighter { + Q_OBJECT + +public: + explicit PythonSyntaxHighlighter(QTextDocument *parent = nullptr); + +protected: + void highlightBlock(const QString &text) override; + +private: + QTextCharFormat keywordFormat; + QTextCharFormat classFormat; + QTextCharFormat singleLineCommentFormat; + QTextCharFormat quotationFormat; + QTextCharFormat functionFormat; + + QStringList keywords; + QStringList builtins; + QStringList operators; + QStringList braces; + + void highlightMultiLineString(const QString &text, const QString &delimiter, int startIndex); +}; + +#endif // PYTHONSYNTAXHIGHLIGHTER_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 7c06fb2..3abec0f 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,6 +1,7 @@ #include "mainwindow.h" #include "ui_mainwindow.h" #include "document.h" +#include #include #include #include @@ -11,20 +12,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); - // Remove the initial empty tab if it exists if (ui->documentsTab->count() > 0) { - ui->documentsTab->removeTab(0); // Remove the first tab + ui->documentsTab->removeTab(0); } - // Create the initial "Untitled Document" tab - Document *firstDoc = new Document("", this); // Pass an empty string for the file path + Document *firstDoc = new Document("", this); ui->documentsTab->addTab(firstDoc, "Untitled Document"); - ui->documentsTab->setCurrentWidget(firstDoc); // Set the first tab as current + ui->documentsTab->setCurrentWidget(firstDoc); - // Open files passed as command line arguments QStringList filePaths = QCoreApplication::arguments(); for (const QString &filePath : filePaths) { - if (filePath != QCoreApplication::applicationFilePath()) { // Skip the application path + if (filePath != QCoreApplication::applicationFilePath()) { openDocument(filePath); } } @@ -35,18 +33,15 @@ MainWindow::~MainWindow() { } void MainWindow::openDocument(const QString &filePath) { - // Check if there is an empty tab to reuse if (ui->documentsTab->count() > 0) { Document *existingDoc = qobject_cast(ui->documentsTab->widget(0)); if (existingDoc && existingDoc->filePath().isEmpty()) { - // Reuse the existing empty tab - existingDoc->openFile(filePath); // Open the file in the existing tab - ui->documentsTab->setTabText(0, QFileInfo(filePath).fileName()); // Update the tab title - return; // Exit as we've reused the tab + existingDoc->openFile(filePath); + ui->documentsTab->setTabText(0, QFileInfo(filePath).fileName()); + return; } } - // If no empty tab exists, create a new document tab Document *newDoc = new Document(filePath, this); ui->documentsTab->addTab(newDoc, QFileInfo(filePath).fileName()); ui->documentsTab->setCurrentWidget(newDoc); @@ -62,7 +57,7 @@ void MainWindow::on_action_Open_triggered() { void MainWindow::on_action_Save_triggered() { Document *doc = qobject_cast(ui->documentsTab->currentWidget()); if (doc) { - doc->saveFile(); // Call saveFile method of Document + doc->saveFile(); } else { QMessageBox::warning(this, tr("Error"), tr("No document to save.")); } @@ -73,7 +68,7 @@ void MainWindow::on_actionSave_As_triggered() { if (doc) { QString filePath = QFileDialog::getSaveFileName(this, tr("Save File As"), "", tr("Text Files (*.txt);;All Files (*)")); if (!filePath.isEmpty()) { - doc->saveFileAs(filePath); // Call saveFileAs method of Document + doc->saveFileAs(filePath); } } else { QMessageBox::warning(this, tr("Error"), tr("No document to save.")); @@ -81,31 +76,79 @@ void MainWindow::on_actionSave_As_triggered() { } void MainWindow::on_documentsTab_tabCloseRequested(int index) { - // Get the current document from the tab Document *doc = qobject_cast(ui->documentsTab->widget(index)); if (doc) { - // Call the closeDocument method to handle the closing logic - bool canClose = doc->closeDocument(); // This method should return true if it's okay to close + bool canClose = doc->closeDocument(); if (canClose) { - // If the document has been closed, remove the tab ui->documentsTab->removeTab(index); } } } void MainWindow::on_action_New_triggered() { - Document *newDoc = new Document("", this); // Pass an empty string for the file path + Document *newDoc = new Document("", this); ui->documentsTab->addTab(newDoc, "Untitled Document"); ui->documentsTab->setCurrentWidget(newDoc); } +void MainWindow::on_action_Go_to_line_in_editor_triggered() +{ + Document *doc = qobject_cast(ui->documentsTab->currentWidget()); + if (doc) { + doc->goToLineNumberInEditor (); + } else { + QMessageBox::warning(this, tr("Error"), tr("No document open.")); + } +} + +void MainWindow::on_action_Go_to_line_in_text_triggered() { + Document* doc = qobject_cast(ui->documentsTab->currentWidget()); + if (doc) { + doc->goToLineNumberInText(this); + } else { + QMessageBox::warning(this, tr("No Document"), tr("There is no document open.")); + } +} + +void MainWindow::on_action_Close_triggered() +{ + int activeTabIndex = ui->documentsTab->currentIndex(); // Get the index of the active tab + + if (activeTabIndex != -1) { // Check if there's an active tab + on_documentsTab_tabCloseRequested(activeTabIndex); // Reuse the existing close logic + } +} + + +void MainWindow::on_actionC_lose_all_triggered() +{ + int tabCount = ui->documentsTab->count(); + + for (int i = tabCount - 1; i >= 0; --i) { + on_documentsTab_tabCloseRequested(i); + } +} + void MainWindow::on_actionC_3_triggered() { Document *doc = qobject_cast(ui->documentsTab->currentWidget()); if (doc) { - doc->applySyntaxHighlighter(); // Now this is public + qDebug() << "Menu action triggered for C++ syntax highlighting"; + doc->applySyntaxHighlighter("C++"); + } else { + QMessageBox::warning(this, tr("Error"), tr("No document to format.")); + } +} + +void MainWindow::on_actionPython_triggered() +{ + Document *doc = qobject_cast(ui->documentsTab->currentWidget()); + if (doc) { + qDebug() << "Menu action triggered for Python syntax highlighting"; + doc->applySyntaxHighlighter("Python"); } else { QMessageBox::warning(this, tr("Error"), tr("No document to format.")); } } + diff --git a/mainwindow.h b/mainwindow.h index 8d0f8ed..0c3eae6 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -21,6 +21,14 @@ private slots: void on_documentsTab_tabCloseRequested(int index); void on_action_New_triggered(); void on_actionC_3_triggered(); + void on_action_Go_to_line_in_text_triggered(); + void on_action_Go_to_line_in_editor_triggered(); + + void on_action_Close_triggered(); + + void on_actionC_lose_all_triggered(); + + void on_actionPython_triggered(); private: Ui::MainWindow *ui; diff --git a/mainwindow.ui b/mainwindow.ui index 16786dd..9e75a46 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -105,7 +105,8 @@ - + + @@ -160,28 +161,252 @@ - - + + + A + + + + + + + + + B + + + + + + + D + + + + + + + + + + + + E + + + + + + + + + + + + + F + + + + + + + + + + G + + + + + + + + + H + + + + + + + + + + + I + + + + + + J + + + + + + + + + + + + + + K + + + + + + L + + + + + + + + + M + + + + + + + + + + + + + + + + N + + + + + + + + O + + + + + + + + + P + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + R + + + + + + + + + + + S + + + + + + + + + + + + + + + + + + + + + + + W + + + + + + V + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -401,7 +626,7 @@ - Find &previoud + Find &previous @@ -409,9 +634,9 @@ &Replace... - + - &Go to line... + &Go to line in editor... @@ -507,227 +732,744 @@ C&onvert to... - + + + &Prefrences... + + + + + &Show Menubar + + + + + S&how Toolbar + + + + + Run... + + + + + Modify run commands + + + + + &Open a new window + + + + + &About Notepad-- + + + + + About &Qt + + + + + C + + + + + Clojure + + + + + ClojureScript + + + + + Closure Stylesheets (GSS) + + + + + CMake + + + + + Cobol + + + + + Coffee Script + + + + + Common Lisp + + + + + C++ + + + + + Crystal + + + + + C# + + + + + CSS + + + + + Cypher + + + + + Cython + + + + + G&o to line in text... + + + - A + APL - + - B + ASN.1 - + + + ASP.NET + + + + + Asterisk + + + + + Bash + + + + + Brainfuck + + + D - + + + Dart + + + + + diff + + + + + Django + + + + + Dockerfile + + + + + DTD + + + + + Dylaan + + + + + EBNF + + + + + ECL + + + + + edn + + + + + Eiffel + + + + + Embedded Javascript + + + + + Elm + + + + + Embedded Ruby + + + + + Erlang + + + + + Factor + + + - E + FCL - + - F + Forth - + - G + Fortran - + - H + F# - + - I + Gas - + - J + Gherkin - + - K + Go - + - L + Groovy - + - M + HAML - + - N + Haskell - + - O + Haskell (Literate) - + - P + Haxe - + - T + HTML - + + + HTTP + + + + + IDL + + + + + Jade + + + + + Java + + + + + JavaScript + + + + + Jinja2 + + + + + JSON + + + + + JSON-LD + + + + + JavaServer Pages + + + + + JSX + + + + + Julia + + + + + Kotlin + + + + + LaTeX + + + + + LESS + + + + + Livescript + + + + + Lua + + + + + m4 + + + + + Makefile + + + + + MariaDB + + + + + Markdown (GitHub-flavour) + + + + + Mathematica + + + + + mbox + + + + + mIRC + + + + + Modelica + + + + + mscgen + + + + + msgenny + + + + + MUMPS + + + + + Nginx + + + + + NSIS + + + + + NTriples + + + + + Objective C + + + + + OCaml + + + + + Octave + + + + + Oz + + + + + Pascal + + + + + PEG.js + + + + + Perl + + + + + PGP + + + + + PHP + + + + + Pig + + + + + PLSQL + + + + + PowerShell + + + + + Properties files + + + + + ProtoBuf + + + + + Pug + + + + + Puppet + + + + + Python + + + + + Text + + + + + Tcl + + + + + Textile + + + + + TiddlyWiki + + + + + Tiki wiki + + + + + TOML + + + + + Tornado + + + + + troff + + + + + TTCN + + + + + TTCN-CFG + + + + + Turtle + + + + + Twig + + + + + TypeScript + + + R - + - S + RPM Changes - + - V + RPM Spec - + - W + reStructuredText - + - Y + Ruby - + - Z + Rust - - QAction::MenuRole::NoRole + + + + SAS - + - &Prefrences... + Sass - + - &Show Menubar + Scala - + - S&how Toolbar + Scheme - + - Run... + SCSS - + - Modify run commands + Sieve - + - &Open a new window + Slim - + - &About Notepad-- + Smalltalk - + - About &Qt + Smarty - + - C + Solr - + - Clojure + Soy - + - ClojureScript + SPARQL - + - Closure Stylesheets (GSS) + Spreadsheet - + - CMake + SQL - + - Cobol + Squirrel - + - Coffee Script + sTeX - + - Common Lisp + Swift - + - C++ + SysstemVerilog - + - Crystal + Web IDL - + - C# + VB.NET - + - CSS + VBScript - + - Cypher + Velocity - + - Cython + Verilog + + + + + VHDL