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.
This commit is contained in:
parent
f5b532c77d
commit
07af27e82c
14
docs/PLAN.md
14
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
|
existing explicit pick is honoured **exactly** (no walk-up — VSCode/Sublime
|
||||||
"open folder" semantics); otherwise it derives via `findRoot(startDir)` with
|
"open folder" semantics); otherwise it derives via `findRoot(startDir)` with
|
||||||
cwd as the final fallback. A stale explicit root reverts to discovery.
|
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
|
- Explicit folder-as-project is **not** reimplemented: Kate's project plugin
|
||||||
project plugin rather than replacing it): **Alt+O** "Open Folder as Project…"
|
already opens folders and shows a file tree, so we build on it rather than
|
||||||
pops a directory chooser and makes the pick the **sticky** root. It persists
|
duplicate it (see `KateProjectBridge` below).
|
||||||
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.
|
|
||||||
- `KateProjectBridge` (`src/project/kateprojectbridge.{h,cpp}`, in
|
- `KateProjectBridge` (`src/project/kateprojectbridge.{h,cpp}`, in
|
||||||
`project_lib`, 7 unit tests) reads Kate's built-in `kateprojectplugin` view
|
`project_lib`, 7 unit tests) reads Kate's built-in `kateprojectplugin` view
|
||||||
through the **meta-object system** — `projectBaseDir` / `projectName` /
|
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` /
|
attaches it on construction and follows `MainWindow::pluginViewCreated` /
|
||||||
`pluginViewDeleted`. Tested headlessly against a stand-in QObject that
|
`pluginViewDeleted`. Tested headlessly against a stand-in QObject that
|
||||||
mirrors the plugin's property/signal surface.
|
mirrors the plugin's property/signal surface.
|
||||||
- `currentProjectRoot()` is a **three-tier** resolution: explicit sticky
|
- `currentProjectRoot()` is a **two-tier** resolution:
|
||||||
folder → `KateProjectBridge::baseDir()` (Kate's loaded project) →
|
`KateProjectBridge::baseDir()` (Kate's loaded project) →
|
||||||
`ProjectIndex::resolveRoot("", activeDocDir)` (VCS discovery, cwd fallback).
|
`ProjectIndex::resolveRoot("", activeDocDir)` (VCS discovery, cwd fallback).
|
||||||
- `listFiles(root)` prefers `git ls-files --cached --others
|
- `listFiles(root)` prefers `git ls-files --cached --others
|
||||||
--exclude-standard` inside a git work tree (honours .gitignore, includes
|
--exclude-standard` inside a git work tree (honours .gitignore, includes
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
|
@ -263,26 +261,9 @@ OllieView::OllieView(OlliePlugin *plugin, KTextEditor::MainWindow *mainWindow)
|
||||||
w->addAction(gotoSymbolAction);
|
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
|
// 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
|
// follow it as plugins come and go. The bridge is read-only; the switchers
|
||||||
// folder still takes precedence in currentProjectRoot().
|
// scope to its project when one is loaded, else to VCS discovery.
|
||||||
{
|
{
|
||||||
static const QString kProjectPlugin = QStringLiteral("kateprojectplugin");
|
static const QString kProjectPlugin = QStringLiteral("kateprojectplugin");
|
||||||
m_projectBridge.setProjectView(m_mainWindow->pluginView(kProjectPlugin));
|
m_projectBridge.setProjectView(m_mainWindow->pluginView(kProjectPlugin));
|
||||||
|
|
@ -485,23 +466,6 @@ void OllieView::showCommandPalette()
|
||||||
items.push_back(item);
|
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.
|
// Apply the persisted frecency bonus so habitual commands rank higher.
|
||||||
const qint64 now = QDateTime::currentSecsSinceEpoch();
|
const qint64 now = QDateTime::currentSecsSinceEpoch();
|
||||||
for (PaletteItem &item : items) {
|
for (PaletteItem &item : items) {
|
||||||
|
|
@ -537,12 +501,9 @@ QString OllieView::activeDocumentDir() const
|
||||||
|
|
||||||
QString OllieView::currentProjectRoot() const
|
QString OllieView::currentProjectRoot() const
|
||||||
{
|
{
|
||||||
// Precedence: an explicit folder the user opened (sticky) wins; then Kate's
|
// Precedence: Kate's project plugin (if a project is loaded) wins; else
|
||||||
// project plugin (if a project is loaded); then discovery from the active
|
// discovery from the active document, with cwd as the final fallback
|
||||||
// document, with cwd as the final fallback inside resolveRoot().
|
// inside resolveRoot().
|
||||||
if (!m_projectRoot.isEmpty() && QDir(m_projectRoot).exists()) {
|
|
||||||
return QDir(m_projectRoot).absolutePath();
|
|
||||||
}
|
|
||||||
const QString bridgeDir = m_projectBridge.baseDir();
|
const QString bridgeDir = m_projectBridge.baseDir();
|
||||||
if (!bridgeDir.isEmpty() && QDir(bridgeDir).exists()) {
|
if (!bridgeDir.isEmpty() && QDir(bridgeDir).exists()) {
|
||||||
return QDir(bridgeDir).absolutePath();
|
return QDir(bridgeDir).absolutePath();
|
||||||
|
|
@ -550,59 +511,6 @@ QString OllieView::currentProjectRoot() const
|
||||||
return ProjectIndex::resolveRoot(QString(), activeDocumentDir());
|
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)
|
void OllieView::runAction(const QString &actionId)
|
||||||
{
|
{
|
||||||
// Count every command-palette activation toward frecency (verbs, switcher
|
// Count every command-palette activation toward frecency (verbs, switcher
|
||||||
|
|
@ -633,16 +541,6 @@ void OllieView::runAction(const QString &actionId)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project management.
|
|
||||||
if (actionId == QLatin1String("ollie:project:open")) {
|
|
||||||
openProjectFolder();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (actionId == QLatin1String("ollie:project:close")) {
|
|
||||||
closeProjectFolder();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointer<QAction> action = m_actionsById.value(actionId);
|
QPointer<QAction> action = m_actionsById.value(actionId);
|
||||||
if (action) {
|
if (action) {
|
||||||
action->trigger();
|
action->trigger();
|
||||||
|
|
|
||||||
|
|
@ -67,22 +67,17 @@ private Q_SLOTS:
|
||||||
void openProjectFile(const QString &relativePath);
|
void openProjectFile(const QString &relativePath);
|
||||||
void showSymbolSwitcher();
|
void showSymbolSwitcher();
|
||||||
void goToSymbol(const QString &locationId);
|
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:
|
private:
|
||||||
// Record one activation of \a id and persist. Feeds the frecency bonus so
|
// Record one activation of \a id and persist. Feeds the frecency bonus so
|
||||||
// habitual choices float up in every palette.
|
// habitual choices float up in every palette.
|
||||||
void recordUsage(const QString &id);
|
void recordUsage(const QString &id);
|
||||||
|
|
||||||
// The effective project root: the explicitly opened folder when set,
|
// The effective project root: Kate's loaded project (via the bridge) when
|
||||||
// otherwise derived from the active document's directory (fallback cwd).
|
// available, otherwise derived from the active document's directory.
|
||||||
QString currentProjectRoot() const;
|
QString currentProjectRoot() const;
|
||||||
// Directory of the active document, or empty when none/untitled.
|
// Directory of the active document, or empty when none/untitled.
|
||||||
QString activeDocumentDir() const;
|
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
|
// One harvested, runnable action with the provenance needed to build a
|
||||||
// unique id and to disambiguate labels shared by several commands.
|
// unique id and to disambiguate labels shared by several commands.
|
||||||
struct ActionEntry {
|
struct ActionEntry {
|
||||||
|
|
@ -139,7 +134,6 @@ private:
|
||||||
bool m_swallowRightRelease = false; // swallow the release paired with a left+right paste chord
|
bool m_swallowRightRelease = false; // swallow the release paired with a left+right paste chord
|
||||||
QHash<QString, QPointer<QAction>> m_actionsById;
|
QHash<QString, QPointer<QAction>> m_actionsById;
|
||||||
FrecencyStore m_frecency; // persisted usage feeding the palette ranking
|
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
|
KateProjectBridge m_projectBridge; // read-only view of Kate's project plugin
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue