From d151fe4f0e1d0ddb5d264d4e59b11160e1115add Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Mar 2025 15:13:18 -0700 Subject: [PATCH 001/160] [Refactor] Update include file path to fix testing errors --- src/CodeEditor.cpp | 6 +++--- src/MainWindow.cpp | 2 +- src/main.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 4259f81..947dc59 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -1,6 +1,6 @@ -#include "CodeEditor.h" -#include "MainWindow.h" -#include "LineNumberArea.h" +#include "../include/CodeEditor.h" +#include "../include/MainWindow.h" +#include "../include/LineNumberArea.h" #include #include diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ae44543..a687ac3 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,4 +1,4 @@ -#include "MainWindow.h" +#include "../include/MainWindow.h" #include #include diff --git a/src/main.cpp b/src/main.cpp index b7aa619..313b85a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "MainWindow.h" +#include "../include/MainWindow.h" #include #include From bcca0dbaec35a51f4ada195d2de8d06d9f83298e Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Mar 2025 15:13:46 -0700 Subject: [PATCH 002/160] Added testing to the build --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49c46d0..aa40a27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ endif() set(CMAKE_PREFIX_PATH ${Qt_DIR}) # Find Qt Widgets -find_package(Qt${QT_MAJOR_VERSION} COMPONENTS Widgets REQUIRED) +find_package(Qt${QT_MAJOR_VERSION} COMPONENTS Widgets Test REQUIRED) # Add executable and source files add_executable(${TARGET_NAME} @@ -58,6 +58,8 @@ add_executable(${TARGET_NAME} include/CodeEditor.h ) +add_subdirectory(tests) + qt_add_resources(APP_RESOURCES resources.qrc) target_sources(${TARGET_NAME} PRIVATE ${APP_RESOURCES}) From e068893f2c23b0f1250a6f3c70f715dc7c48acfa Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Mar 2025 15:14:05 -0700 Subject: [PATCH 003/160] [Make] Added automating testing from Make file --- Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Makefile b/Makefile index f990940..b0973ed 100644 --- a/Makefile +++ b/Makefile @@ -75,3 +75,11 @@ install: fi \ fi @echo "$(PROJECT) installed." + +test: + @for test in ./build/tests/test_*; do \ + if [ -f $$test ]; then \ + echo "Running $$test..."; \ + $$test; \ + fi; \ + done From f165366cea8efb64ed703f5d3f853be95f72eb47 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Mar 2025 15:14:26 -0700 Subject: [PATCH 004/160] [Test] CMakeLists file for testing directory --- tests/CMakeLists.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/CMakeLists.txt diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..7bf9e34 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +# Enable testing +enable_testing() + +# Add individual test executables +add_executable(test_mainwindow + test_mainwindow.cpp + ../src/MainWindow.cpp + ../src/CodeEditor.cpp + ../include/MainWindow.h + ../include/CodeEditor.h +) + +# Link each test with necessary libraries +target_link_libraries(test_mainwindow PRIVATE Qt6::Widgets Qt6::Test) + +# Register each test with CTest +add_test(NAME test_mainwindow COMMAND test_mainwindow) + +# Set the runtime output directory for the test executables +set_target_properties(test_mainwindow PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests +) \ No newline at end of file From d07ddc3db7cb876e9837c6121cf5a521fc285962 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Mar 2025 15:14:51 -0700 Subject: [PATCH 005/160] [Test] Added init :test: mainwindow --- tests/test_mainwindow.cpp | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/test_mainwindow.cpp diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp new file mode 100644 index 0000000..67797be --- /dev/null +++ b/tests/test_mainwindow.cpp @@ -0,0 +1,51 @@ +#include "../include/MainWindow.h" + +#include +#include +#include + +class TestMainWindow : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); // Runs before all tests + void cleanupTestCase(); // Runs after all tests + void testWindowTitle(); + void testMenuBar(); + void testNewFileAction(); +}; + +void TestMainWindow::initTestCase() +{ + qDebug() << "Initializing MainWindow tests..."; +} + +void TestMainWindow::cleanupTestCase() +{ + qDebug() << "Cleaning up MainWindow tests..."; +} + +void TestMainWindow::testWindowTitle() +{ + MainWindow window; + QCOMPARE(window.windowTitle(), QString("CodeAstra ~ Code Editor")); +} + +void TestMainWindow::testMenuBar() +{ + MainWindow window; + QMenuBar *menuBar = window.menuBar(); + QVERIFY(menuBar != nullptr); + QCOMPARE(menuBar->actions().size(), 3); // File, Help, CodeAstra +} + +void TestMainWindow::testNewFileAction() +{ + MainWindow window; + QAction *newAction = window.findChild("New File"); + QVERIFY(newAction != nullptr); +} + +QTEST_MAIN(TestMainWindow) +#include "test_mainwindow.moc" From ef882ff4834adcf2a1171cc7ca08c8a90eb5f9cf Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Mar 2025 18:13:36 -0700 Subject: [PATCH 006/160] [Update] Added Syntax to the test build --- src/Syntax.cpp | 2 +- tests/CMakeLists.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Syntax.cpp b/src/Syntax.cpp index 553c927..80d1043 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -1,4 +1,4 @@ -#include "Syntax.h" +#include "../include/Syntax.h" Syntax::Syntax(QTextDocument *parent) : QSyntaxHighlighter(parent) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7bf9e34..a291c85 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,8 +6,10 @@ add_executable(test_mainwindow test_mainwindow.cpp ../src/MainWindow.cpp ../src/CodeEditor.cpp + ../src/Syntax.cpp ../include/MainWindow.h ../include/CodeEditor.h + ../include/Syntax.h ) # Link each test with necessary libraries From 0ba2f8335ccc0f63f9525e61dc1c27f7d9093226 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 16 Mar 2025 12:38:29 -0700 Subject: [PATCH 007/160] [Update] Refactor MainWindow initialization and enhance file dialog filters --- src/MainWindow.cpp | 59 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 23e3a83..1333f98 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -12,33 +12,45 @@ #include #include -MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), + editor(new CodeEditor(this)), + syntax(new Syntax(editor->document())), + tree(nullptr) { setWindowTitle("CodeAstra ~ Code Editor"); - editor = new CodeEditor(this); - syntax = new Syntax(editor->document()); - + // Set tab width to 4 spaces QFontMetrics metrics(editor->font()); int spaceWidth = metrics.horizontalAdvance(" "); editor->setTabStopDistance(spaceWidth * 4); + editor->setLineWrapMode(QPlainTextEdit::NoWrap); + + initTree(); + createMenuBar(); + showMaximized(); +} +MainWindow::~MainWindow() {} + +void MainWindow::initTree() +{ QSplitter *splitter = new QSplitter(Qt::Horizontal, this); setCentralWidget(splitter); tree = new Tree(splitter, this); - splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - splitter->setHandleWidth(5); - splitter->setSizes(QList() << 20 << 950); splitter->addWidget(editor); - createMenuBar(); - showMaximized(); + splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + splitter->setHandleWidth(5); + splitter->setSizes(QList() << 150 << 800); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 3); + splitter->setChildrenCollapsible(false); + splitter->setOpaqueResize(true); } -MainWindow::~MainWindow() {} - void MainWindow::createMenuBar() { QMenuBar *menuBar = new QMenuBar(this); @@ -56,17 +68,11 @@ void MainWindow::createMenuBar() void MainWindow::createFileActions(QMenu *fileMenu) { - QAction *newAction = createAction(QIcon::fromTheme("document-new"), tr("&New File..."), QKeySequence::New, tr("Create a new file"), &MainWindow::newFile); - fileMenu->addAction(newAction); - - QAction *openAction = createAction(QIcon::fromTheme("document-open"), tr("&Open..."), QKeySequence::Open, tr("Open an existing file"), &MainWindow::openFile); - fileMenu->addAction(openAction); - - QAction *saveAction = createAction(QIcon::fromTheme("document-save"), tr("&Save"), QKeySequence::Save, tr("Save your file"), &MainWindow::saveFile); - fileMenu->addAction(saveAction); - - QAction *saveAsAction = createAction(QIcon::fromTheme("document-saveAs"), tr("&Save As"), QKeySequence::SaveAs, tr("Save current file as..."), &MainWindow::saveFileAs); - fileMenu->addAction(saveAsAction); + fileMenu->addAction(createAction(QIcon(), tr("&New"), QKeySequence::New, tr("Create a new file"), &MainWindow::newFile)); + fileMenu->addAction(createAction(QIcon(), tr("&Open"), QKeySequence::Open, tr("Open an existing file"), &MainWindow::openFile)); + fileMenu->addSeparator(); + fileMenu->addAction(createAction(QIcon(), tr("&Save"), QKeySequence::Save, tr("Save the current file"), &MainWindow::saveFile)); + fileMenu->addAction(createAction(QIcon(), tr("Save &As"), QKeySequence::SaveAs, tr("Save the file with a new name"), &MainWindow::saveFileAs)); } void MainWindow::createHelpActions(QMenu *helpMenu) @@ -143,12 +149,13 @@ void MainWindow::showAbout() QString::number((QT_VERSION >> 8) & 0xFF) + "." + // Minor version QString::number(QT_VERSION & 0xFF)); // Patch version - QMessageBox::about(this, "About Code Astra", aboutText); + QMessageBox::about(this, tr("About"), aboutText); } void MainWindow::openFile() { - QString fileName = QFileDialog::getOpenFileName(this, "Open File"); + QString fileName = QFileDialog::getOpenFileName(this, "Open File", QString(), + "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); if (!fileName.isEmpty()) { QFile file(fileName); @@ -202,7 +209,9 @@ void MainWindow::saveFile() void MainWindow::saveFileAs() { - QString fileName = QFileDialog::getSaveFileName(this, "Save File As"); + QString fileName = QFileDialog::getSaveFileName(this, "Save File As", QString(), + "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); + if (!fileName.isEmpty()) { currentFileName = fileName; From a12b36d0dfe18d230b5d61cc6e51c50035c69c09 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 16 Mar 2025 12:38:39 -0700 Subject: [PATCH 008/160] [Update] Simplify CodeEditor constructor by initializing lineNumberArea in the member initializer list --- src/CodeEditor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 4259f81..3ccb5be 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -5,10 +5,10 @@ #include #include -CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent) +CodeEditor::CodeEditor(QWidget *parent) + : QPlainTextEdit(parent), + lineNumberArea(new LineNumberArea(this)) { - lineNumberArea = new LineNumberArea(this); - connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth); connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea); connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine); From e352206ba1468ffeb70f9a9021870e51eedd1f74 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 16 Mar 2025 12:38:47 -0700 Subject: [PATCH 009/160] [Update] Improve icon creation by adding error handling and using antialiasing for round icons --- src/main.cpp | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b7aa619..aadd252 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,19 +13,22 @@ QIcon createRoundIcon(const QString &iconPath) { QPixmap pixmap(iconPath); - - // Create a round mask - QBitmap mask(pixmap.size()); - mask.fill(Qt::white); - - QPainter painter(&mask); - painter.setBrush(Qt::black); + if (pixmap.isNull()) + { + qWarning() << "Failed to load icon:" << iconPath; + return QIcon(); + } + + QPixmap roundPixmap(pixmap.size()); + roundPixmap.fill(Qt::transparent); + + QPainter painter(&roundPixmap); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(QBrush(pixmap)); painter.setPen(Qt::NoPen); painter.drawEllipse(0, 0, pixmap.width(), pixmap.height()); - pixmap.setMask(mask); - - return QIcon(pixmap); + return QIcon(roundPixmap); } int main(int argc, char *argv[]) @@ -33,17 +36,26 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); QIcon roundIcon = createRoundIcon(":/resources/app_icon.png"); - QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + if (roundIcon.isNull()) + { + qWarning() << "Failed to load round icon!"; + } + + QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); font.setPointSize(12); - + app.setFont(font); app.setWindowIcon(roundIcon); - app.setApplicationVersion("0.0.1"); - app.setOrganizationName("Chris Dedman"); - app.setApplicationName("CodeAstra"); - MainWindow window; - window.show(); + app.setApplicationVersion(QStringLiteral("0.0.1")); + app.setOrganizationName(QStringLiteral("Chris Dedman")); + app.setApplicationName(QStringLiteral("CodeAstra")); + app.setApplicationDisplayName(QStringLiteral("CodeAstra")); + + app.setStyle("Fusion"); + + QScopedPointer window(new MainWindow); + window->show(); return app.exec(); } \ No newline at end of file From fecca4b965949797d0ba2e5a340004a6c50dafa4 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 16 Mar 2025 12:38:52 -0700 Subject: [PATCH 010/160] [Add] Declare initTree method in MainWindow class for tree initialization --- include/MainWindow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 81bcf80..e48cb8b 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -10,7 +10,6 @@ #include #include #include -#include class MainWindow : public QMainWindow { @@ -20,6 +19,7 @@ class MainWindow : public QMainWindow explicit MainWindow(QWidget *parent = nullptr); virtual ~MainWindow(); void loadFileInEditor(const QString &filePath); + void initTree(); private slots: void newFile(); From 8286019e43e2a84ddd37fa9625b1b651d8022228 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 16 Mar 2025 12:39:09 -0700 Subject: [PATCH 011/160] [Refactor] Refactor Tree constructor to initialize members in the initializer list and enhance setupTree method --- src/Tree.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index c5405a0..01adf03 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -7,11 +7,12 @@ #include #include -Tree::Tree(QSplitter *splitter, MainWindow *mainWindow) : QObject(splitter), mainWindow(mainWindow) +Tree::Tree(QSplitter *splitter, MainWindow *mainWindow) + : QObject(splitter), + model(new QFileSystemModel()), + tree(new QTreeView(splitter)), + mainWindow(mainWindow) { - model = new QFileSystemModel(); - tree = new QTreeView(splitter); - setupModel(); setupTree(); @@ -25,7 +26,6 @@ void Tree::setupModel() model->setRootPath(getDirectoryPath()); model->setIconProvider(new QFileIconProvider); model->setFilter(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot); - } void Tree::setupTree() @@ -37,14 +37,15 @@ void Tree::setupTree() tree->setIndentation(20); tree->setSortingEnabled(false); tree->sortByColumn(1, Qt::AscendingOrder); + tree->setHeaderHidden(true); - tree->setContextMenuPolicy(Qt::CustomContextMenu); - connect(tree, &QTreeView::customContextMenuRequested, this, &Tree::showContextMenu); - - for (int i = 1; i <= 3; ++i) + for (int i = 1; i <= model->columnCount(); ++i) { tree->setColumnHidden(i, true); } + + tree->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tree, &QTreeView::customContextMenuRequested, this, &Tree::showContextMenu); } QString Tree::getDirectoryPath() From bb89ad41e1bedd123cd78766b76b9c2ed979f85c Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 16 Mar 2025 12:39:16 -0700 Subject: [PATCH 012/160] Update README to reflect progress on file creation and syntax highlighting features --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f868f0..03be487 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ Please, check the [wiki](https://github.com/sandbox-science/CodeAstra/wiki) for - [x] Basic text editing - [x] Open a file - [x] Save file - - [ ] Create a new file -- [ ] File tree navigation -- [ ] Syntax highlighting + - [ ] Create a new file ~ in progress +- [x] File tree navigation +- [ ] Syntax highlighting ~ in progress - [ ] Plugin system ## To-Do From f564991c1ff70f4da3073140ef3d2e6bac61712d Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 18 Mar 2025 19:18:07 -0700 Subject: [PATCH 013/160] [Update] Bump application version to 0.1.0 in main.cpp --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index aadd252..93247d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,7 +47,7 @@ int main(int argc, char *argv[]) app.setFont(font); app.setWindowIcon(roundIcon); - app.setApplicationVersion(QStringLiteral("0.0.1")); + app.setApplicationVersion(QStringLiteral("0.1.0")); app.setOrganizationName(QStringLiteral("Chris Dedman")); app.setApplicationName(QStringLiteral("CodeAstra")); app.setApplicationDisplayName(QStringLiteral("CodeAstra")); From a984cf44d0ba272632cb15bd1861650bdd7ecbf7 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 18 Mar 2025 20:46:01 -0700 Subject: [PATCH 014/160] [Add] Implement comment functionality in CodeEditor with language-specific symbols --- src/CodeEditor.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 4259f81..8ebbd13 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -26,6 +26,11 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) moveCursor(QTextCursor::WordLeft, QTextCursor::KeepAnchor); return; } + if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Slash) + { + addComment(); + return; + } if (mode == NORMAL) { @@ -57,6 +62,99 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) } } +QString CodeEditor::getFileExtension() +{ + QString filePath = getCurrentFileName(); + if (!QFile::exists(filePath)) + { + return QString(); + } + + // Extract the file extension from the file path + return QFileInfo(filePath).suffix().toLower(); +} + +void CodeEditor::addLanguageSymbol(QTextCursor &cursor, const QString commentSymbol) +{ + // If text is selected, comment/uncomment selected text + if (cursor.hasSelection()) + { + int start = cursor.selectionStart(); + int end = cursor.selectionEnd(); + + cursor.setPosition(start); + int startBlockNumber = cursor.blockNumber(); + cursor.setPosition(end); + int endBlockNumber = cursor.blockNumber(); + + cursor.setPosition(start); + for (int i = startBlockNumber; i <= endBlockNumber; ++i) + { + cursor.movePosition(QTextCursor::StartOfLine); + QString lineText = cursor.block().text(); + + if (lineText.startsWith(commentSymbol)) + { + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 3); + cursor.removeSelectedText(); + } + else + { + cursor.insertText(commentSymbol + " "); + } + + cursor.movePosition(QTextCursor::NextBlock); + } + } + else + { + // If no text is selected, comment/uncomment the current line + cursor.select(QTextCursor::LineUnderCursor); + QString lineText = cursor.selectedText(); + + if (lineText.startsWith(commentSymbol)) + { + lineText.remove(0, 3); + } + else + { + lineText.prepend(commentSymbol + " "); + } + + cursor.insertText(lineText); + } +} + +void CodeEditor::addComment() +{ + QTextCursor cursor = textCursor(); + QString fileExtension = getFileExtension(); + qDebug() << "File Extension:" << fileExtension; + + if (fileExtension == "cpp" || fileExtension == "h" || + fileExtension == "hpp" || fileExtension == "c" || + fileExtension == "java" || fileExtension == "go" || + fileExtension == "json") + { + addLanguageSymbol(cursor, "//"); + } + else if (fileExtension == "py" || fileExtension == "yaml" || + fileExtension == "yml" || fileExtension == "sh" || + fileExtension == "bash") + { + addLanguageSymbol(cursor, "#"); + } + else if (fileExtension == "sql") + { + addLanguageSymbol(cursor, "--"); + } + else + { + qDebug() << "Unsupported file extension for commenting."; + return; + } +} + int CodeEditor::lineNumberAreaWidth() { int digits = 1; From 09370ce340a5a9d0599aee8370ff49df13443c18 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 18 Mar 2025 20:46:15 -0700 Subject: [PATCH 015/160] [Add] Implement functionality to set current file name in CodeEditor upon loading a file --- include/CodeEditor.h | 6 ++++++ src/MainWindow.cpp | 1 + 2 files changed, 7 insertions(+) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index e1f4d59..ae8c6b2 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -19,6 +19,8 @@ class CodeEditor : public QPlainTextEdit Mode mode = NORMAL; void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); + QString getCurrentFileName() const { return currentFileName; } + void setCurrentFileName(const QString &fileName) { currentFileName = fileName; } protected: void keyPressEvent(QKeyEvent *event) override; @@ -31,6 +33,10 @@ private slots: private: QWidget *lineNumberArea; + QString currentFileName; + QString getFileExtension(); + void addLanguageSymbol(QTextCursor &cursor, const QString commentSymbol); + void addComment(); }; #endif // CODEEDITOR_H diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 23e3a83..cb7cd3b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -223,6 +223,7 @@ void MainWindow::loadFileInEditor(const QString &filePath) editor->setPlainText(in.readAll()); file.close(); + editor->setCurrentFileName(filePath); currentFileName = filePath; setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); } From e683a52cf19d8940a2faac2610e72453574d1c42 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 16:27:57 -0700 Subject: [PATCH 016/160] [Updated] Refactor CodeEditor.h to correct parameter type in addLanguageSymbol and add comment-related methods --- include/CodeEditor.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index ae8c6b2..0446920 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -35,7 +35,9 @@ private slots: QWidget *lineNumberArea; QString currentFileName; QString getFileExtension(); - void addLanguageSymbol(QTextCursor &cursor, const QString commentSymbol); + void addLanguageSymbol(QTextCursor &cursor, const QString &commentSymbol); + void commentSelection(QTextCursor &cursor, const QString &commentSymbol); + void commentLine(QTextCursor &cursor, const QString &commentSymbol); void addComment(); }; From 4e8137b317b46fb3d451f2e40b6dd27a83c746d3 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 16:29:06 -0700 Subject: [PATCH 017/160] Refactor MainWindow to remove currentFileName and delegate file name management to CodeEditor --- include/MainWindow.h | 1 - src/MainWindow.cpp | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 81bcf80..c2f8451 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -37,7 +37,6 @@ private slots: const QKeySequence &shortcut, const QString &statusTip, void (MainWindow::*slot)()); CodeEditor *editor; - QString currentFileName; Syntax *syntax; Tree *tree; }; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index cb7cd3b..14f159b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -169,7 +169,7 @@ void MainWindow::openFile() } file.close(); - currentFileName = fileName; + editor->setCurrentFileName(fileName); setWindowTitle("CodeAstra ~ " + QFileInfo(fileName).fileName()); } @@ -177,13 +177,13 @@ void MainWindow::openFile() void MainWindow::saveFile() { - if (currentFileName.isEmpty()) + if (editor->getCurrentFileName().isEmpty()) { saveFileAs(); return; } - QFile file(currentFileName); + QFile file(editor->getCurrentFileName()); if (!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, "Error", "Cannot save file: " + file.errorString()); @@ -205,7 +205,7 @@ void MainWindow::saveFileAs() QString fileName = QFileDialog::getSaveFileName(this, "Save File As"); if (!fileName.isEmpty()) { - currentFileName = fileName; + editor->setCurrentFileName(fileName); saveFile(); } } @@ -224,6 +224,5 @@ void MainWindow::loadFileInEditor(const QString &filePath) file.close(); editor->setCurrentFileName(filePath); - currentFileName = filePath; setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); } From 6c5371472a8f99697ee00283e94b2390946c4d5f Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 16:29:24 -0700 Subject: [PATCH 018/160] Refactor comment functionality in CodeEditor to streamline selection and line commenting --- src/CodeEditor.cpp | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 8ebbd13..0bd81af 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -74,57 +74,67 @@ QString CodeEditor::getFileExtension() return QFileInfo(filePath).suffix().toLower(); } -void CodeEditor::addLanguageSymbol(QTextCursor &cursor, const QString commentSymbol) +void CodeEditor::addLanguageSymbol(QTextCursor &cursor, const QString &commentSymbol) { - // If text is selected, comment/uncomment selected text if (cursor.hasSelection()) { - int start = cursor.selectionStart(); - int end = cursor.selectionEnd(); - - cursor.setPosition(start); - int startBlockNumber = cursor.blockNumber(); - cursor.setPosition(end); - int endBlockNumber = cursor.blockNumber(); - - cursor.setPosition(start); - for (int i = startBlockNumber; i <= endBlockNumber; ++i) - { - cursor.movePosition(QTextCursor::StartOfLine); - QString lineText = cursor.block().text(); - - if (lineText.startsWith(commentSymbol)) - { - cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 3); - cursor.removeSelectedText(); - } - else - { - cursor.insertText(commentSymbol + " "); - } - - cursor.movePosition(QTextCursor::NextBlock); - } + commentSelection(cursor, commentSymbol); } else { - // If no text is selected, comment/uncomment the current line - cursor.select(QTextCursor::LineUnderCursor); - QString lineText = cursor.selectedText(); + commentLine(cursor, commentSymbol); + } +} + +// Comment/uncomment the selected text or the current line +void CodeEditor::commentSelection(QTextCursor &cursor, const QString &commentSymbol) +{ + int start = cursor.selectionStart(); + int end = cursor.selectionEnd(); + + cursor.setPosition(start); + int startBlockNumber = cursor.blockNumber(); + cursor.setPosition(end); + int endBlockNumber = cursor.blockNumber(); + + cursor.setPosition(start); + for (int i = startBlockNumber; i <= endBlockNumber; ++i) + { + cursor.movePosition(QTextCursor::StartOfLine); + QString lineText = cursor.block().text(); if (lineText.startsWith(commentSymbol)) { - lineText.remove(0, 3); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 3); + cursor.removeSelectedText(); } else { - lineText.prepend(commentSymbol + " "); + cursor.insertText(commentSymbol + " "); } - cursor.insertText(lineText); + cursor.movePosition(QTextCursor::NextBlock); } } +// Comment/uncomment the single current line +void CodeEditor::commentLine(QTextCursor &cursor, const QString &commentSymbol) +{ + cursor.select(QTextCursor::LineUnderCursor); + QString lineText = cursor.selectedText(); + + if (lineText.startsWith(commentSymbol)) + { + lineText.remove(0, 3); + } + else + { + lineText.prepend(commentSymbol + " "); + } + + cursor.insertText(lineText); +} + void CodeEditor::addComment() { QTextCursor cursor = textCursor(); From 98c430777ac1a164279e23984e3245290da9a534 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:19:55 -0700 Subject: [PATCH 019/160] [Add] Include LineNumberArea.h in the executable target --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc4f222..75eb2f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${TARGET_NAME} include/CodeEditor.h include/Syntax.h include/Tree.h + include/LineNumberArea.h ) qt_add_resources(APP_RESOURCES resources.qrc) From c5ece45e574667aec6c753cc1e65a2121b9f7041 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:20:33 -0700 Subject: [PATCH 020/160] [Refactor] Replace include guards with #pragma once in CodeEditor and LineNumberArea headers --- include/CodeEditor.h | 7 ++----- include/LineNumberArea.h | 27 ++++++++++++--------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index e1f4d59..4054768 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -1,5 +1,4 @@ -#ifndef CODEEDITOR_H -#define CODEEDITOR_H +#pragma once #include #include @@ -31,6 +30,4 @@ private slots: private: QWidget *lineNumberArea; -}; - -#endif // CODEEDITOR_H +}; \ No newline at end of file diff --git a/include/LineNumberArea.h b/include/LineNumberArea.h index ac052ef..dd5980c 100644 --- a/include/LineNumberArea.h +++ b/include/LineNumberArea.h @@ -1,5 +1,4 @@ -#ifndef LINENUMBER_H -#define LINENUMBER_H +#pragma once #include "CodeEditor.h" @@ -10,21 +9,19 @@ class LineNumberArea : public QWidget { public: - LineNumberArea(CodeEditor *editor) : QWidget(editor), codeEditor(editor) {} + LineNumberArea(CodeEditor *editor) : QWidget(editor), m_codeEditor(editor) {} - QSize sizeHint() const override - { - return QSize(codeEditor->lineNumberAreaWidth(), 0); - } + QSize sizeHint() const override + { + return QSize(m_codeEditor->lineNumberAreaWidth(), 0); + } protected: - void paintEvent(QPaintEvent *event) override - { - codeEditor->lineNumberAreaPaintEvent(event); - } + void paintEvent(QPaintEvent *event) override + { + m_codeEditor->lineNumberAreaPaintEvent(event); + } private: - CodeEditor *codeEditor; -}; - -#endif // LINENUMBER_H \ No newline at end of file + CodeEditor *m_codeEditor; +}; \ No newline at end of file From b4980287f2290f5e75ee0809a57e791e782105a5 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:21:12 -0700 Subject: [PATCH 021/160] [Refactor] Replace include guards with #pragma once in MainWindow and Tree headers + change row pointer to smart pointer --- include/MainWindow.h | 91 ++++++++++++++++++++++---------------------- include/Tree.h | 26 +++++++------ 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index e48cb8b..402bc18 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -1,45 +1,46 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "CodeEditor.h" -#include "Syntax.h" -#include "Tree.h" - -#include -#include -#include -#include -#include - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - virtual ~MainWindow(); - void loadFileInEditor(const QString &filePath); - void initTree(); - -private slots: - void newFile(); - void openFile(); - void saveFile(); - void saveFileAs(); - void showAbout(); - -private: - void createMenuBar(); - void createFileActions(QMenu *fileMenu); - void createHelpActions(QMenu *helpMenu); - void createAppActions(QMenu *appMenu); - QAction *createAction(const QIcon &icon, const QString &text, - const QKeySequence &shortcut, const QString &statusTip, - void (MainWindow::*slot)()); - CodeEditor *editor; - QString currentFileName; - Syntax *syntax; - Tree *tree; -}; - -#endif // MAINWINDOW_H +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +class CodeEditor; +class Syntax; +class Tree; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + virtual ~MainWindow(); + void loadFileInEditor(const QString &filePath); + // Initialize the file tree view and set it as the central widget + // of the main window, alongside the code editor + void initTree(); + +private slots: + void newFile(); + void openFile(); + void saveFile(); + void saveFileAs(); + void showAbout(); + +private: + void createMenuBar(); + void createFileActions(QMenu *fileMenu); + void createHelpActions(QMenu *helpMenu); + void createAppActions(QMenu *appMenu); + QAction *createAction(const QIcon &icon, const QString &text, + const QKeySequence &shortcut, const QString &statusTip, + void (MainWindow::*slot)()); + std::unique_ptr editor; + QString currentFileName; + std::unique_ptr syntax; + std::unique_ptr tree; +}; \ No newline at end of file diff --git a/include/Tree.h b/include/Tree.h index 9584606..4a54eb9 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -1,19 +1,21 @@ -#ifndef TREE_H -#define TREE_H +#pragma once #include -#include -#include #include +#include -class MainWindow; // Forward declaration +// Forward declarations +class MainWindow; +class QTreeView; +class QFileSystemModel; +class QFileIconProvider; class Tree : public QObject { Q_OBJECT public: - Tree(QSplitter *splitter, MainWindow *mainWindow); + explicit Tree(QSplitter *splitter, MainWindow *mainWindow); ~Tree(); private: @@ -21,11 +23,11 @@ class Tree : public QObject void setupModel(); void setupTree(); void openFile(const QModelIndex &index); - QString getDirectoryPath(); + QString getDirectoryPath() const; - QFileSystemModel *model; - QTreeView *tree; - MainWindow *mainWindow; -}; + std::unique_ptr m_iconProvider; + std::unique_ptr m_model; + std::unique_ptr m_tree; -#endif // TREE_H + MainWindow *m_mainWindow; +}; \ No newline at end of file From 113d20646634da16e2f8d757d64c05fb6c66bd4f Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:21:22 -0700 Subject: [PATCH 022/160] [Refactor] Replace include guards with #pragma once and change QList to QVector for syntax rules in Syntax.h --- include/Syntax.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/include/Syntax.h b/include/Syntax.h index 4b40f5a..9adfed0 100644 --- a/include/Syntax.h +++ b/include/Syntax.h @@ -1,5 +1,4 @@ -#ifndef SYNTAX_H -#define SYNTAX_H +#pragma once #include #include @@ -22,7 +21,7 @@ class Syntax : public QSyntaxHighlighter QRegularExpression pattern; QTextCharFormat format; }; - QList syntaxRules; + QVector syntaxRules; QTextCharFormat keywordFormat; QTextCharFormat singleLineCommentFormat; @@ -30,8 +29,15 @@ class Syntax : public QSyntaxHighlighter QTextCharFormat functionFormat; QTextCharFormat parenthesisFormat; QTextCharFormat charFormat; + QTextCharFormat iterationFormat; void addPattern(const QString &pattern, const QTextCharFormat &format); -}; -#endif // SYNTAX_H \ No newline at end of file + // Initialization functions for different syntax highlighting rules + void initKeywordRules(); + void initCommentRules(); + void initFunctionRules(); + void initParenthesisRules(); + void initCharRules(); + void initQuotationRules(); +}; From b12e995453d8043bf5e5ff1f5cce2a9e7e710df6 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:21:37 -0700 Subject: [PATCH 023/160] [Refactor] Improve memory management by using smart pointers for editor and tree in MainWindow --- src/MainWindow.cpp | 47 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 1333f98..c0b5b94 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,6 +1,7 @@ #include "MainWindow.h" #include "Syntax.h" #include "Tree.h" +#include "CodeEditor.h" #include #include @@ -14,8 +15,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - editor(new CodeEditor(this)), - syntax(new Syntax(editor->document())), + editor(std::make_unique(this)), + syntax(std::make_unique(editor->document())), tree(nullptr) { setWindowTitle("CodeAstra ~ Code Editor"); @@ -38,10 +39,9 @@ void MainWindow::initTree() QSplitter *splitter = new QSplitter(Qt::Horizontal, this); setCentralWidget(splitter); - tree = new Tree(splitter, this); - - splitter->addWidget(editor); + tree = std::make_unique(splitter, this); + splitter->addWidget(editor.get()); splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); splitter->setHandleWidth(5); splitter->setSizes(QList() << 150 << 800); @@ -158,27 +158,7 @@ void MainWindow::openFile() "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); if (!fileName.isEmpty()) { - QFile file(fileName); - if (!file.open(QFile::ReadOnly | QFile::Text)) - { - QMessageBox::warning(this, "Error", "Cannot open file: " + file.errorString()); - return; - } - - QTextStream in(&file); - if (editor) - { - editor->setPlainText(in.readAll()); - } - else - { - QMessageBox::critical(this, "Error", "Editor is not initialized."); - } - file.close(); - - currentFileName = fileName; - - setWindowTitle("CodeAstra ~ " + QFileInfo(fileName).fileName()); + loadFileInEditor(fileName); } } @@ -202,6 +182,11 @@ void MainWindow::saveFile() { out << editor->toPlainText(); } + else + { + QMessageBox::critical(this, "Error", "Editor is not initialized."); + return; + } file.close(); statusBar()->showMessage("File saved successfully.", 2000); @@ -229,7 +214,15 @@ void MainWindow::loadFileInEditor(const QString &filePath) } QTextStream in(&file); - editor->setPlainText(in.readAll()); + if (editor) + { + editor->setPlainText(in.readAll()); + } + else + { + QMessageBox::critical(this, "Error", "Editor is not initialized."); + return; + } file.close(); currentFileName = filePath; From 53aafe5b64a042ece41077a70388b10021ffebaa Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:22:21 -0700 Subject: [PATCH 024/160] [Refactor] Organize syntax highlighting rules into separate initialization functions for better readability and maintainability --- src/Syntax.cpp | 132 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 43 deletions(-) diff --git a/src/Syntax.cpp b/src/Syntax.cpp index 553c927..3b35f4a 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -1,49 +1,95 @@ #include "Syntax.h" +#include +#include -Syntax::Syntax(QTextDocument *parent) : QSyntaxHighlighter(parent) +Syntax::Syntax(QTextDocument *parent) + : QSyntaxHighlighter(parent) { - keywordFormat.setForeground(Qt::blue); - keywordFormat.setFontWeight(QFont::Bold); - QStringList keywordPatterns; - 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" << "\\bforeach\\b"; - foreach (const QString &pattern, keywordPatterns) - { - addPattern(pattern, keywordFormat); - } + initKeywordRules(); + initFunctionRules(); + initParenthesisRules(); + initCharRules(); - // Single line comment format expression - singleLineCommentFormat.setForeground(Qt::darkGray); - addPattern("//[^\n]*", singleLineCommentFormat); + // Keep these two calls are at the end + // to correctly apply the rules for strings and comments + initCommentRules(); + initQuotationRules(); +} + +void Syntax::initKeywordRules() +{ + // Keyword format + keywordFormat.setForeground(Qt::blue); + keywordFormat.setFontWeight(QFont::Bold); + + 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", "\\bforeach\\b"}; + + for (const QString &pattern : keywordPatterns) + { + addPattern(pattern, keywordFormat); + } - // Double quotation mark for string - quotationMark.setForeground(Qt::darkGreen); - addPattern("\".*\"", quotationMark); + iterationFormat.setForeground(Qt::darkMagenta); + iterationFormat.setFontWeight(QFont::Bold); + QStringList iterationPatterns = { + "\\bfor\\b", "\\bwhile\\b", "\\bdo\\b", "\\bif\\b", "\\belse\\b"}; + for (const QString &pattern : iterationPatterns) + { + addPattern(pattern, iterationFormat); + } +} - // Function format expression - functionFormat.setFontItalic(true); - functionFormat.setForeground(Qt::darkYellow); - addPattern("\\b[a-zA-Z_][a-zA-Z0-9_]*(?=\\s*\\()", functionFormat); +void Syntax::initCommentRules() +{ + // Single line comment format expression + singleLineCommentFormat.setForeground(Qt::darkGray); + addPattern("//[^\n]*", singleLineCommentFormat); - // Color pattern for parenthesis - QColor parenthesisColor("#6495ED"); - parenthesisFormat.setForeground(parenthesisColor); - addPattern("[()]", parenthesisFormat); + // TO-DO: Add multi-line comment support +} - // Regex for single character format 'a', '\n', etc - charFormat.setForeground(Qt::darkCyan); - addPattern("'(\\\\.|[^'])'", charFormat); +void Syntax::initQuotationRules() +{ + // Double quotation mark for string + quotationMark.setForeground(Qt::darkGreen); + addPattern("\"(\\\\.|[^\"\\\\])*\"", quotationMark); + + // TO-DO: Add single quotation mark for character +} + +void Syntax::initFunctionRules() +{ + // Function format expression + functionFormat.setFontItalic(true); + functionFormat.setForeground(Qt::darkYellow); + addPattern("\\b(?!for\\b|if\\b|else\\b|while\\b|do\\b)[a-zA-Z_][a-zA-Z0-9_]*(?=\\s*\\()", functionFormat); +} + +void Syntax::initParenthesisRules() +{ + // Color pattern for parenthesis + QColor parenthesisColor("#6495ED"); + parenthesisFormat.setForeground(parenthesisColor); + addPattern("[()]", parenthesisFormat); +} + +// Regex for single character format 'a', '\n', etc. +void Syntax::initCharRules() +{ + charFormat.setForeground(Qt::darkCyan); + addPattern("'(\\\\.|[^'])'", charFormat); } -// Add syntax highlighting patterns void Syntax::addPattern(const QString &pattern, const QTextCharFormat &format) { SyntaxRule rule; @@ -54,13 +100,13 @@ void Syntax::addPattern(const QString &pattern, const QTextCharFormat &format) void Syntax::highlightBlock(const QString &text) { - foreach (const SyntaxRule &rule, syntaxRules) - { - QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); - while (matchIterator.hasNext()) + for (const SyntaxRule &rule : syntaxRules) { - QRegularExpressionMatch match = matchIterator.next(); - setFormat(match.capturedStart(), match.capturedLength(), rule.format); + QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); + while (matchIterator.hasNext()) + { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.format); + } } - } } From ed48a0c7ee721a592265f5557ebe8ff442f5dd2d Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:22:38 -0700 Subject: [PATCH 025/160] [Refactor] Replace raw pointers with smart pointers for improved memory management in Tree class --- src/Tree.cpp | 112 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index 01adf03..acaabde 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -4,76 +4,102 @@ #include #include +#include #include #include Tree::Tree(QSplitter *splitter, MainWindow *mainWindow) : QObject(splitter), - model(new QFileSystemModel()), - tree(new QTreeView(splitter)), - mainWindow(mainWindow) + m_iconProvider(std::make_unique()), + m_model(std::make_unique()), + m_tree(std::make_unique(splitter)), + m_mainWindow(mainWindow) { - setupModel(); - setupTree(); + setupModel(); + setupTree(); - connect(tree, &QTreeView::doubleClicked, this, &Tree::openFile); + connect(m_tree.get(), &QTreeView::doubleClicked, this, &Tree::openFile); } Tree::~Tree() {} void Tree::setupModel() { - model->setRootPath(getDirectoryPath()); - model->setIconProvider(new QFileIconProvider); - model->setFilter(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot); + m_model->setRootPath(getDirectoryPath()); + m_model->setIconProvider(m_iconProvider.get()); + m_model->setFilter(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot); } void Tree::setupTree() { - tree->setModel(model); - tree->setRootIndex(model->index(model->rootPath())); - tree->setRootIsDecorated(true); - tree->setAnimated(true); - tree->setIndentation(20); - tree->setSortingEnabled(false); - tree->sortByColumn(1, Qt::AscendingOrder); - tree->setHeaderHidden(true); - - for (int i = 1; i <= model->columnCount(); ++i) - { - tree->setColumnHidden(i, true); - } - - tree->setContextMenuPolicy(Qt::CustomContextMenu); - connect(tree, &QTreeView::customContextMenuRequested, this, &Tree::showContextMenu); + m_tree->setModel(m_model.get()); + m_tree->setRootIndex(m_model->index(m_model->rootPath())); + m_tree->setRootIsDecorated(true); + m_tree->setAnimated(true); + m_tree->setIndentation(20); + m_tree->setSortingEnabled(false); + m_tree->sortByColumn(1, Qt::AscendingOrder); + m_tree->setHeaderHidden(true); + + for (int i = 1; i <= m_model->columnCount(); ++i) + { + m_tree->setColumnHidden(i, true); + } + + m_tree->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_tree.get(), &QTreeView::customContextMenuRequested, this, &Tree::showContextMenu); } -QString Tree::getDirectoryPath() +QString Tree::getDirectoryPath() const { - return QFileDialog::getExistingDirectory( - nullptr, QObject::tr("Open Directory"), QDir::homePath(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + return QFileDialog::getExistingDirectory( + nullptr, QObject::tr("Open Directory"), QDir::homePath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); } void Tree::openFile(const QModelIndex &index) { - QString filePath = model->filePath(index); - QFileInfo fileInfo(filePath); - - // Ensure it's a file, not a folder before loading - if (fileInfo.isFile()) - { - mainWindow->loadFileInEditor(filePath); - } + QString filePath = m_model->filePath(index); + QFileInfo fileInfo(filePath); + + // Ensure it's a file, not a folder before loading + if (!fileInfo.exists() || !fileInfo.isFile()) + { + qWarning() << "Selected index is not a valid file:" << filePath; + return; + } + + m_mainWindow->loadFileInEditor(filePath); } +// Context menu for file operations +// such as creating new files, folders, renaming, and deleting +// This function is called when the user right-clicks on the tree view void Tree::showContextMenu(const QPoint &pos) { - // TO_DO: Implement delete a file - // TO_DO: Implement rename a file - // TO_DO: Implement create a new file - // TO_DO: Implement create a new folder + QMenu contextMenu; + + QAction *newFileAction = contextMenu.addAction("New File"); + QAction *newFolderAction = contextMenu.addAction("New Folder"); + QAction *renameAction = contextMenu.addAction("Rename"); + QAction *deleteAction = contextMenu.addAction("Delete"); + + QAction *selectedAction = contextMenu.exec(m_tree->viewport()->mapToGlobal(pos)); - // use pos param for testing purpose for now - tree->indexAt(pos); + if (selectedAction == newFileAction) + { + // TO-DO: implement file creation + } + else if (selectedAction == newFolderAction) + { + // TO-DO: implement folder creation + } + else if (selectedAction == renameAction) + { + // TO-DO: implement rename file/folder + } + else if (selectedAction == deleteAction) + { + // TO-DO: implement file deletion + } } From a6657d0295cc656d4b4c95dfb5ea2eecfcd14e57 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 19:40:13 -0700 Subject: [PATCH 026/160] [Refactor] Consistently prefix member variables with 'm_' in CodeEditor, LineNumberArea, MainWindow, and Syntax classes for improved readability --- include/CodeEditor.h | 2 +- include/LineNumberArea.h | 8 +++---- include/MainWindow.h | 8 +++---- include/Syntax.h | 22 +++++++++--------- src/CodeEditor.cpp | 14 ++++++------ src/MainWindow.cpp | 32 +++++++++++++------------- src/Syntax.cpp | 49 ++++++++++++++++++++-------------------- 7 files changed, 67 insertions(+), 68 deletions(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index 4054768..b3d1ed5 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -29,5 +29,5 @@ private slots: void updateLineNumberArea(const QRect &rect, int dy); private: - QWidget *lineNumberArea; + QWidget *m_lineNumberArea; }; \ No newline at end of file diff --git a/include/LineNumberArea.h b/include/LineNumberArea.h index dd5980c..64f923d 100644 --- a/include/LineNumberArea.h +++ b/include/LineNumberArea.h @@ -9,19 +9,19 @@ class LineNumberArea : public QWidget { public: - LineNumberArea(CodeEditor *editor) : QWidget(editor), m_codeEditor(editor) {} + LineNumberArea(CodeEditor *editor) : QWidget(editor), codeEditor(editor) {} QSize sizeHint() const override { - return QSize(m_codeEditor->lineNumberAreaWidth(), 0); + return QSize(codeEditor->lineNumberAreaWidth(), 0); } protected: void paintEvent(QPaintEvent *event) override { - m_codeEditor->lineNumberAreaPaintEvent(event); + codeEditor->lineNumberAreaPaintEvent(event); } private: - CodeEditor *m_codeEditor; + CodeEditor *codeEditor; }; \ No newline at end of file diff --git a/include/MainWindow.h b/include/MainWindow.h index 402bc18..9487c91 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -39,8 +39,8 @@ private slots: QAction *createAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, const QString &statusTip, void (MainWindow::*slot)()); - std::unique_ptr editor; - QString currentFileName; - std::unique_ptr syntax; - std::unique_ptr tree; + std::unique_ptr m_editor; + std::unique_ptr m_syntax; + std::unique_ptr m_tree; + QString m_currentFileName; }; \ No newline at end of file diff --git a/include/Syntax.h b/include/Syntax.h index 9adfed0..35f0ea0 100644 --- a/include/Syntax.h +++ b/include/Syntax.h @@ -18,18 +18,18 @@ class Syntax : public QSyntaxHighlighter private: struct SyntaxRule { - QRegularExpression pattern; - QTextCharFormat format; + QRegularExpression m_pattern; + QTextCharFormat m_format; }; - QVector syntaxRules; - - QTextCharFormat keywordFormat; - QTextCharFormat singleLineCommentFormat; - QTextCharFormat quotationMark; - QTextCharFormat functionFormat; - QTextCharFormat parenthesisFormat; - QTextCharFormat charFormat; - QTextCharFormat iterationFormat; + QVector m_syntaxRules; + + QTextCharFormat m_keywordFormat; + QTextCharFormat m_singleLineCommentFormat; + QTextCharFormat m_quotationMark; + QTextCharFormat m_functionFormat; + QTextCharFormat m_parenthesisFormat; + QTextCharFormat m_charFormat; + QTextCharFormat m_iterationFormat; void addPattern(const QString &pattern, const QTextCharFormat &format); diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 3ccb5be..47c88ab 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -7,7 +7,7 @@ CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), - lineNumberArea(new LineNumberArea(this)) + m_lineNumberArea(new LineNumberArea(this)) { connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth); connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea); @@ -82,11 +82,11 @@ void CodeEditor::updateLineNumberArea(const QRect &rect, int dy) { if (dy) { - lineNumberArea->scroll(0, dy); + m_lineNumberArea->scroll(0, dy); } else { - lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); + m_lineNumberArea->update(0, rect.y(), m_lineNumberArea->width(), rect.height()); } if (rect.contains(viewport()->rect())) @@ -100,7 +100,7 @@ void CodeEditor::resizeEvent(QResizeEvent *e) QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); - lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); + m_lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); } void CodeEditor::highlightCurrentLine() @@ -128,13 +128,13 @@ void CodeEditor::highlightCurrentLine() void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) { - QPainter painter(lineNumberArea); + QPainter painter(m_lineNumberArea); // Match the background color of the editor painter.fillRect(event->rect(), palette().color(QPalette::Base)); // Draw a separating line between the number area and the text editor - int separatorX = lineNumberArea->width() - 4; + int separatorX = m_lineNumberArea->width() - 4; painter.drawLine(separatorX, event->rect().top(), separatorX, event->rect().bottom()); QTextBlock block = firstVisibleBlock(); @@ -152,7 +152,7 @@ void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) QString number = QString::number(blockNumber + 1); painter.setPen(Qt::darkGray); - painter.drawText(0, top + padding, lineNumberArea->width(), lineHeight, + painter.drawText(0, top + padding, m_lineNumberArea->width(), lineHeight, Qt::AlignCenter, number); } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c0b5b94..5f29718 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -15,17 +15,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - editor(std::make_unique(this)), - syntax(std::make_unique(editor->document())), - tree(nullptr) + m_editor(std::make_unique(this)), + m_syntax(std::make_unique(m_editor->document())), + m_tree(nullptr) { setWindowTitle("CodeAstra ~ Code Editor"); // Set tab width to 4 spaces - QFontMetrics metrics(editor->font()); + QFontMetrics metrics(m_editor->font()); int spaceWidth = metrics.horizontalAdvance(" "); - editor->setTabStopDistance(spaceWidth * 4); - editor->setLineWrapMode(QPlainTextEdit::NoWrap); + m_editor->setTabStopDistance(spaceWidth * 4); + m_editor->setLineWrapMode(QPlainTextEdit::NoWrap); initTree(); createMenuBar(); @@ -39,9 +39,9 @@ void MainWindow::initTree() QSplitter *splitter = new QSplitter(Qt::Horizontal, this); setCentralWidget(splitter); - tree = std::make_unique(splitter, this); + m_tree = std::make_unique(splitter, this); - splitter->addWidget(editor.get()); + splitter->addWidget(m_editor.get()); splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); splitter->setHandleWidth(5); splitter->setSizes(QList() << 150 << 800); @@ -164,13 +164,13 @@ void MainWindow::openFile() void MainWindow::saveFile() { - if (currentFileName.isEmpty()) + if (m_currentFileName.isEmpty()) { saveFileAs(); return; } - QFile file(currentFileName); + QFile file(m_currentFileName); if (!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, "Error", "Cannot save file: " + file.errorString()); @@ -178,9 +178,9 @@ void MainWindow::saveFile() } QTextStream out(&file); - if (editor) + if (m_editor) { - out << editor->toPlainText(); + out << m_editor->toPlainText(); } else { @@ -199,7 +199,7 @@ void MainWindow::saveFileAs() if (!fileName.isEmpty()) { - currentFileName = fileName; + m_currentFileName = fileName; saveFile(); } } @@ -214,9 +214,9 @@ void MainWindow::loadFileInEditor(const QString &filePath) } QTextStream in(&file); - if (editor) + if (m_editor) { - editor->setPlainText(in.readAll()); + m_editor->setPlainText(in.readAll()); } else { @@ -225,6 +225,6 @@ void MainWindow::loadFileInEditor(const QString &filePath) } file.close(); - currentFileName = filePath; + m_currentFileName = filePath; setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); } diff --git a/src/Syntax.cpp b/src/Syntax.cpp index 3b35f4a..9f841c2 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -19,8 +19,8 @@ Syntax::Syntax(QTextDocument *parent) void Syntax::initKeywordRules() { // Keyword format - keywordFormat.setForeground(Qt::blue); - keywordFormat.setFontWeight(QFont::Bold); + m_keywordFormat.setForeground(Qt::blue); + m_keywordFormat.setFontWeight(QFont::Bold); QStringList keywordPatterns = { "\\bchar\\b", "\\bclass\\b", "\\bconst\\b", @@ -36,24 +36,23 @@ void Syntax::initKeywordRules() for (const QString &pattern : keywordPatterns) { - addPattern(pattern, keywordFormat); + addPattern(pattern, m_keywordFormat); } - iterationFormat.setForeground(Qt::darkMagenta); - iterationFormat.setFontWeight(QFont::Bold); - QStringList iterationPatterns = { - "\\bfor\\b", "\\bwhile\\b", "\\bdo\\b", "\\bif\\b", "\\belse\\b"}; + m_iterationFormat.setForeground(Qt::darkMagenta); + m_iterationFormat.setFontWeight(QFont::Bold); + QStringList iterationPatterns = {"\\bfor\\b", "\\bwhile\\b", "\\bdo\\b", "\\bif\\b", "\\belse\\b"}; for (const QString &pattern : iterationPatterns) { - addPattern(pattern, iterationFormat); + addPattern(pattern, m_iterationFormat); } } void Syntax::initCommentRules() { // Single line comment format expression - singleLineCommentFormat.setForeground(Qt::darkGray); - addPattern("//[^\n]*", singleLineCommentFormat); + m_singleLineCommentFormat.setForeground(Qt::darkGray); + addPattern("//[^\n]*", m_singleLineCommentFormat); // TO-DO: Add multi-line comment support } @@ -61,8 +60,8 @@ void Syntax::initCommentRules() void Syntax::initQuotationRules() { // Double quotation mark for string - quotationMark.setForeground(Qt::darkGreen); - addPattern("\"(\\\\.|[^\"\\\\])*\"", quotationMark); + m_quotationMark.setForeground(Qt::darkGreen); + addPattern("\"(\\\\.|[^\"\\\\])*\"", m_quotationMark); // TO-DO: Add single quotation mark for character } @@ -70,43 +69,43 @@ void Syntax::initQuotationRules() void Syntax::initFunctionRules() { // Function format expression - functionFormat.setFontItalic(true); - functionFormat.setForeground(Qt::darkYellow); - addPattern("\\b(?!for\\b|if\\b|else\\b|while\\b|do\\b)[a-zA-Z_][a-zA-Z0-9_]*(?=\\s*\\()", functionFormat); + m_functionFormat.setFontItalic(true); + m_functionFormat.setForeground(Qt::darkYellow); + addPattern("\\b(?!for\\b|if\\b|else\\b|while\\b|do\\b)[a-zA-Z_][a-zA-Z0-9_]*(?=\\s*\\()", m_functionFormat); } void Syntax::initParenthesisRules() { // Color pattern for parenthesis QColor parenthesisColor("#6495ED"); - parenthesisFormat.setForeground(parenthesisColor); - addPattern("[()]", parenthesisFormat); + m_parenthesisFormat.setForeground(parenthesisColor); + addPattern("[()]", m_parenthesisFormat); } // Regex for single character format 'a', '\n', etc. void Syntax::initCharRules() { - charFormat.setForeground(Qt::darkCyan); - addPattern("'(\\\\.|[^'])'", charFormat); + m_charFormat.setForeground(Qt::darkCyan); + addPattern("'(\\\\.|[^'])'", m_charFormat); } void Syntax::addPattern(const QString &pattern, const QTextCharFormat &format) { SyntaxRule rule; - rule.pattern = QRegularExpression(pattern); - rule.format = format; - syntaxRules.append(rule); + rule.m_pattern = QRegularExpression(pattern); + rule.m_format = format; + m_syntaxRules.append(rule); } void Syntax::highlightBlock(const QString &text) { - for (const SyntaxRule &rule : syntaxRules) + for (const SyntaxRule &rule : m_syntaxRules) { - QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); + QRegularExpressionMatchIterator matchIterator = rule.m_pattern.globalMatch(text); while (matchIterator.hasNext()) { QRegularExpressionMatch match = matchIterator.next(); - setFormat(match.capturedStart(), match.capturedLength(), rule.format); + setFormat(match.capturedStart(), match.capturedLength(), rule.m_format); } } } From a714767c1632335d5d9dba986bea869b52719f10 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 20:13:05 -0700 Subject: [PATCH 027/160] [Refactor] Emit status messages on mode changes in CodeEditor for improved user feedback --- include/CodeEditor.h | 3 +++ src/CodeEditor.cpp | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index b3d1ed5..2ab350e 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -19,6 +19,9 @@ class CodeEditor : public QPlainTextEdit void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); +signals: + void statusMessageChanged(const QString &message); + protected: void keyPressEvent(QKeyEvent *event) override; void resizeEvent(QResizeEvent *event) override; diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 47c88ab..217e0c4 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -4,6 +4,7 @@ #include #include +#include CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), @@ -33,6 +34,7 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) { case Qt::Key_I: mode = INSERT; + emit statusMessageChanged("Insert mode activated"); break; case Qt::Key_A: moveCursor(QTextCursor::Left); @@ -46,14 +48,22 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) case Qt::Key_W: moveCursor(QTextCursor::Up); break; - case Qt::Key_Escape: - mode = NORMAL; + default: + emit statusMessageChanged("Insert mode is not active. Press 'i' to enter insert mode."); break; } } - else + else if (mode == INSERT) { - QPlainTextEdit::keyPressEvent(event); + if (event->key() == Qt::Key_Escape) + { + mode = NORMAL; + emit statusMessageChanged("Normal mode activated. Press 'escape' to return to normal mode."); + } + else + { + QPlainTextEdit::keyPressEvent(event); + } } } From 1be84619c743202f9cff2176ac122a2d17ad5e14 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 20:13:13 -0700 Subject: [PATCH 028/160] [Refactor] Enhance status message display in MainWindow with timestamps for better user feedback --- src/MainWindow.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5f29718..fa5cf71 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -21,6 +21,12 @@ MainWindow::MainWindow(QWidget *parent) { setWindowTitle("CodeAstra ~ Code Editor"); + connect(m_editor.get(), &CodeEditor::statusMessageChanged, this, [this](const QString &message) + { + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss"); + statusBar()->showMessage("[" + timestamp + "] " + message, 4000); + }); + // Set tab width to 4 spaces QFontMetrics metrics(m_editor->font()); int spaceWidth = metrics.horizontalAdvance(" "); From 8fbb4f4f123b318756b9fd4246af6b494168816c Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 20:19:29 -0700 Subject: [PATCH 029/160] [Refactor] Emit status message on file save in MainWindow for improved user feedback --- src/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index fa5cf71..4078c85 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -195,7 +195,7 @@ void MainWindow::saveFile() } file.close(); - statusBar()->showMessage("File saved successfully.", 2000); + emit m_editor->statusMessageChanged("File saved successfully."); } void MainWindow::saveFileAs() From 2533fed09c9140f8277f0dce2e025b09105b5c07 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 22:43:20 -0700 Subject: [PATCH 030/160] [Refactor] Remove unused file handling methods and reorganize member variables in MainWindow for improved clarity --- include/MainWindow.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 9487c91..bd786f3 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -11,6 +11,7 @@ class CodeEditor; class Syntax; class Tree; +class FileManager; class MainWindow : public QMainWindow { @@ -19,16 +20,12 @@ class MainWindow : public QMainWindow public: explicit MainWindow(QWidget *parent = nullptr); virtual ~MainWindow(); - void loadFileInEditor(const QString &filePath); + // Initialize the file tree view and set it as the central widget // of the main window, alongside the code editor void initTree(); private slots: - void newFile(); - void openFile(); - void saveFile(); - void saveFileAs(); void showAbout(); private: @@ -38,9 +35,11 @@ private slots: void createAppActions(QMenu *appMenu); QAction *createAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, const QString &statusTip, - void (MainWindow::*slot)()); + const std::function &slot); + std::unique_ptr m_editor; std::unique_ptr m_syntax; std::unique_ptr m_tree; - QString m_currentFileName; + + FileManager * m_FileManager; }; \ No newline at end of file From 38b19c84720ae68392cb70742589d9abf00b6bd9 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 22:43:33 -0700 Subject: [PATCH 031/160] [Refactor] Update Tree class constructor to use FileManager instead of MainWindow for improved modularity --- include/Tree.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Tree.h b/include/Tree.h index 4a54eb9..039d9a1 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -5,17 +5,17 @@ #include // Forward declarations -class MainWindow; class QTreeView; class QFileSystemModel; class QFileIconProvider; +class FileManager; class Tree : public QObject { Q_OBJECT public: - explicit Tree(QSplitter *splitter, MainWindow *mainWindow); + explicit Tree(QSplitter *splitter, FileManager *FileManager); ~Tree(); private: @@ -29,5 +29,5 @@ class Tree : public QObject std::unique_ptr m_model; std::unique_ptr m_tree; - MainWindow *m_mainWindow; + FileManager * m_FileManager; }; \ No newline at end of file From ddc3af4ba757e35b590bf9c2b3715bf8f392ede0 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 22:43:46 -0700 Subject: [PATCH 032/160] [Refactor] Update Tree class constructor to use FileManager instead of MainWindow for improved modularity --- src/Tree.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index acaabde..cd02124 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -1,19 +1,20 @@ #include "Tree.h" -#include "MainWindow.h" #include "CodeEditor.h" +#include "FileManager.h" #include #include #include #include #include +#include -Tree::Tree(QSplitter *splitter, MainWindow *mainWindow) +Tree::Tree(QSplitter *splitter, FileManager *FileManager) : QObject(splitter), m_iconProvider(std::make_unique()), m_model(std::make_unique()), m_tree(std::make_unique(splitter)), - m_mainWindow(mainWindow) + m_FileManager(FileManager) { setupModel(); setupTree(); @@ -69,7 +70,7 @@ void Tree::openFile(const QModelIndex &index) return; } - m_mainWindow->loadFileInEditor(filePath); + m_FileManager->loadFileInEditor(filePath); } // Context menu for file operations From 6b06bae7ba6166e2221cf19f1fdbaf05bf816dc9 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 22:43:55 -0700 Subject: [PATCH 033/160] [Refactor] Integrate FileManager into MainWindow for improved file handling and modularity --- src/MainWindow.cpp | 98 +++++----------------------------------------- 1 file changed, 9 insertions(+), 89 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 4078c85..0365279 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -2,6 +2,7 @@ #include "Syntax.h" #include "Tree.h" #include "CodeEditor.h" +#include "FileManager.h" #include #include @@ -17,7 +18,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_editor(std::make_unique(this)), m_syntax(std::make_unique(m_editor->document())), - m_tree(nullptr) + m_tree(nullptr), + m_FileManager(new FileManager(m_editor.get(), this)) { setWindowTitle("CodeAstra ~ Code Editor"); @@ -45,7 +47,7 @@ void MainWindow::initTree() QSplitter *splitter = new QSplitter(Qt::Horizontal, this); setCentralWidget(splitter); - m_tree = std::make_unique(splitter, this); + m_tree = std::make_unique(splitter, m_FileManager); splitter->addWidget(m_editor.get()); splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -74,11 +76,11 @@ void MainWindow::createMenuBar() void MainWindow::createFileActions(QMenu *fileMenu) { - fileMenu->addAction(createAction(QIcon(), tr("&New"), QKeySequence::New, tr("Create a new file"), &MainWindow::newFile)); - fileMenu->addAction(createAction(QIcon(), tr("&Open"), QKeySequence::Open, tr("Open an existing file"), &MainWindow::openFile)); + fileMenu->addAction(createAction(QIcon(), tr("&New"), QKeySequence::New, tr("Create a new file"), [this]() { m_FileManager->newFile(); })); + fileMenu->addAction(createAction(QIcon(), tr("&Open"), QKeySequence::Open, tr("Open an existing file"), [this]() { m_FileManager->openFile(); })); fileMenu->addSeparator(); - fileMenu->addAction(createAction(QIcon(), tr("&Save"), QKeySequence::Save, tr("Save the current file"), &MainWindow::saveFile)); - fileMenu->addAction(createAction(QIcon(), tr("Save &As"), QKeySequence::SaveAs, tr("Save the file with a new name"), &MainWindow::saveFileAs)); + fileMenu->addAction(createAction(QIcon(), tr("&Save"), QKeySequence::Save, tr("Save the current file"), [this]() { m_FileManager->saveFile(); })); + fileMenu->addAction(createAction(QIcon(), tr("Save &As"), QKeySequence::SaveAs, tr("Save the file with a new name"), [this]() { m_FileManager->saveFileAs(); })); } void MainWindow::createHelpActions(QMenu *helpMenu) @@ -97,7 +99,7 @@ void MainWindow::createAppActions(QMenu *appMenu) appMenu->addAction(aboutAction); } -QAction *MainWindow::createAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, const QString &statusTip, void (MainWindow::*slot)()) +QAction *MainWindow::createAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, const QString &statusTip, const std::function &slot) { QAction *action = new QAction(icon, text, this); @@ -108,11 +110,6 @@ QAction *MainWindow::createAction(const QIcon &icon, const QString &text, const return action; } -void MainWindow::newFile() -{ - // TO-DO: Implement new file function -} - void MainWindow::showAbout() { // Extract the C++ version from the __cplusplus macro @@ -157,80 +154,3 @@ void MainWindow::showAbout() QMessageBox::about(this, tr("About"), aboutText); } - -void MainWindow::openFile() -{ - QString fileName = QFileDialog::getOpenFileName(this, "Open File", QString(), - "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); - if (!fileName.isEmpty()) - { - loadFileInEditor(fileName); - } -} - -void MainWindow::saveFile() -{ - if (m_currentFileName.isEmpty()) - { - saveFileAs(); - return; - } - - QFile file(m_currentFileName); - if (!file.open(QFile::WriteOnly | QFile::Text)) - { - QMessageBox::warning(this, "Error", "Cannot save file: " + file.errorString()); - return; - } - - QTextStream out(&file); - if (m_editor) - { - out << m_editor->toPlainText(); - } - else - { - QMessageBox::critical(this, "Error", "Editor is not initialized."); - return; - } - file.close(); - - emit m_editor->statusMessageChanged("File saved successfully."); -} - -void MainWindow::saveFileAs() -{ - QString fileName = QFileDialog::getSaveFileName(this, "Save File As", QString(), - "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); - - if (!fileName.isEmpty()) - { - m_currentFileName = fileName; - saveFile(); - } -} - -void MainWindow::loadFileInEditor(const QString &filePath) -{ - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - QMessageBox::warning(this, "Error", "Cannot open file: " + file.errorString()); - return; - } - - QTextStream in(&file); - if (m_editor) - { - m_editor->setPlainText(in.readAll()); - } - else - { - QMessageBox::critical(this, "Error", "Editor is not initialized."); - return; - } - file.close(); - - m_currentFileName = filePath; - setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); -} From 1497e7fa316ce2665dba80a0722ed543d3880da8 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 22:44:19 -0700 Subject: [PATCH 034/160] [Feature] Implement FileManager class for handling file operations including new, save, and open functionalities --- include/FileManager.h | 31 ++++++++++++ src/FileManager.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 include/FileManager.h create mode 100644 src/FileManager.cpp diff --git a/include/FileManager.h b/include/FileManager.h new file mode 100644 index 0000000..e311f33 --- /dev/null +++ b/include/FileManager.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +class CodeEditor; +class MainWindow; + +/** + * @class FileManager + * @brief Manages file operations such as creating, saving, and opening files. + */ +class FileManager : public QObject +{ + Q_OBJECT + +public: + FileManager(CodeEditor *editor, MainWindow *mainWindow); + ~FileManager(); + +public slots: + void newFile(); + void saveFile(); + void saveFileAs(); + void openFile(); + void loadFileInEditor(const QString &filePath); + +private: + std::unique_ptr m_editor; + QString m_currentFileName; + MainWindow *m_mainWindow; +}; diff --git a/src/FileManager.cpp b/src/FileManager.cpp new file mode 100644 index 0000000..858ca2a --- /dev/null +++ b/src/FileManager.cpp @@ -0,0 +1,109 @@ +#include "FileManager.h" + +#include +#include +#include +#include +#include "CodeEditor.h" +#include "MainWindow.h" + +FileManager::FileManager(CodeEditor *editor, MainWindow *mainWindow) + : m_editor(editor), + m_currentFileName(""), + m_mainWindow(mainWindow) +{ + if (!m_editor) + { + qWarning() << "Editor is NOT initialized in FileManager!"; + } + else + { + qDebug() << "Editor is properly initialized in FileManager."; + } +} + +FileManager::~FileManager() {} + +void FileManager::newFile() +{ + // Logic to create a new file +} + +void FileManager::saveFile() +{ + qDebug() << "Saving file:" << m_currentFileName; + if (m_currentFileName.isEmpty()) + { + saveFileAs(); + return; + } + + QFile file(m_currentFileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) + { + QMessageBox::warning(nullptr, "Error", "Cannot save file: " + file.errorString()); + return; + } + + QTextStream out(&file); + if (m_editor) + { + out << m_editor->toPlainText(); + } + else + { + QMessageBox::critical(nullptr, "Error", "Editor is not initialized."); + return; + } + file.close(); + + emit m_editor->statusMessageChanged("File saved successfully."); +} + +void FileManager::saveFileAs() +{ + QString fileName = QFileDialog::getSaveFileName(nullptr, "Save File As", QString(), + "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); + + if (!fileName.isEmpty()) + { + m_currentFileName = fileName; + saveFile(); + } +} + +void FileManager::openFile() +{ + QString fileName = QFileDialog::getOpenFileName(nullptr, "Open File", QString(), + "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); + if (!fileName.isEmpty()) + { + loadFileInEditor(fileName); + } +} + +void FileManager::loadFileInEditor(const QString &filePath) +{ + qDebug() << "Loading file:" << filePath; + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QMessageBox::warning(nullptr, "Error", "Cannot open file: " + file.errorString()); + return; + } + + QTextStream in(&file); + if (m_editor) + { + m_editor->setPlainText(in.readAll()); + } + else + { + QMessageBox::critical(nullptr, "Error", "Editor is not initialized."); + return; + } + file.close(); + + m_currentFileName = filePath; + m_mainWindow->setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); +} \ No newline at end of file From 2c18d5a93c9eb6d3624d8f48c44ee0d4aad75c72 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 22:44:26 -0700 Subject: [PATCH 035/160] [Refactor] Add FileManager source and header files to CMakeLists for proper inclusion --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 75eb2f2..b1599e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,11 +56,13 @@ add_executable(${TARGET_NAME} src/CodeEditor.cpp src/Syntax.cpp src/Tree.cpp + src/FileManager.cpp include/MainWindow.h include/CodeEditor.h include/Syntax.h include/Tree.h include/LineNumberArea.h + include/FileManager.h ) qt_add_resources(APP_RESOURCES resources.qrc) From d4fbf88412f51bb7e98e0b481939a66feb2f535d Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 23:14:12 -0700 Subject: [PATCH 036/160] [Fix] Fix merge conflict --- include/CodeEditor.h | 3 --- src/CodeEditor.cpp | 1 + src/MainWindow.cpp | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index 8106ba9..740f180 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -24,9 +24,6 @@ class CodeEditor : public QPlainTextEdit signals: void statusMessageChanged(const QString &message); -signals: - void statusMessageChanged(const QString &message); - protected: void keyPressEvent(QKeyEvent *event) override; void resizeEvent(QResizeEvent *event) override; diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index a6d70f9..9070140 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -5,6 +5,7 @@ #include #include #include +#include CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 6e92fac..6d0e950 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -170,13 +170,13 @@ void MainWindow::openFile() void MainWindow::saveFile() { - if (editor->getCurrentFileName().isEmpty()) + if (m_editor->getCurrentFileName().isEmpty()) { saveFileAs(); return; } - QFile file(editor->getCurrentFileName()); + QFile file(m_editor->getCurrentFileName()); if (!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, "Error", "Cannot save file: " + file.errorString()); @@ -205,7 +205,7 @@ void MainWindow::saveFileAs() if (!fileName.isEmpty()) { - editor->setCurrentFileName(fileName); + m_editor->setCurrentFileName(fileName); saveFile(); } } @@ -231,6 +231,6 @@ void MainWindow::loadFileInEditor(const QString &filePath) } file.close(); - editor->setCurrentFileName(filePath); + m_editor->setCurrentFileName(filePath); setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); } From c84a1aafcfe98cfaff958def1b8b7872b8d807cd Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 23:17:32 -0700 Subject: [PATCH 037/160] [Refactor] Correct variable name for current file in CodeEditor class --- include/CodeEditor.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index 740f180..a0e04f2 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -18,8 +18,8 @@ class CodeEditor : public QPlainTextEdit Mode mode = NORMAL; void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); - QString getCurrentFileName() const { return currentFileName; } - void setCurrentFileName(const QString &fileName) { currentFileName = fileName; } + QString getCurrentFileName() const { return m_currentFileName; } + void setCurrentFileName(const QString &fileName) { m_currentFileName = fileName; } signals: void statusMessageChanged(const QString &message); @@ -35,10 +35,11 @@ private slots: private: QWidget *m_lineNumberArea; - QString currentFileName; + QString m_currentFileName; + QString getFileExtension(); void addLanguageSymbol(QTextCursor &cursor, const QString &commentSymbol); void commentSelection(QTextCursor &cursor, const QString &commentSymbol); void commentLine(QTextCursor &cursor, const QString &commentSymbol); - void addComment(); + void addComment(); }; \ No newline at end of file From 35016a46cda2b97b3efe45dc02ec87f437d0269e Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 20 Mar 2025 23:26:14 -0700 Subject: [PATCH 038/160] [Add] Emit status message on file load in MainWindow for improved user feedback --- src/MainWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 6d0e950..366b94d 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -233,4 +233,5 @@ void MainWindow::loadFileInEditor(const QString &filePath) m_editor->setCurrentFileName(filePath); setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); + emit m_editor->statusMessageChanged("File loaded successfully: " + QFileInfo(filePath).fileName()); } From 1c4683da8eab162d64619283d2011ea165f26923 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:43:52 -0700 Subject: [PATCH 039/160] [CI] Inital CI implementation Implement first version of GitHub Action CI for cross-platform testing --- .github/workflows/cpp.yml | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/cpp.yml diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml new file mode 100644 index 0000000..2356b05 --- /dev/null +++ b/.github/workflows/cpp.yml @@ -0,0 +1,72 @@ +name: C++ CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + + # Install C++ Compiler & Build Tools + - name: Set up C++ environment (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y g++ make cmake + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 100 + g++ --version + cmake --version + make --version + + - name: Set up C++ environment (macOS) + if: matrix.os == 'macos-latest' + run: | + brew install gcc make cmake + echo 'export PATH="/usr/local/opt/gcc/bin:$PATH"' >> ~/.bash_profile + source ~/.bash_profile + g++ --version + cmake --version + make --version + + - name: Set up C++ environment (Windows) + if: matrix.os == 'windows-latest' + run: | + choco install mingw --version=8.1.0-1 + choco install make cmake + echo C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + echo C:\ProgramData\chocolatey\lib\cmake\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + g++ --version + cmake --version + make --version + + # Install Qt6 + - name: Install Qt6 (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get install -y qt6-base-dev + + - name: Install Qt6 (macOS) + if: matrix.os == 'macos-latest' + run: | + brew install qt6 + echo 'export PATH="/opt/homebrew/opt/qt6/bin:$PATH"' >> ~/.bash_profile + source ~/.bash_profile + + - name: Install Qt6 (Windows) + if: matrix.os == 'windows-latest' + run: | + choco install qt6 --version=6.2.4 + echo C:\Qt\6.2.4\msvc2019_64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + # Build the Project + - name: Build project + run: make From 20cdcdedc1817f6579db673e34b5b98803165d8e Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:50:05 -0700 Subject: [PATCH 040/160] [CI] Fix bug on Ubuntu --- .github/workflows/cpp.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 2356b05..de91346 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -22,7 +22,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y g++ make cmake - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 100 g++ --version cmake --version make --version @@ -30,7 +29,7 @@ jobs: - name: Set up C++ environment (macOS) if: matrix.os == 'macos-latest' run: | - brew install gcc make cmake + brew install make cmake echo 'export PATH="/usr/local/opt/gcc/bin:$PATH"' >> ~/.bash_profile source ~/.bash_profile g++ --version From 64ce837162af1d219b9a70c4ea14a7b9766202fa Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:53:01 -0700 Subject: [PATCH 041/160] [CI] Bug Fix second attempt --- .github/workflows/cpp.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index de91346..fc1e907 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -52,6 +52,8 @@ jobs: if: matrix.os == 'ubuntu-latest' run: | sudo apt-get install -y qt6-base-dev + echo 'export PATH="/usr/lib/qt6/bin:$PATH"' >> ~/.bashrc + source ~/.bashrc - name: Install Qt6 (macOS) if: matrix.os == 'macos-latest' @@ -59,13 +61,16 @@ jobs: brew install qt6 echo 'export PATH="/opt/homebrew/opt/qt6/bin:$PATH"' >> ~/.bash_profile source ~/.bash_profile + qmake --version - name: Install Qt6 (Windows) if: matrix.os == 'windows-latest' run: | - choco install qt6 --version=6.2.4 - echo C:\Qt\6.2.4\msvc2019_64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + Invoke-WebRequest -Uri "https://download.qt.io/official_releases/online_installers/qt-unified-windows-x64-online.exe" -OutFile "qt-installer.exe" + Start-Process -FilePath ".\qt-installer.exe" -ArgumentList "--silent", "--platform minimal", "--accept-licenses", "--default-answer" -NoNewWindow -Wait + echo C:\Qt\6.6.0\msvc2019_64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + qmake --version # Build the Project - name: Build project - run: make + run: make build From 9650369be5b7e8c8c3c8516920b32dce1da4f370 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:59:01 -0700 Subject: [PATCH 042/160] [CI] Added installation and try fix window error --- .github/workflows/cpp.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index fc1e907..68d16b8 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -66,11 +66,15 @@ jobs: - name: Install Qt6 (Windows) if: matrix.os == 'windows-latest' run: | - Invoke-WebRequest -Uri "https://download.qt.io/official_releases/online_installers/qt-unified-windows-x64-online.exe" -OutFile "qt-installer.exe" - Start-Process -FilePath ".\qt-installer.exe" -ArgumentList "--silent", "--platform minimal", "--accept-licenses", "--default-answer" -NoNewWindow -Wait - echo C:\Qt\6.6.0\msvc2019_64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + python -m pip install aqtinstall + python -m aqt install-qt windows desktop 6.6.0 win64_mingw --outputdir C:\Qt + echo C:\Qt\6.6.0\mingw_64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append qmake --version # Build the Project - name: Build project run: make build + + # Install the Project with desktop shortcut + - name: Install project + run: echo "y" | make install From 38c9317adcd0b6ad1f4251717d538382bd1546ed Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:11:42 -0700 Subject: [PATCH 043/160] [CI] fix bug on errors --- .github/workflows/cpp.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 68d16b8..3cbd8e1 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -12,6 +12,9 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] + fail-fast: false + + continue-on-error: true steps: - uses: actions/checkout@v4 @@ -61,7 +64,6 @@ jobs: brew install qt6 echo 'export PATH="/opt/homebrew/opt/qt6/bin:$PATH"' >> ~/.bash_profile source ~/.bash_profile - qmake --version - name: Install Qt6 (Windows) if: matrix.os == 'windows-latest' @@ -69,7 +71,6 @@ jobs: python -m pip install aqtinstall python -m aqt install-qt windows desktop 6.6.0 win64_mingw --outputdir C:\Qt echo C:\Qt\6.6.0\mingw_64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - qmake --version # Build the Project - name: Build project From 923f2ea06ee518cb0c8ff6892db77be9a34e64ae Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:16:45 -0700 Subject: [PATCH 044/160] [CI Bug] Fix bug on windows python version + linux install work around --- .github/workflows/cpp.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 3cbd8e1..33e294a 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -19,6 +19,13 @@ jobs: steps: - uses: actions/checkout@v4 + # Set up Python 3.10 or later + - name: Set up Python 3.10 (Windows) + if: matrix.os == 'windows-latest' + uses: actions/setup-python@v2 + with: + python-version: '3.10' + # Install C++ Compiler & Build Tools - name: Set up C++ environment (Ubuntu) if: matrix.os == 'ubuntu-latest' @@ -77,5 +84,12 @@ jobs: run: make build # Install the Project with desktop shortcut - - name: Install project + - name: Install project (Mac/Windows) + if: matrix.os != 'ubuntu-latest' run: echo "y" | make install + + # Install the Project without desktop shortcut (Linux) + # This is a work around for Linux shortcut bug + - name: Install project (Linux) + if: matrix.os == 'ubuntu-latest' + run: echo "n" | make install From 880d4632dcb719fd90b08695f4d35108dd8e9674 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:22:21 -0700 Subject: [PATCH 045/160] [Bug] Fixing Windows CI bug --- .github/workflows/cpp.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 33e294a..cc6a5e6 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -86,7 +86,9 @@ jobs: # Install the Project with desktop shortcut - name: Install project (Mac/Windows) if: matrix.os != 'ubuntu-latest' - run: echo "y" | make install + run: | + cd ${{ github.workspace }} + echo "y" | make install # Install the Project without desktop shortcut (Linux) # This is a work around for Linux shortcut bug From e66e8a795e59a75f6c5c77b0e7506ce9170dc5fa Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:31:07 -0700 Subject: [PATCH 046/160] [CI] Removed windows CI test --- .github/workflows/cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index cc6a5e6..677e800 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, macos-latest] # TO-DO: Add back windows-latest when the project is tested on a Windows machine. fail-fast: false continue-on-error: true From 41d2f70a067184ff1f45e981d5d96be1bdd02b08 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 21 Mar 2025 21:19:16 -0700 Subject: [PATCH 047/160] [Doc] Added development badges --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 03be487..67753b0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,27 @@ CodeAstra Logo

