Commands: register verbs on the ':' command line (fix name collision)
Root cause: the KTextEditor command registry aborts registering a Command object entirely if ANY of its names collides with an existing command. Our list led with 'sort', a Kate built-in, so the collision silently dropped ALL our verbs from queryCommand/commandList — which is why ':' never saw them (the palette worked only because it calls runVerb directly). Fix: register the sort verb as 'osort'. runVerb/help accept both 'sort' and 'osort', so the palette is unchanged; the ':' line now resolves every verb (osort/rsort/case/b64enc/b64dec/rot13/uuid/pipe) to our object. Add test_ollieregistration asserting all verbs register at the global editor and guarding against reclaiming 'sort'. Palette verb hints updated to 'osort'.
This commit is contained in:
parent
219ad8e56c
commit
5b31bf6e40
22
docs/PLAN.md
22
docs/PLAN.md
|
|
@ -178,21 +178,29 @@ Therefore the palette is replaced, not extended, and the replacement is built on
|
||||||
consuming it — no registered shortcut, so no ambiguity, no per-view sweeping.
|
consuming it — no registered shortcut, so no ambiguity, no per-view sweeping.
|
||||||
|
|
||||||
### M5 — Command vocabulary + project model + switchers — IN PROGRESS
|
### M5 — Command vocabulary + project model + switchers — IN PROGRESS
|
||||||
- `:`-verb pack — DONE (palette path). `src/commands/`: `texttransforms.{h,cpp}`
|
- `:`-verb pack — DONE (palette **and** `:` command line). `src/commands/`:
|
||||||
(pure, unit-tested: sort/rsort, case upper|lower|title|snake|camel|kebab,
|
`texttransforms.{h,cpp}` (pure, unit-tested: sort/rsort, case
|
||||||
base64 enc/dec, rot13, uuid) + `olliecommands.{h,cpp}`. The verb logic lives in
|
upper|lower|title|snake|camel|kebab, base64 enc/dec, rot13, uuid) +
|
||||||
|
`olliecommands.{h,cpp}`. The verb logic lives in
|
||||||
`OllieCommands::runVerb(view, cmd, msg)` and transforms the selection in place
|
`OllieCommands::runVerb(view, cmd, msg)` and transforms the selection in place
|
||||||
(or the whole document when nothing is selected); `pipe <shell>` runs
|
(or the whole document when nothing is selected); `pipe <shell>` runs
|
||||||
`sh -c <shell>` with the selection on stdin.
|
`sh -c <shell>` with the selection on stdin.
|
||||||
- Verbs are invoked from the **M-x palette** (entries prefixed `ollie:cmd:`,
|
- Verbs are invoked from the **M-x palette** (entries prefixed `ollie:cmd:`,
|
||||||
executed by calling `OllieCommands::runVerb` directly). `case` is expanded
|
executed by calling `OllieCommands::runVerb` directly). `case` is expanded
|
||||||
into one entry per style; `pipe` is palette-omitted (needs free text).
|
into one entry per style; `pipe` is palette-omitted (needs free text).
|
||||||
|
- They are **also reachable from Kate's `:` command line**. `OllieCommands`
|
||||||
|
is a `KTextEditor::Command`; its base constructor auto-registers its names
|
||||||
|
at the global `Editor::instance()`, and `exec()` forwards to `runVerb`.
|
||||||
|
- ROOT-CAUSE FIX of the earlier "not reachable from `:`" problem: the
|
||||||
|
KTextEditor registry **aborts registering a Command object entirely if any
|
||||||
|
one of its names collides** with an already-registered command. Our list
|
||||||
|
led with `sort`, which Kate ships as a built-in, so the collision silently
|
||||||
|
dropped **all** our verbs. The sort verb is now registered as **`osort`**
|
||||||
|
(runVerb still accepts the bare `sort`, so palette entries are unchanged).
|
||||||
|
`test_ollieregistration` asserts every verb resolves to our object via
|
||||||
|
`Editor::queryCommand`/`commandList` and guards against reclaiming `sort`.
|
||||||
- A live test (`test_olliecommands_live`, headless KTextEditor doc/view)
|
- A live test (`test_olliecommands_live`, headless KTextEditor doc/view)
|
||||||
proves the transforms mutate a real buffer.
|
proves the transforms mutate a real buffer.
|
||||||
- KNOWN LIMITATION: `KTextEditor::Command` auto-registration via the base
|
|
||||||
constructor is NOT visible to `Editor::queryCommand()`/`commandList()` on
|
|
||||||
this build, so the verbs are not (yet) reachable from Kate's `:` command
|
|
||||||
line — only via the palette. Proper command-line registration is a follow-up.
|
|
||||||
- Project = directory ("open folder == open project"): DONE (core + switcher).
|
- Project = directory ("open folder == open project"): DONE (core + switcher).
|
||||||
`src/project/projectindex.{h,cpp}` (`project_lib`, Qt::Core only, 12 unit
|
`src/project/projectindex.{h,cpp}` (`project_lib`, Qt::Core only, 12 unit
|
||||||
tests):
|
tests):
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,9 @@ if(Qt6Test_FOUND)
|
||||||
target_link_libraries(test_olliecommands_live PRIVATE
|
target_link_libraries(test_olliecommands_live PRIVATE
|
||||||
olliecommands_lib Qt6::Test KF6::TextEditor)
|
olliecommands_lib Qt6::Test KF6::TextEditor)
|
||||||
add_test(NAME olliecommands_live COMMAND test_olliecommands_live)
|
add_test(NAME olliecommands_live COMMAND test_olliecommands_live)
|
||||||
|
|
||||||
|
add_executable(test_ollieregistration test_ollieregistration.cpp)
|
||||||
|
target_link_libraries(test_ollieregistration PRIVATE
|
||||||
|
olliecommands_lib Qt6::Test Qt6::Widgets KF6::TextEditor)
|
||||||
|
add_test(NAME ollieregistration COMMAND test_ollieregistration)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,13 @@ using namespace texttransforms;
|
||||||
QStringList OllieCommands::supportedCmds()
|
QStringList OllieCommands::supportedCmds()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
QStringLiteral("sort"),
|
// NOTE: "sort" is a built-in Kate command. The KTextEditor command
|
||||||
|
// registry aborts registering a Command object entirely if ANY of its
|
||||||
|
// names collides with an existing one, so claiming "sort" would silently
|
||||||
|
// drop ALL of our verbs from the ":" command line. We therefore register
|
||||||
|
// the sort verb as "osort" (runVerb still accepts the bare "sort" so the
|
||||||
|
// palette entries are unaffected).
|
||||||
|
QStringLiteral("osort"),
|
||||||
QStringLiteral("rsort"),
|
QStringLiteral("rsort"),
|
||||||
QStringLiteral("case"),
|
QStringLiteral("case"),
|
||||||
QStringLiteral("b64enc"),
|
QStringLiteral("b64enc"),
|
||||||
|
|
@ -88,7 +94,7 @@ bool OllieCommands::runVerb(KTextEditor::View *view, const QString &cmd, QString
|
||||||
|
|
||||||
const Target t = currentTarget(view);
|
const Target t = currentTarget(view);
|
||||||
|
|
||||||
if (verb == QLatin1String("sort")) {
|
if (verb == QLatin1String("sort") || verb == QLatin1String("osort")) {
|
||||||
const bool unique = args.contains(QLatin1String("u"));
|
const bool unique = args.contains(QLatin1String("u"));
|
||||||
const bool ci = args.contains(QLatin1String("i"));
|
const bool ci = args.contains(QLatin1String("i"));
|
||||||
applyResult(view, t, sortLines(t.text, unique, ci));
|
applyResult(view, t, sortLines(t.text, unique, ci));
|
||||||
|
|
@ -173,8 +179,8 @@ bool OllieCommands::runVerb(KTextEditor::View *view, const QString &cmd, QString
|
||||||
bool OllieCommands::help(KTextEditor::View *, const QString &cmd, QString &msg)
|
bool OllieCommands::help(KTextEditor::View *, const QString &cmd, QString &msg)
|
||||||
{
|
{
|
||||||
const QString verb = cmd.section(QLatin1Char(' '), 0, 0);
|
const QString verb = cmd.section(QLatin1Char(' '), 0, 0);
|
||||||
if (verb == QLatin1String("sort")) {
|
if (verb == QLatin1String("sort") || verb == QLatin1String("osort")) {
|
||||||
msg = i18n("sort [u] [i] — sort selected lines; u=unique, i=case-insensitive");
|
msg = i18n("osort [u] [i] — sort selected lines; u=unique, i=case-insensitive");
|
||||||
} else if (verb == QLatin1String("rsort")) {
|
} else if (verb == QLatin1String("rsort")) {
|
||||||
msg = i18n("rsort — reverse the order of selected lines");
|
msg = i18n("rsort — reverse the order of selected lines");
|
||||||
} else if (verb == QLatin1String("case")) {
|
} else if (verb == QLatin1String("case")) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
* the editor. Each verb transforms the current selection in place (or the whole
|
* the editor. Each verb transforms the current selection in place (or the whole
|
||||||
* document when nothing is selected). Verbs:
|
* document when nothing is selected). Verbs:
|
||||||
*
|
*
|
||||||
* sort [u] [i] sort selected lines (u = unique, i = case-insensitive)
|
* osort [u] [i] sort selected lines (u = unique, i = case-insensitive)
|
||||||
|
* (registered as "osort" because Kate already ships "sort")
|
||||||
* rsort reverse the order of selected lines
|
* rsort reverse the order of selected lines
|
||||||
* case <style> upper|lower|title|snake|camel|kebab
|
* case <style> upper|lower|title|snake|camel|kebab
|
||||||
* b64enc / b64dec base64 encode / decode
|
* b64enc / b64dec base64 encode / decode
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*
|
||||||
|
* Verifies that OllieCommands registers its verbs at the global
|
||||||
|
* KTextEditor::Editor instance so they are reachable from Kate's ":" command
|
||||||
|
* line.
|
||||||
|
*
|
||||||
|
* Background: the KTextEditor command registry aborts registering a Command
|
||||||
|
* object entirely if ANY of its names collides with an already-registered one.
|
||||||
|
* Kate ships a built-in "sort", so an earlier version that claimed "sort"
|
||||||
|
* silently dropped ALL of our verbs. We now register the sort verb as "osort";
|
||||||
|
* this test guards against reintroducing a colliding name.
|
||||||
|
*/
|
||||||
|
#include "olliecommands.h"
|
||||||
|
|
||||||
|
#include <KTextEditor/Command>
|
||||||
|
#include <KTextEditor/Editor>
|
||||||
|
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
using namespace katecustom;
|
||||||
|
|
||||||
|
class TestOllieRegistration : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private Q_SLOTS:
|
||||||
|
void verbsRegisterAtGlobalEditor()
|
||||||
|
{
|
||||||
|
auto *cmds = new OllieCommands();
|
||||||
|
auto *ed = KTextEditor::Editor::instance();
|
||||||
|
QVERIFY(ed);
|
||||||
|
|
||||||
|
// Every verb we register must resolve back to OUR command object and be
|
||||||
|
// present in the editor's command list.
|
||||||
|
const QStringList ours = {
|
||||||
|
QStringLiteral("osort"), QStringLiteral("rsort"),
|
||||||
|
QStringLiteral("case"), QStringLiteral("b64enc"),
|
||||||
|
QStringLiteral("b64dec"), QStringLiteral("rot13"),
|
||||||
|
QStringLiteral("uuid"), QStringLiteral("pipe"),
|
||||||
|
};
|
||||||
|
const QStringList list = ed->commandList();
|
||||||
|
for (const QString &v : ours) {
|
||||||
|
QVERIFY2(list.contains(v), qPrintable(QStringLiteral("missing from commandList: %1").arg(v)));
|
||||||
|
QCOMPARE(ed->queryCommand(v), static_cast<KTextEditor::Command *>(cmds));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must NOT claim "sort": it collides with Kate's built-in, which
|
||||||
|
// would abort registration of the whole object. queryCommand("sort")
|
||||||
|
// therefore resolves to Kate's command (or null), never ours.
|
||||||
|
QVERIFY(ed->queryCommand(QStringLiteral("sort")) != static_cast<KTextEditor::Command *>(cmds));
|
||||||
|
|
||||||
|
delete cmds;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_MAIN(TestOllieRegistration)
|
||||||
|
#include "test_ollieregistration.moc"
|
||||||
|
|
@ -154,8 +154,8 @@ struct VerbItem {
|
||||||
QList<VerbItem> paletteVerbs()
|
QList<VerbItem> paletteVerbs()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{QStringLiteral("sort"), QStringLiteral("Sort Lines")},
|
{QStringLiteral("osort"), QStringLiteral("Sort Lines")},
|
||||||
{QStringLiteral("sort u"), QStringLiteral("Sort Lines (unique)")},
|
{QStringLiteral("osort u"), QStringLiteral("Sort Lines (unique)")},
|
||||||
{QStringLiteral("rsort"), QStringLiteral("Reverse Lines")},
|
{QStringLiteral("rsort"), QStringLiteral("Reverse Lines")},
|
||||||
{QStringLiteral("case upper"), QStringLiteral("Case: UPPER")},
|
{QStringLiteral("case upper"), QStringLiteral("Case: UPPER")},
|
||||||
{QStringLiteral("case lower"), QStringLiteral("Case: lower")},
|
{QStringLiteral("case lower"), QStringLiteral("Case: lower")},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue