From 94f9c09189674679ae9876f877cf5cbd13f60975 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 30 Mar 2025 19:27:53 -0700 Subject: [PATCH 01/35] 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 02/35] 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 03/35] 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 04/35] 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 05/35] [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 06/35] [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 07/35] [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 08/35] [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 09/35] [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 10/35] [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 11/35] [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 12/35] [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 13/35] [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 14/35] [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 15/35] [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 16/35] [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 17/35] [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 18/35] [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 19/35] [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 20/35] [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 21/35] [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 22/35] [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 23/35] [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 24/35] [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 b09ed0ad269826b9c5ed64fac7fc90de2a25cdff Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 16 Apr 2025 21:13:45 -0700 Subject: [PATCH 25/35] [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 26/35] [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 27/35] [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 28/35] [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 29/35] [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 30/35] [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 31/35] [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 32/35] [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 33/35] [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 34/35] [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 35/35] [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."); }