+
+ + + [![C++ CI](https://github.com/sandbox-science/CodeAstra/actions/workflows/cpp.yml/badge.svg)](https://github.com/sandbox-science/CodeAstra/actions/workflows/cpp.yml) + + + + CodeAstra Latest Release + + + + + C++ Version 17+ + + + + + Qt Version 6.8 + +
+

CodeAstra ~ Modern Code Editor

CodeAstra is a modern, extensible, and lightweight code editor built using C++ and Qt6, designed to offer a fast, customizable, and feature-rich development experience. Inspired by NeoVim and VSCode, it **will** provide efficient file navigation, syntax highlighting, and a powerful plugin system, making it an ideal choice for developers who need speed, flexibility, and control. With a focus on performance and usability, the editor **will** support split views, an integrated terminal, customizable key bindings, and seamless Git integration, catering to both beginners and power users. From cc1c56fd99a00366e41c4e075f76339a6c0c5631 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:09:17 -0700 Subject: [PATCH 048/160] [Refactor] Adjust formatting and organization in FileManager class for improved readability + conged FileManager to instance --- include/FileManager.h | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/include/FileManager.h b/include/FileManager.h index e311f33..4209eae 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include class CodeEditor; class MainWindow; @@ -11,21 +12,36 @@ class MainWindow; */ class FileManager : public QObject { - Q_OBJECT + Q_OBJECT public: - FileManager(CodeEditor *editor, MainWindow *mainWindow); - ~FileManager(); + static FileManager &getInstance(CodeEditor *editor = nullptr, MainWindow *mainWindow = nullptr) + { + static FileManager instance(editor, mainWindow); + return instance; + } + + FileManager(const FileManager &) = delete; + FileManager &operator=(const FileManager &) = delete; + + QString getFileExtension() const; + QString getCurrentFileName() const; + + void setCurrentFileName(const QString fileName); + void initialize(CodeEditor *editor, MainWindow *mainWindow); public slots: - void newFile(); - void saveFile(); - void saveFileAs(); - void openFile(); - void loadFileInEditor(const QString &filePath); + void newFile(); + void saveFile(); + void saveFileAs(); + void openFile(); + void loadFileInEditor(const QString &filePath); private: - std::unique_ptr m_editor; - QString m_currentFileName; - MainWindow *m_mainWindow; + FileManager(CodeEditor *editor, MainWindow *mainWindow); + ~FileManager(); + + CodeEditor *m_editor; + MainWindow *m_mainWindow; + QString m_currentFileName; }; From a61e4ddfa1698c006eb8a4ce311bef69379cea6f Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:10:50 -0700 Subject: [PATCH 049/160] [Refactor] Improve FileManager class initialization and file handling logic --- src/FileManager.cpp | 74 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 858ca2a..7ce460c 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -8,22 +8,29 @@ #include "MainWindow.h" FileManager::FileManager(CodeEditor *editor, MainWindow *mainWindow) - : m_editor(editor), - m_currentFileName(""), - m_mainWindow(mainWindow) + : m_editor(editor), m_mainWindow(mainWindow) { - if (!m_editor) - { - qWarning() << "Editor is NOT initialized in FileManager!"; - } - else - { - qDebug() << "Editor is properly initialized in FileManager."; - } + qDebug() << "FileManager initialized."; } FileManager::~FileManager() {} +void FileManager::initialize(CodeEditor *editor, MainWindow *mainWindow) +{ + m_editor = editor; + m_mainWindow = mainWindow; +} + +QString FileManager::getCurrentFileName() const +{ + return m_currentFileName; +} + +void FileManager::setCurrentFileName(const QString fileName) +{ + m_currentFileName = fileName; +} + void FileManager::newFile() { // Logic to create a new file @@ -31,13 +38,14 @@ void FileManager::newFile() void FileManager::saveFile() { - qDebug() << "Saving file:" << m_currentFileName; if (m_currentFileName.isEmpty()) { saveFileAs(); return; } + qDebug() << "Saving file:" << m_currentFileName; + QFile file(m_currentFileName); if (!file.open(QFile::WriteOnly | QFile::Text)) { @@ -62,8 +70,14 @@ void FileManager::saveFile() void FileManager::saveFileAs() { - QString fileName = QFileDialog::getSaveFileName(nullptr, "Save File As", QString(), - "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); + QString fileExtension = getFileExtension(); + QString filter = "All Files (*);;C++ Files (*.cpp *.h);;Text Files (*.txt)"; + if (!fileExtension.isEmpty()) + { + filter = QString("%1 Files (*.%2);;%3").arg(fileExtension.toUpper(), fileExtension, filter); + } + + QString fileName = QFileDialog::getSaveFileName(nullptr, "Save File As", QString(), filter); if (!fileName.isEmpty()) { @@ -75,13 +89,20 @@ void FileManager::saveFileAs() void FileManager::openFile() { QString fileName = QFileDialog::getOpenFileName(nullptr, "Open File", QString(), - "C++ Files (*.cpp *.h);;Text Files (*.txt);;All Files (*)"); + "All Files (*);;C++ Files (*.cpp *.h);;Text Files (*.txt)"); if (!fileName.isEmpty()) { + qDebug() << "Opening file: " << fileName; + m_currentFileName = fileName; loadFileInEditor(fileName); } + else + { + qDebug() << "No file selected."; + } } + void FileManager::loadFileInEditor(const QString &filePath) { qDebug() << "Loading file:" << filePath; @@ -104,6 +125,23 @@ void FileManager::loadFileInEditor(const QString &filePath) } file.close(); - m_currentFileName = filePath; - m_mainWindow->setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); -} \ No newline at end of file + if (m_mainWindow) + { + m_mainWindow->setWindowTitle("CodeAstra ~ " + QFileInfo(filePath).fileName()); + } + else + { + qWarning() << "MainWindow is initialized in FileManager."; + } +} + +QString FileManager::getFileExtension() const +{ + if (m_currentFileName.isEmpty()) + { + qDebug() << "Error: No File name set!"; + return QString(); + } + + return QFileInfo(m_currentFileName).suffix().toLower(); +} From 7894c2d32e64a32098e8f4b6ed1ead16b5d6a594 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:11:04 -0700 Subject: [PATCH 050/160] [Refactor] Remove unused current file name methods and update FileManager declaration in CodeEditor --- include/CodeEditor.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index a0e04f2..1b67b00 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -3,6 +3,8 @@ #include #include +class FileManager; // Forward declaration + class CodeEditor : public QPlainTextEdit { Q_OBJECT @@ -18,8 +20,6 @@ class CodeEditor : public QPlainTextEdit Mode mode = NORMAL; void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); - QString getCurrentFileName() const { return m_currentFileName; } - void setCurrentFileName(const QString &fileName) { m_currentFileName = fileName; } signals: void statusMessageChanged(const QString &message); @@ -35,9 +35,8 @@ private slots: private: QWidget *m_lineNumberArea; - QString m_currentFileName; + FileManager *m_fileManager; - QString getFileExtension(); void addLanguageSymbol(QTextCursor &cursor, const QString &commentSymbol); void commentSelection(QTextCursor &cursor, const QString &commentSymbol); void commentLine(QTextCursor &cursor, const QString &commentSymbol); From 741250b99141ac0ea368776a89c5e3aeef1e8eb6 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:11:22 -0700 Subject: [PATCH 051/160] [Refactor] Integrate FileManager instance into CodeEditor and remove unused getFileExtension method --- src/CodeEditor.cpp | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 9070140..0d23421 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -1,6 +1,7 @@ #include "CodeEditor.h" #include "MainWindow.h" #include "LineNumberArea.h" +#include "FileManager.h" #include #include @@ -9,7 +10,8 @@ CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), - m_lineNumberArea(new LineNumberArea(this)) + m_lineNumberArea(new LineNumberArea(this)), + m_fileManager(&FileManager::getInstance()) { connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth); connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea); @@ -73,18 +75,6 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) } } -QString CodeEditor::getFileExtension() -{ - QString filePath = getCurrentFileName(); - if (!QFile::exists(filePath)) - { - return QString(); - } - - // Extract the file extension from the file path - return QFileInfo(filePath).suffix().toLower(); -} - void CodeEditor::addLanguageSymbol(QTextCursor &cursor, const QString &commentSymbol) { if (cursor.hasSelection()) @@ -116,7 +106,7 @@ void CodeEditor::commentSelection(QTextCursor &cursor, const QString &commentSym if (lineText.startsWith(commentSymbol)) { - cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 3); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, commentSymbol.length() + 1); cursor.removeSelectedText(); } else @@ -136,7 +126,7 @@ void CodeEditor::commentLine(QTextCursor &cursor, const QString &commentSymbol) if (lineText.startsWith(commentSymbol)) { - lineText.remove(0, 3); + lineText.remove(0, commentSymbol.length() + 1); } else { @@ -149,7 +139,7 @@ void CodeEditor::commentLine(QTextCursor &cursor, const QString &commentSymbol) void CodeEditor::addComment() { QTextCursor cursor = textCursor(); - QString fileExtension = getFileExtension(); + QString fileExtension = m_fileManager->getFileExtension(); qDebug() << "File Extension:" << fileExtension; if (fileExtension == "cpp" || fileExtension == "h" || @@ -172,7 +162,6 @@ void CodeEditor::addComment() else { qDebug() << "Unsupported file extension for commenting."; - return; } } From 7de8ed4551808c0c75098a445c9a2493d50688cd Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:11:40 -0700 Subject: [PATCH 052/160] [Refactor] Remove unused QFile include and adjust FileManager pointer formatting in MainWindow --- include/MainWindow.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index bd786f3..acf4366 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -6,7 +6,6 @@ #include #include #include -#include class CodeEditor; class Syntax; @@ -41,5 +40,5 @@ private slots: std::unique_ptr m_syntax; std::unique_ptr m_tree; - FileManager * m_FileManager; + FileManager *m_fileManager; }; \ No newline at end of file From 999cc444b9d3a03dfc7718418dfdeb2bb257bd47 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:12:11 -0700 Subject: [PATCH 053/160] [Refactor] Replace FileManager instance creation with singleton access and update related usages in MainWindow --- src/MainWindow.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 0365279..b3c78a2 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -6,21 +6,19 @@ #include #include -#include -#include #include #include #include #include -#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_editor(std::make_unique(this)), m_syntax(std::make_unique(m_editor->document())), m_tree(nullptr), - m_FileManager(new FileManager(m_editor.get(), this)) + m_fileManager(&FileManager::getInstance()) { + m_fileManager->initialize(m_editor.get(), this); setWindowTitle("CodeAstra ~ Code Editor"); connect(m_editor.get(), &CodeEditor::statusMessageChanged, this, [this](const QString &message) @@ -47,7 +45,7 @@ void MainWindow::initTree() QSplitter *splitter = new QSplitter(Qt::Horizontal, this); setCentralWidget(splitter); - m_tree = std::make_unique(splitter, m_FileManager); + m_tree = std::make_unique(splitter, m_fileManager); splitter->addWidget(m_editor.get()); splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -76,18 +74,21 @@ void MainWindow::createMenuBar() void MainWindow::createFileActions(QMenu *fileMenu) { - fileMenu->addAction(createAction(QIcon(), tr("&New"), QKeySequence::New, tr("Create a new file"), [this]() { m_FileManager->newFile(); })); - fileMenu->addAction(createAction(QIcon(), tr("&Open"), QKeySequence::Open, tr("Open an existing file"), [this]() { m_FileManager->openFile(); })); + fileMenu->addAction(createAction(QIcon(), tr("&New"), QKeySequence::New, tr("Create a new file"), [this]() { m_fileManager->newFile(); })); + fileMenu->addAction(createAction(QIcon(), tr("&Open"), QKeySequence::Open, tr("Open an existing file"), [this]() { m_fileManager->openFile(); })); fileMenu->addSeparator(); - fileMenu->addAction(createAction(QIcon(), tr("&Save"), QKeySequence::Save, tr("Save the current file"), [this]() { m_FileManager->saveFile(); })); - fileMenu->addAction(createAction(QIcon(), tr("Save &As"), QKeySequence::SaveAs, tr("Save the file with a new name"), [this]() { m_FileManager->saveFileAs(); })); + fileMenu->addAction(createAction(QIcon(), tr("&Save"), QKeySequence::Save, tr("Save the current file"), [this]() { m_fileManager->saveFile(); })); + fileMenu->addAction(createAction(QIcon(), tr("Save &As"), QKeySequence::SaveAs, tr("Save the file with a new name"), [this]() { m_fileManager->saveFileAs(); })); } void MainWindow::createHelpActions(QMenu *helpMenu) { QAction *helpDoc = new QAction(tr("Documentation"), this); connect(helpDoc, &QAction::triggered, this, []() - { QDesktopServices::openUrl(QUrl("https://github.com/sandbox-science/CodeAstra/wiki")); }); + { + QDesktopServices::openUrl(QUrl("https://github.com/sandbox-science/CodeAstra/wiki")); + }); + helpDoc->setStatusTip(tr("Open Wiki")); helpMenu->addAction(helpDoc); } From c198be04a1b6a9b40252903119a1aba21ae6b1fc Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:12:23 -0700 Subject: [PATCH 054/160] [Refactor] Update openFile method to use FileManager singleton for file operations --- src/Tree.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index cd02124..f8fab29 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -70,7 +70,8 @@ void Tree::openFile(const QModelIndex &index) return; } - m_FileManager->loadFileInEditor(filePath); + FileManager::getInstance().setCurrentFileName(filePath); + FileManager::getInstance().loadFileInEditor(filePath); } // Context menu for file operations From bbae1349308fd3fdeb1f4f4f05364b80c0210c59 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:51:04 -0700 Subject: [PATCH 055/160] [Refactor] Add class documentation for classes header file --- include/CodeEditor.h | 8 ++++++++ include/FileManager.h | 6 ++++++ include/LineNumberArea.h | 9 +++++++++ include/MainWindow.h | 8 ++++++++ include/Syntax.h | 11 +++++++++++ include/Tree.h | 8 ++++++++ 6 files changed, 50 insertions(+) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index 1b67b00..dd8afa8 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -5,6 +5,14 @@ class FileManager; // Forward declaration +/** + * @class CodeEditor + * @brief A custom code editor widget that extends QPlainTextEdit. + * + * The CodeEditor class provides a code editor with line number area, syntax highlighting, + * and basic editing modes (NORMAL and INSERT). It emits signals for status messages and + * handles key press and resize events. + */ class CodeEditor : public QPlainTextEdit { Q_OBJECT diff --git a/include/FileManager.h b/include/FileManager.h index 4209eae..160026f 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -9,6 +9,12 @@ class MainWindow; /** * @class FileManager * @brief Manages file operations such as creating, saving, and opening files. + * + * The FileManager class is a singleton that handles multiple file-related operations + * within the application. It interacts with the CodeEditor and MainWindow classes + * to perform tasks such as creating new files, saving existing files, and opening + * files from the filesystem. The class ensures that only one instance of FileManager + * exists and provides a global point of access to it. */ class FileManager : public QObject { diff --git a/include/LineNumberArea.h b/include/LineNumberArea.h index 64f923d..1f2a85e 100644 --- a/include/LineNumberArea.h +++ b/include/LineNumberArea.h @@ -6,6 +6,15 @@ #include #include +/** + * @class LineNumberArea + * @brief A widget that displays line numbers for the CodeEditor. + * + * The LineNumberArea class is a QWidget that is used to display line numbers + * alongside the CodeEditor widget. + * + * @note This class is intended to be used as a part of the CodeEditor widget. + */ class LineNumberArea : public QWidget { public: diff --git a/include/MainWindow.h b/include/MainWindow.h index acf4366..7cd1ce8 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -12,6 +12,13 @@ class Syntax; class Tree; class FileManager; +/** + * @class MainWindow + * @brief The MainWindow class represents the main UI window of the application. + * + * This class is responsible for initializing and managing the main components + * of the application, including the file tree view, code editor, and menu bar. + */ class MainWindow : public QMainWindow { Q_OBJECT @@ -32,6 +39,7 @@ private slots: void createFileActions(QMenu *fileMenu); void createHelpActions(QMenu *helpMenu); void createAppActions(QMenu *appMenu); + QAction *createAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, const QString &statusTip, const std::function &slot); diff --git a/include/Syntax.h b/include/Syntax.h index 35f0ea0..dcfc5c7 100644 --- a/include/Syntax.h +++ b/include/Syntax.h @@ -5,6 +5,17 @@ #include #include +/** + * @class Syntax + * @brief A class for syntax highlighting in a QTextDocument. + * + * This class inherits from QSyntaxHighlighter and provides functionality + * to highlight different syntax elements such as keywords, comments, + * functions, parentheses, characters, and quotations in a QTextDocument. + * + * The Syntax class uses regular expressions to define patterns for different + * syntax elements and applies corresponding text formats to them. + */ class Syntax : public QSyntaxHighlighter { Q_OBJECT diff --git a/include/Tree.h b/include/Tree.h index 039d9a1..2fc65a1 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -10,6 +10,13 @@ class QFileSystemModel; class QFileIconProvider; class FileManager; +/** + * @class Tree + * @brief A class that represents a tree view for displaying the file system. + * + * The Tree class is responsible for creating and managing a tree view that displays + * the file system. + */ class Tree : public QObject { Q_OBJECT @@ -23,6 +30,7 @@ class Tree : public QObject void setupModel(); void setupTree(); void openFile(const QModelIndex &index); + QString getDirectoryPath() const; std::unique_ptr m_iconProvider; From f1eef649c765ef0b8c4f345cd5dddbc5bc87996a Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 18:51:11 -0700 Subject: [PATCH 056/160] [Refactor] Remove unnecessary newline in Syntax constructor for improved readability --- src/FileManager.cpp | 1 - src/Syntax.cpp | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 7ce460c..ab3c6a3 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -102,7 +102,6 @@ void FileManager::openFile() } } - void FileManager::loadFileInEditor(const QString &filePath) { qDebug() << "Loading file:" << filePath; diff --git a/src/Syntax.cpp b/src/Syntax.cpp index 9f841c2..4f71a6e 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -2,8 +2,7 @@ #include #include -Syntax::Syntax(QTextDocument *parent) - : QSyntaxHighlighter(parent) +Syntax::Syntax(QTextDocument *parent) : QSyntaxHighlighter(parent) { initKeywordRules(); initFunctionRules(); From 036bfe3b30e518ad74551555332c9d451d8435d9 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 20:59:16 -0700 Subject: [PATCH 057/160] [Refactor] Update include directives to use relative paths for unit test --- src/FileManager.cpp | 6 +++--- src/Syntax.cpp | 3 ++- src/Tree.cpp | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index ab3c6a3..a9728db 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -1,11 +1,11 @@ -#include "FileManager.h" +#include "../include/FileManager.h" +#include "../include/CodeEditor.h" +#include "../include/MainWindow.h" #include #include #include #include -#include "CodeEditor.h" -#include "MainWindow.h" FileManager::FileManager(CodeEditor *editor, MainWindow *mainWindow) : m_editor(editor), m_mainWindow(mainWindow) diff --git a/src/Syntax.cpp b/src/Syntax.cpp index e516db1..18f2266 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -1,6 +1,7 @@ +#include "../include/Syntax.h" + #include #include -#include "../include/Syntax.h" Syntax::Syntax(QTextDocument *parent) : QSyntaxHighlighter(parent) { diff --git a/src/Tree.cpp b/src/Tree.cpp index f8fab29..d60076e 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -1,6 +1,6 @@ -#include "Tree.h" -#include "CodeEditor.h" -#include "FileManager.h" +#include "../include/Tree.h" +#include "../include/CodeEditor.h" +#include "../include/FileManager.h" #include #include From 04ed7f4318cf2944624dc3838b59da0b7b449a61 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 20:59:31 -0700 Subject: [PATCH 058/160] [Refactor] Add object names to menu items in createMenuBar for improved accessibility (required for UnitTests) --- src/MainWindow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index de4b2c2..8203ee8 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -62,8 +62,13 @@ void MainWindow::createMenuBar() QMenuBar *menuBar = new QMenuBar(this); QMenu *fileMenu = menuBar->addMenu("File"); + fileMenu->setObjectName("File"); + QMenu *helpMenu = menuBar->addMenu("Help"); - QMenu *appMenu = menuBar->addMenu("CodeAstra"); + helpMenu->setObjectName("Help"); + + QMenu *appMenu = menuBar->addMenu("CodeAstra"); + appMenu->setObjectName("CodeAstra"); createFileActions(fileMenu); createHelpActions(helpMenu); From c44394a5fb92475a179e01ed2493048e7e76a14e Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 21:33:32 -0700 Subject: [PATCH 059/160] [Refactor] Rename target variables and restructure CMakeLists.txt for clarity and organization (to work with Unit Tests) --- CMakeLists.txt | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c293c7..06a1427 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,8 @@ cmake_minimum_required(VERSION 3.16) # Project name -set(TARGET_NAME CodeAstra) +set(TARGET_NAME CodeAstraApp) +set(EXECUTABLE_NAME CodeAstra) set(QT_MAJOR_VERSION 6) @@ -49,9 +50,8 @@ set(CMAKE_PREFIX_PATH ${Qt_DIR}) # Find Qt Widgets find_package(Qt${QT_MAJOR_VERSION} COMPONENTS Widgets Test REQUIRED) -# Add executable and source files -add_executable(${TARGET_NAME} - src/main.cpp +# Create the CodeAstra library +add_library(${TARGET_NAME} src/MainWindow.cpp src/CodeEditor.cpp src/Syntax.cpp @@ -65,31 +65,42 @@ add_executable(${TARGET_NAME} include/FileManager.h ) +# Create the executable for the application +add_executable(${EXECUTABLE_NAME} + src/main.cpp +) + +# Link the main executable with the CodeAstra library and Qt libraries +target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${TARGET_NAME} Qt${QT_MAJOR_VERSION}::Widgets) + +# Add the tests subdirectory add_subdirectory(tests) +# Qt resource files (if any) qt_add_resources(APP_RESOURCES resources.qrc) -target_sources(${TARGET_NAME} PRIVATE ${APP_RESOURCES}) +target_sources(${EXECUTABLE_NAME} PRIVATE ${APP_RESOURCES}) # Compiler flags per OS if(MSVC) - target_compile_options(${TARGET_NAME} PRIVATE /W4 /WX) + target_compile_options(${EXECUTABLE_NAME} PRIVATE /W4 /WX) elseif(APPLE) - target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) - set_target_properties(${TARGET_NAME} PROPERTIES MACOSX_BUNDLE TRUE) + target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE TRUE) else() - target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) endif() # Include directories -target_include_directories(${TARGET_NAME} PRIVATE ${Qt${QT_MAJOR_VERSION}_INCLUDE_DIRS}) +target_include_directories(${EXECUTABLE_NAME} PRIVATE ${Qt${QT_MAJOR_VERSION}_INCLUDE_DIRS}) +target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) # Set output names properly for Debug and Release -set_target_properties(${TARGET_NAME} PROPERTIES +set_target_properties(${EXECUTABLE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" - DEBUG_OUTPUT_NAME "${TARGET_NAME}d" - RELEASE_OUTPUT_NAME ${TARGET_NAME} + DEBUG_OUTPUT_NAME "${EXECUTABLE_NAME}d" + RELEASE_OUTPUT_NAME ${EXECUTABLE_NAME} ) -# Link necessary Qt libraries +# Link necessary Qt libraries to CodeAstra library target_link_libraries(${TARGET_NAME} PRIVATE Qt${QT_MAJOR_VERSION}::Widgets) \ No newline at end of file From 14c2589dea064515f29c573a10e1a52d3d5d435c Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 21:33:56 -0700 Subject: [PATCH 060/160] [Refactor] Update test_mainwindow CMake configuration to link with CodeAstra library and improve organization --- tests/CMakeLists.txt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a291c85..7023a6e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,16 +4,10 @@ enable_testing() # Add individual test executables add_executable(test_mainwindow test_mainwindow.cpp - ../src/MainWindow.cpp - ../src/CodeEditor.cpp - ../src/Syntax.cpp - ../include/MainWindow.h - ../include/CodeEditor.h - ../include/Syntax.h ) -# Link each test with necessary libraries -target_link_libraries(test_mainwindow PRIVATE Qt6::Widgets Qt6::Test) +# Link the test with the CodeAstra library and necessary Qt libraries +target_link_libraries(test_mainwindow PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test) # Register each test with CTest add_test(NAME test_mainwindow COMMAND test_mainwindow) @@ -21,4 +15,8 @@ add_test(NAME test_mainwindow COMMAND test_mainwindow) # Set the runtime output directory for the test executables set_target_properties(test_mainwindow PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests -) \ No newline at end of file +) + +set_property(SOURCE test_mainwindow.cpp PROPERTY SKIP_AUTOMOC OFF) + +target_include_directories(test_mainwindow PRIVATE ${CMAKE_SOURCE_DIR}/include) \ No newline at end of file From c0b69c62682f16d26716da2170236b024ee96fe2 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 21:34:53 -0700 Subject: [PATCH 061/160] [Refactor] Update include directives to use local paths instead of relative path --- src/CodeEditor.cpp | 8 ++++---- src/FileManager.cpp | 6 +++--- src/MainWindow.cpp | 10 +++++----- src/Syntax.cpp | 2 +- src/Tree.cpp | 6 +++--- src/main.cpp | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 4dec245..0d23421 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -1,7 +1,7 @@ -#include "../include/CodeEditor.h" -#include "../include/MainWindow.h" -#include "../include/LineNumberArea.h" -#include "../include/FileManager.h" +#include "CodeEditor.h" +#include "MainWindow.h" +#include "LineNumberArea.h" +#include "FileManager.h" #include #include diff --git a/src/FileManager.cpp b/src/FileManager.cpp index a9728db..454321c 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -1,6 +1,6 @@ -#include "../include/FileManager.h" -#include "../include/CodeEditor.h" -#include "../include/MainWindow.h" +#include "FileManager.h" +#include "CodeEditor.h" +#include "MainWindow.h" #include #include diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 8203ee8..68ce95b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,8 +1,8 @@ -#include "../include/MainWindow.h" -#include "../include/Syntax.h" -#include "../include/Tree.h" -#include "../include/CodeEditor.h" -#include "../include/FileManager.h" +#include "MainWindow.h" +#include "Syntax.h" +#include "Tree.h" +#include "CodeEditor.h" +#include "FileManager.h" #include #include diff --git a/src/Syntax.cpp b/src/Syntax.cpp index 18f2266..5896a88 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -1,4 +1,4 @@ -#include "../include/Syntax.h" +#include "Syntax.h" #include #include diff --git a/src/Tree.cpp b/src/Tree.cpp index d60076e..f8fab29 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -1,6 +1,6 @@ -#include "../include/Tree.h" -#include "../include/CodeEditor.h" -#include "../include/FileManager.h" +#include "Tree.h" +#include "CodeEditor.h" +#include "FileManager.h" #include #include diff --git a/src/main.cpp b/src/main.cpp index cfe70f2..93247d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "../include/MainWindow.h" +#include "MainWindow.h" #include #include From fa6e3425130d68c79c717f936655a4573d82555c Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 21:36:19 -0700 Subject: [PATCH 062/160] [Refactor] Improve test structure and initialization in TestMainWindow --- tests/test_mainwindow.cpp | 52 +++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 67797be..b25a1cb 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -1,51 +1,67 @@ -#include "../include/MainWindow.h" - #include #include -#include + +#include "MainWindow.h" +#include "CodeEditor.h" +#include "FileManager.h" class TestMainWindow : public QObject { Q_OBJECT private slots: - void initTestCase(); // Runs before all tests - void cleanupTestCase(); // Runs after all tests + void initTestCase(); + void cleanupTestCase(); void testWindowTitle(); + void testEditorInitialization(); void testMenuBar(); - void testNewFileAction(); + +private: + std::unique_ptr mainWindow; }; void TestMainWindow::initTestCase() { qDebug() << "Initializing MainWindow tests..."; + mainWindow = std::make_unique(); + mainWindow->show(); } void TestMainWindow::cleanupTestCase() { qDebug() << "Cleaning up MainWindow tests..."; + mainWindow.reset(); } void TestMainWindow::testWindowTitle() { - MainWindow window; - QCOMPARE(window.windowTitle(), QString("CodeAstra ~ Code Editor")); + QCOMPARE(mainWindow->windowTitle(), "CodeAstra ~ Code Editor"); } -void TestMainWindow::testMenuBar() +void TestMainWindow::testEditorInitialization() { - MainWindow window; - QMenuBar *menuBar = window.menuBar(); - QVERIFY(menuBar != nullptr); - QCOMPARE(menuBar->actions().size(), 3); // File, Help, CodeAstra + QVERIFY2(mainWindow->findChild() != nullptr, + "MainWindow must contain a CodeEditor."); } -void TestMainWindow::testNewFileAction() +void TestMainWindow::testMenuBar() { - MainWindow window; - QAction *newAction = window.findChild("New File"); - QVERIFY(newAction != nullptr); + QMenuBar *menuBar = mainWindow->menuBar(); + QVERIFY2(menuBar != nullptr, "MainWindow must have a QMenuBar."); + QCOMPARE(menuBar->actions().size(), 3); // File, Help, CodeAstra + + QMenu *fileMenu = menuBar->findChild("File"); + QVERIFY2(fileMenu != nullptr, "QMenuBar must contain a 'File' menu."); + QCOMPARE(fileMenu->title(), "File"); + + QMenu *helpMenu = menuBar->findChild("Help"); + QVERIFY2(helpMenu != nullptr, "QMenuBar must contain a 'Help' menu."); + QCOMPARE(helpMenu->title(), "Help"); + + QMenu *appMenu = menuBar->findChild("CodeAstra"); + QVERIFY2(appMenu != nullptr, "QMenuBar must contain a 'CodeAstra' menu."); + QCOMPARE(appMenu->title(), "CodeAstra"); } QTEST_MAIN(TestMainWindow) -#include "test_mainwindow.moc" +#include "test_mainwindow.moc" \ No newline at end of file From 3d54fb9d69ab9d851f0efd263feba7bfd79ce342 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Mar 2025 21:54:34 -0700 Subject: [PATCH 063/160] [Refactor] Update comment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06a1427..e7d76fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${TARGET_NAME} Qt${QT_MAJOR_VER # Add the tests subdirectory add_subdirectory(tests) -# Qt resource files (if any) +# Qt resource files qt_add_resources(APP_RESOURCES resources.qrc) target_sources(${EXECUTABLE_NAME} PRIVATE ${APP_RESOURCES}) From 4f48a2ea1add87bc280d109fb1a851cd5e747279 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Sun, 23 Mar 2025 14:32:02 -0700 Subject: [PATCH 064/160] [Doc] updated C++ version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67753b0..3a0d99e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - C++ Version 17+ + C++ Version 20+ From 6f1c6490cdf8005459bf38176d577c8261fbddf5 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:21:24 -0700 Subject: [PATCH 065/160] [added] Add test_tree executable and update CMake configuration --- tests/CMakeLists.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7023a6e..48b3ff8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,17 +6,30 @@ add_executable(test_mainwindow test_mainwindow.cpp ) +# Add test_tree executable +add_executable(test_tree + test_tree.cpp +) + # Link the test with the CodeAstra library and necessary Qt libraries target_link_libraries(test_mainwindow PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test) +target_link_libraries(test_tree PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test) # Register each test with CTest add_test(NAME test_mainwindow COMMAND test_mainwindow) +add_test(NAME test_tree COMMAND test_tree) # Set the runtime output directory for the test executables set_target_properties(test_mainwindow PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests ) +set_target_properties(test_tree PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests +) set_property(SOURCE test_mainwindow.cpp PROPERTY SKIP_AUTOMOC OFF) +set_property(SOURCE test_tree.cpp PROPERTY SKIP_AUTOMOC OFF) -target_include_directories(test_mainwindow PRIVATE ${CMAKE_SOURCE_DIR}/include) \ No newline at end of file +# Include directories for tests +target_include_directories(test_mainwindow PRIVATE ${CMAKE_SOURCE_DIR}/include) +target_include_directories(test_tree PRIVATE ${CMAKE_SOURCE_DIR}/include) From 4aeafd55a0d9505df55aebb8550068c46356fde0 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:22:25 -0700 Subject: [PATCH 066/160] [Refactor] Moved getDirectoryPath method from FileManager and createAction method from MainWindow from private to public --- include/FileManager.h | 2 ++ include/MainWindow.h | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/FileManager.h b/include/FileManager.h index 160026f..bccf188 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -43,6 +43,8 @@ public slots: void openFile(); void loadFileInEditor(const QString &filePath); + QString getDirectoryPath() const; + private: FileManager(CodeEditor *editor, MainWindow *mainWindow); ~FileManager(); diff --git a/include/MainWindow.h b/include/MainWindow.h index 7cd1ce8..591a273 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -31,6 +31,10 @@ class MainWindow : public QMainWindow // of the main window, alongside the code editor void initTree(); + QAction *createAction(const QIcon &icon, const QString &text, + const QKeySequence &shortcut, const QString &statusTip, + const std::function &slot); + private slots: void showAbout(); @@ -40,10 +44,6 @@ private slots: void createHelpActions(QMenu *helpMenu); void createAppActions(QMenu *appMenu); - QAction *createAction(const QIcon &icon, const QString &text, - const QKeySequence &shortcut, const QString &statusTip, - const std::function &slot); - std::unique_ptr m_editor; std::unique_ptr m_syntax; std::unique_ptr m_tree; From 45fc75395acaa128aa438f6db0f0fb734f9d3324 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:22:34 -0700 Subject: [PATCH 067/160] [Refactor] Simplify Tree constructor by removing FileManager dependency and reorganizing method visibility --- include/Tree.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/include/Tree.h b/include/Tree.h index 2fc65a1..210c733 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -22,20 +22,18 @@ class Tree : public QObject Q_OBJECT public: - explicit Tree(QSplitter *splitter, FileManager *FileManager); + explicit Tree(QSplitter *splitter); ~Tree(); -private: - void showContextMenu(const QPoint &pos); - void setupModel(); + void initialize(const QString &directory); + void setupModel(const QString &directory); void setupTree(); void openFile(const QModelIndex &index); - QString getDirectoryPath() const; +private: + void showContextMenu(const QPoint &pos); std::unique_ptr m_iconProvider; std::unique_ptr m_model; std::unique_ptr m_tree; - - FileManager * m_FileManager; }; \ No newline at end of file From 0f41a6b92bbbe9794757c10352421060182d80c2 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:22:49 -0700 Subject: [PATCH 068/160] [added] Implement getDirectoryPath method to allow directory selection --- src/FileManager.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 454321c..6f6c89e 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -144,3 +144,10 @@ QString FileManager::getFileExtension() const return QFileInfo(m_currentFileName).suffix().toLower(); } + +QString FileManager::getDirectoryPath() const +{ + return QFileDialog::getExistingDirectory( + nullptr, QObject::tr("Open Directory"), QDir::homePath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); +} From a10d9244b841b65094bd129aaf3e08866832072f Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:23:00 -0700 Subject: [PATCH 069/160] [added] Implement Open Project action in file menu to initialize tree with selected directory --- src/MainWindow.cpp | 333 +++++++++++++++++++++++---------------------- 1 file changed, 171 insertions(+), 162 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 68ce95b..1e1b2a6 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,162 +1,171 @@ -#include "MainWindow.h" -#include "Syntax.h" -#include "Tree.h" -#include "CodeEditor.h" -#include "FileManager.h" - -#include -#include -#include -#include -#include -#include - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), - m_editor(std::make_unique(this)), - m_syntax(std::make_unique(m_editor->document())), - m_tree(nullptr), - m_fileManager(&FileManager::getInstance()) -{ - m_fileManager->initialize(m_editor.get(), this); - setWindowTitle("CodeAstra ~ Code Editor"); - - connect(m_editor.get(), &CodeEditor::statusMessageChanged, this, [this](const QString &message) - { - QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss"); - statusBar()->showMessage("[" + timestamp + "] " + message, 4000); - }); - - // Set tab width to 4 spaces - QFontMetrics metrics(m_editor->font()); - int spaceWidth = metrics.horizontalAdvance(" "); - m_editor->setTabStopDistance(spaceWidth * 4); - m_editor->setLineWrapMode(QPlainTextEdit::NoWrap); - - initTree(); - createMenuBar(); - showMaximized(); -} - -MainWindow::~MainWindow() {} - -void MainWindow::initTree() -{ - QSplitter *splitter = new QSplitter(Qt::Horizontal, this); - setCentralWidget(splitter); - - m_tree = std::make_unique(splitter, m_fileManager); - - splitter->addWidget(m_editor.get()); - splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - splitter->setHandleWidth(5); - splitter->setSizes(QList() << 150 << 800); - splitter->setStretchFactor(0, 1); - splitter->setStretchFactor(1, 3); - splitter->setChildrenCollapsible(false); - splitter->setOpaqueResize(true); -} - -void MainWindow::createMenuBar() -{ - QMenuBar *menuBar = new QMenuBar(this); - - QMenu *fileMenu = menuBar->addMenu("File"); - fileMenu->setObjectName("File"); - - QMenu *helpMenu = menuBar->addMenu("Help"); - helpMenu->setObjectName("Help"); - - QMenu *appMenu = menuBar->addMenu("CodeAstra"); - appMenu->setObjectName("CodeAstra"); - - createFileActions(fileMenu); - createHelpActions(helpMenu); - createAppActions(appMenu); - - setMenuBar(menuBar); -} - -void MainWindow::createFileActions(QMenu *fileMenu) -{ - fileMenu->addAction(createAction(QIcon(), tr("&New"), QKeySequence::New, tr("Create a new file"), [this]() { m_fileManager->newFile(); })); - fileMenu->addAction(createAction(QIcon(), tr("&Open"), QKeySequence::Open, tr("Open an existing file"), [this]() { m_fileManager->openFile(); })); - fileMenu->addSeparator(); - fileMenu->addAction(createAction(QIcon(), tr("&Save"), QKeySequence::Save, tr("Save the current file"), [this]() { m_fileManager->saveFile(); })); - fileMenu->addAction(createAction(QIcon(), tr("Save &As"), QKeySequence::SaveAs, tr("Save the file with a new name"), [this]() { m_fileManager->saveFileAs(); })); -} - -void MainWindow::createHelpActions(QMenu *helpMenu) -{ - QAction *helpDoc = new QAction(tr("Documentation"), this); - connect(helpDoc, &QAction::triggered, this, []() - { - QDesktopServices::openUrl(QUrl("https://github.com/sandbox-science/CodeAstra/wiki")); - }); - - helpDoc->setStatusTip(tr("Open Wiki")); - helpMenu->addAction(helpDoc); -} - -void MainWindow::createAppActions(QMenu *appMenu) -{ - QAction *aboutAction = new QAction("About CodeAstra", this); - connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout); - appMenu->addAction(aboutAction); -} - -QAction *MainWindow::createAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, const QString &statusTip, const std::function &slot) -{ - QAction *action = new QAction(icon, text, this); - - action->setShortcuts(QList{shortcut}); - action->setStatusTip(statusTip); - connect(action, &QAction::triggered, this, slot); - - return action; -} - -void MainWindow::showAbout() -{ - // Extract the C++ version from the __cplusplus macro - QString cppVersion; - if (__cplusplus == 201103L) - { - cppVersion = "C++11"; - } - else if (__cplusplus == 201402L) - { - cppVersion = "C++14"; - } - else if (__cplusplus == 201703L) - { - cppVersion = "C++17"; - } - else if (__cplusplus == 202002L) - { - cppVersion = "C++20"; - } - else - { - cppVersion = "C++"; - } - - // Construct the about text - QString aboutText = QString( - "

" - "%1
" - "Version: %2

" - "Developed by %3.
" - "Built with %4 and Qt %5.

" - "© 2025 %3. All rights reserved." - "

") - .arg(QApplication::applicationName().toHtmlEscaped(), - QApplication::applicationVersion().toHtmlEscaped(), - QApplication::organizationName().toHtmlEscaped(), - cppVersion, - QString::number(QT_VERSION >> 16) + "." + // Major version - QString::number((QT_VERSION >> 8) & 0xFF) + "." + // Minor version - QString::number(QT_VERSION & 0xFF)); // Patch version - - QMessageBox::about(this, tr("About"), aboutText); -} +#include "MainWindow.h" +#include "Syntax.h" +#include "Tree.h" +#include "CodeEditor.h" +#include "FileManager.h" + +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), + m_editor(std::make_unique(this)), + m_syntax(std::make_unique(m_editor->document())), + m_tree(nullptr), + m_fileManager(&FileManager::getInstance()) +{ + m_fileManager->initialize(m_editor.get(), this); + setWindowTitle("CodeAstra ~ Code Editor"); + + connect(m_editor.get(), &CodeEditor::statusMessageChanged, this, [this](const QString &message) + { + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss"); + statusBar()->showMessage("[" + timestamp + "] " + message, 4000); + }); + + // Set tab width to 4 spaces + QFontMetrics metrics(m_editor->font()); + int spaceWidth = metrics.horizontalAdvance(" "); + m_editor->setTabStopDistance(spaceWidth * 4); + m_editor->setLineWrapMode(QPlainTextEdit::NoWrap); + + initTree(); + createMenuBar(); + showMaximized(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::initTree() +{ + QSplitter *splitter = new QSplitter(Qt::Horizontal, this); + setCentralWidget(splitter); + + m_tree = std::make_unique(splitter); + + splitter->addWidget(m_editor.get()); + splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + splitter->setHandleWidth(5); + splitter->setSizes(QList() << 150 << 800); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 3); + splitter->setChildrenCollapsible(false); + splitter->setOpaqueResize(true); +} + +void MainWindow::createMenuBar() +{ + QMenuBar *menuBar = new QMenuBar(this); + + QMenu *fileMenu = menuBar->addMenu("File"); + fileMenu->setObjectName("File"); + + QMenu *helpMenu = menuBar->addMenu("Help"); + helpMenu->setObjectName("Help"); + + QMenu *appMenu = menuBar->addMenu("CodeAstra"); + appMenu->setObjectName("CodeAstra"); + + createFileActions(fileMenu); + createHelpActions(helpMenu); + createAppActions(appMenu); + + setMenuBar(menuBar); +} + +void MainWindow::createFileActions(QMenu *fileMenu) +{ + fileMenu->addAction(createAction(QIcon(), tr("&New"), QKeySequence::New, tr("Create a new file"), [this]() { m_fileManager->newFile(); })); + fileMenu->addSeparator(); + fileMenu->addAction(createAction(QIcon(), tr("&Open &Project"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O), tr("Open a project"), [this]() + { + QString projectPath = m_fileManager->getDirectoryPath(); + if (!projectPath.isEmpty()) + { + m_tree->initialize(projectPath); + } + })); + fileMenu->addAction(createAction(QIcon(), tr("&Open"), QKeySequence::Open, tr("Open an existing file"), [this]() { m_fileManager->openFile(); })); + fileMenu->addSeparator(); + fileMenu->addAction(createAction(QIcon(), tr("&Save"), QKeySequence::Save, tr("Save the current file"), [this]() { m_fileManager->saveFile(); })); + fileMenu->addAction(createAction(QIcon(), tr("Save &As"), QKeySequence::SaveAs, tr("Save the file with a new name"), [this]() { m_fileManager->saveFileAs(); })); +} + +void MainWindow::createHelpActions(QMenu *helpMenu) +{ + QAction *helpDoc = new QAction(tr("Documentation"), this); + connect(helpDoc, &QAction::triggered, this, []() + { + QDesktopServices::openUrl(QUrl("https://github.com/sandbox-science/CodeAstra/wiki")); + }); + + helpDoc->setStatusTip(tr("Open Wiki")); + helpMenu->addAction(helpDoc); +} + +void MainWindow::createAppActions(QMenu *appMenu) +{ + QAction *aboutAction = new QAction("About CodeAstra", this); + connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout); + appMenu->addAction(aboutAction); +} + +QAction *MainWindow::createAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, const QString &statusTip, const std::function &slot) +{ + QAction *action = new QAction(icon, text, this); + + action->setShortcuts(QList{shortcut}); + action->setStatusTip(statusTip); + connect(action, &QAction::triggered, this, slot); + + return action; +} + +void MainWindow::showAbout() +{ + // Extract the C++ version from the __cplusplus macro + QString cppVersion; + if (__cplusplus == 201103L) + { + cppVersion = "C++11"; + } + else if (__cplusplus == 201402L) + { + cppVersion = "C++14"; + } + else if (__cplusplus == 201703L) + { + cppVersion = "C++17"; + } + else if (__cplusplus == 202002L) + { + cppVersion = "C++20"; + } + else + { + cppVersion = "C++"; + } + + // Construct the about text + QString aboutText = QString( + "

