From 117449b1a2e91341483fb70f25ad17533a0eec0c Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 29 Jun 2026 09:53:31 -0300 Subject: [PATCH] Add \ne to edit a named query in the external editor \ne loads a named query's SQL into $EDITOR and writes the edited result back to the [named queries] section on save, creating it if it does not exist. Complements \ns for editing longer queries. Addresses the editor part of issue #1430. --- AUTHORS | 1 + changelog.rst | 10 ++++++++++ pgcli/main.py | 32 ++++++++++++++++++++++++++++++++ tests/test_main.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/AUTHORS b/AUTHORS index aef249cef..0e06574e4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -148,6 +148,7 @@ Contributors: * Charalampos Stratakis * Laszlo Bimba (bimlas) * Anjanna + * Diego Creator: -------- diff --git a/changelog.rst b/changelog.rst index 4d2f529b2..18c2a480d 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,3 +1,13 @@ +Upcoming +======== + +Features: +--------- +* Add ``\\ne `` to edit a named query in the external editor. Loads the + named query's SQL into ``$EDITOR``; on save it is written back to the + ``[named queries]`` section, creating it if it does not exist. Complements + ``\\ns`` (save) by making longer queries easier to edit ([issue 1430](https://github.com/dbcli/pgcli/issues/1430)). + 4.5.0 (2026-06-02) ================== diff --git a/pgcli/main.py b/pgcli/main.py index ad01a46fd..ccf50a26d 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -319,6 +319,31 @@ def toggle_named_query_quiet(self): message = f"Named query quiet mode: {status}" return [(None, None, None, message)] + def edit_named_query(self, pattern, **_): + r"""Edit (or create) a named query in the external editor (\ne name). + + Loads the named query's SQL into ``$EDITOR``; on save, persists it back + to the ``[named queries]`` section. If the name does not exist, the + editor opens empty and saving creates it. + """ + name = pattern.strip() + if not name: + return [(None, None, None, "Usage: \\ne ")] + + existing = NamedQueries.instance.get(name) + sql, message = special.open_external_editor(sql=existing or "") + if message: + return [(None, None, None, message)] + + sql = (sql or "").strip() + if not sql: + return [(None, None, None, f"{name}: empty query, not saved.")] + if existing is not None and sql == existing.strip(): + return [(None, None, None, f"{name}: no changes.")] + + NamedQueries.instance.save(name, sql) + return [(None, None, None, f"{name}: {'Created' if existing is None else 'Saved'}")] + def _is_named_query_execution(self, text): """Check if the command is a named query execution (\n ).""" text = text.strip() @@ -334,6 +359,13 @@ def register_special_commands(self): case_sensitive=True, ) + self.pgspecial.register( + self.edit_named_query, + "\\ne", + "\\ne name", + "Edit a named query in the external editor.", + ) + self.pgspecial.register( self.change_db, "\\c", diff --git a/tests/test_main.py b/tests/test_main.py index 52415a008..40e96ab05 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -636,3 +636,48 @@ def test_notifications(executor): with mock.patch("pgcli.main.click.secho") as mock_secho: run(executor, "notify chan1, 'testing2'") mock_secho.assert_not_called() + + +def test_edit_named_query(): + """Test \\ne edits/creates a named query via the external editor.""" + from pgspecial.namedqueries import NamedQueries + + with tempfile.TemporaryDirectory() as tmpdir: + config_file = os.path.join(tmpdir, "config") + with open(config_file, "w") as f: + f.write("[main]\n") + f.write(f"log_file = {os.path.join(tmpdir, 'pgcli.log')}\n") + + cli = PGCli(pgclirc_file=config_file) + + # Create a new named query (editor returns SQL, no error). + with mock.patch("pgcli.main.special.open_external_editor", return_value=("select 1", None)): + out = cli.edit_named_query("foo") + assert "Created" in out[0][3] + assert NamedQueries.instance.get("foo") == "select 1" + + # Update the existing one. + with mock.patch("pgcli.main.special.open_external_editor", return_value=("select 2", None)): + out = cli.edit_named_query("foo") + assert "Saved" in out[0][3] + assert NamedQueries.instance.get("foo") == "select 2" + + # No changes -> not re-saved. + with mock.patch("pgcli.main.special.open_external_editor", return_value=("select 2", None)): + out = cli.edit_named_query("foo") + assert "no changes" in out[0][3] + + # Empty editor result -> not saved, previous value kept. + with mock.patch("pgcli.main.special.open_external_editor", return_value=("", None)): + out = cli.edit_named_query("foo") + assert "not saved" in out[0][3] + assert NamedQueries.instance.get("foo") == "select 2" + + # Editor reported an error -> surfaced, nothing saved. + with mock.patch("pgcli.main.special.open_external_editor", return_value=(None, "boom")): + out = cli.edit_named_query("foo") + assert out[0][3] == "boom" + + # Missing name -> usage message. + out = cli.edit_named_query("") + assert "Usage" in out[0][3]