diff --git a/AUTHORS b/AUTHORS index 06c8d9a17..c8a2d4d34 100644 --- a/AUTHORS +++ b/AUTHORS @@ -152,6 +152,7 @@ Contributors: * Shayan Golshani (shgol) * Tommi Kyntölä (kynde) * Diego + * Chris (ChrisJr404) Creator: -------- diff --git a/changelog.rst b/changelog.rst index 0c3905e38..6e4c47c36 100644 --- a/changelog.rst +++ b/changelog.rst @@ -10,6 +10,9 @@ Bug fixes: Features: --------- +* Honor the ``PSQL_EDITOR`` environment variable when opening the external + editor (``\\e``, ``\\ev``, ``\\ef``, ``\\ne``), matching psql's precedence of + ``PSQL_EDITOR``, then ``EDITOR``, then ``VISUAL`` ([issue 1398](https://github.com/dbcli/pgcli/issues/1398)). * 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 diff --git a/pgcli/main.py b/pgcli/main.py index 8a9b1b024..e3ba5bfa8 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -146,6 +146,17 @@ def notify_callback(notify: Notify): ) +def get_editor(): + """Pick the external editor for ``\\e``/``\\ev``/``\\ef``/``\\ne``. + + Mirrors psql, which checks ``PSQL_EDITOR`` first, then ``EDITOR``, then + ``VISUAL``. Returning ``None`` when none are set lets click fall back to + its platform default, so the behaviour is unchanged for anyone who wasn't + setting ``PSQL_EDITOR``. + """ + return os.environ.get("PSQL_EDITOR") or os.environ.get("EDITOR") or os.environ.get("VISUAL") or None + + class PGCli: default_prompt = "\\u@\\h:\\d> " max_len_prompt = 30 @@ -331,7 +342,7 @@ def edit_named_query(self, pattern, **_): return [(None, None, None, "Usage: \\ne ")] existing = NamedQueries.instance.get(name) - sql, message = special.open_external_editor(sql=existing or "") + sql, message = special.open_external_editor(sql=existing or "", editor=get_editor()) if message: return [(None, None, None, message)] @@ -832,7 +843,7 @@ def handle_editor_command(self, text): query = self.pgexecute.view_definition(spec) elif editor_command == "\\ef": query = self.pgexecute.function_definition(spec) - sql, message = special.open_external_editor(filename, sql=query) + sql, message = special.open_external_editor(filename, sql=query, editor=get_editor()) if message: # Something went wrong. Raise an exception and bail. raise RuntimeError(message) diff --git a/tests/test_main.py b/tests/test_main.py index 40e96ab05..ba990631a 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -16,6 +16,7 @@ obfuscate_process_password, duration_in_words, format_output, + get_editor, notify_callback, PGCli, OutputSettings, @@ -681,3 +682,22 @@ def test_edit_named_query(): # Missing name -> usage message. out = cli.edit_named_query("") assert "Usage" in out[0][3] + + +def test_get_editor_precedence(): + """PSQL_EDITOR wins over EDITOR/VISUAL, like psql; None when nothing is set.""" + env = {"PSQL_EDITOR": "psqled", "EDITOR": "myedit", "VISUAL": "myvisual"} + with mock.patch.dict(os.environ, env, clear=False): + assert get_editor() == "psqled" + + # PSQL_EDITOR unset -> fall back to EDITOR. + with mock.patch.dict(os.environ, {"EDITOR": "myedit", "VISUAL": "myvisual"}, clear=True): + assert get_editor() == "myedit" + + # Only VISUAL set. + with mock.patch.dict(os.environ, {"VISUAL": "myvisual"}, clear=True): + assert get_editor() == "myvisual" + + # Nothing set -> None, so click uses its platform default. + with mock.patch.dict(os.environ, {}, clear=True): + assert get_editor() is None