" + "%1
" + "Version: %2

" + "Developed by %3.
" + "Built with %4 and Qt %5.

" + "© 2025 %3. All rights reserved." + "

") + .arg(QApplication::applicationName().toHtmlEscaped(), + QApplication::applicationVersion().toHtmlEscaped(), + QApplication::organizationName().toHtmlEscaped(), + cppVersion, + QString::number(QT_VERSION >> 16) + "." + // Major version + QString::number((QT_VERSION >> 8) & 0xFF) + "." + // Minor version + QString::number(QT_VERSION & 0xFF)); // Patch version + + QMessageBox::about(this, tr("About"), aboutText); +} From ef2f1dd2cbbc48d0cf07b784cc60139e52922322 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:23:11 -0700 Subject: [PATCH 070/160] Refactor Tree class to remove FileManager dependency and streamline initialization process --- src/Tree.cpp | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index f8fab29..185d0ab 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -9,24 +9,26 @@ #include #include -Tree::Tree(QSplitter *splitter, FileManager *FileManager) +Tree::Tree(QSplitter *splitter) : QObject(splitter), m_iconProvider(std::make_unique()), m_model(std::make_unique()), - m_tree(std::make_unique(splitter)), - m_FileManager(FileManager) + m_tree(std::make_unique(splitter)) { - setupModel(); - setupTree(); - connect(m_tree.get(), &QTreeView::doubleClicked, this, &Tree::openFile); } Tree::~Tree() {} -void Tree::setupModel() +void Tree::initialize(const QString &directory) { - m_model->setRootPath(getDirectoryPath()); + setupModel(directory); + setupTree(); +} + +void Tree::setupModel(const QString &directory) +{ + m_model->setRootPath(directory); m_model->setIconProvider(m_iconProvider.get()); m_model->setFilter(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot); } @@ -51,13 +53,6 @@ void Tree::setupTree() connect(m_tree.get(), &QTreeView::customContextMenuRequested, this, &Tree::showContextMenu); } -QString Tree::getDirectoryPath() const -{ - return QFileDialog::getExistingDirectory( - nullptr, QObject::tr("Open Directory"), QDir::homePath(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); -} - void Tree::openFile(const QModelIndex &index) { QString filePath = m_model->filePath(index); From d463bdd7bb1d97071bd500368bad8e023af384b4 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:23:24 -0700 Subject: [PATCH 071/160] [added] Implement tests for QSplitter initialization and action creation in TestMainWindow --- tests/test_mainwindow.cpp | 47 +++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index b25a1cb..14567c0 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "MainWindow.h" #include "CodeEditor.h" @@ -15,6 +16,8 @@ private slots: void testWindowTitle(); void testEditorInitialization(); void testMenuBar(); + void testInitTree(); + void testCreateAction(); private: std::unique_ptr mainWindow; @@ -48,19 +51,55 @@ void TestMainWindow::testMenuBar() { QMenuBar *menuBar = mainWindow->menuBar(); QVERIFY2(menuBar != nullptr, "MainWindow must have a QMenuBar."); - QCOMPARE(menuBar->actions().size(), 3); // File, Help, CodeAstra + QCOMPARE_EQ(menuBar->actions().size(), 3); // File, Help, CodeAstra QMenu *fileMenu = menuBar->findChild("File"); QVERIFY2(fileMenu != nullptr, "QMenuBar must contain a 'File' menu."); - QCOMPARE(fileMenu->title(), "File"); + QCOMPARE_EQ(fileMenu->title(), "File"); QMenu *helpMenu = menuBar->findChild("Help"); QVERIFY2(helpMenu != nullptr, "QMenuBar must contain a 'Help' menu."); - QCOMPARE(helpMenu->title(), "Help"); + QCOMPARE_EQ(helpMenu->title(), "Help"); QMenu *appMenu = menuBar->findChild("CodeAstra"); QVERIFY2(appMenu != nullptr, "QMenuBar must contain a 'CodeAstra' menu."); - QCOMPARE(appMenu->title(), "CodeAstra"); + QCOMPARE_EQ(appMenu->title(), "CodeAstra"); +} + +void TestMainWindow::testInitTree() +{ + QSplitter *splitter = dynamic_cast(mainWindow->centralWidget()); + QVERIFY2(splitter != nullptr, "Central widget should be a QSplitter."); + + QCOMPARE_EQ(splitter->handleWidth(), 5); + QCOMPARE_EQ(splitter->childrenCollapsible(), false); + QCOMPARE_EQ(splitter->opaqueResize(), true); + + QList sizes = splitter->sizes(); + QCOMPARE_EQ(sizes.size(), 2); +} + +void TestMainWindow::testCreateAction() +{ + // Mock parameters for createAction + QIcon icon; + QString text = "Test Action"; + QKeySequence shortcut = QKeySequence(Qt::CTRL | Qt::Key_T); + QString statusTip = "This is a test action"; + bool slotCalled = false; + + auto slot = [&slotCalled]() { slotCalled = true; }; + + QAction *action = mainWindow->createAction(icon, text, shortcut, statusTip, slot); + + QVERIFY2(action != nullptr, "Action should be successfully created."); + QCOMPARE_EQ(action->text(), text); + QCOMPARE_EQ(action->shortcuts().first(), shortcut); + QCOMPARE_EQ(action->statusTip(), statusTip); + + // Simulate triggering the action + action->trigger(); + QCOMPARE_EQ(slotCalled, true); } QTEST_MAIN(TestMainWindow) From 03ff07606c8167cea2bf7fe382139bf4470ccdb8 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 00:23:34 -0700 Subject: [PATCH 072/160] [added] Implement unit tests for Tree class to validate openFile method with invalid input --- tests/test_tree.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/test_tree.cpp diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp new file mode 100644 index 0000000..f00465c --- /dev/null +++ b/tests/test_tree.cpp @@ -0,0 +1,44 @@ +#include +#include +#include "Tree.h" +#include "FileManager.h" +#include "CodeEditor.h" + +#include +#include +#include + +class TestTree : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testOpenFile_invalid(); +}; + +void TestTree::initTestCase() +{ + qDebug() << "Initializing TestTree tests..."; +} + +void TestTree::cleanupTestCase() +{ + qDebug() << "Cleaning up TestTree tests..."; +} + +void TestTree::testOpenFile_invalid() +{ + QSplitter *splitter = new QSplitter; + Tree tree(splitter); + + QModelIndex index; + + tree.openFile(index); + + QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), "FileManager should not process an invalid file."); +} + +QTEST_MAIN(TestTree) +#include "test_tree.moc" From 3a1ac20faa6d01dba103bddb597df02f5cb276d6 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Wed, 26 Mar 2025 20:22:13 -0700 Subject: [PATCH 073/160] =?UTF-8?q?=E2=99=BB=20Refactor:=20Add=20Makefile?= =?UTF-8?q?=20deps=20for=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b0973ed..92474bb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PROJECT = CodeAstra -BUILD_DIR = build +BUILD_DIR = $(PWD)/build EXECUTABLE = $(PROJECT) # Set CMake options @@ -45,7 +45,7 @@ uninstall: clean fi # Install the project -install: +install: build @echo "Installing $(PROJECT)..." @cd $(BUILD_DIR) && make @echo "Do you want to create a shortcut on the desktop? (Y/n)" @@ -76,7 +76,10 @@ install: fi @echo "$(PROJECT) installed." -test: +build_tests: build + @cd $(BUILD_DIR)/tests/ && make + +test: build_tests @for test in ./build/tests/test_*; do \ if [ -f $$test ]; then \ echo "Running $$test..."; \ From 223ad42c63772656ed6301de2dfb31077384c6d0 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Wed, 26 Mar 2025 20:24:38 -0700 Subject: [PATCH 074/160] =?UTF-8?q?=F0=9F=94=A5=20Feature:=20Add=20dockeri?= =?UTF-8?q?zed=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 26 ++++++++++++++++++++++++++ run_dockerized_tests.sh | 8 ++++++++ 2 files changed, 34 insertions(+) create mode 100644 Dockerfile create mode 100644 run_dockerized_tests.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9a550a3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:22.04 AS builder +WORKDIR / + +# Add dependencies +RUN apt-get update +RUN apt-get install -y --no-install-recommends \ + git \ + ca-certificates \ + g++ \ + cmake \ + make \ + mesa-common-dev \ + qt6-base-dev \ + x11-utils \ + x11-xserver-utils \ + xvfb +RUN rm -rf /var/lib/apt/lists/* + +# Copy CodeAstra into the container +COPY . /CodeAstra +WORKDIR /CodeAstra + +# Run dockerized tests script +RUN chmod +x ./run_dockerized_tests.sh + +CMD ["./run_dockerized_tests.sh"] diff --git a/run_dockerized_tests.sh b/run_dockerized_tests.sh new file mode 100644 index 0000000..c0b8574 --- /dev/null +++ b/run_dockerized_tests.sh @@ -0,0 +1,8 @@ +#!/bin/bash -e + +# Start Xvfb server +Xvfb :99 -screen 0 1024x768x24 & +export DISPLAY=:99 + +# Run tests +make test From e81e2c8d1d2cf9998eae50637049e457d7b84db9 Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Thu, 27 Mar 2025 00:46:01 -0700 Subject: [PATCH 075/160] =?UTF-8?q?=E2=99=BB=20Refactor:=20Fix=20docker=20?= =?UTF-8?q?command=20errors=20and=20add=20run=5Fdocker.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 6 +++--- run_docker.sh | 5 +++++ run_dockerized_tests.sh => test_with_xvbf.sh | 0 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100755 run_docker.sh rename run_dockerized_tests.sh => test_with_xvbf.sh (100%) diff --git a/Dockerfile b/Dockerfile index 9a550a3..7c3b361 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 AS builder +FROM ubuntu:24.04 AS builder WORKDIR / # Add dependencies @@ -21,6 +21,6 @@ COPY . /CodeAstra WORKDIR /CodeAstra # Run dockerized tests script -RUN chmod +x ./run_dockerized_tests.sh +RUN chmod +x ./test_with_xvbf.sh -CMD ["./run_dockerized_tests.sh"] +CMD ["./test_with_xvbf.sh"] diff --git a/run_docker.sh b/run_docker.sh new file mode 100755 index 0000000..2aa18f5 --- /dev/null +++ b/run_docker.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +docker build -t "code-astra:tester" . +docker run --name "code-astra-tester" "code-astra:tester" +docker rm "code-astra-tester" diff --git a/run_dockerized_tests.sh b/test_with_xvbf.sh similarity index 100% rename from run_dockerized_tests.sh rename to test_with_xvbf.sh From dba4f8701f0113f084ef49da1a0b34245b4dcc7a Mon Sep 17 00:00:00 2001 From: Giovanni Rivera Date: Thu, 27 Mar 2025 01:03:10 -0700 Subject: [PATCH 076/160] =?UTF-8?q?=F0=9F=9A=A8=20Test:=20Add=20test=20wor?= =?UTF-8?q?kflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 94 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..331582d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,94 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + +jobs: + native: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] # TODO: Add windows-latest when officially supported + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + # Set up Python 3.10 or later + - name: Set up Python 3.10 (Windows) + if: matrix.os == 'windows-latest' + uses: actions/setup-python@v2 + with: + python-version: '3.10' + + # Install C++ Compiler & Build Tools + - name: Set up C++ environment (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y g++ make cmake + g++ --version + cmake --version + make --version + + - name: Set up C++ environment (macOS) + if: matrix.os == 'macos-latest' + run: | + brew install make cmake + echo 'export PATH="/usr/local/opt/gcc/bin:$PATH"' >> ~/.bash_profile + source ~/.bash_profile + g++ --version + cmake --version + make --version + + - name: Set up C++ environment (Windows) + if: matrix.os == 'windows-latest' + run: | + choco install mingw --version=8.1.0-1 + choco install make cmake + echo C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + echo C:\ProgramData\chocolatey\lib\cmake\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + g++ --version + cmake --version + make --version + + # Install Qt6 + - name: Install Qt6 (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get install -y qt6-base-dev + echo 'export PATH="/usr/lib/qt6/bin:$PATH"' >> ~/.bashrc + source ~/.bashrc + + - name: Install Qt6 (macOS) + if: matrix.os == 'macos-latest' + run: | + brew install qt6 + echo 'export PATH="/opt/homebrew/opt/qt6/bin:$PATH"' >> ~/.bash_profile + source ~/.bash_profile + + - name: Install Qt6 (Windows) + if: matrix.os == 'windows-latest' + run: | + python -m pip install aqtinstall + python -m aqt install-qt windows desktop 6.6.0 win64_mingw --outputdir C:\Qt + echo C:\Qt\6.6.0\mingw_64\bin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + # Build the Project + - name: Build project + run: make build + + - name: Run Unit Tests + run: make test + + docker: + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Run Docker Test + run: | + chmod +x ./run_docker.sh + ./run_docker.sh From 712f5c073c5f51908265c483a044225223b22818 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Thu, 27 Mar 2025 01:25:14 -0700 Subject: [PATCH 077/160] fix: Fix CI stratgey for runs-on --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 331582d..3e92e19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,6 +84,10 @@ jobs: run: make test docker: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} steps: - name: Checkout Repository uses: actions/checkout@v4 From ca15903a23a5ba2dd8190bd3afe1647cf848391b Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Thu, 27 Mar 2025 01:28:46 -0700 Subject: [PATCH 078/160] Fix: Fix tentative for docker test --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e92e19..a42cd6f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,10 +84,7 @@ jobs: run: make test docker: - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 From a6af4f2bc967646bb8dc2132473ffa733ad95aee Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:22:16 -0700 Subject: [PATCH 079/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Reintroduce=20Sy?= =?UTF-8?q?ntax=20class=20for=20custom=20syntax=20highlighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Syntax.h | 54 ------------------------------------ include/Syntax/Syntax.h | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 54 deletions(-) delete mode 100644 include/Syntax.h create mode 100644 include/Syntax/Syntax.h diff --git a/include/Syntax.h b/include/Syntax.h deleted file mode 100644 index dcfc5c7..0000000 --- a/include/Syntax.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -/** - * @class Syntax - * @brief A class for syntax highlighting in a QTextDocument. - * - * This class inherits from QSyntaxHighlighter and provides functionality - * to highlight different syntax elements such as keywords, comments, - * functions, parentheses, characters, and quotations in a QTextDocument. - * - * The Syntax class uses regular expressions to define patterns for different - * syntax elements and applies corresponding text formats to them. - */ -class Syntax : public QSyntaxHighlighter -{ - Q_OBJECT - -public: - Syntax(QTextDocument *parent = nullptr); - -protected: - void highlightBlock(const QString &text) override; - -private: - struct SyntaxRule - { - QRegularExpression m_pattern; - QTextCharFormat m_format; - }; - QVector m_syntaxRules; - - QTextCharFormat m_keywordFormat; - QTextCharFormat m_singleLineCommentFormat; - QTextCharFormat m_quotationMark; - QTextCharFormat m_functionFormat; - QTextCharFormat m_parenthesisFormat; - QTextCharFormat m_charFormat; - QTextCharFormat m_iterationFormat; - - void addPattern(const QString &pattern, const QTextCharFormat &format); - - // Initialization functions for different syntax highlighting rules - void initKeywordRules(); - void initCommentRules(); - void initFunctionRules(); - void initParenthesisRules(); - void initCharRules(); - void initQuotationRules(); -}; diff --git a/include/Syntax/Syntax.h b/include/Syntax/Syntax.h new file mode 100644 index 0000000..c3efe17 --- /dev/null +++ b/include/Syntax/Syntax.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +/** + * @class Syntax + * @brief A custom syntax highlighter class that extends QSyntaxHighlighter to provide + * syntax highlighting functionality based on user-defined rules. + * + * This class allows you to define syntax highlighting rules using regular expressions + * and associated text formats. It applies these rules to text blocks to highlight + * specific patterns. + * + * @note This class inherits the constructor from QSyntaxHighlighter. + */ +class Syntax : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + using QSyntaxHighlighter::QSyntaxHighlighter; + + virtual ~Syntax() = default; + +protected: + /** + * @brief Highlights the given text block based on the defined syntax rules. + * + * @param text The text block to be highlighted. + */ + void highlightBlock(const QString &text) override; + +public: + /** + * @struct SyntaxRule + * @brief Represents a single syntax highlighting rule. + * + * A syntax rule consists of a regular expression pattern and a text format + * to apply to matching text. + */ + struct SyntaxRule + { + QRegularExpression m_pattern; + QTextCharFormat m_format; + }; + + QVector m_syntaxRules; + + /** + * @brief Adds a new syntax highlighting rule. + * + * This method allows you to define a new rule by specifying a regular expression + * pattern and the corresponding text format. + * + * @param pattern The regular expression pattern for the rule. + * @param format The text format to apply to matches of the pattern. + */ + void addPattern(const QString &pattern, const QTextCharFormat &format); +}; \ No newline at end of file From e65b1a331fe36501ed45664109186556e60b4e42 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:22:27 -0700 Subject: [PATCH 080/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Add=20SyntaxCpp?= =?UTF-8?q?=20class=20for=20C/C++=20syntax=20highlighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Syntax/SyntaxCpp.h | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 include/Syntax/SyntaxCpp.h diff --git a/include/Syntax/SyntaxCpp.h b/include/Syntax/SyntaxCpp.h new file mode 100644 index 0000000..d67a23b --- /dev/null +++ b/include/Syntax/SyntaxCpp.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include + +/** + * @class SyntaxCpp + * @brief A syntax highlighter class for C/C++ code, inheriting from the Syntax base class. + * + * This class provides syntax highlighting for C and C++ code by defining various + * QTextCharFormat rules for different C++ language elements. + */ +class SyntaxCpp : public Syntax +{ +public: + SyntaxCpp(QTextDocument *parent = nullptr); + +private: + QTextCharFormat m_keywordFormat; + QTextCharFormat m_singleLineCommentFormat; + QTextCharFormat m_quotationMark; + QTextCharFormat m_functionFormat; + QTextCharFormat m_parenthesisFormat; + QTextCharFormat m_charFormat; + QTextCharFormat m_iterationFormat; + + // Initialization functions for different syntax highlighting rules + void initKeywordRules(); + void initCommentRules(); + void initFunctionRules(); + void initParenthesisRules(); + void initCharRules(); + void initQuotationRules(); +}; From 51cbcf9a1d63f07f65340b91ef77980598a0e7c5 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:22:51 -0700 Subject: [PATCH 081/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Add=20SyntaxGo?= =?UTF-8?q?=20foundation=20class=20for=20Go=20syntax=20highlighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Syntax/SyntaxGo.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 include/Syntax/SyntaxGo.h diff --git a/include/Syntax/SyntaxGo.h b/include/Syntax/SyntaxGo.h new file mode 100644 index 0000000..5dc9644 --- /dev/null +++ b/include/Syntax/SyntaxGo.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include + +/** + * @class SyntaxGo + * @brief A syntax highlighter class for Go code, inheriting from the Syntax base class. + * + * This class provides syntax highlighting for Golang code by defining + * various QTextCharFormat rules for different Go language elements. + */ +class SyntaxGo : public Syntax +{ +public: + SyntaxGo(QTextDocument *parent = nullptr); + +private: + QTextCharFormat m_quotationMark; + + // Initialization functions for different syntax highlighting rules + void initQuotationRules(); +}; From 2038876e46db94fd39f57a08b37a895155ab8a52 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:23:00 -0700 Subject: [PATCH 082/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Reintroduce=20Sy?= =?UTF-8?q?ntax=20class=20with=20highlightBlock=20and=20addPattern=20metho?= =?UTF-8?q?ds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Syntax.cpp | 111 ------------------------------------------ src/Syntax/Syntax.cpp | 22 +++++++++ 2 files changed, 22 insertions(+), 111 deletions(-) delete mode 100644 src/Syntax.cpp create mode 100644 src/Syntax/Syntax.cpp diff --git a/src/Syntax.cpp b/src/Syntax.cpp deleted file mode 100644 index 5896a88..0000000 --- a/src/Syntax.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "Syntax.h" - -#include -#include - -Syntax::Syntax(QTextDocument *parent) : QSyntaxHighlighter(parent) -{ - initKeywordRules(); - initFunctionRules(); - initParenthesisRules(); - initCharRules(); - - // Keep these two calls are at the end - // to correctly apply the rules for strings and comments - initCommentRules(); - initQuotationRules(); -} - -void Syntax::initKeywordRules() -{ - // Keyword format - m_keywordFormat.setForeground(Qt::blue); - m_keywordFormat.setFontWeight(QFont::Bold); - - 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", "\\bforeach\\b"}; - - for (const QString &pattern : keywordPatterns) - { - addPattern(pattern, m_keywordFormat); - } - - m_iterationFormat.setForeground(Qt::darkMagenta); - m_iterationFormat.setFontWeight(QFont::Bold); - QStringList iterationPatterns = {"\\bfor\\b", "\\bwhile\\b", "\\bdo\\b", "\\bif\\b", "\\belse\\b"}; - for (const QString &pattern : iterationPatterns) - { - addPattern(pattern, m_iterationFormat); - } -} - -void Syntax::initCommentRules() -{ - // Single line comment format expression - m_singleLineCommentFormat.setForeground(Qt::darkGray); - addPattern("//[^\n]*", m_singleLineCommentFormat); - - // TO-DO: Add multi-line comment support -} - -void Syntax::initQuotationRules() -{ - // Double quotation mark for string - m_quotationMark.setForeground(Qt::darkGreen); - addPattern("\"(\\\\.|[^\"\\\\])*\"", m_quotationMark); - - // TO-DO: Add single quotation mark for character -} - -void Syntax::initFunctionRules() -{ - // Function format expression - m_functionFormat.setFontItalic(true); - m_functionFormat.setForeground(Qt::darkYellow); - addPattern("\\b(?!for\\b|if\\b|else\\b|while\\b|do\\b)[a-zA-Z_][a-zA-Z0-9_]*(?=\\s*\\()", m_functionFormat); -} - -void Syntax::initParenthesisRules() -{ - // Color pattern for parenthesis - QColor parenthesisColor("#6495ED"); - m_parenthesisFormat.setForeground(parenthesisColor); - addPattern("[()]", m_parenthesisFormat); -} - -// Regex for single character format 'a', '\n', etc. -void Syntax::initCharRules() -{ - m_charFormat.setForeground(Qt::darkCyan); - addPattern("'(\\\\.|[^'])'", m_charFormat); -} - -void Syntax::addPattern(const QString &pattern, const QTextCharFormat &format) -{ - SyntaxRule rule; - rule.m_pattern = QRegularExpression(pattern); - rule.m_format = format; - m_syntaxRules.append(rule); -} - -void Syntax::highlightBlock(const QString &text) -{ - for (const SyntaxRule &rule : m_syntaxRules) - { - QRegularExpressionMatchIterator matchIterator = rule.m_pattern.globalMatch(text); - while (matchIterator.hasNext()) - { - QRegularExpressionMatch match = matchIterator.next(); - setFormat(match.capturedStart(), match.capturedLength(), rule.m_format); - } - } -} diff --git a/src/Syntax/Syntax.cpp b/src/Syntax/Syntax.cpp new file mode 100644 index 0000000..1f5a582 --- /dev/null +++ b/src/Syntax/Syntax.cpp @@ -0,0 +1,22 @@ +#include "Syntax/Syntax.h" + +void Syntax::highlightBlock(const QString &text) +{ + for (const SyntaxRule &rule : m_syntaxRules) + { + QRegularExpressionMatchIterator matchIterator = rule.m_pattern.globalMatch(text); + while (matchIterator.hasNext()) + { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.m_format); + } + } +} + +void Syntax::addPattern(const QString &pattern, const QTextCharFormat &format) +{ + SyntaxRule rule; + rule.m_pattern = QRegularExpression(pattern); + rule.m_format = format; + m_syntaxRules.append(rule); +} \ No newline at end of file From d515736c884edf4ca4516d5bfe19df8b33ffeebb Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:23:13 -0700 Subject: [PATCH 083/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Implement=20Synt?= =?UTF-8?q?axCpp=20class=20for=20C++=20syntax=20highlighting=20with=20keyw?= =?UTF-8?q?ord,=20function,=20comment,=20quotation,=20parenthesis,=20and?= =?UTF-8?q?=20character=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Syntax/SyntaxCpp.cpp | 91 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/Syntax/SyntaxCpp.cpp diff --git a/src/Syntax/SyntaxCpp.cpp b/src/Syntax/SyntaxCpp.cpp new file mode 100644 index 0000000..e1159df --- /dev/null +++ b/src/Syntax/SyntaxCpp.cpp @@ -0,0 +1,91 @@ +#include "Syntax/SyntaxCpp.h" + +#include +#include + +SyntaxCpp::SyntaxCpp(QTextDocument *parent) : Syntax(parent) +{ + qDebug() << "SyntaxCpp highlighter created"; + initKeywordRules(); + initFunctionRules(); + initParenthesisRules(); + initCharRules(); + + // Keep these two calls are at the end + // to correctly apply the rules for strings and comments + initCommentRules(); + initQuotationRules(); +} + +void SyntaxCpp::initKeywordRules() +{ + // Keyword format + m_keywordFormat.setForeground(Qt::blue); + m_keywordFormat.setFontWeight(QFont::Bold); + + 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", "\\bforeach\\b"}; + + for (const QString &pattern : keywordPatterns) + { + addPattern(pattern, m_keywordFormat); + } + + m_iterationFormat.setForeground(Qt::darkMagenta); + m_iterationFormat.setFontWeight(QFont::Bold); + QStringList iterationPatterns = {"\\bfor\\b", "\\bwhile\\b", "\\bdo\\b", "\\bif\\b", "\\belse\\b"}; + for (const QString &pattern : iterationPatterns) + { + addPattern(pattern, m_iterationFormat); + } +} + +void SyntaxCpp::initCommentRules() +{ + // Single line comment format expression + m_singleLineCommentFormat.setForeground(Qt::darkGray); + addPattern("//[^\n]*", m_singleLineCommentFormat); + + // TO-DO: Add multi-line comment support +} + +void SyntaxCpp::initQuotationRules() +{ + // Double quotation mark for string + m_quotationMark.setForeground(Qt::darkGreen); + addPattern("\"(\\\\.|[^\"\\\\])*\"", m_quotationMark); + + // TO-DO: Add single quotation mark for character +} + +void SyntaxCpp::initFunctionRules() +{ + // Function format expression + m_functionFormat.setFontItalic(true); + m_functionFormat.setForeground(Qt::darkYellow); + addPattern("\\b(?!for\\b|if\\b|else\\b|while\\b|do\\b)[a-zA-Z_][a-zA-Z0-9_]*(?=\\s*\\()", m_functionFormat); +} + +void SyntaxCpp::initParenthesisRules() +{ + // Color pattern for parenthesis + QColor parenthesisColor("#6495ED"); + m_parenthesisFormat.setForeground(parenthesisColor); + addPattern("[()]", m_parenthesisFormat); +} + +// Regex for single character format 'a', '\n', etc. +void SyntaxCpp::initCharRules() +{ + m_charFormat.setForeground(Qt::darkCyan); + addPattern("'(\\\\.|[^'])'", m_charFormat); +} \ No newline at end of file From c4ec1a04798ec302dd1daa16913221984d195c52 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:23:36 -0700 Subject: [PATCH 084/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Add=20foundation?= =?UTF-8?q?=20SyntaxGo=20implementation=20for=20Go=20syntax=20highlighting?= =?UTF-8?q?=20with=20quotation=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Syntax/SyntaxGo.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/Syntax/SyntaxGo.cpp diff --git a/src/Syntax/SyntaxGo.cpp b/src/Syntax/SyntaxGo.cpp new file mode 100644 index 0000000..b90b9d7 --- /dev/null +++ b/src/Syntax/SyntaxGo.cpp @@ -0,0 +1,18 @@ +#include "Syntax/SyntaxGo.h" + +#include +#include + +SyntaxGo::SyntaxGo(QTextDocument *parent) : Syntax(parent) +{ + qDebug() << "SyntaxGo highlighter created"; + initQuotationRules(); +} + +void SyntaxGo::initQuotationRules() +{ + // Double quotation mark for string + m_quotationMark.setForeground(Qt::darkGreen); + addPattern("\"(\\\\.|[^\"\\\\])*\"", m_quotationMark); + // TO-DO: Add single quotation mark for character +} \ No newline at end of file From b3d0340c35821321bfe1b304c9e217ca36f8f349 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:23:48 -0700 Subject: [PATCH 085/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Add=20QSyntaxHig?= =?UTF-8?q?hlighter=20member=20to=20FileManager=20for=20syntax=20highlight?= =?UTF-8?q?ing=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/FileManager.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/FileManager.h b/include/FileManager.h index bccf188..178e8f3 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -2,6 +2,7 @@ #include #include +#include class CodeEditor; class MainWindow; @@ -51,5 +52,6 @@ public slots: CodeEditor *m_editor; MainWindow *m_mainWindow; + QSyntaxHighlighter *m_currentHighlighter = nullptr; QString m_currentFileName; }; From d9ed3f002162ce185bb99156fee88f977bf86622 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:24:00 -0700 Subject: [PATCH 086/160] =?UTF-8?q?=E2=9C=A8=20Refactor:=20Remove=20unused?= =?UTF-8?q?=20Syntax=20member=20from=20MainWindow=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/MainWindow.h | 1 - src/MainWindow.cpp | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 591a273..30c5a8c 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -45,7 +45,6 @@ private slots: void createAppActions(QMenu *appMenu); std::unique_ptr m_editor; - std::unique_ptr m_syntax; std::unique_ptr m_tree; FileManager *m_fileManager; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 1e1b2a6..1bde3a0 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,5 +1,4 @@ #include "MainWindow.h" -#include "Syntax.h" #include "Tree.h" #include "CodeEditor.h" #include "FileManager.h" @@ -13,10 +12,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - m_editor(std::make_unique(this)), - m_syntax(std::make_unique(m_editor->document())), - m_tree(nullptr), - m_fileManager(&FileManager::getInstance()) + m_editor(std::make_unique(this)), + m_tree(nullptr), + m_fileManager(&FileManager::getInstance()) { m_fileManager->initialize(m_editor.get(), this); setWindowTitle("CodeAstra ~ Code Editor"); From 70cd4fb07c19fc8799f7b6afea58a13200add6b6 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:24:10 -0700 Subject: [PATCH 087/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Add=20SyntaxMana?= =?UTF-8?q?ger=20class=20for=20dynamic=20syntax=20highlighter=20creation?= =?UTF-8?q?=20based=20on=20file=20extensions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/SyntaxManager.h | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 include/SyntaxManager.h diff --git a/include/SyntaxManager.h b/include/SyntaxManager.h new file mode 100644 index 0000000..35243ce --- /dev/null +++ b/include/SyntaxManager.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Syntax/SyntaxCpp.h" +#include "Syntax/SyntaxGo.h" +// #include "Syntax/SyntaxPy.h" // Uncomment when implemented +// #include "Syntax/SyntaxEx.h" // Uncomment when implemented + +#include +#include +#include + +/** + * @class SyntaxManager + * @brief A utility class for creating syntax highlighters based on file extensions. + * + * This class provides a static method to create appropriate syntax highlighters + * for different programming languages based on the file extension. If a syntax + * highlighter for a specific file type is not implemented, it returns a nullptr. + */ +class SyntaxManager +{ +public: + static std::unique_ptr createSyntaxHighlighter(const QString &extension, QTextDocument *doc) + { + qDebug() << "Creating highlighter for extension:" << extension; + if (extension == "cpp" || extension == "h" || extension == "c") + { + qDebug() << "C/C++ syntax highlighter selected."; + return std::make_unique(doc); + } + else if (extension == "py") + { + qDebug() << "Python syntax highlighter not implemented."; + // return std::make_unique(doc); + } + else if (extension == "go") + { + qDebug() << "Go syntax highlighter selected."; + return std::make_unique(doc); + } + else if (extension == "ex") + { + qDebug() << "Elixir syntax highlighter not implemented."; + // return std::make_unique(doc); + } + else + { + qDebug() << "No syntax highlighter available for" << extension << "file type."; + } + + return nullptr; + } +}; From 7f67166d31fcae4a49b7e5acf6bcf62b5b808916 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:24:26 -0700 Subject: [PATCH 088/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Integrate=20dyna?= =?UTF-8?q?mic=20syntax=20highlighting=20in=20FileManager=20for=20loaded?= =?UTF-8?q?=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/FileManager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 6f6c89e..f05f3fa 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -1,6 +1,7 @@ #include "FileManager.h" #include "CodeEditor.h" #include "MainWindow.h" +#include "SyntaxManager.h" #include #include @@ -116,6 +117,11 @@ void FileManager::loadFileInEditor(const QString &filePath) if (m_editor) { m_editor->setPlainText(in.readAll()); + + delete m_currentHighlighter; + + // Create and assign a new syntax highlighter based on language extension + m_currentHighlighter = SyntaxManager::createSyntaxHighlighter(getFileExtension(), m_editor->document()).release(); } else { @@ -130,7 +136,7 @@ void FileManager::loadFileInEditor(const QString &filePath) } else { - qWarning() << "MainWindow is initialized in FileManager."; + qWarning() << "MainWindow is not initialized in FileManager."; } } From a71c04f8e032a02473bb6d86fec6a096dd4445a1 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:24:37 -0700 Subject: [PATCH 089/160] =?UTF-8?q?=E2=9C=A8=20Feature:=20Organize=20synta?= =?UTF-8?q?x-related=20files=20into=20a=20dedicated=20Syntax=20directory?= =?UTF-8?q?=20and=20update=20CMakeLists.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7d76fa..47f31ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,15 +54,20 @@ find_package(Qt${QT_MAJOR_VERSION} COMPONENTS Widgets Test REQUIRED) add_library(${TARGET_NAME} src/MainWindow.cpp src/CodeEditor.cpp - src/Syntax.cpp src/Tree.cpp src/FileManager.cpp + src/Syntax/Syntax.cpp + src/Syntax/SyntaxCpp.cpp + src/Syntax/SyntaxGo.cpp include/MainWindow.h include/CodeEditor.h - include/Syntax.h include/Tree.h include/LineNumberArea.h include/FileManager.h + include/SyntaxManager.h + include/Syntax/SyntaxCpp.h + include/Syntax/SyntaxGo.h + include/Syntax/Syntax.h ) # Create the executable for the application From 884ce777aacfefac5f40d38d1835e22d0df3d019 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Mar 2025 15:27:57 -0700 Subject: [PATCH 090/160] =?UTF-8?q?=E2=9C=A8=20Update=20README.md=20to=20i?= =?UTF-8?q?nclude=20supported=20languages=20for=20syntax=20highlighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3a0d99e..d68f055 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,13 @@ Please, check the [wiki](https://github.com/sandbox-science/CodeAstra/wiki) for - [ ] Create a new file ~ in progress - [x] File tree navigation - [ ] Syntax highlighting ~ in progress + - Supported Languagues: + - [ ] C/C++ (**in progress**) + - [ ] Golang (**in progress**) + - [ ] Python (**Backlog**) + - [ ] Elixir (**Backlog**) + - more to come ... ([contribution welcomed](https://github.com/sandbox-science/CodeAstra/issues/4)) + - [ ] Plugin system ## To-Do From 8200c9beec55590a6312066dfedc5af7c66eeb47 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:07:22 -0700 Subject: [PATCH 091/160] =?UTF-8?q?=E2=9C=A8=20Refactor:=20Remove=20obsole?= =?UTF-8?q?te=20syntax=20highlighter=20classes=20for=20C++,=20Go,=20and=20?= =?UTF-8?q?general=20syntax?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Syntax/Syntax.cpp | 22 ---------- src/Syntax/SyntaxCpp.cpp | 91 ---------------------------------------- src/Syntax/SyntaxGo.cpp | 18 -------- 3 files changed, 131 deletions(-) delete mode 100644 src/Syntax/Syntax.cpp delete mode 100644 src/Syntax/SyntaxCpp.cpp delete mode 100644 src/Syntax/SyntaxGo.cpp diff --git a/src/Syntax/Syntax.cpp b/src/Syntax/Syntax.cpp deleted file mode 100644 index 1f5a582..0000000 --- a/src/Syntax/Syntax.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "Syntax/Syntax.h" - -void Syntax::highlightBlock(const QString &text) -{ - for (const SyntaxRule &rule : m_syntaxRules) - { - QRegularExpressionMatchIterator matchIterator = rule.m_pattern.globalMatch(text); - while (matchIterator.hasNext()) - { - QRegularExpressionMatch match = matchIterator.next(); - setFormat(match.capturedStart(), match.capturedLength(), rule.m_format); - } - } -} - -void Syntax::addPattern(const QString &pattern, const QTextCharFormat &format) -{ - SyntaxRule rule; - rule.m_pattern = QRegularExpression(pattern); - rule.m_format = format; - m_syntaxRules.append(rule); -} \ No newline at end of file diff --git a/src/Syntax/SyntaxCpp.cpp b/src/Syntax/SyntaxCpp.cpp deleted file mode 100644 index e1159df..0000000 --- a/src/Syntax/SyntaxCpp.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "Syntax/SyntaxCpp.h" - -#include -#include - -SyntaxCpp::SyntaxCpp(QTextDocument *parent) : Syntax(parent) -{ - qDebug() << "SyntaxCpp highlighter created"; - initKeywordRules(); - initFunctionRules(); - initParenthesisRules(); - initCharRules(); - - // Keep these two calls are at the end - // to correctly apply the rules for strings and comments - initCommentRules(); - initQuotationRules(); -} - -void SyntaxCpp::initKeywordRules() -{ - // Keyword format - m_keywordFormat.setForeground(Qt::blue); - m_keywordFormat.setFontWeight(QFont::Bold); - - 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", "\\bforeach\\b"}; - - for (const QString &pattern : keywordPatterns) - { - addPattern(pattern, m_keywordFormat); - } - - m_iterationFormat.setForeground(Qt::darkMagenta); - m_iterationFormat.setFontWeight(QFont::Bold); - QStringList iterationPatterns = {"\\bfor\\b", "\\bwhile\\b", "\\bdo\\b", "\\bif\\b", "\\belse\\b"}; - for (const QString &pattern : iterationPatterns) - { - addPattern(pattern, m_iterationFormat); - } -} - -void SyntaxCpp::initCommentRules() -{ - // Single line comment format expression - m_singleLineCommentFormat.setForeground(Qt::darkGray); - addPattern("//[^\n]*", m_singleLineCommentFormat); - - // TO-DO: Add multi-line comment support -} - -void SyntaxCpp::initQuotationRules() -{ - // Double quotation mark for string - m_quotationMark.setForeground(Qt::darkGreen); - addPattern("\"(\\\\.|[^\"\\\\])*\"", m_quotationMark); - - // TO-DO: Add single quotation mark for character -} - -void SyntaxCpp::initFunctionRules() -{ - // Function format expression - m_functionFormat.setFontItalic(true); - m_functionFormat.setForeground(Qt::darkYellow); - addPattern("\\b(?!for\\b|if\\b|else\\b|while\\b|do\\b)[a-zA-Z_][a-zA-Z0-9_]*(?=\\s*\\()", m_functionFormat); -} - -void SyntaxCpp::initParenthesisRules() -{ - // Color pattern for parenthesis - QColor parenthesisColor("#6495ED"); - m_parenthesisFormat.setForeground(parenthesisColor); - addPattern("[()]", m_parenthesisFormat); -} - -// Regex for single character format 'a', '\n', etc. -void SyntaxCpp::initCharRules() -{ - m_charFormat.setForeground(Qt::darkCyan); - addPattern("'(\\\\.|[^'])'", m_charFormat); -} \ No newline at end of file diff --git a/src/Syntax/SyntaxGo.cpp b/src/Syntax/SyntaxGo.cpp deleted file mode 100644 index b90b9d7..0000000 --- a/src/Syntax/SyntaxGo.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "Syntax/SyntaxGo.h" - -#include -#include - -SyntaxGo::SyntaxGo(QTextDocument *parent) : Syntax(parent) -{ - qDebug() << "SyntaxGo highlighter created"; - initQuotationRules(); -} - -void SyntaxGo::initQuotationRules() -{ - // Double quotation mark for string - m_quotationMark.setForeground(Qt::darkGreen); - addPattern("\"(\\\\.|[^\"\\\\])*\"", m_quotationMark); - // TO-DO: Add single quotation mark for character -} \ No newline at end of file From 27b36c9686c23c261ff9a9e8f1a7110095c241ab Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:07:34 -0700 Subject: [PATCH 092/160] =?UTF-8?q?=E2=9C=A8=20Refactor:=20Remove=20obsole?= =?UTF-8?q?te=20syntax=20highlighter=20classes=20for=20C++=20and=20Go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Syntax/SyntaxCpp.h | 36 ------------------------------------ include/Syntax/SyntaxGo.h | 25 ------------------------- 2 files changed, 61 deletions(-) delete mode 100644 include/Syntax/SyntaxCpp.h delete mode 100644 include/Syntax/SyntaxGo.h diff --git a/include/Syntax/SyntaxCpp.h b/include/Syntax/SyntaxCpp.h deleted file mode 100644 index d67a23b..0000000 --- a/include/Syntax/SyntaxCpp.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -/** - * @class SyntaxCpp - * @brief A syntax highlighter class for C/C++ code, inheriting from the Syntax base class. - * - * This class provides syntax highlighting for C and C++ code by defining various - * QTextCharFormat rules for different C++ language elements. - */ -class SyntaxCpp : public Syntax -{ -public: - SyntaxCpp(QTextDocument *parent = nullptr); - -private: - QTextCharFormat m_keywordFormat; - QTextCharFormat m_singleLineCommentFormat; - QTextCharFormat m_quotationMark; - QTextCharFormat m_functionFormat; - QTextCharFormat m_parenthesisFormat; - QTextCharFormat m_charFormat; - QTextCharFormat m_iterationFormat; - - // Initialization functions for different syntax highlighting rules - void initKeywordRules(); - void initCommentRules(); - void initFunctionRules(); - void initParenthesisRules(); - void initCharRules(); - void initQuotationRules(); -}; diff --git a/include/Syntax/SyntaxGo.h b/include/Syntax/SyntaxGo.h deleted file mode 100644 index 5dc9644..0000000 --- a/include/Syntax/SyntaxGo.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -/** - * @class SyntaxGo - * @brief A syntax highlighter class for Go code, inheriting from the Syntax base class. - * - * This class provides syntax highlighting for Golang code by defining - * various QTextCharFormat rules for different Go language elements. - */ -class SyntaxGo : public Syntax -{ -public: - SyntaxGo(QTextDocument *parent = nullptr); - -private: - QTextCharFormat m_quotationMark; - - // Initialization functions for different syntax highlighting rules - void initQuotationRules(); -}; From 221d8eba1bfdeae5c8efe7f9563d2ec5fc766385 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:07:53 -0700 Subject: [PATCH 093/160] =?UTF-8?q?=E2=9C=A8=20Refactor:=20Update=20Syntax?= =?UTF-8?q?=20class=20constructor=20to=20accept=20YAML=20configuration=20a?= =?UTF-8?q?nd=20remove=20unnecessary=20using=20declaration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Syntax/Syntax.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/Syntax/Syntax.h b/include/Syntax/Syntax.h index c3efe17..25eb89c 100644 --- a/include/Syntax/Syntax.h +++ b/include/Syntax/Syntax.h @@ -3,6 +3,7 @@ #include #include #include +#include /** * @class Syntax @@ -20,9 +21,8 @@ class Syntax : public QSyntaxHighlighter Q_OBJECT public: - using QSyntaxHighlighter::QSyntaxHighlighter; - - virtual ~Syntax() = default; + Syntax(QTextDocument *parent, const YAML::Node &config); + ~Syntax() = default; protected: /** @@ -58,4 +58,6 @@ class Syntax : public QSyntaxHighlighter * @param format The text format to apply to matches of the pattern. */ void addPattern(const QString &pattern, const QTextCharFormat &format); + + void loadSyntaxRules(const YAML::Node &config); }; \ No newline at end of file From 4fb7f24d9f20bc85568679431be5f0791e5bf23c Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:10:31 -0700 Subject: [PATCH 094/160] =?UTF-8?q?=E2=9C=A8=20Refactor:=20Simplify=20Synt?= =?UTF-8?q?axManager=20class=20by=20consolidating=20highlighter=20creation?= =?UTF-8?q?=20logic=20and=20updating=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/SyntaxManager.h | 55 ++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/include/SyntaxManager.h b/include/SyntaxManager.h index 35243ce..9e41f3b 100644 --- a/include/SyntaxManager.h +++ b/include/SyntaxManager.h @@ -1,53 +1,34 @@ #pragma once -#include "Syntax/SyntaxCpp.h" -#include "Syntax/SyntaxGo.h" -// #include "Syntax/SyntaxPy.h" // Uncomment when implemented -// #include "Syntax/SyntaxEx.h" // Uncomment when implemented +#include "syntax/Syntax.h" #include #include #include +#include + /** * @class SyntaxManager - * @brief A utility class for creating syntax highlighters based on file extensions. + * @brief Manages the creation of syntax highlighters for different file types. + * + * The SyntaxManager class provides functionality to create syntax highlighters + * based on file extensions. It supports dynamic creation of highlighters by + * utilizing configuration data and applies them to QTextDocument objects. * - * This class provides a static method to create appropriate syntax highlighters - * for different programming languages based on the file extension. If a syntax - * highlighter for a specific file type is not implemented, it returns a nullptr. + * @note This class is designed to work with the Qt framework and YAML configuration. */ class SyntaxManager { public: - static std::unique_ptr createSyntaxHighlighter(const QString &extension, QTextDocument *doc) - { - qDebug() << "Creating highlighter for extension:" << extension; - if (extension == "cpp" || extension == "h" || extension == "c") - { - qDebug() << "C/C++ syntax highlighter selected."; - return std::make_unique(doc); - } - else if (extension == "py") - { - qDebug() << "Python syntax highlighter not implemented."; - // return std::make_unique(doc); - } - else if (extension == "go") - { - qDebug() << "Go syntax highlighter selected."; - return std::make_unique(doc); - } - else if (extension == "ex") - { - qDebug() << "Elixir syntax highlighter not implemented."; - // return std::make_unique(doc); - } - else - { - qDebug() << "No syntax highlighter available for" << extension << "file type."; - } + /** + * @brief Creates a syntax highlighter based on the file extension. + * @param extension The file extension (e.g., "cpp", "py"). + * @param doc The QTextDocument to which the highlighter will be applied. + * @return A unique pointer to the appropriate syntax highlighter, or nullptr if not available. + */ + static std::unique_ptr createSyntaxHighlighter(const QString &extension, QTextDocument *doc); - return nullptr; - } +private: + static std::unique_ptr createHighlighter(QTextDocument *doc, const std::vector &config, const QString &extension); }; From c4d4ad9c1abd1c3aa107d90482380f410e061a57 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:10:44 -0700 Subject: [PATCH 095/160] =?UTF-8?q?=E2=9C=A8=20Refactor:=20Consolidate=20t?= =?UTF-8?q?est=20executable=20definitions=20and=20ensure=20proper=20linkin?= =?UTF-8?q?g=20for=20yaml-cpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/CMakeLists.txt | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 48b3ff8..9f7c693 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,22 +2,19 @@ enable_testing() # Add individual test executables -add_executable(test_mainwindow - test_mainwindow.cpp -) - -# Add test_tree executable -add_executable(test_tree - test_tree.cpp -) +add_executable(test_mainwindow test_mainwindow.cpp) +add_executable(test_tree test_tree.cpp) +add_executable(test_syntax test_syntax.cpp) -# Link the test with the CodeAstra library and necessary Qt libraries -target_link_libraries(test_mainwindow PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test) -target_link_libraries(test_tree PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test) +# Make sure the test executables link with the necessary libraries +target_link_libraries(test_mainwindow PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test yaml-cpp) +target_link_libraries(test_tree PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test yaml-cpp) +target_link_libraries(test_syntax PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test yaml-cpp) # Register each test with CTest add_test(NAME test_mainwindow COMMAND test_mainwindow) add_test(NAME test_tree COMMAND test_tree) +add_test(NAME test_syntax COMMAND test_tree) # Set the runtime output directory for the test executables set_target_properties(test_mainwindow PROPERTIES @@ -26,10 +23,29 @@ set_target_properties(test_mainwindow PROPERTIES set_target_properties(test_tree PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests ) +set_target_properties(test_syntax PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests +) set_property(SOURCE test_mainwindow.cpp PROPERTY SKIP_AUTOMOC OFF) set_property(SOURCE test_tree.cpp PROPERTY SKIP_AUTOMOC OFF) +set_property(SOURCE test_syntax.cpp PROPERTY SKIP_AUTOMOC OFF) # Include directories for tests target_include_directories(test_mainwindow PRIVATE ${CMAKE_SOURCE_DIR}/include) target_include_directories(test_tree PRIVATE ${CMAKE_SOURCE_DIR}/include) +target_include_directories(test_syntax PRIVATE ${CMAKE_SOURCE_DIR}/include) + +# Ensure proper linking directories are set for yaml-cpp +target_include_directories(test_mainwindow PRIVATE /opt/homebrew/include) +target_include_directories(test_tree PRIVATE /opt/homebrew/include) +target_include_directories(test_syntax PRIVATE /opt/homebrew/include) + +target_link_directories(test_mainwindow PRIVATE /opt/homebrew/lib) +target_link_directories(test_tree PRIVATE /opt/homebrew/lib) +target_link_directories(test_syntax PRIVATE /opt/homebrew/lib) + +# Link the libraries explicitly for both main executable and tests +target_link_libraries(test_mainwindow PRIVATE /opt/homebrew/lib/libyaml-cpp.dylib) +target_link_libraries(test_tree PRIVATE /opt/homebrew/lib/libyaml-cpp.dylib) +target_link_libraries(test_syntax PRIVATE /opt/homebrew/lib/libyaml-cpp.dylib) From 0edee19154ce3fac9616d5d4738ac7f5dd12d76d Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:12:35 -0700 Subject: [PATCH 096/160] :white_check_mark: Test: Add unit tests for Syntax class to validate loading of syntax rules from YAML configuration --- tests/test_syntax.cpp | 105 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 tests/test_syntax.cpp diff --git a/tests/test_syntax.cpp b/tests/test_syntax.cpp new file mode 100644 index 0000000..e851b75 --- /dev/null +++ b/tests/test_syntax.cpp @@ -0,0 +1,105 @@ +#include +#include "syntax/Syntax.h" + +#include +#include +#include + +// Helper function to create a YAML node for testing +YAML::Node createTestConfig() +{ + YAML::Node config; + YAML::Node keywords = config["keywords"]; + + YAML::Node category1; + YAML::Node rule1; + rule1["regex"] = "\\bint\\b"; + rule1["color"] = "#ff0000"; + rule1["bold"] = true; + rule1["italic"] = false; + category1.push_back(rule1); + + YAML::Node rule2; + rule2["regex"] = "\\bfloat\\b"; + rule2["color"] = "#00ff00"; + rule2["bold"] = false; + rule2["italic"] = true; + category1.push_back(rule2); + + keywords["types"] = category1; + + return config; +} + +class TestSyntax : public QObject +{ + Q_OBJECT + +protected: + QTextDocument *document; + Syntax *syntax; + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testLoadValidSyntaxRules(); + void testLoadEmptySyntaxRules(); + void testLoadMissingKeywords(); +}; + +void TestSyntax::initTestCase() +{ + qDebug() << "Initializing TestSyntax tests..."; + document = new QTextDocument(); + syntax = new Syntax(document, YAML::Node()); +} + +void TestSyntax::cleanupTestCase() +{ + qDebug() << "Cleaning up TestSyntax tests..."; +} + +void TestSyntax::testLoadEmptySyntaxRules() +{ + YAML::Node config; + syntax->loadSyntaxRules(config); + + // Verify that no rules were loaded + QVERIFY(syntax->m_syntaxRules.isEmpty()); +} + +void TestSyntax::testLoadValidSyntaxRules() +{ + YAML::Node config = createTestConfig(); + syntax->loadSyntaxRules(config); + + // Verify that the rules were loaded correctly + QVERIFY(syntax->m_syntaxRules.size() == 2); + + // Check the first rule + const auto &rule1 = syntax->m_syntaxRules[0]; + QCOMPARE_EQ(rule1.m_pattern.pattern(), "\\bint\\b"); + QCOMPARE_EQ(rule1.m_format.foreground().color(), QColor("#ff0000")); + QCOMPARE_EQ(rule1.m_format.fontWeight(), QFont::Bold); + QCOMPARE_NE(rule1.m_format.fontItalic(), true); + + // Check the second rule + const auto &rule2 = syntax->m_syntaxRules[1]; + QCOMPARE_EQ(rule2.m_pattern.pattern(), "\\bfloat\\b"); + QCOMPARE_EQ(rule2.m_format.foreground().color(), QColor("#00ff00")); + QCOMPARE_EQ(rule2.m_format.fontWeight(), QFont::Normal); + QCOMPARE_EQ(rule2.m_format.fontItalic(), true); +} + +void TestSyntax::testLoadMissingKeywords() +{ + YAML::Node config; + config["other_section"] = YAML::Node(); + syntax->loadSyntaxRules(config); + + // Verify that no rules were loaded + QVERIFY(syntax->m_syntaxRules.isEmpty()); +} + +QTEST_MAIN(TestSyntax) +#include "test_syntax.moc" \ No newline at end of file From 67e2423cc50c74e7792a33ec4c4691ef312a5769 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:13:09 -0700 Subject: [PATCH 097/160] =?UTF-8?q?=E2=9C=A8=20Add=20syntax=20configuratio?= =?UTF-8?q?n=20files=20for=20C++,=20Go,=20Markdown,=20Python,=20and=20YAML?= =?UTF-8?q?=20with=20keyword=20highlighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/cpp.syntax.yaml | 32 ++++++++++++++++++++++++++++++++ config/go.syntax.yaml | 12 ++++++++++++ config/markdown.syntax.yaml | 7 +++++++ config/python.syntax.yaml | 21 +++++++++++++++++++++ config/yaml.syntax.yaml | 19 +++++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 config/cpp.syntax.yaml create mode 100644 config/go.syntax.yaml create mode 100644 config/markdown.syntax.yaml create mode 100644 config/python.syntax.yaml create mode 100644 config/yaml.syntax.yaml diff --git a/config/cpp.syntax.yaml b/config/cpp.syntax.yaml new file mode 100644 index 0000000..f97b264 --- /dev/null +++ b/config/cpp.syntax.yaml @@ -0,0 +1,32 @@ +extensions: [cpp, c, h, hpp] + +keywords: + keyword: + - regex: "\\b(char|class|const|double|enum|explicit|friend|inline|int|long|namespace|operator|private|protected|public|short|signals|signed|slots|static|struct|template|typedef|typename|union|unsigned|virtual|void|volatile|foreach)\\b" + color: "#003478" + bold: "true" + - regex: "\\b(for|while|do|if|else)\\b" + color: "#D9001D" + - regex: "(? Date: Fri, 28 Mar 2025 17:13:54 -0700 Subject: [PATCH 098/160] :recycle: Refactor: Update CMake configuration to improve YAML file handling and ensure proper linking of Qt components --- CMakeLists.txt | 60 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 47f31ad..dba7f2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Set default build output directories set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) +set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}) # Detect operating system if(WIN32) @@ -47,8 +48,24 @@ endif() # Set Qt path for find_package set(CMAKE_PREFIX_PATH ${Qt_DIR}) -# Find Qt Widgets -find_package(Qt${QT_MAJOR_VERSION} COMPONENTS Widgets Test REQUIRED) +# Find Qt components +find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS Core Widgets Test) + +# Locate yaml-cpp +if(APPLE) + set(yaml-cpp_DIR "/opt/homebrew/Cellar/yaml-cpp/0.8.0/lib/cmake/yaml-cpp") +endif() +find_package(yaml-cpp REQUIRED CONFIG) + +# Copy YAML files to the build directory (non-macOS case) +set(YAML_SOURCE_DIR ${CMAKE_SOURCE_DIR}/config) +set(YAML_DEST_DIR ${CMAKE_BINARY_DIR}/config) +file(MAKE_DIRECTORY ${YAML_DEST_DIR}) +file(GLOB YAML_FILES "${YAML_SOURCE_DIR}/*.yaml") + +foreach(YAML_FILE ${YAML_FILES}) + file(COPY ${YAML_FILE} DESTINATION ${YAML_DEST_DIR}) +endforeach() # Create the CodeAstra library add_library(${TARGET_NAME} @@ -56,27 +73,34 @@ add_library(${TARGET_NAME} src/CodeEditor.cpp src/Tree.cpp src/FileManager.cpp - src/Syntax/Syntax.cpp - src/Syntax/SyntaxCpp.cpp - src/Syntax/SyntaxGo.cpp + src/Syntax.cpp + src/SyntaxManager.cpp include/MainWindow.h include/CodeEditor.h include/Tree.h include/LineNumberArea.h include/FileManager.h - include/SyntaxManager.h - include/Syntax/SyntaxCpp.h - include/Syntax/SyntaxGo.h - include/Syntax/Syntax.h + include/syntaxManager.h + include/syntax/Syntax.h ) +# Link YAML-CPP to the CodeAstra library +target_link_libraries(${TARGET_NAME} PRIVATE yaml-cpp) + # Create the executable for the application -add_executable(${EXECUTABLE_NAME} - src/main.cpp -) +add_executable(${EXECUTABLE_NAME} src/main.cpp) + +# Ensure YAML config files are copied into macOS app bundle +# if(APPLE) +# add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD +# COMMAND ${CMAKE_COMMAND} -E make_directory "$/Contents/Resources/config" +# COMMAND ${CMAKE_COMMAND} -E copy_directory "${YAML_SOURCE_DIR}" "$/Contents/Resources/config" +# COMMENT "Copying YAML config files into macOS app bundle..." +# ) +# endif() # Link the main executable with the CodeAstra library and Qt libraries -target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${TARGET_NAME} Qt${QT_MAJOR_VERSION}::Widgets) +target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${TARGET_NAME} Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Widgets) # Add the tests subdirectory add_subdirectory(tests) @@ -90,7 +114,7 @@ if(MSVC) target_compile_options(${EXECUTABLE_NAME} PRIVATE /W4 /WX) elseif(APPLE) target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) - set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE TRUE) + # set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE TRUE) else() target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) endif() @@ -108,4 +132,10 @@ set_target_properties(${EXECUTABLE_NAME} PROPERTIES ) # Link necessary Qt libraries to CodeAstra library -target_link_libraries(${TARGET_NAME} PRIVATE Qt${QT_MAJOR_VERSION}::Widgets) \ No newline at end of file +target_link_libraries(${TARGET_NAME} PRIVATE Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Widgets) + +# Ensure correct linking of yaml-cpp (macOS) +if(APPLE) + target_include_directories(${EXECUTABLE_NAME} PRIVATE /opt/homebrew/include) + target_link_directories(${EXECUTABLE_NAME} PRIVATE /opt/homebrew/lib) +endif() \ No newline at end of file From 7de9a8b223fd795e440c982b14eadd42d9de21a3 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:14:10 -0700 Subject: [PATCH 099/160] :recycle: Update: Enhance output messages in Makefile for clarity during build and uninstall processes --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 92474bb..10cfe39 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ all: build install # Run CMake to build the project build: - @echo "Building project with CMake..." + @echo "Building $(PROJECT) with CMake..." @mkdir -p $(BUILD_DIR) @cd $(BUILD_DIR) && cmake $(CMAKE_OPTIONS) @@ -21,7 +21,7 @@ clean: # Uninstalling the software uninstall: clean - @echo "Uninstalling the software..." + @echo "Uninstalling $(PROJECT)..." @rm -rf $(EXECUTABLE).app $(EXECUTABLE)d.app @OS_NAME=$(shell uname -s 2>/dev/null || echo "Windows"); \ if [ "$$OS_NAME" = "Darwin" ]; then \ From 2fe8aa5822a377d15cd91e7f72ade44454adeefc Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:14:20 -0700 Subject: [PATCH 100/160] :recycle: Update: Add libyaml-cpp-dev to Dockerfile dependencies for YAML support --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7c3b361..b138b66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,8 @@ RUN apt-get install -y --no-install-recommends \ qt6-base-dev \ x11-utils \ x11-xserver-utils \ - xvfb + xvfb \ + libyaml-cpp-dev RUN rm -rf /var/lib/apt/lists/* # Copy CodeAstra into the container From d1f7288763357d846c7b8a85df53e92bd3a8f270 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:26:24 -0700 Subject: [PATCH 101/160] :fire: Remove: Delete Syntax class header file as part of codebase cleanup --- include/Syntax/Syntax.h | 63 ----------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 include/Syntax/Syntax.h diff --git a/include/Syntax/Syntax.h b/include/Syntax/Syntax.h deleted file mode 100644 index 25eb89c..0000000 --- a/include/Syntax/Syntax.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -/** - * @class Syntax - * @brief A custom syntax highlighter class that extends QSyntaxHighlighter to provide - * syntax highlighting functionality based on user-defined rules. - * - * This class allows you to define syntax highlighting rules using regular expressions - * and associated text formats. It applies these rules to text blocks to highlight - * specific patterns. - * - * @note This class inherits the constructor from QSyntaxHighlighter. - */ -class Syntax : public QSyntaxHighlighter -{ - Q_OBJECT - -public: - Syntax(QTextDocument *parent, const YAML::Node &config); - ~Syntax() = default; - -protected: - /** - * @brief Highlights the given text block based on the defined syntax rules. - * - * @param text The text block to be highlighted. - */ - void highlightBlock(const QString &text) override; - -public: - /** - * @struct SyntaxRule - * @brief Represents a single syntax highlighting rule. - * - * A syntax rule consists of a regular expression pattern and a text format - * to apply to matching text. - */ - struct SyntaxRule - { - QRegularExpression m_pattern; - QTextCharFormat m_format; - }; - - QVector m_syntaxRules; - - /** - * @brief Adds a new syntax highlighting rule. - * - * This method allows you to define a new rule by specifying a regular expression - * pattern and the corresponding text format. - * - * @param pattern The regular expression pattern for the rule. - * @param format The text format to apply to matches of the pattern. - */ - void addPattern(const QString &pattern, const QTextCharFormat &format); - - void loadSyntaxRules(const YAML::Node &config); -}; \ No newline at end of file From 90eb445434d14145cee395ad4c2bbf67561b0227 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:27:08 -0700 Subject: [PATCH 102/160] :recycle: Refactor: Moved Syntax.h out of the syntax directory (removed directory) --- include/Syntax.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 include/Syntax.h diff --git a/include/Syntax.h b/include/Syntax.h new file mode 100644 index 0000000..25eb89c --- /dev/null +++ b/include/Syntax.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +/** + * @class Syntax + * @brief A custom syntax highlighter class that extends QSyntaxHighlighter to provide + * syntax highlighting functionality based on user-defined rules. + * + * This class allows you to define syntax highlighting rules using regular expressions + * and associated text formats. It applies these rules to text blocks to highlight + * specific patterns. + * + * @note This class inherits the constructor from QSyntaxHighlighter. + */ +class Syntax : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + Syntax(QTextDocument *parent, const YAML::Node &config); + ~Syntax() = default; + +protected: + /** + * @brief Highlights the given text block based on the defined syntax rules. + * + * @param text The text block to be highlighted. + */ + void highlightBlock(const QString &text) override; + +public: + /** + * @struct SyntaxRule + * @brief Represents a single syntax highlighting rule. + * + * A syntax rule consists of a regular expression pattern and a text format + * to apply to matching text. + */ + struct SyntaxRule + { + QRegularExpression m_pattern; + QTextCharFormat m_format; + }; + + QVector m_syntaxRules; + + /** + * @brief Adds a new syntax highlighting rule. + * + * This method allows you to define a new rule by specifying a regular expression + * pattern and the corresponding text format. + * + * @param pattern The regular expression pattern for the rule. + * @param format The text format to apply to matches of the pattern. + */ + void addPattern(const QString &pattern, const QTextCharFormat &format); + + void loadSyntaxRules(const YAML::Node &config); +}; \ No newline at end of file From b4e531ff7fe7470dc364ac5c5a8f516f82f2ab70 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:27:22 -0700 Subject: [PATCH 103/160] :recycle: Refactor: Update include paths for Syntax.h to maintain consistency across files --- CMakeLists.txt | 4 ++-- include/SyntaxManager.h | 2 +- tests/test_syntax.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dba7f2c..cb09344 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,8 +80,8 @@ add_library(${TARGET_NAME} include/Tree.h include/LineNumberArea.h include/FileManager.h - include/syntaxManager.h - include/syntax/Syntax.h + include/SyntaxManager.h + include/Syntax.h ) # Link YAML-CPP to the CodeAstra library diff --git a/include/SyntaxManager.h b/include/SyntaxManager.h index 9e41f3b..83c7234 100644 --- a/include/SyntaxManager.h +++ b/include/SyntaxManager.h @@ -1,6 +1,6 @@ #pragma once -#include "syntax/Syntax.h" +#include "Syntax.h" #include #include diff --git a/tests/test_syntax.cpp b/tests/test_syntax.cpp index e851b75..0166bbe 100644 --- a/tests/test_syntax.cpp +++ b/tests/test_syntax.cpp @@ -1,5 +1,5 @@ #include -#include "syntax/Syntax.h" +#include "Syntax.h" #include #include From e6f30d5eda416864b9b5bf279158506d760ff5c2 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:27:39 -0700 Subject: [PATCH 104/160] :heavy_plus_sign: Add: Implement Syntax class for syntax highlighting functionality --- src/Syntax.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/Syntax.cpp diff --git a/src/Syntax.cpp b/src/Syntax.cpp new file mode 100644 index 0000000..3a2f666 --- /dev/null +++ b/src/Syntax.cpp @@ -0,0 +1,69 @@ +#include "Syntax.h" + +Syntax::Syntax(QTextDocument *parent, const YAML::Node &config) + : QSyntaxHighlighter(parent) +{ + qDebug() << "Syntax highlighter created"; + loadSyntaxRules(config); +} + +void Syntax::highlightBlock(const QString &text) +{ + for (const SyntaxRule &rule : m_syntaxRules) + { + QRegularExpressionMatchIterator matchIterator = rule.m_pattern.globalMatch(text); + while (matchIterator.hasNext()) + { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.m_format); + } + } +} + +void Syntax::addPattern(const QString &pattern, const QTextCharFormat &format) +{ + SyntaxRule rule; + rule.m_pattern = QRegularExpression(pattern); + rule.m_format = format; + m_syntaxRules.append(rule); +} + +void Syntax::loadSyntaxRules(const YAML::Node &config) +{ + m_syntaxRules.clear(); + + if (!config["keywords"]) + { + return; + } + + auto keywords = config["keywords"]; + for (const auto &category : keywords) + { + const std::string key = category.first.as(); + const YAML::Node &rules = category.second; + + // Iterate through each rule in the category + for (const auto &rule : rules) + { + QString regex = QString::fromStdString(rule["regex"].as()); + QColor color(QString::fromStdString(rule["color"].as())); + qDebug() << "regex: " << regex; + + // Create a QTextCharFormat for the rule + QTextCharFormat format; + format.setForeground(color); + if (rule["bold"] && rule["bold"].as()) + { + format.setFontWeight(QFont::Bold); + } + if (rule["italic"] && rule["italic"].as()) + { + format.setFontItalic(true); + } + + // Append the rule to the list of syntax rules + m_syntaxRules.append({QRegularExpression(regex), format}); + } + } +} From ec1a7f93cf2f5056d49df1cd584e6ffc62f05ace Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:28:16 -0700 Subject: [PATCH 105/160] :heavy_plus_sign: Add: Implement SyntaxManager class for managing syntax highlighting based on YAML configurations --- src/SyntaxManager.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/SyntaxManager.cpp diff --git a/src/SyntaxManager.cpp b/src/SyntaxManager.cpp new file mode 100644 index 0000000..9a8bf8f --- /dev/null +++ b/src/SyntaxManager.cpp @@ -0,0 +1,76 @@ +#include "SyntaxManager.h" +#include "Syntax.h" + +#include +#include +#include + +std::unique_ptr SyntaxManager::createSyntaxHighlighter(const QString &extension, QTextDocument *doc) +{ + QString configPath = qgetenv("CONFIG_DIR"); + if (configPath.isEmpty()) + { + configPath = "config"; + } + + QDir syntaxDir(configPath); + + QStringList yamlFiles = syntaxDir.entryList({"*.yaml", "*.yml"}, QDir::Files); + qDebug() << "Directory being scanned: " << syntaxDir.absolutePath(); + + if (syntaxDir.exists()) + { + qDebug() << "Directory exists."; + } + else + { + qDebug() << "Directory does not exist."; + } + + std::vector config; + // Iterate over all YAML files and store their contents as separate nodes + for (const QString &fileName : yamlFiles) + { + QFile file(syntaxDir.filePath(fileName)); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + YAML::Node fileConfig = YAML::Load(file.readAll().toStdString()); + file.close(); + qDebug() << "Loaded YAML from: " << file.fileName(); + + config.push_back(fileConfig); + } + else + { + qDebug() << "Failed to open file: " << file.fileName(); + } + } + + return createHighlighter(doc, config, extension); +} + +std::unique_ptr SyntaxManager::createHighlighter(QTextDocument *doc, const std::vector &config, const QString &extension) +{ + qDebug() << "Creating highlighter for extension:" << extension; + for (const auto &node : config) + { + if (node["extensions"]) + { + for (const auto &ext : node["extensions"]) + { + std::string extensionInConfig = ext.as(); + if (extensionInConfig == extension.toStdString()) + { + return std::make_unique(doc, node); + } + } + } + else + { + qDebug() << "No extensions key in YAML config."; + } + } + + qDebug() << "No matching highlighter found for extension:" << extension; + return nullptr; +} From b29b908b490019419f035e7411f5363733417c1d Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:33:03 -0700 Subject: [PATCH 106/160] Update: Add libyaml-cpp-dev dependency for C++ environment setup in CI workflows --- .github/workflows/cpp.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 677e800..ffc57f9 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -31,7 +31,7 @@ jobs: if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -y g++ make cmake + sudo apt-get install -y g++ make cmake libyaml-cpp-dev g++ --version cmake --version make --version @@ -39,7 +39,7 @@ jobs: - name: Set up C++ environment (macOS) if: matrix.os == 'macos-latest' run: | - brew install make cmake + brew install make cmake yaml-cpp echo 'export PATH="/usr/local/opt/gcc/bin:$PATH"' >> ~/.bash_profile source ~/.bash_profile g++ --version diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 331582d..73e7a0d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -y g++ make cmake + sudo apt-get install -y g++ make cmake libyaml-cpp-dev g++ --version cmake --version make --version @@ -36,7 +36,7 @@ jobs: - name: Set up C++ environment (macOS) if: matrix.os == 'macos-latest' run: | - brew install make cmake + brew install make cmake yaml-cpp echo 'export PATH="/usr/local/opt/gcc/bin:$PATH"' >> ~/.bash_profile source ~/.bash_profile g++ --version From 6ce010f4169cd65349a8f7e0c15279bf4e61f5dd Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:40:54 -0700 Subject: [PATCH 107/160] Add: Specify 'runs-on' for docker job in CI workflow --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 73e7a0d..3620365 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,6 +84,7 @@ jobs: run: make test docker: + runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 From 2b211dfd7c8fa28686ef0d1a79cf5d0213834234 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:40:57 -0700 Subject: [PATCH 108/160] Update: Modify project installation step to avoid desktop shortcut on Mac/Windows --- .github/workflows/cpp.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index ffc57f9..4770a26 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -83,15 +83,9 @@ jobs: - name: Build project run: make build - # Install the Project with desktop shortcut + # Install the Project without desktop shortcut - name: Install project (Mac/Windows) if: matrix.os != 'ubuntu-latest' run: | cd ${{ github.workspace }} - echo "y" | make install - - # Install the Project without desktop shortcut (Linux) - # This is a work around for Linux shortcut bug - - name: Install project (Linux) - if: matrix.os == 'ubuntu-latest' - run: echo "n" | make install + echo "n" | make install \ No newline at end of file From ba58bd2837f5439a92254472ba75fff65482581a Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:50:20 -0700 Subject: [PATCH 109/160] Update: Remove redundant library linking for test executables in CMakeLists.txt --- tests/CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f7c693..a6ec947 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,6 @@ # Enable testing enable_testing() - +find_package(yaml-cpp REQUIRED) # Add individual test executables add_executable(test_mainwindow test_mainwindow.cpp) add_executable(test_tree test_tree.cpp) @@ -46,6 +46,4 @@ target_link_directories(test_tree PRIVATE /opt/homebrew/lib) target_link_directories(test_syntax PRIVATE /opt/homebrew/lib) # Link the libraries explicitly for both main executable and tests -target_link_libraries(test_mainwindow PRIVATE /opt/homebrew/lib/libyaml-cpp.dylib) -target_link_libraries(test_tree PRIVATE /opt/homebrew/lib/libyaml-cpp.dylib) target_link_libraries(test_syntax PRIVATE /opt/homebrew/lib/libyaml-cpp.dylib) From 51f84cc1e21c58b9cb97d7930df94811a1d5f725 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:50:28 -0700 Subject: [PATCH 110/160] Update: Simplify project installation step in CI workflow by removing OS-specific condition --- .github/workflows/cpp.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 4770a26..19f3d65 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -84,8 +84,7 @@ jobs: run: make build # Install the Project without desktop shortcut - - name: Install project (Mac/Windows) - if: matrix.os != 'ubuntu-latest' + - name: Install project run: | cd ${{ github.workspace }} echo "n" | make install \ No newline at end of file From 195a4f9abe7e04ba00bc1a0aeff748e66b41552c Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 17:53:33 -0700 Subject: [PATCH 111/160] Update: Remove explicit linking of yaml-cpp library for test_syntax executable --- tests/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a6ec947..b1cd9dd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,5 +45,3 @@ target_link_directories(test_mainwindow PRIVATE /opt/homebrew/lib) target_link_directories(test_tree PRIVATE /opt/homebrew/lib) target_link_directories(test_syntax PRIVATE /opt/homebrew/lib) -# Link the libraries explicitly for both main executable and tests -target_link_libraries(test_syntax PRIVATE /opt/homebrew/lib/libyaml-cpp.dylib) From c3ec1426150b9f5110b33da3afed0bd5d292b472 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 18:04:15 -0700 Subject: [PATCH 112/160] Update: Add Test CI badge to README for improved visibility of test status --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d68f055..35180dd 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![C++ CI](https://github.com/sandbox-science/CodeAstra/actions/workflows/cpp.yml/badge.svg)](https://github.com/sandbox-science/CodeAstra/actions/workflows/cpp.yml) + [![Test CI](https://github.com/sandbox-science/CodeAstra/actions/workflows/test.yml/badge.svg)](https://github.com/sandbox-science/CodeAstra/actions/workflows/test.yml) From d753cb5d2f2fc7be0690844464d2100179b6c6db Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 19:21:41 -0700 Subject: [PATCH 113/160] Update: Add color comments for syntax highlighting in YAML configuration files --- config/cpp.syntax.yaml | 26 ++++++++++++++++---------- config/go.syntax.yaml | 8 +++++--- config/python.syntax.yaml | 15 +++++++++------ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/config/cpp.syntax.yaml b/config/cpp.syntax.yaml index f97b264..e99eb86 100644 --- a/config/cpp.syntax.yaml +++ b/config/cpp.syntax.yaml @@ -3,30 +3,36 @@ extensions: [cpp, c, h, hpp] keywords: keyword: - regex: "\\b(char|class|const|double|enum|explicit|friend|inline|int|long|namespace|operator|private|protected|public|short|signals|signed|slots|static|struct|template|typedef|typename|union|unsigned|virtual|void|volatile|foreach)\\b" - color: "#003478" + color: "#003478" # Dark Blue bold: "true" - regex: "\\b(for|while|do|if|else)\\b" - color: "#D9001D" + color: "#D9001D" # Bright Red - regex: "(? Date: Fri, 28 Mar 2025 19:21:51 -0700 Subject: [PATCH 114/160] Update: Enhance markdown syntax highlighting configuration with additional styles for headings, bold, italic, links, inline code, lists, blockquotes, and todos --- config/markdown.syntax.yaml | 44 +++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/config/markdown.syntax.yaml b/config/markdown.syntax.yaml index 3f87606..cca0de8 100644 --- a/config/markdown.syntax.yaml +++ b/config/markdown.syntax.yaml @@ -3,5 +3,45 @@ extensions: [md] keywords: comment: - regex: "#[^\n]*" - color: "#336934" - italic: "true" \ No newline at end of file + color: "#336934" # Dark Green + italic: "true" + + heading: + - regex: "^#{1,6}\\s+.*" + color: "#003478" # Dark Blue + bold: "true" + + bold: + - regex: "(?\\s+.*" + color: "#B48EAD" # Light Purple + italic: "true" + + todo: + - regex: "- \\[ \\]" + color: "#FF8C00" # Orange for unchecked tasks + - regex: "- \\[x\\]" + color: "#32CD32" # Green for completed tasks + - regex: "(?<=\\[)[^\\]]*(?=\\])" + color: "#E37100" \ No newline at end of file From ffae7e31f207ab7918abb5c9db60d2fb0a857bca Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 19:22:04 -0700 Subject: [PATCH 115/160] Update: Add comments to color definitions in YAML syntax highlighting for clarity --- config/yaml.syntax.yaml | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/config/yaml.syntax.yaml b/config/yaml.syntax.yaml index cec8265..f341327 100644 --- a/config/yaml.syntax.yaml +++ b/config/yaml.syntax.yaml @@ -3,17 +3,33 @@ extensions: [yaml, yml] keywords: comment: - regex: "#[^\n]*" - color: "#336934" + color: "#336934" # Dark Green italic: "true" + keyword: - - regex: "\\b\\w+(?=\\s*:)" - color: "#003478" - regex: "\\[([^\\]]*)\\]" - color: "#DD9042" + color: "#DD9042" # Brownish Orange - regex: "\\[|\\]" - color: "#E6B400" + color: "#E6B400" # Golden Yellow + + number: + - regex: "\\b[+-]?([0-9]*[.])?[0-9]+([eE][+-]?[0-9]+)?\\b" + color: "#BA68C8" # Purple + + boolean: + - regex: "\\b(true|false)\\b" + color: "#4CAF50" # Green + + null: + - regex: "\\b(null)\\b" + color: "#9E9E9E" # Gray + + key: + - regex: "(?<=^|\\s)([a-zA-Z0-9_-]+)(?=:)" + color: "#009688" # Teal + string: - regex: "\"(\\\\.|[^\"\\\\])*\"" - color: "#E37100" + color: "#E37100" # Orange - regex: "\\\\" - color: "#FFDE00" \ No newline at end of file + color: "#FFDE00" # Yellow \ No newline at end of file From 87aad456fded0502a8c9a311d050936e1c0fff43 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 19:54:30 -0700 Subject: [PATCH 116/160] Update: Simplify project installation step in CI workflow by removing desktop shortcut option --- .github/workflows/cpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 19f3d65..9af8448 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -83,8 +83,8 @@ jobs: - name: Build project run: make build - # Install the Project without desktop shortcut + # Install the Project - name: Install project run: | cd ${{ github.workspace }} - echo "n" | make install \ No newline at end of file + make install \ No newline at end of file From 1950334818f32a425387c31c572447d99447b03e Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 19:54:37 -0700 Subject: [PATCH 117/160] Update: Remove desktop shortcut creation and removal from installation and uninstallation process --- Makefile | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/Makefile b/Makefile index 10cfe39..2a564b8 100644 --- a/Makefile +++ b/Makefile @@ -23,57 +23,11 @@ clean: uninstall: clean @echo "Uninstalling $(PROJECT)..." @rm -rf $(EXECUTABLE).app $(EXECUTABLE)d.app - @OS_NAME=$(shell uname -s 2>/dev/null || echo "Windows"); \ - if [ "$$OS_NAME" = "Darwin" ]; then \ - rm -rf "$(HOME)/Desktop/$(EXECUTABLE).app"; \ - echo "MacOS: Shortcut removed..."; \ - elif [ "$$OS_NAME" = "Linux" ] && grep -qi "microsoft" /proc/version 2>/dev/null; then \ - rm -rf "$(HOME)/Desktop/$(EXECUTABLE)"; \ - echo "WSL: Shortcut removed..."; \ - elif [ "$$OS_NAME" = "Linux" ]; then \ - rm -rf "$(HOME)/Desktop/$(EXECUTABLE)"; \ - echo "Linux: Shortcut removed..."; \ - elif echo "$$OS_NAME" | grep -qE "CYGWIN|MINGW|MSYS"; then \ - rm -f "$(USERPROFILE)/Desktop/$(EXECUTABLE).exe"; \ - echo "Cygwin/Mingw/MSYS: Shortcut removed..."; \ - elif [ "$$OS_NAME" = "Windows" ]; then \ - if [ -n "$$USERPROFILE" ]; then \ - cmd /c "if exist \"$$USERPROFILE\\Desktop\\$(EXECUTABLE).exe\" del /f /q \"$$USERPROFILE\\Desktop\\$(EXECUTABLE).exe\"" && echo "Windows: Shortcut removed..."; \ - else \ - echo "Windows: Could not determine user profile directory."; \ - fi \ - fi # Install the project install: build @echo "Installing $(PROJECT)..." @cd $(BUILD_DIR) && make - @echo "Do you want to create a shortcut on the desktop? (Y/n)" - @read choice; \ - if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ - echo "Creating shortcut..."; \ - OS_NAME=$(shell uname -s); \ - if [ "$$OS_NAME" = "Darwin" ]; then \ - echo "MacOS Detected..."; \ - cp -R $(EXECUTABLE).app ~/Desktop/; \ - elif [ "$$OS_NAME" = "Linux" ] && grep -qi "microsoft" /proc/version 2>/dev/null; then \ - echo "WSL Detected..."; \ - cp $(EXECUTABLE) ~/Desktop/; \ - elif [ "$$OS_NAME" = "Linux" ]; then \ - echo "Linux Detected..."; \ - cp $(EXECUTABLE) ~/Desktop/; \ - elif echo "$$OS_NAME" | grep -qE "CYGWIN|MINGW|MSYS"; then \ - echo "Windows-like Environment Detected (Cygwin/MSYS)..."; \ - cp $(EXECUTABLE).exe "$$USERPROFILE/Desktop/"; \ - elif [ "$$OS_NAME" = "Windows" ]; then \ - echo "Native Windows Detected..."; \ - if [ -n "$$USERPROFILE" ]; then \ - cp $(EXECUTABLE).exe "$$USERPROFILE/Desktop/"; \ - else \ - echo "Windows: Could not determine user profile directory."; \ - fi \ - fi \ - fi @echo "$(PROJECT) installed." build_tests: build From e1a0b8f3c2a248865afa3869138b04d3f20147c1 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 19:54:45 -0700 Subject: [PATCH 118/160] Update: Add Markdown and YAML to supported languages in syntax highlighting progress --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 35180dd..855b6e9 100644 --- a/README.md +++ b/README.md @@ -53,12 +53,13 @@ Please, check the [wiki](https://github.com/sandbox-science/CodeAstra/wiki) for - [x] File tree navigation - [ ] Syntax highlighting ~ in progress - Supported Languagues: + - [x] Markdown (**foundation**) + - [x] YAML (**foundation**) - [ ] C/C++ (**in progress**) - [ ] Golang (**in progress**) - [ ] Python (**Backlog**) - [ ] Elixir (**Backlog**) - more to come ... ([contribution welcomed](https://github.com/sandbox-science/CodeAstra/issues/4)) - - [ ] Plugin system ## To-Do From 332ec743f8003af160d0bb78cd78d5587b4733b8 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Fri, 28 Mar 2025 20:26:44 -0700 Subject: [PATCH 119/160] Added link to wiki for contribution syntax highlighting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 855b6e9..b60d19a 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Please, check the [wiki](https://github.com/sandbox-science/CodeAstra/wiki) for - [ ] Golang (**in progress**) - [ ] Python (**Backlog**) - [ ] Elixir (**Backlog**) - - more to come ... ([contribution welcomed](https://github.com/sandbox-science/CodeAstra/issues/4)) + - more to come ... ([contribution welcomed](https://github.com/sandbox-science/CodeAstra/issues/4)) Read our [wiki](https://github.com/sandbox-science/CodeAstra/wiki/Config) for more information - [ ] Plugin system ## To-Do From a4cfcd30d3f47571642ff8f83a2ca409c5312ce3 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Mar 2025 20:49:51 -0700 Subject: [PATCH 120/160] :recycle: Refactor: removed quotation mark around booleon in yaml files --- config/cpp.syntax.yaml | 4 ++-- config/markdown.syntax.yaml | 10 +++++----- config/python.syntax.yaml | 4 ++-- config/yaml.syntax.yaml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config/cpp.syntax.yaml b/config/cpp.syntax.yaml index e99eb86..c44f15a 100644 --- a/config/cpp.syntax.yaml +++ b/config/cpp.syntax.yaml @@ -4,7 +4,7 @@ keywords: keyword: - regex: "\\b(char|class|const|double|enum|explicit|friend|inline|int|long|namespace|operator|private|protected|public|short|signals|signed|slots|static|struct|template|typedef|typename|union|unsigned|virtual|void|volatile|foreach)\\b" color: "#003478" # Dark Blue - bold: "true" + bold: true - regex: "\\b(for|while|do|if|else)\\b" color: "#D9001D" # Bright Red - regex: "(?\\s+.*" color: "#B48EAD" # Light Purple - italic: "true" + italic: true todo: - regex: "- \\[ \\]" diff --git a/config/python.syntax.yaml b/config/python.syntax.yaml index e618070..7c3fc58 100644 --- a/config/python.syntax.yaml +++ b/config/python.syntax.yaml @@ -8,12 +8,12 @@ keywords: color: "#800080" # Purple - regex: "\\bclass\\b" color: "#008000" # Green - bold: "true" + bold: true comment: - regex: "#[^\n]*" color: "#808080" # Gray - italic: "true" + italic: true string: - regex: "\"(\\\\.|[^\"\\\\])*\"" diff --git a/config/yaml.syntax.yaml b/config/yaml.syntax.yaml index f341327..25ff721 100644 --- a/config/yaml.syntax.yaml +++ b/config/yaml.syntax.yaml @@ -4,7 +4,7 @@ keywords: comment: - regex: "#[^\n]*" color: "#336934" # Dark Green - italic: "true" + italic: true keyword: - regex: "\\[([^\\]]*)\\]" From faa01398a00a02fcd7b2790c2ca43ea9c6c53564 Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:04:00 -0700 Subject: [PATCH 121/160] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 31 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/custom.md | 34 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..ca0e385 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. MacOS, WSL, Windows] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000..a993ac5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,34 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + +## Feature Request + +### Description +_A brief and clear description of the feature request._ + +### Motivation +_Why is this feature needed? What problem does it solve?_ + +### Tasks +List possible implementation steps (use checkboxes for progress tracking): +- [ ] Task 1 +- [ ] Task 2 +- [ ] Task 3 + +### Alternatives Considered +_Have you considered any alternative solutions? If so, explain why this approach is preferred._ + +### Acceptance Criteria +_Define what conditions must be met for the feature to be considered complete._ + +### Additional Context +_Add relevant links, screenshots, or other information that would help understand the request._ + +> [!TIP] +> Add any related Labels to this issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..ea157d4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Tasks** +Add possible steps to implement this new feature here. + +**Additional context** +Add any other context or screenshots about the feature request here. From 29e07426ad813fd47b868554645294b96ad5db2f Mon Sep 17 00:00:00 2001 From: Chris Dedman Rollet <61106361+chrisdedman@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:06:12 -0700 Subject: [PATCH 122/160] Update issue templates --- .../{custom.md => feature-request.md} | 4 ++-- .github/ISSUE_TEMPLATE/feature_request.md | 20 ------------------- 2 files changed, 2 insertions(+), 22 deletions(-) rename .github/ISSUE_TEMPLATE/{custom.md => feature-request.md} (90%) delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/feature-request.md similarity index 90% rename from .github/ISSUE_TEMPLATE/custom.md rename to .github/ISSUE_TEMPLATE/feature-request.md index a993ac5..58cada2 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,6 +1,6 @@ --- -name: Custom issue template -about: Describe this issue template's purpose here. +name: Feature request +about: Suggest and idea for this project title: '' labels: '' assignees: '' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index ea157d4..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Tasks** -Add possible steps to implement this new feature here. - -**Additional context** -Add any other context or screenshots about the feature request here. From 94f9c09189674679ae9876f877cf5cbd13f60975 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 30 Mar 2025 19:27:53 -0700 Subject: [PATCH 123/160] Feature: Added File/Folder deletion in context menu --- src/Tree.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index 185d0ab..1e4fa1d 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include Tree::Tree(QSplitter *splitter) : QObject(splitter), @@ -69,6 +71,13 @@ void Tree::openFile(const QModelIndex &index) FileManager::getInstance().loadFileInEditor(filePath); } +QFileSystemModel *Tree::getModel() const +{ + if (!m_model) + throw std::runtime_error("Tree model is not initialized!"); + return m_model.get(); +} + // Context menu for file operations // such as creating new files, folders, renaming, and deleting // This function is called when the user right-clicks on the tree view @@ -97,6 +106,78 @@ void Tree::showContextMenu(const QPoint &pos) } else if (selectedAction == deleteAction) { - // TO-DO: implement file deletion + QFileInfo pathInfo = getPathInfo(); + if (!pathInfo.exists()) + { + qWarning() << "File does not exist: " << pathInfo.fileName(); + return; + } + QMessageBox::StandardButton reply = QMessageBox::question(nullptr, "Confirm Deletion", + "Are you sure you want to delete\n'" + pathInfo.fileName() + "'?", + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::No) + { + qInfo() << "Deletion cancelled."; + } + else + { + deleteFile(pathInfo); + } + } +} + +QFileInfo Tree::getPathInfo() +{ + QModelIndex index = m_tree->currentIndex(); + if (!index.isValid()) + { + qWarning() << "Invalid index."; + return QFileInfo(); + } + + return QFileInfo(m_model->filePath(index)); +} + +bool Tree::deleteFile(const QFileInfo &pathInfo) +{ + std::error_code err; + QString filePath = pathInfo.absoluteFilePath(); + + if (pathInfo.isDir()) + { + if (std::filesystem::remove_all(filePath.toStdString(), err)) + { + qInfo() << "Successfully deleted" << pathInfo.fileName(); + } + else + { + qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); + return false; + } + } + else + { + if (std::filesystem::remove(filePath.toStdString(), err)) + { + qInfo() << "Successfully deleted" << pathInfo.fileName(); + } + else + { + qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); + return false; + } } + return true; } + +bool Tree::renameFile(const QFileInfo &filePath, QString newFileName) +{ + qWarning() << "renamedFile Function not yet implemented"; + return false; +} + +bool Tree::newFile(QString newFilePath) +{ + qWarning() << "newFile Function not yet implemented"; + return false; +} \ No newline at end of file From 8a5b00e51887d059daa6415c505a0f943a508e8f Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 30 Mar 2025 19:28:21 -0700 Subject: [PATCH 124/160] Added context menu headers --- include/Tree.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/Tree.h b/include/Tree.h index 210c733..3490c2a 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -3,6 +3,7 @@ #include #include #include +#include // Forward declarations class QTreeView; @@ -30,8 +31,15 @@ class Tree : public QObject void setupTree(); void openFile(const QModelIndex &index); + bool deleteFile(const QFileInfo &filePath); + bool renameFile(const QFileInfo &filePath, QString newFileName); + bool newFile(QString newFilePath); + + QFileSystemModel* getModel() const; + private: void showContextMenu(const QPoint &pos); + QFileInfo getPathInfo(); std::unique_ptr m_iconProvider; std::unique_ptr m_model; From e12fe53d6e836970ceab68b166470c3e42e7f458 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 30 Mar 2025 19:30:16 -0700 Subject: [PATCH 125/160] UnitTest: Added unit test for deletion file --- tests/test_tree.cpp | 96 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 14 deletions(-) diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp index f00465c..ebe1c8b 100644 --- a/tests/test_tree.cpp +++ b/tests/test_tree.cpp @@ -1,43 +1,111 @@ -#include -#include #include "Tree.h" #include "FileManager.h" #include "CodeEditor.h" +#include +#include #include #include -#include +#include +#include +#include +#include class TestTree : public QObject { - Q_OBJECT + Q_OBJECT + +private: + QSplitter *splitter; + Tree *tree; + QString testFilePath; + QString filePath; private slots: - void initTestCase(); - void cleanupTestCase(); - void testOpenFile_invalid(); + void initTestCase(); + void cleanupTestCase(); + void testOpenFile_invalid(); + void testDeleteFile(); + void testRenameFile(); + void testNewFile(); }; void TestTree::initTestCase() { - qDebug() << "Initializing TestTree tests..."; + qDebug() << "Initializing TestTree tests..."; + splitter = new QSplitter; + tree = new Tree(splitter); + + // Create a temporary test file for the Unit Tests + testFilePath = QDir::temp().filePath("test_file.cpp"); + QFile testFile(testFilePath); + if (!testFile.open(QIODevice::WriteOnly)) + { + qDebug() << "Failed to create test file at" << testFilePath; + QFAIL("Failed to create test file."); + } + testFile.close(); + + QFileSystemModel *model = tree->getModel(); + if (model) + { + QModelIndex index = model->index(testFilePath); + filePath = model->filePath(index); + } } void TestTree::cleanupTestCase() { - qDebug() << "Cleaning up TestTree tests..."; + qDebug() << "Cleaning up TestTree tests..."; + delete tree; + delete splitter; + + if (QFile::exists(testFilePath)) + { + QFile::remove(testFilePath); + } } void TestTree::testOpenFile_invalid() { - QSplitter *splitter = new QSplitter; - Tree tree(splitter); + QModelIndex index; + tree->openFile(index); - QModelIndex index; + QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), "FileManager should not process an invalid file."); +} + +void TestTree::testDeleteFile() +{ + QVERIFY2(QFile::exists(testFilePath), "Test file should exist before deletion."); - tree.openFile(index); + QFileSystemModel *model = tree->getModel(); + QVERIFY2(model != nullptr, "getModel() should not return nullptr."); + + QModelIndex index = model->index(testFilePath); + QVERIFY2(index.isValid(), "QModelIndex should be valid for the test file."); + + QString filePath = model->filePath(index); + tree->deleteFile(QFileInfo(filePath)); + + QVERIFY2(!QFile::exists(testFilePath), "File should be deleted."); +} + +void TestTree::testRenameFile() +{ + QString newFilePath = QDir::temp().filePath("renamed_file.cpp"); + bool fileRenamed = tree->renameFile(QFileInfo(testFilePath), newFilePath); + + QVERIFY2(fileRenamed, "File should be renamed successfully."); + QVERIFY2(QFile::exists(newFilePath), "Renamed file should exist."); +} + +void TestTree::testNewFile() +{ + QString newFilePath = QDir::temp().filePath("new_test_file.cpp"); + bool fileCreated = tree->newFile(newFilePath); - QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), "FileManager should not process an invalid file."); + QVERIFY2(fileCreated, "New file should be created."); + QVERIFY2(QFile::exists(newFilePath), "Newly created file should exist."); } QTEST_MAIN(TestTree) From d5fb7bd3d5bbe29bf8fd5894639efe1fd4ff0350 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 30 Mar 2025 19:32:42 -0700 Subject: [PATCH 126/160] Doc: Fixed typo in the documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b60d19a..22ba0b4 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Please, check the [wiki](https://github.com/sandbox-science/CodeAstra/wiki) for - [ ] Create a new file ~ in progress - [x] File tree navigation - [ ] Syntax highlighting ~ in progress - - Supported Languagues: + - Supported Languages: - [x] Markdown (**foundation**) - [x] YAML (**foundation**) - [ ] C/C++ (**in progress**) @@ -63,4 +63,4 @@ Please, check the [wiki](https://github.com/sandbox-science/CodeAstra/wiki) for - [ ] Plugin system ## To-Do -Find tasks to-do on our open [issues](https://github.com/sandbox-science/CodeAstra/issues) +Find tasks to do on our open [issues](https://github.com/sandbox-science/CodeAstra/issues) From 165cab7febe409f738617c0b65b373488f754854 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 31 Mar 2025 21:09:13 -0700 Subject: [PATCH 127/160] [Refactor] Separated logic for deleting file and folder --- include/Tree.h | 1 + src/Tree.cpp | 50 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/include/Tree.h b/include/Tree.h index 3490c2a..2cf2066 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -32,6 +32,7 @@ class Tree : public QObject void openFile(const QModelIndex &index); bool deleteFile(const QFileInfo &filePath); + bool deleteFolder(const QFileInfo &folderPath); bool renameFile(const QFileInfo &filePath, QString newFileName); bool newFile(QString newFilePath); diff --git a/src/Tree.cpp b/src/Tree.cpp index 1e4fa1d..0e12cc5 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -121,7 +121,14 @@ void Tree::showContextMenu(const QPoint &pos) } else { - deleteFile(pathInfo); + if(pathInfo.isDir()) + { + deleteFolder(pathInfo); + } + else + { + deleteFile(pathInfo); + } } } } @@ -143,29 +150,32 @@ bool Tree::deleteFile(const QFileInfo &pathInfo) std::error_code err; QString filePath = pathInfo.absoluteFilePath(); - if (pathInfo.isDir()) + if (std::filesystem::remove(filePath.toStdString(), err)) { - if (std::filesystem::remove_all(filePath.toStdString(), err)) - { - qInfo() << "Successfully deleted" << pathInfo.fileName(); - } - else - { - qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); - return false; - } + qInfo() << "Successfully deleted" << pathInfo.fileName(); } else { - if (std::filesystem::remove(filePath.toStdString(), err)) - { - qInfo() << "Successfully deleted" << pathInfo.fileName(); - } - else - { - qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); - return false; - } + qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); + return false; + } + + return true; +} + +bool Tree::deleteFolder(const QFileInfo &pathInfo) +{ + std::error_code err; + QString filePath = pathInfo.absoluteFilePath(); + + if (std::filesystem::remove_all(filePath.toStdString(), err)) + { + qInfo() << "Successfully deleted" << pathInfo.fileName(); + } + else + { + qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); + return false; } return true; } From 42fbef0403d6e819b76cd10af8c07fbafaf87266 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 31 Mar 2025 21:10:07 -0700 Subject: [PATCH 128/160] [Add] Added auto indentation when keyboard enter key is pressed + delete work when cmd + backspace key is pressed --- include/CodeEditor.h | 1 + src/CodeEditor.cpp | 55 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/include/CodeEditor.h b/include/CodeEditor.h index dd8afa8..5ec9ab6 100644 --- a/include/CodeEditor.h +++ b/include/CodeEditor.h @@ -28,6 +28,7 @@ class CodeEditor : public QPlainTextEdit Mode mode = NORMAL; void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); + void autoIndentation(); signals: void statusMessageChanged(const QString &message); diff --git a/src/CodeEditor.cpp b/src/CodeEditor.cpp index 0d23421..63815f9 100644 --- a/src/CodeEditor.cpp +++ b/src/CodeEditor.cpp @@ -30,12 +30,7 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) moveCursor(QTextCursor::WordLeft, QTextCursor::KeepAnchor); return; } - if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Slash) - { - addComment(); - return; - } - + if (mode == NORMAL) { switch (event->key()) @@ -61,6 +56,7 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) break; } } + else if (mode == INSERT) { if (event->key() == Qt::Key_Escape) @@ -68,6 +64,23 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) mode = NORMAL; emit statusMessageChanged("Normal mode activated. Press 'escape' to return to normal mode."); } + else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) + { + autoIndentation(); + return; + } + else if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Slash) + { + addComment(); + return; + } + else if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Backspace) + { + moveCursor(QTextCursor::WordLeft, QTextCursor::KeepAnchor); + textCursor().removeSelectedText(); + textCursor().deletePreviousChar(); + return; + } else { QPlainTextEdit::keyPressEvent(event); @@ -75,6 +88,36 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) } } +// Add auto indentation when writing code and pressing enter keyboard key +void CodeEditor::autoIndentation() +{ + auto cursor = textCursor(); + auto currentBlock = cursor.block(); + QString currentText = currentBlock.text(); + + int indentLevel = 0; + for (int i = 0; i < currentText.size(); ++i) + { + if (currentText.at(i) == ' ') + { + ++indentLevel; + } + + else if (currentText.at(i) == '\t') + { + indentLevel += 4; + } + + else + { + break; + } + } + + cursor.insertText("\n" + QString(indentLevel, ' ')); + setTextCursor(cursor); +} + void CodeEditor::addLanguageSymbol(QTextCursor &cursor, const QString &commentSymbol) { if (cursor.hasSelection()) From b213da7a28cd63f4a18ddf415f47ef435e6b76e3 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Apr 2025 16:19:36 -0700 Subject: [PATCH 129/160] [Refactor] Move context menu action logic from Tree to FileManager --- include/FileManager.h | 8 ++++++++ include/Tree.h | 5 ----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/FileManager.h b/include/FileManager.h index 178e8f3..4b686f1 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -3,6 +3,7 @@ #include #include #include +#include class CodeEditor; class MainWindow; @@ -37,6 +38,13 @@ class FileManager : public QObject void setCurrentFileName(const QString fileName); void initialize(CodeEditor *editor, MainWindow *mainWindow); + + bool deleteFile(const QFileInfo &pathInfo); + bool deleteFolder(const QFileInfo &pathInfo); + + bool renamePath(const QFileInfo &pathInfo, const QString &newName); + bool newFile(QString newFilePath); + public slots: void newFile(); void saveFile(); diff --git a/include/Tree.h b/include/Tree.h index 2cf2066..89a535a 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -31,11 +31,6 @@ class Tree : public QObject void setupTree(); void openFile(const QModelIndex &index); - bool deleteFile(const QFileInfo &filePath); - bool deleteFolder(const QFileInfo &folderPath); - bool renameFile(const QFileInfo &filePath, QString newFileName); - bool newFile(QString newFilePath); - QFileSystemModel* getModel() const; private: From 82c9728217b9741865029dcb2fc43b45921a2ac2 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Apr 2025 16:36:45 -0700 Subject: [PATCH 130/160] [Refactor] Move context menu logic to FileManager + [Add] Added call for deletion and renaming logic --- src/Tree.cpp | 97 +++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index 0e12cc5..4c9faff 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -10,6 +10,7 @@ #include #include #include +#include Tree::Tree(QSplitter *splitter) : QObject(splitter), @@ -87,7 +88,10 @@ void Tree::showContextMenu(const QPoint &pos) QAction *newFileAction = contextMenu.addAction("New File"); QAction *newFolderAction = contextMenu.addAction("New Folder"); + contextMenu.addSeparator(); QAction *renameAction = contextMenu.addAction("Rename"); + QAction *duplicateAction = contextMenu.addAction("Duplicate"); + contextMenu.addSeparator(); QAction *deleteAction = contextMenu.addAction("Delete"); QAction *selectedAction = contextMenu.exec(m_tree->viewport()->mapToGlobal(pos)); @@ -100,9 +104,35 @@ void Tree::showContextMenu(const QPoint &pos) { // TO-DO: implement folder creation } + else if (selectedAction == duplicateAction) + { + // TO-DO: implement folder creation + } else if (selectedAction == renameAction) { - // TO-DO: implement rename file/folder + QFileInfo oldPathInfo = getPathInfo(); + if (!oldPathInfo.exists()) + { + qWarning() << "File does not exist: " << oldPathInfo.fileName(); + return; + } + + bool ok; + QString newFileName = QInputDialog::getText( + nullptr, + "Rename File", + "Enter new file name:", + QLineEdit::Normal, + oldPathInfo.fileName(), + &ok); + + if (ok && !newFileName.isEmpty()) + { + if (FileManager::getInstance().renamePath(oldPathInfo, newFileName)) + { + qInfo() << "Renamed successfully!"; + } + } } else if (selectedAction == deleteAction) { @@ -121,13 +151,25 @@ void Tree::showContextMenu(const QPoint &pos) } else { - if(pathInfo.isDir()) + bool isDeleted = false; + QString itemType = pathInfo.isDir() ? "Folder" : "File"; + + if (pathInfo.isDir()) + { + isDeleted = FileManager::getInstance().deleteFolder(pathInfo); + } + else { - deleteFolder(pathInfo); + isDeleted = FileManager::getInstance().deleteFile(pathInfo); + } + + if (isDeleted) + { + qInfo() << itemType << "successfully deleted!"; } else { - deleteFile(pathInfo); + QMessageBox::critical(nullptr, "Error", QString("%1 failed to delete.").arg(itemType)); } } } @@ -144,50 +186,3 @@ QFileInfo Tree::getPathInfo() return QFileInfo(m_model->filePath(index)); } - -bool Tree::deleteFile(const QFileInfo &pathInfo) -{ - std::error_code err; - QString filePath = pathInfo.absoluteFilePath(); - - if (std::filesystem::remove(filePath.toStdString(), err)) - { - qInfo() << "Successfully deleted" << pathInfo.fileName(); - } - else - { - qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); - return false; - } - - return true; -} - -bool Tree::deleteFolder(const QFileInfo &pathInfo) -{ - std::error_code err; - QString filePath = pathInfo.absoluteFilePath(); - - if (std::filesystem::remove_all(filePath.toStdString(), err)) - { - qInfo() << "Successfully deleted" << pathInfo.fileName(); - } - else - { - qWarning() << "Failed to delete" << pathInfo.fileName() << "- Error:" << QString::fromStdString(err.message()); - return false; - } - return true; -} - -bool Tree::renameFile(const QFileInfo &filePath, QString newFileName) -{ - qWarning() << "renamedFile Function not yet implemented"; - return false; -} - -bool Tree::newFile(QString newFilePath) -{ - qWarning() << "newFile Function not yet implemented"; - return false; -} \ No newline at end of file From 75ae46b10485d3f5f7eadacadb638129e5ae9b15 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Apr 2025 16:38:34 -0700 Subject: [PATCH 131/160] [Feature] Added renaming file/folder logic + moved deletion file/folder to FileManager class from Tree class. --- src/FileManager.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index f05f3fa..16447b3 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include FileManager::FileManager(CodeEditor *editor, MainWindow *mainWindow) : m_editor(editor), m_mainWindow(mainWindow) @@ -157,3 +159,74 @@ QString FileManager::getDirectoryPath() const nullptr, QObject::tr("Open Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); } + +bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) +{ + if (!pathInfo.exists()) + { + qWarning() << "Path does not exist: " << pathInfo.fileName(); + return false; + } + + std::filesystem::path oldPath = pathInfo.absoluteFilePath().toStdString(); + std::filesystem::path newPath = oldPath.parent_path() / newName.toStdString(); + + if (QFileInfo(newPath).exists()) + { + QMessageBox::critical(nullptr, "Error", QString("%1 already takken.").arg(QString::fromStdString(newPath.filename()))); + return false; + } + + try + { + std::filesystem::rename(oldPath, newPath); + return true; + } + catch (const std::filesystem::filesystem_error &e) + { + QMessageBox::critical(nullptr, "Error", QString(e.what())); + return false; + } +} + +bool FileManager::deleteFile(const QFileInfo &pathInfo) +{ + std::error_code err; + std::filesystem::path filePath = pathInfo.absoluteFilePath().toStdString(); + + try + { + std::filesystem::remove(filePath, err); + } + catch (const std::filesystem::filesystem_error &e) + { + qWarning() << "Failed to delete: " << e.what(); + return false; + } + + return true; +} + +bool FileManager::deleteFolder(const QFileInfo &pathInfo) +{ + std::error_code err; + std::filesystem::path dirPath = pathInfo.absoluteFilePath().toStdString(); + + try + { + std::filesystem::remove_all(dirPath, err); + } + catch (const std::filesystem::filesystem_error &e) + { + qWarning() << "Failed to delete: " << e.what(); + return false; + } + + return true; +} + +bool FileManager::newFile(QString newFilePath) +{ + qWarning() << "newFile Function not yet implemented"; + return false; +} \ No newline at end of file From 7187bb76d7a3145b8222dd0fc089ab2d564f3a44 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Apr 2025 16:39:51 -0700 Subject: [PATCH 132/160] [Test] Added unit test for renaming path --- tests/test_tree.cpp | 71 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp index ebe1c8b..3ddf2f3 100644 --- a/tests/test_tree.cpp +++ b/tests/test_tree.cpp @@ -26,7 +26,8 @@ private slots: void cleanupTestCase(); void testOpenFile_invalid(); void testDeleteFile(); - void testRenameFile(); + void testDeleteDir(); + void testRenamePath(); void testNewFile(); }; @@ -37,14 +38,16 @@ void TestTree::initTestCase() tree = new Tree(splitter); // Create a temporary test file for the Unit Tests - testFilePath = QDir::temp().filePath("test_file.cpp"); + testFilePath = "test_file.cpp"; QFile testFile(testFilePath); - if (!testFile.open(QIODevice::WriteOnly)) + if (!testFile.exists()) { - qDebug() << "Failed to create test file at" << testFilePath; - QFAIL("Failed to create test file."); + if (!testFile.open(QIODevice::WriteOnly)) + { + QFAIL("Failed to create test file."); + } + testFile.close(); } - testFile.close(); QFileSystemModel *model = tree->getModel(); if (model) @@ -74,6 +77,30 @@ void TestTree::testOpenFile_invalid() QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), "FileManager should not process an invalid file."); } +void TestTree::testRenamePath() +{ + QString originalFileName = "test_file.cpp"; + QString newFileName = "renamed_file.cpp"; + + QFile originalFile(originalFileName); + if (!originalFile.exists()) + { + if (!originalFile.open(QIODevice::WriteOnly)) + { + QFAIL("Failed to create test file."); + } + originalFile.close(); + } + + bool fileRenamed = FileManager::getInstance().renamePath(QFileInfo(originalFileName), newFileName); + + QVERIFY2(fileRenamed, "File should be renamed successfully."); + QVERIFY2(QFile::exists(newFileName), "Renamed file should exist."); + QVERIFY2(!QFile::exists(originalFileName), "Original file should no longer exist."); + + QFile::remove(newFileName); +} + void TestTree::testDeleteFile() { QVERIFY2(QFile::exists(testFilePath), "Test file should exist before deletion."); @@ -85,24 +112,42 @@ void TestTree::testDeleteFile() QVERIFY2(index.isValid(), "QModelIndex should be valid for the test file."); QString filePath = model->filePath(index); - tree->deleteFile(QFileInfo(filePath)); + FileManager::getInstance().deleteFile(QFileInfo(filePath)); QVERIFY2(!QFile::exists(testFilePath), "File should be deleted."); } -void TestTree::testRenameFile() +void TestTree::testDeleteDir() { - QString newFilePath = QDir::temp().filePath("renamed_file.cpp"); - bool fileRenamed = tree->renameFile(QFileInfo(testFilePath), newFilePath); + // Temporary directory for deletion test + QString directory = QDir::temp().absolutePath() + "/testDeleteDir"; + QDir tempDir(directory); + if (!tempDir.exists()) + { + if (!tempDir.mkpath(".")) + { + QFAIL("Failed to create test directory."); + } + } - QVERIFY2(fileRenamed, "File should be renamed successfully."); - QVERIFY2(QFile::exists(newFilePath), "Renamed file should exist."); + QVERIFY2(QFile::exists(directory), "Test directory should exist before deletion."); + + QFileSystemModel *model = tree->getModel(); + QVERIFY2(model != nullptr, "getModel() should not return nullptr."); + + QModelIndex index = model->index(directory); + QVERIFY2(index.isValid(), "QModelIndex should be valid for the test directory."); + + QString dirPath = model->filePath(index); + FileManager::getInstance().deleteFolder(QFileInfo(dirPath)); + + QVERIFY2(!QFile::exists(directory), "Directory should be deleted."); } void TestTree::testNewFile() { QString newFilePath = QDir::temp().filePath("new_test_file.cpp"); - bool fileCreated = tree->newFile(newFilePath); + bool fileCreated = FileManager::getInstance().newFile(newFilePath); QVERIFY2(fileCreated, "New file should be created."); QVERIFY2(QFile::exists(newFilePath), "Newly created file should exist."); From 0d62803e528b3328b8f794bdb450d000d6c2b9ab Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Apr 2025 16:40:08 -0700 Subject: [PATCH 133/160] [Make] Updated CodeAstra versioning --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb09344..8f59a97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(EXECUTABLE_NAME CodeAstra) set(QT_MAJOR_VERSION 6) -project(${TARGET_NAME} VERSION 0.0.1 DESCRIPTION "Code Editor written in C++ using Qt6") +project(${TARGET_NAME} VERSION 0.1.0 DESCRIPTION "Code Editor written in C++ using Qt6") # Enable automatic MOC (Meta-Object Compiler) handling for Qt set(CMAKE_AUTOMOC ON) From 4d2ee4bd6fad5eb39bbc2f1a3ce5efb17610d330 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 17:05:19 -0700 Subject: [PATCH 134/160] [Add] Added function header for new folder + duplcated path --- include/FileManager.h | 5 +++++ include/Tree.h | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/FileManager.h b/include/FileManager.h index 4b686f1..26a2a0a 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -26,6 +26,9 @@ class FileManager : public QObject static FileManager &getInstance(CodeEditor *editor = nullptr, MainWindow *mainWindow = nullptr) { static FileManager instance(editor, mainWindow); + if (editor && mainWindow) { + instance.initialize(editor, mainWindow); + } return instance; } @@ -44,6 +47,8 @@ class FileManager : public QObject bool renamePath(const QFileInfo &pathInfo, const QString &newName); bool newFile(QString newFilePath); + bool newFolder(QString newFolderPath); + bool duplicatePath(const QFileInfo &pathInfo); public slots: void newFile(); diff --git a/include/Tree.h b/include/Tree.h index 89a535a..6a63a51 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -9,7 +9,6 @@ class QTreeView; class QFileSystemModel; class QFileIconProvider; -class FileManager; /** * @class Tree From d400d93bf045d7cb06bfafdcbd58b59086bb80a3 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 17:06:20 -0700 Subject: [PATCH 135/160] [Feature] Added duplicate a file + check if a path if valid --- src/FileManager.cpp | 85 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 16447b3..6fd00a9 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -160,6 +160,18 @@ QString FileManager::getDirectoryPath() const QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); } +// Check for invalid char or pattern to prevent path traversal attack +bool isValidPath(const std::filesystem::path &path) +{ + std::string pathStr = path.string(); + if (pathStr.find("..") != std::string::npos) + { + return false; + } + + return true; +} + bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) { if (!pathInfo.exists()) @@ -169,6 +181,14 @@ bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) } std::filesystem::path oldPath = pathInfo.absoluteFilePath().toStdString(); + + // Validate the input path + if (!isValidPath(oldPath)) + { + QMessageBox::critical(nullptr, "Error", "Invalid file path."); + return false; + } + std::filesystem::path newPath = oldPath.parent_path() / newName.toStdString(); if (QFileInfo(newPath).exists()) @@ -189,11 +209,21 @@ bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) } } +// TO-DO: use QFile moveToTrash instead +// to avoid permanently deleting files and folders +// by moving them to recycling bin bool FileManager::deleteFile(const QFileInfo &pathInfo) { std::error_code err; std::filesystem::path filePath = pathInfo.absoluteFilePath().toStdString(); + // Validate the input path + if (!isValidPath(filePath)) + { + QMessageBox::critical(nullptr, "Error", "Invalid file path."); + return false; + } + try { std::filesystem::remove(filePath, err); @@ -210,7 +240,14 @@ bool FileManager::deleteFile(const QFileInfo &pathInfo) bool FileManager::deleteFolder(const QFileInfo &pathInfo) { std::error_code err; - std::filesystem::path dirPath = pathInfo.absoluteFilePath().toStdString(); + std::filesystem::path dirPath = pathInfo.absolutePath().toStdString(); + + // Validate the input path + if (!isValidPath(dirPath)) + { + QMessageBox::critical(nullptr, "Error", "Invalid file path."); + return false; + } try { @@ -229,4 +266,48 @@ bool FileManager::newFile(QString newFilePath) { qWarning() << "newFile Function not yet implemented"; return false; -} \ No newline at end of file +} + +bool FileManager::newFolder(QString newFolderPath) +{ + qWarning() << "newFolder Function not yet implemented"; + return false; +} + +bool FileManager::duplicatePath(const QFileInfo &pathInfo) +{ + std::error_code err; + std::filesystem::path filePath = pathInfo.absoluteFilePath().toStdString(); + + // Validate the input path + if (!isValidPath(filePath)) + { + QMessageBox::critical(nullptr, "Error", "Invalid file path."); + return false; + } + + std::string fileName = filePath.filename(); + size_t extensionPosition = fileName.find_last_of("."); + fileName = (std::string::npos == extensionPosition) ? fileName : fileName.substr(0, extensionPosition); + + std::filesystem::path dupPath = filePath.parent_path() / (fileName + "_copy" + filePath.extension().c_str()); + + int counter = 1; + while (QFileInfo(dupPath).exists()) + { + dupPath = filePath.parent_path() / (fileName + "_copy" + std::to_string(counter) + filePath.extension().c_str()); + counter++; + } + + try + { + std::filesystem::copy(filePath, dupPath); + } + catch (const std::filesystem::filesystem_error &e) + { + QMessageBox::critical(nullptr, "Error", QString("Failed to duplicate: ") + e.what()); + return false; + } + + return true; +} From 4477f4121afdc6136513a0470dd9db1ff5dadcbf Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 17:06:41 -0700 Subject: [PATCH 136/160] [Feature] Added duplicate file action to the context --- src/Tree.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Tree.cpp b/src/Tree.cpp index 4c9faff..ebb7989 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -107,6 +107,17 @@ void Tree::showContextMenu(const QPoint &pos) else if (selectedAction == duplicateAction) { // TO-DO: implement folder creation + QFileInfo oldPathInfo = getPathInfo(); + if (!oldPathInfo.exists()) + { + qWarning() << "File does not exist: " << oldPathInfo.fileName(); + return; + } + + if (FileManager::getInstance().duplicatePath(oldPathInfo)) + { + qInfo() << "Duplicated successfully!"; + } } else if (selectedAction == renameAction) { From afd6918d4e36103c6dbf5da5133a871471ab4b9a Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 17:07:10 -0700 Subject: [PATCH 137/160] [UnitTests] Added init unit tests for new folder and duplicate path --- tests/test_tree.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp index 3ddf2f3..3d1b80a 100644 --- a/tests/test_tree.cpp +++ b/tests/test_tree.cpp @@ -29,6 +29,8 @@ private slots: void testDeleteDir(); void testRenamePath(); void testNewFile(); + void testNewFolder(); + void testDuplicatePath(); }; void TestTree::initTestCase() @@ -153,5 +155,22 @@ void TestTree::testNewFile() QVERIFY2(QFile::exists(newFilePath), "Newly created file should exist."); } +void TestTree::testNewFolder() +{ + QString newFolderPath = QDir::temp().absolutePath() + "/testNewDir"; + bool folderCreated = FileManager::getInstance().newFolder(newFolderPath); + + QVERIFY2(folderCreated, "New folder should be created."); + QVERIFY2(QFile::exists(newFolderPath), "Newly created folder should exist."); +} + +void TestTree::testDuplicatePath() +{ + QString dupPath = QDir::temp().absolutePath() + "/testNewDir"; + bool pathDuplicated = FileManager::getInstance().duplicatePath(QFileInfo(dupPath)); + + QVERIFY2(pathDuplicated, "New folder should be created."); +} + QTEST_MAIN(TestTree) #include "test_tree.moc" From 4b6793397256bddd7b3f77eb5b2bc66ce0a2514b Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 17:07:25 -0700 Subject: [PATCH 138/160] [Make] added flag for build --- CMakeLists.txt | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f59a97..4755b93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,15 +90,6 @@ target_link_libraries(${TARGET_NAME} PRIVATE yaml-cpp) # Create the executable for the application add_executable(${EXECUTABLE_NAME} src/main.cpp) -# Ensure YAML config files are copied into macOS app bundle -# if(APPLE) -# add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD -# COMMAND ${CMAKE_COMMAND} -E make_directory "$/Contents/Resources/config" -# COMMAND ${CMAKE_COMMAND} -E copy_directory "${YAML_SOURCE_DIR}" "$/Contents/Resources/config" -# COMMENT "Copying YAML config files into macOS app bundle..." -# ) -# endif() - # Link the main executable with the CodeAstra library and Qt libraries target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${TARGET_NAME} Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Widgets) @@ -111,12 +102,14 @@ target_sources(${EXECUTABLE_NAME} PRIVATE ${APP_RESOURCES}) # Compiler flags per OS if(MSVC) - target_compile_options(${EXECUTABLE_NAME} PRIVATE /W4 /WX) + target_compile_options(${EXECUTABLE_NAME} PRIVATE /W4 /WX /analyze /sdl /guard:cf) elseif(APPLE) - target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion -Wsign-conversion -fsanitize=address,undefined -fstack-protector) + target_link_libraries(${EXECUTABLE_NAME} PRIVATE -fsanitize=address,undefined) # set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE TRUE) else() - target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion -Wsign-conversion -fsanitize=address,undefined -fstack-protector) + target_link_libraries(${EXECUTABLE_NAME} PRIVATE -fsanitize=address,undefined) endif() # Include directories @@ -125,9 +118,9 @@ target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/includ target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) # Set output names properly for Debug and Release -set_target_properties(${EXECUTABLE_NAME} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" - DEBUG_OUTPUT_NAME "${EXECUTABLE_NAME}d" +set_target_properties(${EXECUTABLE_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + DEBUG_OUTPUT_NAME "${EXECUTABLE_NAME}d" RELEASE_OUTPUT_NAME ${EXECUTABLE_NAME} ) @@ -138,4 +131,4 @@ target_link_libraries(${TARGET_NAME} PRIVATE Qt${QT_MAJOR_VERSION}::Core Qt${QT_ if(APPLE) target_include_directories(${EXECUTABLE_NAME} PRIVATE /opt/homebrew/include) target_link_directories(${EXECUTABLE_NAME} PRIVATE /opt/homebrew/lib) -endif() \ No newline at end of file +endif() From de45f5e300b69ba0d519b9ea0efec59a429627e1 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 17:07:47 -0700 Subject: [PATCH 139/160] [Refactor] refactor makefile for uninstall software --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2a564b8..ba83bfc 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ clean: # Uninstalling the software uninstall: clean @echo "Uninstalling $(PROJECT)..." - @rm -rf $(EXECUTABLE).app $(EXECUTABLE)d.app + @rm -f $(EXECUTABLE) # Install the project install: build From 8fa167f73a818d815161ad89675352e2af4a0c93 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 18:28:06 -0700 Subject: [PATCH 140/160] [Feature] Added safe guard to files/folders deletion from permanent to trash bin --- src/FileManager.cpp | 66 ++++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 6fd00a9..b24673f 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -160,7 +160,7 @@ QString FileManager::getDirectoryPath() const QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); } -// Check for invalid char or pattern to prevent path traversal attack +// Check path to prevent path traversal attack bool isValidPath(const std::filesystem::path &path) { std::string pathStr = path.string(); @@ -209,12 +209,38 @@ bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) } } -// TO-DO: use QFile moveToTrash instead -// to avoid permanently deleting files and folders -// by moving them to recycling bin +// Check if the path is a valid directory +// and not a system or home directory +bool isAValidDirectory(const QFileInfo &pathInfo) +{ + if (!pathInfo.exists()) + { + qWarning() << "Path does not exist: " << pathInfo.fileName(); + return false; + } + + if (pathInfo.absolutePath() == "/" || pathInfo.absolutePath() == QDir::homePath()) + { + QMessageBox::critical(nullptr, "Error", "Cannot delete system or home directory."); + return false; + } + + if (pathInfo.isDir()) + { + return true; + } + + return false; +} + bool FileManager::deleteFile(const QFileInfo &pathInfo) { - std::error_code err; + if (isAValidDirectory(pathInfo)) + { + QMessageBox::critical(nullptr, "Error", "Invalid folder path."); + return false; + } + std::filesystem::path filePath = pathInfo.absoluteFilePath().toStdString(); // Validate the input path @@ -224,13 +250,9 @@ bool FileManager::deleteFile(const QFileInfo &pathInfo) return false; } - try - { - std::filesystem::remove(filePath, err); - } - catch (const std::filesystem::filesystem_error &e) + if (!QFile::moveToTrash(filePath)) { - qWarning() << "Failed to delete: " << e.what(); + QMessageBox::warning(nullptr, "Error", "Failed to move folder to trash: " + pathInfo.absoluteFilePath()); return false; } @@ -239,7 +261,12 @@ bool FileManager::deleteFile(const QFileInfo &pathInfo) bool FileManager::deleteFolder(const QFileInfo &pathInfo) { - std::error_code err; + if (!isAValidDirectory(pathInfo)) + { + QMessageBox::critical(nullptr, "Error", "Invalid folder path."); + return false; + } + std::filesystem::path dirPath = pathInfo.absolutePath().toStdString(); // Validate the input path @@ -249,13 +276,9 @@ bool FileManager::deleteFolder(const QFileInfo &pathInfo) return false; } - try - { - std::filesystem::remove_all(dirPath, err); - } - catch (const std::filesystem::filesystem_error &e) + if (!QFile::moveToTrash(pathInfo.absoluteFilePath())) { - qWarning() << "Failed to delete: " << e.what(); + QMessageBox::warning(nullptr, "Error", "Failed to move folder to trash: " + pathInfo.absoluteFilePath()); return false; } @@ -309,5 +332,12 @@ bool FileManager::duplicatePath(const QFileInfo &pathInfo) return false; } + qDebug() << "Duplicated file to:" << QString::fromStdString(dupPath.string()); + if (err) + { + QMessageBox::critical(nullptr, "Error", QString("Failed to duplicate: ") + err.message().c_str()); + return false; + } + return true; } From d6282d2a649d7b105d2978cc4156043ca60f233b Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Apr 2025 18:29:20 -0700 Subject: [PATCH 141/160] [UnitTests] Added duplicate path test + refactor temporary file creation for unit tests --- tests/test_tree.cpp | 53 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp index 3d1b80a..f3aac2e 100644 --- a/tests/test_tree.cpp +++ b/tests/test_tree.cpp @@ -81,8 +81,8 @@ void TestTree::testOpenFile_invalid() void TestTree::testRenamePath() { - QString originalFileName = "test_file.cpp"; - QString newFileName = "renamed_file.cpp"; + QString originalFileName = QDir::temp().filePath("testFile.cpp"); + QString newFileName = QDir::temp().filePath("renamedTestFile.cpp"); QFile originalFile(originalFileName); if (!originalFile.exists()) @@ -105,18 +105,28 @@ void TestTree::testRenamePath() void TestTree::testDeleteFile() { - QVERIFY2(QFile::exists(testFilePath), "Test file should exist before deletion."); + QString tempFilePath = QDir::temp().filePath("testDeleteFile.cpp"); + QFile tempFile(tempFilePath); + if (!tempFile.open(QIODevice::WriteOnly)) + { + QFAIL("Failed to create temporary test file for deletion."); + } + tempFile.write("// test content"); + tempFile.close(); + + QVERIFY2(QFile::exists(tempFilePath), "Temporary file should exist before deletion."); QFileSystemModel *model = tree->getModel(); - QVERIFY2(model != nullptr, "getModel() should not return nullptr."); + QVERIFY2(model != nullptr, "Tree model should not be null."); - QModelIndex index = model->index(testFilePath); - QVERIFY2(index.isValid(), "QModelIndex should be valid for the test file."); + QModelIndex index = model->index(tempFilePath); + QVERIFY2(index.isValid(), "Model index should be valid for the temporary file."); QString filePath = model->filePath(index); + FileManager::getInstance().deleteFile(QFileInfo(filePath)); - QVERIFY2(!QFile::exists(testFilePath), "File should be deleted."); + QVERIFY2(!QFile::exists(tempFilePath), "Temporary file should be deleted."); } void TestTree::testDeleteDir() @@ -153,6 +163,9 @@ void TestTree::testNewFile() QVERIFY2(fileCreated, "New file should be created."); QVERIFY2(QFile::exists(newFilePath), "Newly created file should exist."); + + // Cleanup + QFile::remove(newFilePath); } void TestTree::testNewFolder() @@ -162,14 +175,34 @@ void TestTree::testNewFolder() QVERIFY2(folderCreated, "New folder should be created."); QVERIFY2(QFile::exists(newFolderPath), "Newly created folder should exist."); + + // Cleanup + QDir dir(newFolderPath); + dir.removeRecursively(); } void TestTree::testDuplicatePath() { - QString dupPath = QDir::temp().absolutePath() + "/testNewDir"; - bool pathDuplicated = FileManager::getInstance().duplicatePath(QFileInfo(dupPath)); + QString basePath = QDir::temp().absolutePath() + "/testDuplicateDir"; + QDir().mkpath(basePath); + + bool pathDuplicated = FileManager::getInstance().duplicatePath(QFileInfo(basePath)); + + QVERIFY2(pathDuplicated, "Path should be duplicated successfully."); + + // Find the duplicated path created for this test and clean it up + QDir tempDir(QDir::tempPath()); + QStringList entries = tempDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &entry : entries) + { + if (entry.startsWith("testDuplicateDir_copy")) + { + QDir(tempDir.absoluteFilePath(entry)).removeRecursively(); + } + } - QVERIFY2(pathDuplicated, "New folder should be created."); + // Clean up the original path + QDir(basePath).removeRecursively(); } QTEST_MAIN(TestTree) From afe903a3b67fbf7edd2c7341a9afdf1a39669e9e Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 6 Apr 2025 20:16:00 -0700 Subject: [PATCH 142/160] [Update] Added structure for operation result --- include/FileManager.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/include/FileManager.h b/include/FileManager.h index 26a2a0a..a322bb8 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -8,6 +8,12 @@ class CodeEditor; class MainWindow; +struct OperationResult +{ + bool success; + std::string message; +}; + /** * @class FileManager * @brief Manages file operations such as creating, saving, and opening files. @@ -31,7 +37,6 @@ class FileManager : public QObject } return instance; } - FileManager(const FileManager &) = delete; FileManager &operator=(const FileManager &) = delete; @@ -41,14 +46,11 @@ class FileManager : public QObject void setCurrentFileName(const QString fileName); void initialize(CodeEditor *editor, MainWindow *mainWindow); - - bool deleteFile(const QFileInfo &pathInfo); - bool deleteFolder(const QFileInfo &pathInfo); - bool renamePath(const QFileInfo &pathInfo, const QString &newName); - bool newFile(QString newFilePath); - bool newFolder(QString newFolderPath); - bool duplicatePath(const QFileInfo &pathInfo); + bool newFile(const QFileInfo &pathInfo, QString newFilePath); + static OperationResult newFolder(const QFileInfo &pathInfo, QString newFolderPath); + static OperationResult duplicatePath(const QFileInfo &pathInfo); + bool deletePath(const QFileInfo &pathInfo); public slots: void newFile(); @@ -67,4 +69,4 @@ public slots: MainWindow *m_mainWindow; QSyntaxHighlighter *m_currentHighlighter = nullptr; QString m_currentFileName; -}; +}; \ No newline at end of file From 2cfaa380a7e1b174e9591a69b8c75024d88be4d6 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 6 Apr 2025 20:18:04 -0700 Subject: [PATCH 143/160] [Feature] Implemented newFile + newFolder with a refactor of some other features --- src/FileManager.cpp | 109 +++++++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/src/FileManager.cpp b/src/FileManager.cpp index b24673f..277988c 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include FileManager::FileManager(CodeEditor *editor, MainWindow *mainWindow) : m_editor(editor), m_mainWindow(mainWindow) @@ -200,13 +201,14 @@ bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) try { std::filesystem::rename(oldPath, newPath); - return true; } catch (const std::filesystem::filesystem_error &e) { QMessageBox::critical(nullptr, "Error", QString(e.what())); return false; } + + return true; } // Check if the path is a valid directory @@ -225,94 +227,111 @@ bool isAValidDirectory(const QFileInfo &pathInfo) return false; } - if (pathInfo.isDir()) - { - return true; - } - - return false; + return true; } -bool FileManager::deleteFile(const QFileInfo &pathInfo) +bool FileManager::deletePath(const QFileInfo &pathInfo) { - if (isAValidDirectory(pathInfo)) + if (!isAValidDirectory(pathInfo)) { QMessageBox::critical(nullptr, "Error", "Invalid folder path."); return false; } - std::filesystem::path filePath = pathInfo.absoluteFilePath().toStdString(); + std::filesystem::path pathToDelete = pathInfo.absoluteFilePath().toStdString(); // Validate the input path - if (!isValidPath(filePath)) + if (!isValidPath(pathToDelete)) { QMessageBox::critical(nullptr, "Error", "Invalid file path."); return false; } - if (!QFile::moveToTrash(filePath)) + if (!QFile::moveToTrash(pathToDelete)) { - QMessageBox::warning(nullptr, "Error", "Failed to move folder to trash: " + pathInfo.absoluteFilePath()); + QMessageBox::warning(nullptr, "Error", "Failed to delete: " + QString::fromStdString(pathToDelete)); return false; } return true; } -bool FileManager::deleteFolder(const QFileInfo &pathInfo) +bool FileManager::newFile(const QFileInfo &pathInfo, QString newFilePath) { - if (!isAValidDirectory(pathInfo)) + std::filesystem::path dirPath = pathInfo.absolutePath().toStdString(); + + if (pathInfo.isDir()) { - QMessageBox::critical(nullptr, "Error", "Invalid folder path."); - return false; + dirPath = pathInfo.absoluteFilePath().toStdString(); } - std::filesystem::path dirPath = pathInfo.absolutePath().toStdString(); - - // Validate the input path if (!isValidPath(dirPath)) { QMessageBox::critical(nullptr, "Error", "Invalid file path."); return false; } - if (!QFile::moveToTrash(pathInfo.absoluteFilePath())) + std::filesystem::path filePath = dirPath / newFilePath.toStdString(); + + if (QFileInfo(filePath).exists()) { - QMessageBox::warning(nullptr, "Error", "Failed to move folder to trash: " + pathInfo.absoluteFilePath()); + QMessageBox::critical(nullptr, "Error", "Filename already used " + QFileInfo(filePath).fileName()); return false; } + std::ofstream file(filePath); + if (file.is_open()) + { + file.close(); + } + qDebug() << "New file created."; + + m_currentFileName = QString::fromStdString(filePath.string()); return true; } -bool FileManager::newFile(QString newFilePath) +OperationResult FileManager::newFolder(const QFileInfo &pathInfo, QString newFolderPath) { - qWarning() << "newFile Function not yet implemented"; - return false; -} + // TO-DO: look up which is prefered: error_code or exception + std::error_code err{}; + std::filesystem::path dirPath = pathInfo.absolutePath().toStdString(); -bool FileManager::newFolder(QString newFolderPath) -{ - qWarning() << "newFolder Function not yet implemented"; - return false; + // Check if the path is a directory + if (pathInfo.isDir()) + { + dirPath = pathInfo.absoluteFilePath().toStdString(); + } + + // Validate the input path + if (!isValidPath(dirPath)) + { + return {false, "Invalid file path."}; + } + std::filesystem::path newPath = dirPath / newFolderPath.toStdString(); + + std::filesystem::create_directory(newPath, err); + if (err) + { + qDebug() << "Error creating directory:" << QString::fromStdString(err.message()); + return {false, err.message().c_str()}; + } + + qDebug() << "New folder created at:" << QString::fromStdString(newPath.string()); + + return {true, newPath.filename().string()}; } -bool FileManager::duplicatePath(const QFileInfo &pathInfo) +OperationResult FileManager::duplicatePath(const QFileInfo &pathInfo) { - std::error_code err; std::filesystem::path filePath = pathInfo.absoluteFilePath().toStdString(); // Validate the input path if (!isValidPath(filePath)) { - QMessageBox::critical(nullptr, "Error", "Invalid file path."); - return false; + return {false , "Invalid path."}; } - std::string fileName = filePath.filename(); - size_t extensionPosition = fileName.find_last_of("."); - fileName = (std::string::npos == extensionPosition) ? fileName : fileName.substr(0, extensionPosition); - + std::string fileName = filePath.stem().string(); std::filesystem::path dupPath = filePath.parent_path() / (fileName + "_copy" + filePath.extension().c_str()); int counter = 1; @@ -324,20 +343,14 @@ bool FileManager::duplicatePath(const QFileInfo &pathInfo) try { - std::filesystem::copy(filePath, dupPath); + std::filesystem::copy(filePath, dupPath, std::filesystem::copy_options::recursive); // copy_option is needed for duplicating nested directories } catch (const std::filesystem::filesystem_error &e) { - QMessageBox::critical(nullptr, "Error", QString("Failed to duplicate: ") + e.what()); - return false; + return {false, e.what()}; } qDebug() << "Duplicated file to:" << QString::fromStdString(dupPath.string()); - if (err) - { - QMessageBox::critical(nullptr, "Error", QString("Failed to duplicate: ") + err.message().c_str()); - return false; - } - return true; -} + return {true, dupPath.filename().string()}; +} \ No newline at end of file From 4a37ab5c69740734035c968003f136507f60cf61 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 6 Apr 2025 20:18:32 -0700 Subject: [PATCH 144/160] [Feature] Added newFile and newFolder context action --- src/Tree.cpp | 93 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index ebb7989..1ea5ec3 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -98,25 +98,80 @@ void Tree::showContextMenu(const QPoint &pos) if (selectedAction == newFileAction) { - // TO-DO: implement file creation + QFileInfo pathInfo = getPathInfo(); + if (!pathInfo.exists()) + { + qWarning() << "Path does not exist: " << pathInfo.fileName(); + return; + } + + bool ok; + QString newFileName = QInputDialog::getText( + nullptr, + "New File", + "Enter file name:", + QLineEdit::Normal, + nullptr, + &ok + ); + + if (ok && !newFileName.isEmpty()) + { + if (FileManager::getInstance().newFile(pathInfo, newFileName)) + { + qInfo() << "File created successfully!"; + } + } } else if (selectedAction == newFolderAction) { - // TO-DO: implement folder creation + QFileInfo pathInfo = getPathInfo(); + if (!pathInfo.exists()) + { + qWarning() << "Path does not exist: " << pathInfo.fileName(); + return; + } + + bool ok; + QString newFolderName = QInputDialog::getText( + nullptr, + "New Folder", + "Enter folder name:", + QLineEdit::Normal, + nullptr, + &ok + ); + + if (ok && !newFolderName.isEmpty()) + { + OperationResult result = FileManager::getInstance().newFolder(pathInfo, newFolderName); + if (result.success) + { + qInfo() << result.message << " created successfully."; + } + else + { + QMessageBox::critical(nullptr, "Error", QString::fromStdString(result.message)); + } + } } else if (selectedAction == duplicateAction) { - // TO-DO: implement folder creation - QFileInfo oldPathInfo = getPathInfo(); - if (!oldPathInfo.exists()) + QFileInfo pathInfo = getPathInfo(); + if (!pathInfo.exists()) { - qWarning() << "File does not exist: " << oldPathInfo.fileName(); + qWarning() << "File does not exist: " << pathInfo.fileName(); return; } - if (FileManager::getInstance().duplicatePath(oldPathInfo)) + OperationResult result = FileManager::getInstance().duplicatePath(pathInfo); + if (result.success) { - qInfo() << "Duplicated successfully!"; + qInfo() << result.message << " created successfully."; + } + else + { + QMessageBox::critical(nullptr, "Error", QString::fromStdString(result.message)); } } else if (selectedAction == renameAction) @@ -162,25 +217,9 @@ void Tree::showContextMenu(const QPoint &pos) } else { - bool isDeleted = false; - QString itemType = pathInfo.isDir() ? "Folder" : "File"; - - if (pathInfo.isDir()) + if (FileManager::getInstance().deletePath(pathInfo)) { - isDeleted = FileManager::getInstance().deleteFolder(pathInfo); - } - else - { - isDeleted = FileManager::getInstance().deleteFile(pathInfo); - } - - if (isDeleted) - { - qInfo() << itemType << "successfully deleted!"; - } - else - { - QMessageBox::critical(nullptr, "Error", QString("%1 failed to delete.").arg(itemType)); + qInfo() << "Deleted successfully!"; } } } @@ -196,4 +235,4 @@ QFileInfo Tree::getPathInfo() } return QFileInfo(m_model->filePath(index)); -} +} \ No newline at end of file From 3b2ce127af8eb03be78d586b26721757b2b16ba7 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 6 Apr 2025 20:18:52 -0700 Subject: [PATCH 145/160] [UnitTests] Added unit tests for newFile and newFolder actions --- tests/test_tree.cpp | 56 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp index f3aac2e..9f60f28 100644 --- a/tests/test_tree.cpp +++ b/tests/test_tree.cpp @@ -20,6 +20,7 @@ class TestTree : public QObject Tree *tree; QString testFilePath; QString filePath; + CodeEditor *editor; private slots: void initTestCase(); @@ -30,6 +31,7 @@ private slots: void testRenamePath(); void testNewFile(); void testNewFolder(); + void testNewFolderFail(); void testDuplicatePath(); }; @@ -124,7 +126,7 @@ void TestTree::testDeleteFile() QString filePath = model->filePath(index); - FileManager::getInstance().deleteFile(QFileInfo(filePath)); + FileManager::getInstance().deletePath(QFileInfo(filePath)); QVERIFY2(!QFile::exists(tempFilePath), "Temporary file should be deleted."); } @@ -151,34 +153,62 @@ void TestTree::testDeleteDir() QVERIFY2(index.isValid(), "QModelIndex should be valid for the test directory."); QString dirPath = model->filePath(index); - FileManager::getInstance().deleteFolder(QFileInfo(dirPath)); + FileManager::getInstance().deletePath(QFileInfo(dirPath)); QVERIFY2(!QFile::exists(directory), "Directory should be deleted."); } void TestTree::testNewFile() { - QString newFilePath = QDir::temp().filePath("new_test_file.cpp"); - bool fileCreated = FileManager::getInstance().newFile(newFilePath); + // Create a temporary directory for the test + QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; + QDir dir(folderPath); + if (!dir.exists()) + { + dir.mkpath("."); + } + QVERIFY2(QFile::exists(folderPath), "Temporary directory should exist."); + + // Create a new file in the temporary directory + bool fileCreated = FileManager::getInstance().newFile(QFileInfo(folderPath), "newFileTest1.c"); QVERIFY2(fileCreated, "New file should be created."); - QVERIFY2(QFile::exists(newFilePath), "Newly created file should exist."); + QVERIFY2(QFile::exists(folderPath + "/newFileTest1.c"), "Newly created file should exist."); // Cleanup - QFile::remove(newFilePath); + QFile::remove(folderPath + "/newFileTest1.c"); + QVERIFY2(!QFile::exists(folderPath + "/newFileTest1.c"), "Newly created file should be deleted."); + + dir.removeRecursively(); + QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } void TestTree::testNewFolder() { - QString newFolderPath = QDir::temp().absolutePath() + "/testNewDir"; - bool folderCreated = FileManager::getInstance().newFolder(newFolderPath); + QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; + OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), "newDirTest"); - QVERIFY2(folderCreated, "New folder should be created."); - QVERIFY2(QFile::exists(newFolderPath), "Newly created folder should exist."); + QVERIFY2(folderCreated.success, "New folder should be created."); + QVERIFY2(QFile::exists(QDir::temp().absolutePath() + "/newDirTest"), "Newly created folder should exist."); // Cleanup - QDir dir(newFolderPath); + QDir dir(folderPath); dir.removeRecursively(); + QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); +} + +void TestTree::testNewFolderFail() +{ + QString folderPath = QDir::temp().absolutePath() + "../testNewDir"; + OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), ""); + + QVERIFY2(!folderCreated.success, "Folder creation should fail."); + + // Cleanup + QDir dir(folderPath); + dir.removeRecursively(); + + QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } void TestTree::testDuplicatePath() @@ -186,9 +216,9 @@ void TestTree::testDuplicatePath() QString basePath = QDir::temp().absolutePath() + "/testDuplicateDir"; QDir().mkpath(basePath); - bool pathDuplicated = FileManager::getInstance().duplicatePath(QFileInfo(basePath)); + OperationResult pathDuplicated = FileManager::getInstance().duplicatePath(QFileInfo(basePath)); - QVERIFY2(pathDuplicated, "Path should be duplicated successfully."); + QVERIFY2(pathDuplicated.success, "Path should be duplicated successfully."); // Find the duplicated path created for this test and clean it up QDir tempDir(QDir::tempPath()); From 88294dd5c4c0c92e70fa356e4ac1479daddae87b Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 6 Apr 2025 20:23:48 -0700 Subject: [PATCH 146/160] [fix] wrapped result.message to QString --- src/Tree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index 1ea5ec3..c0c4957 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -147,7 +147,7 @@ void Tree::showContextMenu(const QPoint &pos) OperationResult result = FileManager::getInstance().newFolder(pathInfo, newFolderName); if (result.success) { - qInfo() << result.message << " created successfully."; + qInfo() << QString::fromStdString(result.message) << " created successfully."; } else { @@ -167,7 +167,7 @@ void Tree::showContextMenu(const QPoint &pos) OperationResult result = FileManager::getInstance().duplicatePath(pathInfo); if (result.success) { - qInfo() << result.message << " created successfully."; + qInfo() << QString::fromStdString(result.message) << " created successfully."; } else { From ca17166256c74bee7dc2d23a780dbd3092ea2c28 Mon Sep 17 00:00:00 2001 From: tervicke Date: Mon, 7 Apr 2025 23:55:34 +0530 Subject: [PATCH 147/160] Added exception handling to handle incorrect keyword. Related to #28 --- src/Syntax.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Syntax.cpp b/src/Syntax.cpp index 3a2f666..1f0841a 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -46,10 +46,28 @@ void Syntax::loadSyntaxRules(const YAML::Node &config) // Iterate through each rule in the category for (const auto &rule : rules) { - QString regex = QString::fromStdString(rule["regex"].as()); - QColor color(QString::fromStdString(rule["color"].as())); + + QString regex; + try{ + std::string regexStr = rule["regex"].as(); //will throw exception if the key does not exist + regex = QString::fromStdString(regexStr); + }catch(const YAML::Exception e){ + qWarning() << " YAML exception when parsion the regex in syntax file" << e.what(); + continue; + } + qDebug() << "regex: " << regex; + QColor color; + try{ + std::string colorStr = rule["color"].as(); + color = QColor(QString::fromStdString(colorStr)); + }catch(const YAML::Exception e){ + qWarning() << " YAML exception when parsion the color in syntax file" << e.what(); + continue; + } + + // Create a QTextCharFormat for the rule QTextCharFormat format; format.setForeground(color); From f5f5598e68cdb2fca1c4061dabc635f0166656c3 Mon Sep 17 00:00:00 2001 From: tervicke Date: Tue, 8 Apr 2025 21:51:23 +0530 Subject: [PATCH 148/160] Added if condition to check if the color is valid --- src/Syntax.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Syntax.cpp b/src/Syntax.cpp index 1f0841a..faa6c92 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -67,6 +67,11 @@ void Syntax::loadSyntaxRules(const YAML::Node &config) continue; } + //checks if the color is a valid color + if(!color.isValid()){ + qWarning() << "Invalid COlor : Skipping..."; + continue; + } // Create a QTextCharFormat for the rule QTextCharFormat format; From 6b9014c310536d1e3f41f0c82e3abeb2711078b0 Mon Sep 17 00:00:00 2001 From: tervicke Date: Tue, 8 Apr 2025 22:34:58 +0530 Subject: [PATCH 149/160] fixed nits from review --- src/Syntax.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Syntax.cpp b/src/Syntax.cpp index faa6c92..e3aab1c 100644 --- a/src/Syntax.cpp +++ b/src/Syntax.cpp @@ -48,10 +48,13 @@ void Syntax::loadSyntaxRules(const YAML::Node &config) { QString regex; - try{ + try + { std::string regexStr = rule["regex"].as(); //will throw exception if the key does not exist - regex = QString::fromStdString(regexStr); - }catch(const YAML::Exception e){ + regex = QString::fromStdString(regexStr); + } + catch(const YAML::Exception e) + { qWarning() << " YAML exception when parsion the regex in syntax file" << e.what(); continue; } @@ -59,16 +62,20 @@ void Syntax::loadSyntaxRules(const YAML::Node &config) qDebug() << "regex: " << regex; QColor color; - try{ + try + { std::string colorStr = rule["color"].as(); - color = QColor(QString::fromStdString(colorStr)); - }catch(const YAML::Exception e){ + color = QColor(QString::fromStdString(colorStr)); + } + catch(const YAML::Exception e) + { qWarning() << " YAML exception when parsion the color in syntax file" << e.what(); continue; } //checks if the color is a valid color - if(!color.isValid()){ + if(!color.isValid()) + { qWarning() << "Invalid COlor : Skipping..."; continue; } From b09ed0ad269826b9c5ed64fac7fc90de2a25cdff Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:13:45 -0700 Subject: [PATCH 150/160] [Feature] renamed test tree to test file manager --- tests/{test_tree.cpp => test_filemanager.cpp} | 79 +++++++------------ 1 file changed, 28 insertions(+), 51 deletions(-) rename tests/{test_tree.cpp => test_filemanager.cpp} (74%) diff --git a/tests/test_tree.cpp b/tests/test_filemanager.cpp similarity index 74% rename from tests/test_tree.cpp rename to tests/test_filemanager.cpp index 9f60f28..bb121fe 100644 --- a/tests/test_tree.cpp +++ b/tests/test_filemanager.cpp @@ -1,6 +1,5 @@ #include "Tree.h" #include "FileManager.h" -#include "CodeEditor.h" #include #include @@ -11,16 +10,13 @@ #include #include -class TestTree : public QObject +class TestFileManager : public QObject { Q_OBJECT private: QSplitter *splitter; Tree *tree; - QString testFilePath; - QString filePath; - CodeEditor *editor; private slots: void initTestCase(); @@ -35,45 +31,21 @@ private slots: void testDuplicatePath(); }; -void TestTree::initTestCase() +void TestFileManager::initTestCase() { - qDebug() << "Initializing TestTree tests..."; + qDebug() << "Initializing TestFileManager tests..."; splitter = new QSplitter; tree = new Tree(splitter); - - // Create a temporary test file for the Unit Tests - testFilePath = "test_file.cpp"; - QFile testFile(testFilePath); - if (!testFile.exists()) - { - if (!testFile.open(QIODevice::WriteOnly)) - { - QFAIL("Failed to create test file."); - } - testFile.close(); - } - - QFileSystemModel *model = tree->getModel(); - if (model) - { - QModelIndex index = model->index(testFilePath); - filePath = model->filePath(index); - } } -void TestTree::cleanupTestCase() +void TestFileManager::cleanupTestCase() { - qDebug() << "Cleaning up TestTree tests..."; + qDebug() << "Cleaning up TestFileManager tests..."; delete tree; delete splitter; - - if (QFile::exists(testFilePath)) - { - QFile::remove(testFilePath); - } } -void TestTree::testOpenFile_invalid() +void TestFileManager::testOpenFile_invalid() { QModelIndex index; tree->openFile(index); @@ -81,7 +53,7 @@ void TestTree::testOpenFile_invalid() QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), "FileManager should not process an invalid file."); } -void TestTree::testRenamePath() +void TestFileManager::testRenamePath() { QString originalFileName = QDir::temp().filePath("testFile.cpp"); QString newFileName = QDir::temp().filePath("renamedTestFile.cpp"); @@ -96,16 +68,16 @@ void TestTree::testRenamePath() originalFile.close(); } - bool fileRenamed = FileManager::getInstance().renamePath(QFileInfo(originalFileName), newFileName); + OperationResult fileRenamed = FileManager::getInstance().renamePath(QFileInfo(originalFileName), newFileName); - QVERIFY2(fileRenamed, "File should be renamed successfully."); + QVERIFY2(fileRenamed.success, "File should be renamed successfully."); QVERIFY2(QFile::exists(newFileName), "Renamed file should exist."); QVERIFY2(!QFile::exists(originalFileName), "Original file should no longer exist."); QFile::remove(newFileName); } -void TestTree::testDeleteFile() +void TestFileManager::testDeleteFile() { QString tempFilePath = QDir::temp().filePath("testDeleteFile.cpp"); QFile tempFile(tempFilePath); @@ -131,7 +103,7 @@ void TestTree::testDeleteFile() QVERIFY2(!QFile::exists(tempFilePath), "Temporary file should be deleted."); } -void TestTree::testDeleteDir() +void TestFileManager::testDeleteDir() { // Temporary directory for deletion test QString directory = QDir::temp().absolutePath() + "/testDeleteDir"; @@ -158,7 +130,7 @@ void TestTree::testDeleteDir() QVERIFY2(!QFile::exists(directory), "Directory should be deleted."); } -void TestTree::testNewFile() +void TestFileManager::testNewFile() { // Create a temporary directory for the test QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; @@ -170,9 +142,9 @@ void TestTree::testNewFile() QVERIFY2(QFile::exists(folderPath), "Temporary directory should exist."); // Create a new file in the temporary directory - bool fileCreated = FileManager::getInstance().newFile(QFileInfo(folderPath), "newFileTest1.c"); + OperationResult fileCreated = FileManager::getInstance().newFile(QFileInfo(folderPath), "newFileTest1.c"); - QVERIFY2(fileCreated, "New file should be created."); + QVERIFY2(fileCreated.success, "New file should be created."); QVERIFY2(QFile::exists(folderPath + "/newFileTest1.c"), "Newly created file should exist."); // Cleanup @@ -183,23 +155,28 @@ void TestTree::testNewFile() QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } -void TestTree::testNewFolder() +void TestFileManager::testNewFolder() { - QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; - OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), "newDirTest"); + QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; + QDir dir(folderPath); + if (!dir.exists()) + { + dir.mkpath("."); + } + QVERIFY2(QFile::exists(folderPath), "Temporary directory should exist."); + OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), "newDirTest"); QVERIFY2(folderCreated.success, "New folder should be created."); QVERIFY2(QFile::exists(QDir::temp().absolutePath() + "/newDirTest"), "Newly created folder should exist."); // Cleanup - QDir dir(folderPath); dir.removeRecursively(); QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } -void TestTree::testNewFolderFail() +void TestFileManager::testNewFolderFail() { - QString folderPath = QDir::temp().absolutePath() + "../testNewDir"; + QString folderPath = QDir::temp().absolutePath() + "../testNewDir"; OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), ""); QVERIFY2(!folderCreated.success, "Folder creation should fail."); @@ -211,7 +188,7 @@ void TestTree::testNewFolderFail() QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } -void TestTree::testDuplicatePath() +void TestFileManager::testDuplicatePath() { QString basePath = QDir::temp().absolutePath() + "/testDuplicateDir"; QDir().mkpath(basePath); @@ -235,5 +212,5 @@ void TestTree::testDuplicatePath() QDir(basePath).removeRecursively(); } -QTEST_MAIN(TestTree) -#include "test_tree.moc" +QTEST_MAIN(TestFileManager) +#include "test_filemanager.moc" From 67b80d834fcd85ca09cd64c29e16e650b84028a6 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:18:30 -0700 Subject: [PATCH 151/160] [refactor] Improve test file manager code readability and structure --- tests/test_filemanager.cpp | 51 +++++++++++++------------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/tests/test_filemanager.cpp b/tests/test_filemanager.cpp index bb121fe..c34bb0d 100644 --- a/tests/test_filemanager.cpp +++ b/tests/test_filemanager.cpp @@ -15,8 +15,8 @@ class TestFileManager : public QObject Q_OBJECT private: - QSplitter *splitter; - Tree *tree; + QSplitter *splitter = nullptr; + Tree *tree = nullptr; private slots: void initTestCase(); @@ -50,7 +50,8 @@ void TestFileManager::testOpenFile_invalid() QModelIndex index; tree->openFile(index); - QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), "FileManager should not process an invalid file."); + QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), + "FileManager should not process an invalid file."); } void TestFileManager::testRenamePath() @@ -59,14 +60,11 @@ void TestFileManager::testRenamePath() QString newFileName = QDir::temp().filePath("renamedTestFile.cpp"); QFile originalFile(originalFileName); - if (!originalFile.exists()) + if (!originalFile.exists() && !originalFile.open(QIODevice::WriteOnly)) { - if (!originalFile.open(QIODevice::WriteOnly)) - { - QFAIL("Failed to create test file."); - } - originalFile.close(); + QFAIL("Failed to create test file."); } + originalFile.close(); OperationResult fileRenamed = FileManager::getInstance().renamePath(QFileInfo(originalFileName), newFileName); @@ -91,48 +89,40 @@ void TestFileManager::testDeleteFile() QVERIFY2(QFile::exists(tempFilePath), "Temporary file should exist before deletion."); QFileSystemModel *model = tree->getModel(); - QVERIFY2(model != nullptr, "Tree model should not be null."); + QVERIFY2(model, "Tree model should not be null."); QModelIndex index = model->index(tempFilePath); QVERIFY2(index.isValid(), "Model index should be valid for the temporary file."); - QString filePath = model->filePath(index); - - FileManager::getInstance().deletePath(QFileInfo(filePath)); + FileManager::getInstance().deletePath(QFileInfo(model->filePath(index))); QVERIFY2(!QFile::exists(tempFilePath), "Temporary file should be deleted."); } void TestFileManager::testDeleteDir() { - // Temporary directory for deletion test QString directory = QDir::temp().absolutePath() + "/testDeleteDir"; QDir tempDir(directory); - if (!tempDir.exists()) + if (!tempDir.exists() && !tempDir.mkpath(".")) { - if (!tempDir.mkpath(".")) - { - QFAIL("Failed to create test directory."); - } + QFAIL("Failed to create test directory."); } QVERIFY2(QFile::exists(directory), "Test directory should exist before deletion."); QFileSystemModel *model = tree->getModel(); - QVERIFY2(model != nullptr, "getModel() should not return nullptr."); + QVERIFY2(model, "Tree model should not be null."); QModelIndex index = model->index(directory); - QVERIFY2(index.isValid(), "QModelIndex should be valid for the test directory."); + QVERIFY2(index.isValid(), "Model index should be valid for the test directory."); - QString dirPath = model->filePath(index); - FileManager::getInstance().deletePath(QFileInfo(dirPath)); + FileManager::getInstance().deletePath(QFileInfo(model->filePath(index))); QVERIFY2(!QFile::exists(directory), "Directory should be deleted."); } void TestFileManager::testNewFile() { - // Create a temporary directory for the test QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; QDir dir(folderPath); if (!dir.exists()) @@ -141,13 +131,11 @@ void TestFileManager::testNewFile() } QVERIFY2(QFile::exists(folderPath), "Temporary directory should exist."); - // Create a new file in the temporary directory OperationResult fileCreated = FileManager::getInstance().newFile(QFileInfo(folderPath), "newFileTest1.c"); QVERIFY2(fileCreated.success, "New file should be created."); QVERIFY2(QFile::exists(folderPath + "/newFileTest1.c"), "Newly created file should exist."); - // Cleanup QFile::remove(folderPath + "/newFileTest1.c"); QVERIFY2(!QFile::exists(folderPath + "/newFileTest1.c"), "Newly created file should be deleted."); @@ -164,12 +152,12 @@ void TestFileManager::testNewFolder() dir.mkpath("."); } QVERIFY2(QFile::exists(folderPath), "Temporary directory should exist."); + OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), "newDirTest"); QVERIFY2(folderCreated.success, "New folder should be created."); - QVERIFY2(QFile::exists(QDir::temp().absolutePath() + "/newDirTest"), "Newly created folder should exist."); + QVERIFY2(QFile::exists(folderPath + "/newDirTest"), "Newly created folder should exist."); - // Cleanup dir.removeRecursively(); QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } @@ -181,10 +169,7 @@ void TestFileManager::testNewFolderFail() QVERIFY2(!folderCreated.success, "Folder creation should fail."); - // Cleanup - QDir dir(folderPath); - dir.removeRecursively(); - + QDir(folderPath).removeRecursively(); QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } @@ -197,7 +182,6 @@ void TestFileManager::testDuplicatePath() QVERIFY2(pathDuplicated.success, "Path should be duplicated successfully."); - // Find the duplicated path created for this test and clean it up QDir tempDir(QDir::tempPath()); QStringList entries = tempDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QString &entry : entries) @@ -208,7 +192,6 @@ void TestFileManager::testDuplicatePath() } } - // Clean up the original path QDir(basePath).removeRecursively(); } From c7391d6f408f6801edd769b89d36e7c72fad3f41 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:19:54 -0700 Subject: [PATCH 152/160] [refactor] Change file manager methods to return OperationResult instead of bool for better error handling --- include/FileManager.h | 6 +++--- src/FileManager.cpp | 46 ++++++++++++++++++++----------------------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/include/FileManager.h b/include/FileManager.h index a322bb8..6a44b39 100644 --- a/include/FileManager.h +++ b/include/FileManager.h @@ -46,11 +46,11 @@ class FileManager : public QObject void setCurrentFileName(const QString fileName); void initialize(CodeEditor *editor, MainWindow *mainWindow); - bool renamePath(const QFileInfo &pathInfo, const QString &newName); - bool newFile(const QFileInfo &pathInfo, QString newFilePath); + static OperationResult renamePath(const QFileInfo &pathInfo, const QString &newName); + static OperationResult newFile(const QFileInfo &pathInfo, QString newFilePath); static OperationResult newFolder(const QFileInfo &pathInfo, QString newFolderPath); static OperationResult duplicatePath(const QFileInfo &pathInfo); - bool deletePath(const QFileInfo &pathInfo); + static OperationResult deletePath(const QFileInfo &pathInfo); public slots: void newFile(); diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 277988c..7d35e1b 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -173,12 +173,11 @@ bool isValidPath(const std::filesystem::path &path) return true; } -bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) +OperationResult FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) { if (!pathInfo.exists()) { - qWarning() << "Path does not exist: " << pathInfo.fileName(); - return false; + return {false, "Path does not exist: " + pathInfo.fileName().toStdString()}; } std::filesystem::path oldPath = pathInfo.absoluteFilePath().toStdString(); @@ -186,16 +185,14 @@ bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) // Validate the input path if (!isValidPath(oldPath)) { - QMessageBox::critical(nullptr, "Error", "Invalid file path."); - return false; + return {false, "Invalid file path."}; } std::filesystem::path newPath = oldPath.parent_path() / newName.toStdString(); if (QFileInfo(newPath).exists()) { - QMessageBox::critical(nullptr, "Error", QString("%1 already takken.").arg(QString::fromStdString(newPath.filename()))); - return false; + return {false, newPath.filename().string() + " already takken."}; } try @@ -205,10 +202,10 @@ bool FileManager::renamePath(const QFileInfo &pathInfo, const QString &newName) catch (const std::filesystem::filesystem_error &e) { QMessageBox::critical(nullptr, "Error", QString(e.what())); - return false; + return {false, e.what()}; } - return true; + return {true, newPath.filename().string()}; } // Check if the path is a valid directory @@ -230,12 +227,11 @@ bool isAValidDirectory(const QFileInfo &pathInfo) return true; } -bool FileManager::deletePath(const QFileInfo &pathInfo) +OperationResult FileManager::deletePath(const QFileInfo &pathInfo) { if (!isAValidDirectory(pathInfo)) { - QMessageBox::critical(nullptr, "Error", "Invalid folder path."); - return false; + return {false, "ERROR: invalid folder path." + pathInfo.absolutePath().toStdString()}; } std::filesystem::path pathToDelete = pathInfo.absoluteFilePath().toStdString(); @@ -243,20 +239,18 @@ bool FileManager::deletePath(const QFileInfo &pathInfo) // Validate the input path if (!isValidPath(pathToDelete)) { - QMessageBox::critical(nullptr, "Error", "Invalid file path."); - return false; + return {false, "ERROR: invalid file path." + pathToDelete.filename().string()}; } if (!QFile::moveToTrash(pathToDelete)) { - QMessageBox::warning(nullptr, "Error", "Failed to delete: " + QString::fromStdString(pathToDelete)); - return false; + return {false, "ERROR: failed to delete: " + pathToDelete.string()}; } - return true; + return {true, pathToDelete.filename().string()}; } -bool FileManager::newFile(const QFileInfo &pathInfo, QString newFilePath) +OperationResult FileManager::newFile(const QFileInfo &pathInfo, QString newFilePath) { std::filesystem::path dirPath = pathInfo.absolutePath().toStdString(); @@ -267,16 +261,13 @@ bool FileManager::newFile(const QFileInfo &pathInfo, QString newFilePath) if (!isValidPath(dirPath)) { - QMessageBox::critical(nullptr, "Error", "Invalid file path."); - return false; + return {false, "invalid file path."}; } std::filesystem::path filePath = dirPath / newFilePath.toStdString(); - if (QFileInfo(filePath).exists()) { - QMessageBox::critical(nullptr, "Error", "Filename already used " + QFileInfo(filePath).fileName()); - return false; + return {false, filePath.filename().string() + " already used."}; } std::ofstream file(filePath); @@ -286,8 +277,8 @@ bool FileManager::newFile(const QFileInfo &pathInfo, QString newFilePath) } qDebug() << "New file created."; - m_currentFileName = QString::fromStdString(filePath.string()); - return true; + FileManager::getInstance().setCurrentFileName(QString::fromStdString(filePath.string())); + return {true, filePath.filename().string()}; } OperationResult FileManager::newFolder(const QFileInfo &pathInfo, QString newFolderPath) @@ -307,7 +298,12 @@ OperationResult FileManager::newFolder(const QFileInfo &pathInfo, QString newFol { return {false, "Invalid file path."}; } + std::filesystem::path newPath = dirPath / newFolderPath.toStdString(); + if (QFileInfo(newPath).exists()) + { + return {false, newPath.filename().string() + " already used."}; + } std::filesystem::create_directory(newPath, err); if (err) From e977e10c041a852028fa8e82f1dadb43e9dd5531 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:20:12 -0700 Subject: [PATCH 153/160] [refactor] Added isSuccessful method to Tree class for improved operation result handling --- include/Tree.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/Tree.h b/include/Tree.h index 6a63a51..856e71f 100644 --- a/include/Tree.h +++ b/include/Tree.h @@ -1,5 +1,7 @@ #pragma once +#include "FileManager.h" + #include #include #include @@ -35,6 +37,7 @@ class Tree : public QObject private: void showContextMenu(const QPoint &pos); QFileInfo getPathInfo(); + void isSuccessful(OperationResult result); std::unique_ptr m_iconProvider; std::unique_ptr m_model; From 5e652e058af272fbeff3063d33817374f9a26a4e Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:20:27 -0700 Subject: [PATCH 154/160] [refactor] Simplify file operation handling in Tree class by centralizing success checks in isSuccessful method --- src/Tree.cpp | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/Tree.cpp b/src/Tree.cpp index c0c4957..581685a 100644 --- a/src/Tree.cpp +++ b/src/Tree.cpp @@ -1,6 +1,5 @@ #include "Tree.h" #include "CodeEditor.h" -#include "FileManager.h" #include #include @@ -117,10 +116,8 @@ void Tree::showContextMenu(const QPoint &pos) if (ok && !newFileName.isEmpty()) { - if (FileManager::getInstance().newFile(pathInfo, newFileName)) - { - qInfo() << "File created successfully!"; - } + OperationResult result = FileManager::getInstance().newFile(pathInfo, newFileName); + isSuccessful(result); } } else if (selectedAction == newFolderAction) @@ -145,14 +142,7 @@ void Tree::showContextMenu(const QPoint &pos) if (ok && !newFolderName.isEmpty()) { OperationResult result = FileManager::getInstance().newFolder(pathInfo, newFolderName); - if (result.success) - { - qInfo() << QString::fromStdString(result.message) << " created successfully."; - } - else - { - QMessageBox::critical(nullptr, "Error", QString::fromStdString(result.message)); - } + isSuccessful(result); } } else if (selectedAction == duplicateAction) @@ -165,14 +155,7 @@ void Tree::showContextMenu(const QPoint &pos) } OperationResult result = FileManager::getInstance().duplicatePath(pathInfo); - if (result.success) - { - qInfo() << QString::fromStdString(result.message) << " created successfully."; - } - else - { - QMessageBox::critical(nullptr, "Error", QString::fromStdString(result.message)); - } + isSuccessful(result); } else if (selectedAction == renameAction) { @@ -194,10 +177,8 @@ void Tree::showContextMenu(const QPoint &pos) if (ok && !newFileName.isEmpty()) { - if (FileManager::getInstance().renamePath(oldPathInfo, newFileName)) - { - qInfo() << "Renamed successfully!"; - } + OperationResult result = FileManager::getInstance().renamePath(oldPathInfo, newFileName); + isSuccessful(result); } } else if (selectedAction == deleteAction) @@ -217,10 +198,8 @@ void Tree::showContextMenu(const QPoint &pos) } else { - if (FileManager::getInstance().deletePath(pathInfo)) - { - qInfo() << "Deleted successfully!"; - } + OperationResult result = FileManager::getInstance().deletePath(pathInfo); + isSuccessful(result); } } } @@ -235,4 +214,16 @@ QFileInfo Tree::getPathInfo() } return QFileInfo(m_model->filePath(index)); +} + +void Tree::isSuccessful(OperationResult result) +{ + if (result.success) + { + qInfo() << QString::fromStdString(result.message) << " created successfully."; + } + else + { + QMessageBox::critical(nullptr, "Error", QString::fromStdString(result.message)); + } } \ No newline at end of file From 157e204215eaa7ef8d28453fc01b8804e5804926 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:20:59 -0700 Subject: [PATCH 155/160] [refactor] Clean up Makefile structure and improve build process clarity --- Makefile | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index ba83bfc..329778d 100644 --- a/Makefile +++ b/Makefile @@ -1,42 +1,39 @@ PROJECT = CodeAstra BUILD_DIR = $(PWD)/build -EXECUTABLE = $(PROJECT) -# Set CMake options CMAKE_OPTIONS = .. -# Default target: Run CMake and install the project -all: build install +.PHONY: all build clean install build_tests test + +all: install -# Run CMake to build the project build: - @echo "Building $(PROJECT) with CMake..." + @echo "Building $(PROJECT)..." @mkdir -p $(BUILD_DIR) @cd $(BUILD_DIR) && cmake $(CMAKE_OPTIONS) -# Clean the build directory clean: @echo "Cleaning the build directory..." @rm -rf $(BUILD_DIR) -# Uninstalling the software -uninstall: clean - @echo "Uninstalling $(PROJECT)..." - @rm -f $(EXECUTABLE) - -# Install the project install: build @echo "Installing $(PROJECT)..." - @cd $(BUILD_DIR) && make - @echo "$(PROJECT) installed." + @cmake --build $(BUILD_DIR) + @echo "Installation complete." build_tests: build - @cd $(BUILD_DIR)/tests/ && make + @echo "Building tests..." + @$(MAKE) -C $(BUILD_DIR)/tests test: build_tests + @echo "Running tests..." @for test in ./build/tests/test_*; do \ if [ -f $$test ]; then \ echo "Running $$test..."; \ $$test; \ fi; \ done + +run: + @echo "Running $(PROJECT)..." + @./build/bin/$(PROJECT) \ No newline at end of file From a7d22b4cc400faccc253ec6e5a63efec1d865c16 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:26:30 -0700 Subject: [PATCH 156/160] [refactor] Streamline CMakeLists.txt by consolidating project setup and removing unnecessary OS checks --- CMakeLists.txt | 135 +++++++------------------------------------------ 1 file changed, 18 insertions(+), 117 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4755b93..389bf77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,134 +1,35 @@ cmake_minimum_required(VERSION 3.16) -# Project name -set(TARGET_NAME CodeAstraApp) -set(EXECUTABLE_NAME CodeAstra) +project(CodeAstra VERSION 0.1.0 DESCRIPTION "Code Editor written in modern C++ using Qt6") -set(QT_MAJOR_VERSION 6) - -project(${TARGET_NAME} VERSION 0.1.0 DESCRIPTION "Code Editor written in C++ using Qt6") - -# Enable automatic MOC (Meta-Object Compiler) handling for Qt -set(CMAKE_AUTOMOC ON) - -# Set the CXX standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Set default build output directories -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) -set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}) +# Use cmake/ for custom modules +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") -# Detect operating system -if(WIN32) - set(OS_NAME "Windows") -elseif(APPLE) - set(OS_NAME "macOS") -else() - set(OS_NAME "Linux") -endif() +# Set output directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -message(STATUS "Building for ${OS_NAME}") +# Enable Qt tools +set(CMAKE_AUTOMOC ON) -# Locate Qt installation -if(DEFINED ENV{Qt${QT_MAJOR_VERSION}_HOME}) - set(Qt_DIR "$ENV{Qt${QT_MAJOR_VERSION}_HOME}") - message(STATUS "Using Qt from: ${Qt_DIR}") -else() - if(WIN32) - set(Qt_DIR "C:/Qt/${QT_MAJOR_VERSION}/msvc2022_64/lib/cmake/Qt${QT_MAJOR_VERSION}") - elseif(APPLE) - set(Qt_DIR "/usr/local/opt/qt/lib/cmake/Qt${QT_MAJOR_VERSION}") - else() - set(Qt_DIR "/usr/lib/cmake/Qt${QT_MAJOR_VERSION}") - endif() - message(STATUS "Using default Qt path: ${Qt_DIR}") -endif() +# Define target names +set(TARGET_NAME CodeAstra) +set(EXECUTABLE_NAME ${TARGET_NAME}App) -# Set Qt path for find_package -set(CMAKE_PREFIX_PATH ${Qt_DIR}) +# Set Qt version +set(QT_MAJOR_VERSION 6) -# Find Qt components +# Find Qt find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS Core Widgets Test) -# Locate yaml-cpp -if(APPLE) - set(yaml-cpp_DIR "/opt/homebrew/Cellar/yaml-cpp/0.8.0/lib/cmake/yaml-cpp") -endif() +# yaml-cpp find_package(yaml-cpp REQUIRED CONFIG) -# Copy YAML files to the build directory (non-macOS case) -set(YAML_SOURCE_DIR ${CMAKE_SOURCE_DIR}/config) -set(YAML_DEST_DIR ${CMAKE_BINARY_DIR}/config) -file(MAKE_DIRECTORY ${YAML_DEST_DIR}) -file(GLOB YAML_FILES "${YAML_SOURCE_DIR}/*.yaml") - -foreach(YAML_FILE ${YAML_FILES}) - file(COPY ${YAML_FILE} DESTINATION ${YAML_DEST_DIR}) -endforeach() - -# Create the CodeAstra library -add_library(${TARGET_NAME} - src/MainWindow.cpp - src/CodeEditor.cpp - src/Tree.cpp - src/FileManager.cpp - src/Syntax.cpp - src/SyntaxManager.cpp - include/MainWindow.h - include/CodeEditor.h - include/Tree.h - include/LineNumberArea.h - include/FileManager.h - include/SyntaxManager.h - include/Syntax.h -) - -# Link YAML-CPP to the CodeAstra library -target_link_libraries(${TARGET_NAME} PRIVATE yaml-cpp) - -# Create the executable for the application -add_executable(${EXECUTABLE_NAME} src/main.cpp) - -# Link the main executable with the CodeAstra library and Qt libraries -target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${TARGET_NAME} Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Widgets) - -# Add the tests subdirectory +# Add subdirectories +add_subdirectory(src) add_subdirectory(tests) -# Qt resource files -qt_add_resources(APP_RESOURCES resources.qrc) -target_sources(${EXECUTABLE_NAME} PRIVATE ${APP_RESOURCES}) - -# Compiler flags per OS -if(MSVC) - target_compile_options(${EXECUTABLE_NAME} PRIVATE /W4 /WX /analyze /sdl /guard:cf) -elseif(APPLE) - target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion -Wsign-conversion -fsanitize=address,undefined -fstack-protector) - target_link_libraries(${EXECUTABLE_NAME} PRIVATE -fsanitize=address,undefined) - # set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE TRUE) -else() - target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion -Wsign-conversion -fsanitize=address,undefined -fstack-protector) - target_link_libraries(${EXECUTABLE_NAME} PRIVATE -fsanitize=address,undefined) -endif() - -# Include directories -target_include_directories(${EXECUTABLE_NAME} PRIVATE ${Qt${QT_MAJOR_VERSION}_INCLUDE_DIRS}) -target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) - -# Set output names properly for Debug and Release -set_target_properties(${EXECUTABLE_NAME} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" - DEBUG_OUTPUT_NAME "${EXECUTABLE_NAME}d" - RELEASE_OUTPUT_NAME ${EXECUTABLE_NAME} -) - -# Link necessary Qt libraries to CodeAstra library -target_link_libraries(${TARGET_NAME} PRIVATE Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Widgets) - -# Ensure correct linking of yaml-cpp (macOS) -if(APPLE) - target_include_directories(${EXECUTABLE_NAME} PRIVATE /opt/homebrew/include) - target_link_directories(${EXECUTABLE_NAME} PRIVATE /opt/homebrew/lib) -endif() From ae0883b06b8fce0836b3e91674d6d0a9a2c7c6ca Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:27:13 -0700 Subject: [PATCH 157/160] [Refactor] Create CMakeLists.txt for project configuration and build setup instead of root CMAkeList --- src/CMakeLists.txt | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..04d9147 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,58 @@ +set(TARGET_NAME CodeAstraApp) +set(EXECUTABLE_NAME CodeAstra) + +# Source files +set(SOURCES + MainWindow.cpp + CodeEditor.cpp + Tree.cpp + FileManager.cpp + Syntax.cpp + SyntaxManager.cpp +) + +# Headers +set(HEADERS + ${CMAKE_SOURCE_DIR}/include/MainWindow.h + ${CMAKE_SOURCE_DIR}/include/CodeEditor.h + ${CMAKE_SOURCE_DIR}/include/Tree.h + ${CMAKE_SOURCE_DIR}/include/FileManager.h + ${CMAKE_SOURCE_DIR}/include/Syntax.h + ${CMAKE_SOURCE_DIR}/include/SyntaxManager.h + ${CMAKE_SOURCE_DIR}/include/LineNumberArea.h +) + +# Find yaml-cpp using CMake's package config +find_package(yaml-cpp REQUIRED) + +# Library +add_library(${TARGET_NAME} ${SOURCES} ${HEADERS}) +target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) + +# Link against the proper target +target_link_libraries(${TARGET_NAME} PRIVATE Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Widgets yaml-cpp::yaml-cpp) +set_target_properties(${TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) + +# Executable +add_executable(${EXECUTABLE_NAME} ${CMAKE_SOURCE_DIR}/src/main.cpp) +target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${TARGET_NAME} Qt6::Core Qt6::Widgets) +target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) + +# Resources +qt_add_resources(APP_RESOURCES ${CMAKE_SOURCE_DIR}/resources.qrc) +target_sources(${EXECUTABLE_NAME} PRIVATE ${APP_RESOURCES}) + +# OS-specific flags +if(MSVC) + target_compile_options(${EXECUTABLE_NAME} PRIVATE /W4 /WX /analyze /sdl /guard:cf) +elseif(APPLE OR UNIX) + target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wshadow -Wconversion -Wsign-conversion -fsanitize=address,undefined -fstack-protector) + target_link_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=address,undefined) +endif() + +# Copy config files +file(GLOB YAML_FILES "${CMAKE_SOURCE_DIR}/config/*.yaml") +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/config) +foreach(YAML_FILE ${YAML_FILES}) + configure_file(${YAML_FILE} ${CMAKE_BINARY_DIR}/config/ COPYONLY) +endforeach() From fe46d87a3a27897bae609130d60129581f679a8e Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:27:31 -0700 Subject: [PATCH 158/160] [Refactor] refactor CMakeLists.txt for test executables and library linking --- tests/CMakeLists.txt | 66 +++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b1cd9dd..1f00856 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,47 +1,27 @@ -# Enable testing enable_testing() -find_package(yaml-cpp REQUIRED) -# Add individual test executables -add_executable(test_mainwindow test_mainwindow.cpp) -add_executable(test_tree test_tree.cpp) -add_executable(test_syntax test_syntax.cpp) - -# Make sure the test executables link with the necessary libraries -target_link_libraries(test_mainwindow PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test yaml-cpp) -target_link_libraries(test_tree PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test yaml-cpp) -target_link_libraries(test_syntax PRIVATE ${TARGET_NAME} Qt6::Widgets Qt6::Test yaml-cpp) - -# Register each test with CTest -add_test(NAME test_mainwindow COMMAND test_mainwindow) -add_test(NAME test_tree COMMAND test_tree) -add_test(NAME test_syntax COMMAND test_tree) - -# Set the runtime output directory for the test executables -set_target_properties(test_mainwindow PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests -) -set_target_properties(test_tree PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests -) -set_target_properties(test_syntax PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests -) -set_property(SOURCE test_mainwindow.cpp PROPERTY SKIP_AUTOMOC OFF) -set_property(SOURCE test_tree.cpp PROPERTY SKIP_AUTOMOC OFF) -set_property(SOURCE test_syntax.cpp PROPERTY SKIP_AUTOMOC OFF) +find_package(yaml-cpp REQUIRED CONFIG) -# Include directories for tests -target_include_directories(test_mainwindow PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_include_directories(test_tree PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_include_directories(test_syntax PRIVATE ${CMAKE_SOURCE_DIR}/include) - -# Ensure proper linking directories are set for yaml-cpp -target_include_directories(test_mainwindow PRIVATE /opt/homebrew/include) -target_include_directories(test_tree PRIVATE /opt/homebrew/include) -target_include_directories(test_syntax PRIVATE /opt/homebrew/include) - -target_link_directories(test_mainwindow PRIVATE /opt/homebrew/lib) -target_link_directories(test_tree PRIVATE /opt/homebrew/lib) -target_link_directories(test_syntax PRIVATE /opt/homebrew/lib) +# Add test executables +add_executable(test_mainwindow test_mainwindow.cpp) +add_executable(test_filemanager test_filemanager.cpp) +add_executable(test_syntax test_syntax.cpp) +# Link libraries +foreach(test_target IN ITEMS test_mainwindow test_filemanager test_syntax) + target_link_libraries(${test_target} PRIVATE + ${EXECUTABLE_NAME} + Qt6::Widgets + Qt6::Test + yaml-cpp::yaml-cpp + ) + target_include_directories(${test_target} PRIVATE + ${CMAKE_SOURCE_DIR}/include + ) + set_target_properties(${test_target} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/build/tests + ) + set_property(SOURCE ${test_target}.cpp PROPERTY SKIP_AUTOMOC OFF) + + add_test(NAME ${test_target} COMMAND ${test_target}) +endforeach() From 614f331babba60a22da6c2fc2c9db939e3143e9c Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 22:46:24 -0700 Subject: [PATCH 159/160] [Refactor] Improve test cases in TestFileManager by utilizing QTemporaryDir for file and directory operations --- tests/test_filemanager.cpp | 114 ++++++++++++++----------------------- 1 file changed, 42 insertions(+), 72 deletions(-) diff --git a/tests/test_filemanager.cpp b/tests/test_filemanager.cpp index c34bb0d..95ec042 100644 --- a/tests/test_filemanager.cpp +++ b/tests/test_filemanager.cpp @@ -56,35 +56,32 @@ void TestFileManager::testOpenFile_invalid() void TestFileManager::testRenamePath() { - QString originalFileName = QDir::temp().filePath("testFile.cpp"); - QString newFileName = QDir::temp().filePath("renamedTestFile.cpp"); + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), "Temporary directory should be valid."); - QFile originalFile(originalFileName); - if (!originalFile.exists() && !originalFile.open(QIODevice::WriteOnly)) - { - QFAIL("Failed to create test file."); - } - originalFile.close(); + QString originalFilePath = tempDir.path() + "/testFile.cpp"; + QFile file(originalFilePath); + QVERIFY2(file.open(QIODevice::WriteOnly), "File should be created successfully."); + file.write("// test content"); + file.close(); - OperationResult fileRenamed = FileManager::getInstance().renamePath(QFileInfo(originalFileName), newFileName); + QString newFilePath = tempDir.path() + "/renamedTestFile.cpp"; + OperationResult fileRenamed = FileManager::getInstance().renamePath(QFileInfo(originalFilePath), newFilePath); - QVERIFY2(fileRenamed.success, "File should be renamed successfully."); - QVERIFY2(QFile::exists(newFileName), "Renamed file should exist."); - QVERIFY2(!QFile::exists(originalFileName), "Original file should no longer exist."); - - QFile::remove(newFileName); + QVERIFY2(fileRenamed.success, fileRenamed.message.c_str()); + QVERIFY2(QFile::exists(newFilePath), "Renamed file should exist."); + QVERIFY2(!QFile::exists(originalFilePath), "Original file should no longer exist."); } void TestFileManager::testDeleteFile() { - QString tempFilePath = QDir::temp().filePath("testDeleteFile.cpp"); - QFile tempFile(tempFilePath); - if (!tempFile.open(QIODevice::WriteOnly)) - { - QFAIL("Failed to create temporary test file for deletion."); - } - tempFile.write("// test content"); - tempFile.close(); + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), "Temporary directory should be valid."); + + QString tempFilePath = tempDir.path() + "/testDeleteFile.cpp"; + QFile file(tempFilePath); + QVERIFY2(file.open(QIODevice::WriteOnly), "Temporary file should be created."); + file.close(); QVERIFY2(QFile::exists(tempFilePath), "Temporary file should exist before deletion."); @@ -101,98 +98,71 @@ void TestFileManager::testDeleteFile() void TestFileManager::testDeleteDir() { - QString directory = QDir::temp().absolutePath() + "/testDeleteDir"; - QDir tempDir(directory); - if (!tempDir.exists() && !tempDir.mkpath(".")) - { - QFAIL("Failed to create test directory."); - } + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), "Temporary directory should be valid."); + + QString dirPath = tempDir.path() + "/testDeleteDir"; + QDir().mkdir(dirPath); - QVERIFY2(QFile::exists(directory), "Test directory should exist before deletion."); + QVERIFY2(QFileInfo(dirPath).exists(), "Test directory should exist before deletion."); QFileSystemModel *model = tree->getModel(); QVERIFY2(model, "Tree model should not be null."); - QModelIndex index = model->index(directory); + QModelIndex index = model->index(dirPath); QVERIFY2(index.isValid(), "Model index should be valid for the test directory."); FileManager::getInstance().deletePath(QFileInfo(model->filePath(index))); - QVERIFY2(!QFile::exists(directory), "Directory should be deleted."); + QVERIFY2(!QFile::exists(dirPath), "Directory should be deleted."); } void TestFileManager::testNewFile() { - QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; - QDir dir(folderPath); - if (!dir.exists()) - { - dir.mkpath("."); - } - QVERIFY2(QFile::exists(folderPath), "Temporary directory should exist."); + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), "Temporary directory should be valid."); + QString folderPath = tempDir.path(); OperationResult fileCreated = FileManager::getInstance().newFile(QFileInfo(folderPath), "newFileTest1.c"); QVERIFY2(fileCreated.success, "New file should be created."); QVERIFY2(QFile::exists(folderPath + "/newFileTest1.c"), "Newly created file should exist."); - - QFile::remove(folderPath + "/newFileTest1.c"); - QVERIFY2(!QFile::exists(folderPath + "/newFileTest1.c"), "Newly created file should be deleted."); - - dir.removeRecursively(); - QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } void TestFileManager::testNewFolder() { - QString folderPath = QDir::temp().absolutePath() + "/testNewDir"; - QDir dir(folderPath); - if (!dir.exists()) - { - dir.mkpath("."); - } - QVERIFY2(QFile::exists(folderPath), "Temporary directory should exist."); + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), "Temporary directory should be valid."); + QString folderPath = tempDir.path(); OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), "newDirTest"); QVERIFY2(folderCreated.success, "New folder should be created."); QVERIFY2(QFile::exists(folderPath + "/newDirTest"), "Newly created folder should exist."); - - dir.removeRecursively(); - QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } void TestFileManager::testNewFolderFail() { - QString folderPath = QDir::temp().absolutePath() + "../testNewDir"; + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), "Temporary directory should be valid."); + + QString folderPath = tempDir.path(); OperationResult folderCreated = FileManager::getInstance().newFolder(QFileInfo(folderPath), ""); QVERIFY2(!folderCreated.success, "Folder creation should fail."); - - QDir(folderPath).removeRecursively(); - QVERIFY2(!QDir(folderPath).exists(), "Directory should not exist after deletion."); } void TestFileManager::testDuplicatePath() { - QString basePath = QDir::temp().absolutePath() + "/testDuplicateDir"; - QDir().mkpath(basePath); + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), "Temporary directory should be valid."); + + QString basePath = tempDir.path() + "/testDuplicateDir"; + QDir().mkdir(basePath); OperationResult pathDuplicated = FileManager::getInstance().duplicatePath(QFileInfo(basePath)); QVERIFY2(pathDuplicated.success, "Path should be duplicated successfully."); - - QDir tempDir(QDir::tempPath()); - QStringList entries = tempDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const QString &entry : entries) - { - if (entry.startsWith("testDuplicateDir_copy")) - { - QDir(tempDir.absoluteFilePath(entry)).removeRecursively(); - } - } - - QDir(basePath).removeRecursively(); } QTEST_MAIN(TestFileManager) From 46d2d4152d890bad7f22ae30849413517dcbe4cc Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 22:54:33 -0700 Subject: [PATCH 160/160] [Refactor] Removed whitespace --- tests/test_filemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_filemanager.cpp b/tests/test_filemanager.cpp index 95ec042..9e87e9f 100644 --- a/tests/test_filemanager.cpp +++ b/tests/test_filemanager.cpp @@ -50,7 +50,7 @@ void TestFileManager::testOpenFile_invalid() QModelIndex index; tree->openFile(index); - QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), + QVERIFY2(FileManager::getInstance().getCurrentFileName().isEmpty(), "FileManager should not process an invalid file."); }