From 07af27e82c01643ec764885aae1389c0ca7797e4 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Thu, 8 Oct 2026 10:19:31 +0200 Subject: [PATCH] Project: drop redundant explicit folder-as-project machinery Kate's project plugin already opens folders and shows a file tree, and we now build on it via KateProjectBridge, so the parallel Ollie-side model was redundant. Remove the Alt+O 'Open Folder as Project' action, the sticky m_projectRoot + its $XDG_CONFIG_HOME/olliepalette/project persistence (load/save), 'Close Project', and the ollie:project:* palette entries/handlers. currentProjectRoot() is now two-tier: bridge baseDir -> VCS discovery. Frees Alt+O. 12 ctest suites still pass. --- docs/PLAN.md | 14 ++--- src/plugin/ollieplugin.cpp | 112 ++----------------------------------- src/plugin/ollieplugin.h | 10 +--- 3 files changed, 12 insertions(+), 124 deletions(-) diff --git a/docs/PLAN.md b/docs/PLAN.md index c457f9d..2bff0fb 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -211,13 +211,9 @@ Therefore the palette is replaced, not extended, and the replacement is built on existing explicit pick is honoured **exactly** (no walk-up — VSCode/Sublime "open folder" semantics); otherwise it derives via `findRoot(startDir)` with cwd as the final fallback. A stale explicit root reverts to discovery. - - Explicit folder-as-project (our own model; we build **on top of** Kate's - project plugin rather than replacing it): **Alt+O** "Open Folder as Project…" - pops a directory chooser and makes the pick the **sticky** root. It persists - to `$XDG_CONFIG_HOME/olliepalette/project` and is restored on load (only if - it still exists). "Close Project" (palette `ollie:project:close`) clears it - and reverts to auto-derive. Both are in the M-x palette - (`ollie:project:open` / `:close`), the latter shown only when open. + - Explicit folder-as-project is **not** reimplemented: Kate's project plugin + already opens folders and shows a file tree, so we build on it rather than + duplicate it (see `KateProjectBridge` below). - `KateProjectBridge` (`src/project/kateprojectbridge.{h,cpp}`, in `project_lib`, 7 unit tests) reads Kate's built-in `kateprojectplugin` view through the **meta-object system** — `projectBaseDir` / `projectName` / @@ -227,8 +223,8 @@ Therefore the palette is replaced, not extended, and the replacement is built on attaches it on construction and follows `MainWindow::pluginViewCreated` / `pluginViewDeleted`. Tested headlessly against a stand-in QObject that mirrors the plugin's property/signal surface. - - `currentProjectRoot()` is a **three-tier** resolution: explicit sticky - folder → `KateProjectBridge::baseDir()` (Kate's loaded project) → + - `currentProjectRoot()` is a **two-tier** resolution: + `KateProjectBridge::baseDir()` (Kate's loaded project) → `ProjectIndex::resolveRoot("", activeDocDir)` (VCS discovery, cwd fallback). - `listFiles(root)` prefers `git ls-files --cached --others --exclude-standard` inside a git work tree (honours .gitignore, includes diff --git a/src/plugin/ollieplugin.cpp b/src/plugin/ollieplugin.cpp index 701e352..813c696 100644 --- a/src/plugin/ollieplugin.cpp +++ b/src/plugin/ollieplugin.cpp @@ -28,8 +28,6 @@ #include #include #include -#include -#include #include #include #include @@ -263,26 +261,9 @@ OllieView::OllieView(OlliePlugin *plugin, KTextEditor::MainWindow *mainWindow) w->addAction(gotoSymbolAction); } - // Open a folder as the sticky project root: Alt+O ("Open"). Our own - // folder-as-project model — we do not use Kate's project plugin. The chosen - // folder is honoured exactly and persists across sessions; the Go-to-File - // and Go-to-Symbol switchers are then scoped to it regardless of which file - // is focused. - auto *openProjectAction = new QAction(i18n("Open Folder as Project…"), this); - openProjectAction->setObjectName(QStringLiteral("ollie_open_project")); - openProjectAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_O)); - openProjectAction->setShortcutContext(Qt::ApplicationShortcut); - connect(openProjectAction, &QAction::triggered, this, &OllieView::openProjectFolder); - if (QWidget *w = m_mainWindow->window()) { - w->addAction(openProjectAction); - } - - // Restore the previously opened project folder (if any, and still present). - loadProjectRoot(); - // Build on Kate's project plugin: attach to its view now (if present) and - // follow it as plugins come and go. The bridge is read-only; our explicit - // folder still takes precedence in currentProjectRoot(). + // follow it as plugins come and go. The bridge is read-only; the switchers + // scope to its project when one is loaded, else to VCS discovery. { static const QString kProjectPlugin = QStringLiteral("kateprojectplugin"); m_projectBridge.setProjectView(m_mainWindow->pluginView(kProjectPlugin)); @@ -485,23 +466,6 @@ void OllieView::showCommandPalette() items.push_back(item); } - // Project management: open a folder as the project, or close it (revert to - // auto-deriving the root from the active document). - { - PaletteItem item; - item.id = QStringLiteral("ollie:project:open"); - item.label = i18n("Open Folder as Project…"); - item.group = QStringLiteral("Alt+O"); - items.push_back(item); - } - if (!m_projectRoot.isEmpty()) { - PaletteItem item; - item.id = QStringLiteral("ollie:project:close"); - item.label = i18n("Close Project (%1)", QDir(m_projectRoot).dirName()); - item.group = m_projectRoot; - items.push_back(item); - } - // Apply the persisted frecency bonus so habitual commands rank higher. const qint64 now = QDateTime::currentSecsSinceEpoch(); for (PaletteItem &item : items) { @@ -537,12 +501,9 @@ QString OllieView::activeDocumentDir() const QString OllieView::currentProjectRoot() const { - // Precedence: an explicit folder the user opened (sticky) wins; then Kate's - // project plugin (if a project is loaded); then discovery from the active - // document, with cwd as the final fallback inside resolveRoot(). - if (!m_projectRoot.isEmpty() && QDir(m_projectRoot).exists()) { - return QDir(m_projectRoot).absolutePath(); - } + // Precedence: Kate's project plugin (if a project is loaded) wins; else + // discovery from the active document, with cwd as the final fallback + // inside resolveRoot(). const QString bridgeDir = m_projectBridge.baseDir(); if (!bridgeDir.isEmpty() && QDir(bridgeDir).exists()) { return QDir(bridgeDir).absolutePath(); @@ -550,59 +511,6 @@ QString OllieView::currentProjectRoot() const return ProjectIndex::resolveRoot(QString(), activeDocumentDir()); } -void OllieView::openProjectFolder() -{ - // Start the chooser at the current effective root so re-picking a nearby - // folder is quick. - const QString start = currentProjectRoot(); - const QString dir = QFileDialog::getExistingDirectory( - m_mainWindow->window(), i18n("Open Folder as Project"), start, - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - if (dir.isEmpty()) { - return; // cancelled - } - m_projectRoot = QDir(dir).absolutePath(); - saveProjectRoot(); -} - -void OllieView::closeProjectFolder() -{ - m_projectRoot.clear(); - saveProjectRoot(); // writes an empty marker (removes the sticky root) -} - -void OllieView::loadProjectRoot() -{ - const QString cfgDir = - QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - const QString path = QDir(cfgDir).filePath(QStringLiteral("olliepalette/project")); - QFile f(path); - if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - return; - } - const QString saved = QString::fromUtf8(f.readAll()).trimmed(); - f.close(); - // Only restore a folder that still exists; a moved/deleted project silently - // reverts to auto-derive. - if (!saved.isEmpty() && QDir(saved).exists()) { - m_projectRoot = saved; - } -} - -void OllieView::saveProjectRoot() const -{ - const QString cfgDir = - QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - const QString path = QDir(cfgDir).filePath(QStringLiteral("olliepalette/project")); - QDir().mkpath(QFileInfo(path).absolutePath()); - QFile f(path); - if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - return; - } - f.write(m_projectRoot.toUtf8()); - f.close(); -} - void OllieView::runAction(const QString &actionId) { // Count every command-palette activation toward frecency (verbs, switcher @@ -633,16 +541,6 @@ void OllieView::runAction(const QString &actionId) return; } - // Project management. - if (actionId == QLatin1String("ollie:project:open")) { - openProjectFolder(); - return; - } - if (actionId == QLatin1String("ollie:project:close")) { - closeProjectFolder(); - return; - } - QPointer action = m_actionsById.value(actionId); if (action) { action->trigger(); diff --git a/src/plugin/ollieplugin.h b/src/plugin/ollieplugin.h index 5cd7b46..8cf2e66 100644 --- a/src/plugin/ollieplugin.h +++ b/src/plugin/ollieplugin.h @@ -67,22 +67,17 @@ private Q_SLOTS: void openProjectFile(const QString &relativePath); void showSymbolSwitcher(); void goToSymbol(const QString &locationId); - void openProjectFolder(); // pick a folder; make it the sticky project root - void closeProjectFolder(); // forget the explicit root (back to auto-derive) private: // Record one activation of \a id and persist. Feeds the frecency bonus so // habitual choices float up in every palette. void recordUsage(const QString &id); - // The effective project root: the explicitly opened folder when set, - // otherwise derived from the active document's directory (fallback cwd). + // The effective project root: Kate's loaded project (via the bridge) when + // available, otherwise derived from the active document's directory. QString currentProjectRoot() const; // Directory of the active document, or empty when none/untitled. QString activeDocumentDir() const; - // Persist / restore the explicit project root across sessions. - void loadProjectRoot(); - void saveProjectRoot() const; // One harvested, runnable action with the provenance needed to build a // unique id and to disambiguate labels shared by several commands. struct ActionEntry { @@ -139,7 +134,6 @@ private: bool m_swallowRightRelease = false; // swallow the release paired with a left+right paste chord QHash> m_actionsById; FrecencyStore m_frecency; // persisted usage feeding the palette ranking - QString m_projectRoot; // explicitly opened folder (empty = auto-derive) KateProjectBridge m_projectBridge; // read-only view of Kate's project plugin };