Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Upcoming (TBD)
Features
---------
* Set minimum times for transient toolbar messages.
* `editor_command` entry in `~/.myclirc` which overrides environment.


2.2.0 (2026/07/11)
Expand Down
2 changes: 2 additions & 0 deletions mycli/TIPS
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ use "min_completion_trigger" in ~/.myclirc to defer completions!

colorize search previews with "highlight_preview" in ~/.myclirc!

set editor_command = "code --wait" in ~/.myclirc to edit queries using VS Code!

###
### redirection
###
Expand Down
15 changes: 15 additions & 0 deletions mycli/main_modes/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import os
import random
import re
import shutil
import subprocess
import sys
import time
Expand Down Expand Up @@ -42,6 +43,7 @@
from mycli.clibuffer import cli_is_multiline
from mycli.clistyle import style_factory_ptoolkit
from mycli.clitoolbar import create_toolbar_tokens_func
from mycli.compat import WIN
from mycli.constants import (
DEFAULT_HOST,
DEFAULT_WIDTH,
Expand Down Expand Up @@ -854,12 +856,25 @@ def _tips_picker() -> str:
return random.choice(tips) if tips else r'\? or "help" for help!'


def _configure_editor(mycli: 'MyCli') -> None:
if configured_editor := mycli.config['editor'].get('editor_command'):
os.environ['VISUAL'] = configured_editor
elif not os.environ.get('VISUAL') and not os.environ.get('EDITOR'):
if shutil.which('code'):
os.environ['VISUAL'] = 'code --wait'
elif WIN:
os.environ['VISUAL'] = 'edit'
else:
os.environ['VISUAL'] = 'vi'


def main_repl(mycli: 'MyCli') -> None:
sqlexecute = mycli.sqlexecute
assert sqlexecute is not None
state = ReplState()

mycli.configure_pager()
_configure_editor(mycli)
if mycli.smart_completion and not mycli.sandbox_mode:
mycli.refresh_completions()

Expand Down
10 changes: 10 additions & 0 deletions mycli/myclirc
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,16 @@ pager = 'less'
# Recommanded: auto
use_keyring = False

[editor]

# Command line to invoke for /edit, instead of respecting the $VISUAL/$EDITOR
# environment variables. Suggestions:
# * VS Code: code --wait
# * Vim: vim
# * Emacs: emacsclient
# For emacsclient, first start the server with "M-x server-start".
editor_command =

[search]

# Whether to apply syntax highlighting to the preview window in fuzzy history
Expand Down
10 changes: 10 additions & 0 deletions test/myclirc
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,16 @@ pager = python test/features/wrappager.py ---boundary---
# Recommended: auto
use_keyring = False

[editor]

# Command line to invoke for /edit, instead of respecting the $VISUAL/$EDITOR
# environment variables. Suggestions:
# * VS Code: code --wait
# * Vim: vim
# * Emacs: emacsclient
# For emacsclient, first start the server with "M-x server-start".
editor_command =

[search]

# Whether to apply syntax highlighting to the preview window in fuzzy history
Expand Down
47 changes: 46 additions & 1 deletion test/pytests/test_main_modes_repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def make_repl_cli(sqlexecute: Any | None = None) -> Any:
cli.post_redirect_command = None
cli.logfile = None
cli.smart_completion = False
cli.config = {'main': {'history_file': '~/.mycli-history-testing'}}
cli.config = {'main': {'history_file': '~/.mycli-history-testing'}, 'editor': {'editor_command': ''}}
cli.key_bindings = 'emacs'
cli.wider_completion_menu = False
cli.login_path = None
Expand Down Expand Up @@ -333,6 +333,51 @@ def test_repl_picker_helpers_cover_present_and_missing_resources(monkeypatch: py
assert repl_mode._tips_picker() == r'\? or "help" for help!'


def test_configure_editor_uses_configured_editor(monkeypatch: pytest.MonkeyPatch) -> None:
cli = make_repl_cli()
cli.config['editor']['editor_command'] = 'nano'
monkeypatch.delenv('VISUAL', raising=False)

repl_mode._configure_editor(cli)

assert os.environ['VISUAL'] == 'nano'


def test_configure_editor_uses_code_when_no_editor_environment_is_set(monkeypatch: pytest.MonkeyPatch) -> None:
cli = make_repl_cli()
monkeypatch.delenv('VISUAL', raising=False)
monkeypatch.delenv('EDITOR', raising=False)
monkeypatch.setattr(repl_mode.shutil, 'which', lambda name: '/usr/bin/code' if name == 'code' else None)

repl_mode._configure_editor(cli)

assert os.environ['VISUAL'] == 'code --wait'


def test_configure_editor_falls_back_to_vi_when_no_editor_is_available(monkeypatch: pytest.MonkeyPatch) -> None:
cli = make_repl_cli()
monkeypatch.delenv('VISUAL', raising=False)
monkeypatch.delenv('EDITOR', raising=False)
monkeypatch.setattr(repl_mode.shutil, 'which', lambda name: None)
monkeypatch.setattr(repl_mode, 'WIN', False)

repl_mode._configure_editor(cli)

assert os.environ['VISUAL'] == 'vi'


def test_configure_editor_falls_back_to_edit_on_windows(monkeypatch: pytest.MonkeyPatch) -> None:
cli = make_repl_cli()
monkeypatch.delenv('VISUAL', raising=False)
monkeypatch.delenv('EDITOR', raising=False)
monkeypatch.setattr(repl_mode.shutil, 'which', lambda name: None)
monkeypatch.setattr(repl_mode, 'WIN', True)

repl_mode._configure_editor(cli)

assert os.environ['VISUAL'] == 'edit'


def test_repl_show_startup_banner_and_prompt_helpers(monkeypatch: pytest.MonkeyPatch) -> None:
cli = make_repl_cli(SimpleNamespace(server_info='Server'))
printed: list[str] = []
Expand Down
Loading