From a46b4c58c1922e39bea10341dfad6e584a676a66 Mon Sep 17 00:00:00 2001 From: John Till Date: Sat, 16 Sep 2023 18:44:26 +0100 Subject: [PATCH] Recommend directories Fix state on new project --- .gitignore | 1 + app/mainwindow.cpp | 12 +- app/projectbrowser.cpp | 109 ++++++++++++++---- app/projectbrowser.h | 10 +- src/forscape_program.cpp | 18 ++- src/typeset_model.h | 1 + src/typeset_view.cpp | 13 ++- test/code_interpreter.h | 15 +++ .../in/hello_world_import_dir_up.\317\200" | 2 + .../out/hello_world_import_abs_path.\317\200" | 1 + .../out/hello_world_import_dir_up.\317\200" | 1 + test/test_suggestions.h | 12 +- 12 files changed, 149 insertions(+), 46 deletions(-) create mode 100644 "test/interpreter_scripts/in/hello_world_import_dir_up.\317\200" create mode 100644 "test/interpreter_scripts/out/hello_world_import_abs_path.\317\200" create mode 100644 "test/interpreter_scripts/out/hello_world_import_dir_up.\317\200" diff --git a/.gitignore b/.gitignore index c975984c..29879de9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ src/generated/* *.dll **/config/lambda.ico **/com.automath.forscape/meta/LICENSE +**/in/hello_world_import_abs_path.π #Qt *.pro.user* diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 949a6109..37cfe29a 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -457,12 +457,15 @@ void MainWindow::onKeyboardNew() { dialog.setWindowTitle("Creation Prompt"); dialog.setLabelText(tr("Create new...")); dialog.setComboBoxItems({ - "Project", - "File", + "Project (" + ui->actionNew_Project->shortcut().toString() + ")", + "File (" + ui->actionNew->shortcut().toString() + ")", }); + const QComboBox* const combo_box = dialog.findChild(); + assert( combo_box != nullptr ); + if(dialog.exec() != QDialog::Accepted) return; - switch( dialog.findChild()->currentIndex() ){ + switch( combo_box->currentIndex() ){ case 0: on_actionNew_Project_triggered(); break; case 1: on_actionNew_triggered(); break; default: assert(false); @@ -481,6 +484,7 @@ void MainWindow::on_actionNew_Project_triggered() { ui->actionSave->setEnabled(false); ui->actionSave_All->setEnabled(false); + ui->actionReload->setEnabled(false); console->clearModel(); @@ -512,7 +516,7 @@ void MainWindow::on_actionNew_triggered(){ Program::instance()->program_entry_point = model; setWindowTitle(NEW_SCRIPT_TITLE WINDOW_TITLE_SUFFIX); active_file_path.clear(); - project_browser->addFile(model); + project_browser->addUnsavedFile(model); viewModel(model, 0); } diff --git a/app/projectbrowser.cpp b/app/projectbrowser.cpp index 6e737b09..8c780543 100644 --- a/app/projectbrowser.cpp +++ b/app/projectbrowser.cpp @@ -7,19 +7,28 @@ #include #include +#include #include #include +#include #include #include #include #include -//PROJECT BROWSER UPDATE: the project view is unintuitive. Allow multiple open projects? +//PROJECT BROWSER UPDATE: switch to QTreeView/QFileSystemModel, or continue using QTreeWidget w/ std::filesystem? + +//PROJECT BROWSER UPDATE: a program should not be singleton //PROJECT BROWSER UPDATE: need assurance that the viewing history remains valid as files are deleted, renamed, w/e //PROJECT BROWSER UPDATE: the project browser should probably have an undo stack -//PROJECT BROWSER UPDATE: add copy path option -//PROJECT BROWSER UPDATE: import from absolute path is broken (should probably warn) -//PROJECT BROWSER UPDATE: import from folder up is broken +//PROJECT BROWSER UPDATE: finish implementing interaction actions +//PROJECT BROWSER UPDATE: show/hide unused files which are in a directory, but not imported anywhere +//PROJECT BROWSER UPDATE: fix save-as to rename files, projects +//PROJECT BROWSER UPDATE: allow multiple open projects, with one active project, and ability to close +//PROJECT BROWSER UPDATE: Parse file-level descriptions for use in tooltips, with full paths in tooltips +//PROJECT BROWSER UPDATE: scan for external changes +//PROJECT BROWSER UPDATE: limit external dependence on the project browser +//PROJECT BROWSER UPDATE: should support drag and drop Q_DECLARE_METATYPE(Forscape::Typeset::Model*); //EVENTUALLY: this is only for compability with old versions @@ -53,6 +62,7 @@ void ProjectBrowser::FileEntry::setModel(Forscape::Typeset::Model& m) noexcept { model = &m; path = m.path; setText(0, isSavedToDisk() ? toQString(path.filename()) : "untitled"); + if(isSavedToDisk()) setToolTip(0, toQString(path)); } bool ProjectBrowser::FileEntry::isSavedToDisk() const noexcept { @@ -91,6 +101,7 @@ class ProjectBrowser::DirectoryEntry : public QTreeWidgetItem { void setPath(const std::filesystem::path& p) { path = p; setText(0, toQString(p.filename())); + setToolTip(0, toQString(path)); } const std::filesystem::path& getPath() const noexcept { @@ -103,7 +114,6 @@ ProjectBrowser::ProjectBrowser(QWidget* parent, MainWindow* main_window) setHeaderHidden(true); setIndentation(10); setMinimumWidth(120); - //PROJECT BROWSER UPDATE: should only set this if there are no folders in the project setRootIsDecorated(false); //Hide a universal root entry, so the user has the illusion of multiple "top-level" entries setContextMenuPolicy(Qt::CustomContextMenu); //Needed to enable context menu @@ -122,9 +132,10 @@ void ProjectBrowser::setProject(Forscape::Typeset::Model* model) { FileEntry* main_file = new FileEntry(root); main_file->setIcon(0, main_icon); main_file->setModel(*model); - entries[std_path] = main_file; + files_in_filesystem[std_path] = main_file; + files_in_memory[model] = main_file; const std::filesystem::path parent_path = std_path.parent_path(); - entries[parent_path] = root; + files_in_filesystem[parent_path] = root; root->setData(0, Qt::UserRole, toQString(parent_path)); currently_viewed_file = main_file; QFont normal_font = font(); @@ -133,8 +144,9 @@ void ProjectBrowser::setProject(Forscape::Typeset::Model* model) { currently_viewed_file->setFont(0, bold_font); } -void ProjectBrowser::addFile(Forscape::Typeset::Model* model) { +void ProjectBrowser::addUnsavedFile(Forscape::Typeset::Model* model) { FileEntry* item = new FileEntry(invisibleRootItem()); + files_in_memory[model] = item; item->setText(0, "untitled"); item->setIcon(0, file_icon); item->model = model; @@ -149,15 +161,15 @@ void ProjectBrowser::saveModel(Forscape::Typeset::Model* saved_model, const std: if(rename_file){ //EVENTUALLY: this is a hacky solution to keep the old file, which is likely referenced in code Forscape::Program::instance()->source_files.erase(old_path); - entries.erase(old_path); + files_in_filesystem.erase(old_path); Forscape::Program::instance()->openFromAbsolutePath(old_path); } if(create_new_file || rename_file){ saved_model->path = std_path; - FileEntry* item = debug_cast(entries[std_path]); + FileEntry* item = debug_cast(files_in_filesystem[std_path]); item->setModel(*saved_model); - auto result = entries.insert({std_path, item}); + auto result = files_in_filesystem.insert({std_path, item}); if(!result.second){ //Saving over existing project file QTreeWidgetItem* overwritten = result.first->second; @@ -192,11 +204,12 @@ void ProjectBrowser::updateProjectBrowser() { void ProjectBrowser::addProjectEntry(Forscape::Typeset::Model* model) { std::filesystem::path path = model->path; - assert(entries.find(path) == entries.end()); + assert(files_in_filesystem.find(path) == files_in_filesystem.end()); FileEntry* tree_item = new FileEntry(); tree_item->setModel(*model); - entries[path] = tree_item; + files_in_filesystem[path] = tree_item; + files_in_memory[model] = tree_item; model->write_time = std::filesystem::file_time_type::clock::now(); linkFileToAncestor(tree_item, path); @@ -225,8 +238,8 @@ void ProjectBrowser::setCurrentlyViewed(Forscape::Typeset::Model* model) { if(currently_viewed_file) currently_viewed_file->setFont(0, QFont()); //Highlight the new item - const auto lookup = entries.find(model->path); - if(lookup == entries.end()) return; + const auto lookup = files_in_memory.find(model); + assert(lookup != files_in_memory.end()); currently_viewed_file = debug_cast(lookup->second); currently_viewed_file->setFont(0, bold_font); setCurrentItem(currently_viewed_file); @@ -234,7 +247,10 @@ void ProjectBrowser::setCurrentlyViewed(Forscape::Typeset::Model* model) { void ProjectBrowser::clear() noexcept { QTreeWidget::clear(); - entries.clear(); + files_in_filesystem.clear(); + files_in_memory.clear(); + + setRootIsDecorated(false); //Hide a universal root entry, so the user has the illusion of multiple "top-level" entries } void ProjectBrowser::onFileClicked(QTreeWidgetItem* item, int column) { @@ -266,7 +282,8 @@ void ProjectBrowser::onShowInExplorer() { void ProjectBrowser::onDeleteFile() { FileEntry& item = getSelectedFileEntry(); assert(item.model != Forscape::Program::instance()->program_entry_point); - entries.erase(item.path); + files_in_filesystem.erase(item.path); + files_in_memory.erase(item.model); main_window->removeFile(item.model); item.deleteFile(); @@ -311,6 +328,17 @@ void ProjectBrowser::onRightClick(const QPoint& pos) { QAction* rename_file = menu.addAction(tr("Rename")); rename_file->setToolTip(tr("Change the filename")); connect(rename_file, SIGNAL(triggered(bool)), this, SLOT(onFileRenamed())); + + menu.addSeparator(); + QAction* copy_name = menu.addAction(tr("Copy file name")); + copy_name->setToolTip(tr("Copy the file name to the clipboard")); + connect(copy_name, SIGNAL(triggered(bool)), this, SLOT(onCopyFileName())); + + if( file_item->isSavedToDisk() ){ + QAction* copy_full_path = menu.addAction(tr("Copy full path")); + copy_full_path->setToolTip(tr("Copy the full path to the clipboard")); + connect(copy_full_path, SIGNAL(triggered(bool)), this, SLOT(onCopyFilePath())); + } }else{ QAction* show_in_explorer = menu.addAction(tr("Show in Explorer")); show_in_explorer->setToolTip(tr("Show in the OS file browser")); @@ -322,6 +350,15 @@ void ProjectBrowser::onRightClick(const QPoint& pos) { QAction* expand = menu.addAction(item->isExpanded() ? tr("Collapse") : tr("Expand")); connect(expand, SIGNAL(triggered(bool)), this, SLOT(expandDirectory())); + + menu.addSeparator(); + QAction* copy_name = menu.addAction(tr("Copy directory name")); + copy_name->setToolTip(tr("Copy the directory name to the clipboard")); + connect(copy_name, SIGNAL(triggered(bool)), this, SLOT(onCopyDirName())); + + QAction* copy_full_path = menu.addAction(tr("Copy full path")); + copy_full_path->setToolTip(tr("Copy the full path to the clipboard")); + connect(copy_full_path, SIGNAL(triggered(bool)), this, SLOT(onCopyDirPath())); } menu.exec(mapToGlobal(pos)); @@ -392,6 +429,27 @@ void ProjectBrowser::expandDirectory() { currentItem()->setExpanded(!currentItem()->isExpanded()); } +void ProjectBrowser::onCopyFilePath() { + const FileEntry& entry = getSelectedFileEntry(); + if(!entry.isSavedToDisk()) return; + QGuiApplication::clipboard()->setText(toQString(entry.getPath())); +} + +void ProjectBrowser::onCopyFileName() { + const FileEntry& entry = getSelectedFileEntry(); + QGuiApplication::clipboard()->setText(entry.text(0)); +} + +void ProjectBrowser::onCopyDirPath() { + const DirectoryEntry& entry = getSelectedDirectory(); + QGuiApplication::clipboard()->setText(toQString(entry.getPath())); +} + +void ProjectBrowser::onCopyDirName() { + const DirectoryEntry& entry = getSelectedDirectory(); + QGuiApplication::clipboard()->setText(entry.text(0)); +} + ProjectBrowser::FileEntry& ProjectBrowser::getSelectedFileEntry() const noexcept { return *debug_cast(currentItem()); } @@ -409,8 +467,8 @@ void ProjectBrowser::linkFileToAncestor(FileEntry* file_item, const std::filesys std::filesystem::path folder_path = file_path.parent_path(); //Link the file to it's folder - auto result = entries.find(folder_path); - if(result != entries.end()){ + auto result = files_in_filesystem.find(folder_path); + if(result != files_in_filesystem.end()){ //Folder already exists QTreeWidgetItem* preexisting_folder_entry = result->second; preexisting_folder_entry->addChild(file_item); @@ -424,7 +482,7 @@ void ProjectBrowser::linkFileToAncestor(FileEntry* file_item, const std::filesys //Browser already showing different drives, do nothing }else if(stale_root_path.root_name() != file_path.root_name()){ //These files come from different drives; change root to nothing since no common ancestor - entries.erase(stale_root_path); + files_in_filesystem.erase(stale_root_path); root->setData(0, Qt::UserRole, QString()); taken_children = root->takeChildren(); }else{ @@ -449,8 +507,8 @@ void ProjectBrowser::linkFileToAncestor(FileEntry* file_item, const std::filesys } taken_children = root->takeChildren(); - entries.erase(stale_root_path); - entries[new_root_path] = root; + files_in_filesystem.erase(stale_root_path); + files_in_filesystem[new_root_path] = root; root->setData(0, Qt::UserRole, toQString(new_root_path)); } } @@ -460,7 +518,7 @@ void ProjectBrowser::linkFileToAncestor(FileEntry* file_item, const std::filesys if(!taken_children.empty()){ linkItemToExistingAncestor(taken_children.back(), stale_root_path / "fake"); taken_children.pop_back(); - QTreeWidgetItem* entry_for_stale_root = entries[stale_root_path]; + QTreeWidgetItem* entry_for_stale_root = files_in_filesystem[stale_root_path]; entry_for_stale_root->addChildren(taken_children); setCurrentItem(currently_viewed_file); } @@ -468,9 +526,10 @@ void ProjectBrowser::linkFileToAncestor(FileEntry* file_item, const std::filesys void ProjectBrowser::linkItemToExistingAncestor(QTreeWidgetItem* item, std::filesystem::path path) { path = path.parent_path(); - auto parent_result = entries.insert({path, nullptr}); + auto parent_result = files_in_filesystem.insert({path, nullptr}); while(parent_result.second){ DirectoryEntry* new_item = new DirectoryEntry; + setRootIsDecorated(true); //Effectively adding padding for expand/collapse arrows new_item->addChild(item); item = new_item; new_item->setPath(path); @@ -479,7 +538,7 @@ void ProjectBrowser::linkItemToExistingAncestor(QTreeWidgetItem* item, std::file auto parent_path = path.parent_path(); if(parent_path != path){ //has_parent_path() lies, causing an infinite loop path = parent_path; - parent_result = entries.insert({path, nullptr}); + parent_result = files_in_filesystem.insert({path, nullptr}); }else{ invisibleRootItem()->addChild(item); item->setText(0, toQString(*path.begin())); diff --git a/app/projectbrowser.h b/app/projectbrowser.h index 65ed5be8..01ad8251 100644 --- a/app/projectbrowser.h +++ b/app/projectbrowser.h @@ -16,8 +16,7 @@ class ProjectBrowser : public QTreeWidget { ProjectBrowser(QWidget* parent, MainWindow* main_window); void setProject(Forscape::Typeset::Model* model); void addProject(const std::filesystem::path& entry_point_path); - void addFile(const std::filesystem::path& file_path); - void addFile(Forscape::Typeset::Model* model); + void addUnsavedFile(Forscape::Typeset::Model* model); void saveModel(Forscape::Typeset::Model* saved_model, const std::filesystem::path& std_path); void updateProjectBrowser(); void addProjectEntry(Forscape::Typeset::Model* model); @@ -34,6 +33,10 @@ private slots: void onDirectoryRenamed(); void onFileRenamed(); void expandDirectory(); + void onCopyFilePath(); + void onCopyFileName(); + void onCopyDirPath(); + void onCopyDirName(); private: class FileEntry; @@ -43,7 +46,8 @@ private slots: void linkFileToAncestor(FileEntry* file_item, const std::filesystem::path file_path); void linkItemToExistingAncestor(QTreeWidgetItem* item, std::filesystem::path path); - FORSCAPE_UNORDERED_MAP entries; + FORSCAPE_UNORDERED_MAP files_in_filesystem; + FORSCAPE_UNORDERED_MAP files_in_memory; FileEntry* currently_viewed_file = nullptr; //Used to give affordance MainWindow* main_window; }; diff --git a/src/forscape_program.cpp b/src/forscape_program.cpp index 331999e3..fbdeee21 100644 --- a/src/forscape_program.cpp +++ b/src/forscape_program.cpp @@ -151,8 +151,9 @@ void Program::getFileSuggestions(std::vector& suggestions, Typeset: if(std::filesystem::exists(path_entry)){ for(auto const& dir_entry : std::filesystem::directory_iterator{path_entry}){ const std::string candidate = dir_entry.path().filename().u8string(); - if(notPiFile(candidate) && candidate != model_filename) continue; - suggestions.push_back(candidate); + const bool is_candidate_dir = dir_entry.is_directory(); + if((notPiFile(candidate) && !is_candidate_dir) || (candidate == model_filename)) continue; + suggestions.push_back(candidate + (is_candidate_dir ? "/" : "")); } } } @@ -171,13 +172,18 @@ void Program::getFileSuggestions(std::vector& suggestions, std::str for(const auto& dir_entry : std::filesystem::directory_iterator{dir_of_input}){ const std::filesystem::path candidate_path = dir_entry.path(); const std::string candidate_filename = candidate_path.filename().u8string(); + const bool is_candidate_dir = dir_entry.is_directory(); //EVENTUALLY: doesn't work with symbolic links to directories - if(candidate_filename.size() < input_filename.size() || - notPiFile(candidate_filename) || - std::string_view(candidate_filename.data(), input_filename.size()) != input_filename) continue; + if(candidate_filename.size() < input_filename.size() || //Abort early; search term is larger than candidate + (notPiFile(candidate_filename) && !is_candidate_dir) || //Not the right file type + std::string_view(candidate_filename.data(), input_filename.size()) != input_filename //Doesn't match search term + ) continue; std::filesystem::path rel_path = std::filesystem::relative(candidate_path, path_entry); - if(rel_path != model_rel_path) suggestions.push_back(rel_path.u8string()); + if( rel_path == "." || //Do not suggest same directory, e.g. "../dir_we_came_from/" resolves to "./" + rel_path == model_rel_path //Do not suggest self import + ) continue; + suggestions.push_back(rel_path.u8string() + (is_candidate_dir ? "/" : "")); } } } diff --git a/src/typeset_model.h b/src/typeset_model.h index 18d8812d..5b4f72b3 100644 --- a/src/typeset_model.h +++ b/src/typeset_model.h @@ -38,6 +38,7 @@ class Model { std::string error_warning_buffer; std::filesystem::path path; bool notOnDisk() const noexcept { return path.empty(); } + bool isSavedToDisk() const noexcept { return !notOnDisk(); } #ifdef QT_VERSION std::filesystem::file_time_type write_time; #endif diff --git a/src/typeset_view.cpp b/src/typeset_view.cpp index a6e93ba1..f31a1219 100644 --- a/src/typeset_view.cpp +++ b/src/typeset_view.cpp @@ -1427,11 +1427,10 @@ void Recommender::take() noexcept { editor->getController().anchor.index -= recommend_typeset_phrase_size; editor->insertSerial(lookup); editor->updateModel(); + hide(); }else{ editor->takeRecommendation(controller.selectedText()); } - - hide(); } void Recommender::sizeToFit() { @@ -2034,6 +2033,8 @@ void Editor::suggestModuleFields(const Code::Error& err) { } void Editor::takeRecommendation(const std::string& str){ + bool recommend_again = false; + if(recommend_without_hint){ if(controller.charLeft() != ' ') controller.insertSerial(' ' + str); @@ -2042,6 +2043,7 @@ void Editor::takeRecommendation(const std::string& str){ }else if(filename_start){ controller.anchor = *filename_start; controller.insertSerial(str); + recommend_again = (str[str.size()-1] == '/'); }else{ //EVENTUALLY: mechanism for deciding when to erase the recommender candidate is janky std::string copy = str; @@ -2057,8 +2059,6 @@ void Editor::takeRecommendation(const std::string& str){ model->performSemanticFormatting(); updateXSetpoint(); updateModel(); - recommender->hide(); - setFocus(); ensureCursorVisible(); updateHighlightingFromCursorLocation(); @@ -2067,6 +2067,11 @@ void Editor::takeRecommendation(const std::string& str){ qApp->processEvents(); emit textChanged(); + + if(!recommend_again){ + recommender->hide(); + setFocus(); + } else recommend(); } void Tooltip::leaveEvent(QEvent* event) { diff --git a/test/code_interpreter.h b/test/code_interpreter.h index 61d78e90..a812d3ef 100644 --- a/test/code_interpreter.h +++ b/test/code_interpreter.h @@ -19,6 +19,20 @@ using std::filesystem::directory_iterator; using namespace Forscape; using namespace Code; +inline void writeAbsoluteImportTest() { + const std::string import_filename = BASE_TEST_DIR "/in/hello_world.π"; + const std::filesystem::path import_abs_path = std::filesystem::canonical(std::filesystem::u8path(import_filename)); + const std::string test_filename = BASE_TEST_DIR "/in/hello_world_import_abs_path.π"; + const std::filesystem::path test_path = std::filesystem::u8path(test_filename); + + std::ofstream ofs(test_path); + ofs << "import " << import_abs_path.u8string() << "\n" + "hello_world.helloWorld()"; + ofs.close(); + + assert(std::filesystem::is_regular_file(test_path)); +} + inline bool testExpression(const std::string& in, const std::string& expect){ Typeset::Model* input = Typeset::Model::fromSerial("print(" + in + ")"); Forscape::Program::instance()->setProgramEntryPoint("", input); @@ -97,6 +111,7 @@ inline bool testInterpreter(){ passing &= testExpression("2^2", "4"); passing &= testExpression("4^0.5", "2"); + writeAbsoluteImportTest(); for(directory_iterator end, dir(BASE_TEST_DIR "/in"); dir != end; dir++) if(std::filesystem::is_regular_file(dir->path())) passing &= testCase(dir->path().stem().string()); diff --git "a/test/interpreter_scripts/in/hello_world_import_dir_up.\317\200" "b/test/interpreter_scripts/in/hello_world_import_dir_up.\317\200" new file mode 100644 index 00000000..bd16148e --- /dev/null +++ "b/test/interpreter_scripts/in/hello_world_import_dir_up.\317\200" @@ -0,0 +1,2 @@ +import ..\in\hello_world.π +hello_world.helloWorld() \ No newline at end of file diff --git "a/test/interpreter_scripts/out/hello_world_import_abs_path.\317\200" "b/test/interpreter_scripts/out/hello_world_import_abs_path.\317\200" new file mode 100644 index 00000000..5abd6747 --- /dev/null +++ "b/test/interpreter_scripts/out/hello_world_import_abs_path.\317\200" @@ -0,0 +1 @@ +Hello world!Hello world! \ No newline at end of file diff --git "a/test/interpreter_scripts/out/hello_world_import_dir_up.\317\200" "b/test/interpreter_scripts/out/hello_world_import_dir_up.\317\200" new file mode 100644 index 00000000..5abd6747 --- /dev/null +++ "b/test/interpreter_scripts/out/hello_world_import_dir_up.\317\200" @@ -0,0 +1 @@ +Hello world!Hello world! \ No newline at end of file diff --git a/test/test_suggestions.h b/test/test_suggestions.h index 9ed6b81b..4e977cdf 100644 --- a/test/test_suggestions.h +++ b/test/test_suggestions.h @@ -92,7 +92,7 @@ inline bool testFileSuggestions(){ std::vector local_files; for(directory_iterator end, dir(BASE_TEST_DIR "/in"); dir != end; dir++) - if(std::filesystem::is_regular_file(dir->path())) + if(std::filesystem::is_regular_file(dir->path()) && dir->path() != mocked_file) local_files.push_back(dir->path().filename().u8string()); std::sort(local_files.begin(), local_files.end()); @@ -119,9 +119,13 @@ inline bool testFileSuggestions(){ #endif std::vector parent_files; - for(directory_iterator end, dir(BASE_TEST_DIR); dir != end; dir++) - if(std::filesystem::is_regular_file(dir->path()) && dir->path() != mocked_file) - parent_files.push_back(".." SLASH + dir->path().filename().u8string()); + for(directory_iterator end, dir(BASE_TEST_DIR); dir != end; dir++){ + if(dir->path() == mocked_file) continue; //Don't expect self-import suggestion + if(dir->path().filename() == "in") continue; //Don't suggest return to same directory + if(dir->is_symlink()) continue; //Current implementation doesn't support symlinks + const std::string suffix = (std::filesystem::is_regular_file(dir->path()) ? "" : "/"); + parent_files.push_back(".." SLASH + dir->path().filename().u8string() + suffix); + } std::sort(parent_files.begin(), parent_files.end()); if(view->suggestions != parent_files){