Skip to content

Commit 70b9ebb

Browse files
committed
move complete_while_typing_filter() to repl.py
Still a bit hacky as we need to set repl_package.MIN_COMPLETION_TRIGGER directly. But, one more function migrated out of main.py. No functional change.
1 parent 6f2b4c4 commit 70b9ebb

4 files changed

Lines changed: 66 additions & 61 deletions

File tree

mycli/main.py

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,8 @@
3232
from configobj import ConfigObj
3333
import keyring
3434
from prompt_toolkit import print_formatted_text
35-
from prompt_toolkit.application.current import get_app
3635
from prompt_toolkit.completion import Completion
3736
from prompt_toolkit.document import Document
38-
from prompt_toolkit.filters import Condition
3937
from prompt_toolkit.formatted_text import (
4038
ANSI,
4139
HTML,
@@ -65,6 +63,7 @@
6563
ISSUES_URL,
6664
REPO_URL,
6765
)
66+
from mycli.main_modes import repl as repl_package
6867
from mycli.main_modes.batch import (
6968
main_batch_from_stdin,
7069
main_batch_with_progress_bar,
@@ -93,39 +92,9 @@
9392
sqlparse.engine.grouping.MAX_GROUPING_DEPTH = None # type: ignore[assignment]
9493
sqlparse.engine.grouping.MAX_GROUPING_TOKENS = None # type: ignore[assignment]
9594

96-
MIN_COMPLETION_TRIGGER = 1
9795
EMPTY_PASSWORD_FLAG_SENTINEL = -1
9896

9997

100-
@Condition
101-
def complete_while_typing_filter() -> bool:
102-
"""Whether enough characters have been typed to trigger completion.
103-
104-
Written in a verbose way, with a string slice, for efficiency."""
105-
if MIN_COMPLETION_TRIGGER <= 1:
106-
return True
107-
app = get_app()
108-
text = app.current_buffer.text.lstrip()
109-
text_len = len(text)
110-
if text_len < MIN_COMPLETION_TRIGGER:
111-
return False
112-
last_word = text[-MIN_COMPLETION_TRIGGER:]
113-
if len(last_word) == text_len:
114-
return text_len >= MIN_COMPLETION_TRIGGER
115-
if text[:6].lower() in ['source', r'\.']:
116-
# Different word characters for paths; see comment below.
117-
# In fact, it might be nice if paths had a different threshold.
118-
return not bool(re.search(r'[\s!-,:-@\[-^\{\}-]', last_word))
119-
else:
120-
# This is "whitespace and all punctuation except underscore and backtick"
121-
# acting as word breaks, but it would be neat if we could complete differently
122-
# when inside a backtick, accepting all legal characters towards the trigger
123-
# limit. We would have to parse the statement, or at least go back more
124-
# characters, costing performance. This still works within a backtick! So
125-
# long as there are three trailing non-punctuation characters.
126-
return not bool(re.search(r'[\s!-/:-@\[-^\{-~]', last_word))
127-
128-
12998
class IntOrStringClickParamType(click.ParamType):
13099
name = 'text' # display as TEXT in helpdoc
131100

@@ -179,8 +148,6 @@ def __init__(
179148
warn: bool | None = None,
180149
myclirc: str = "~/.myclirc",
181150
) -> None:
182-
global MIN_COMPLETION_TRIGGER
183-
184151
self.sqlexecute = sqlexecute
185152
self.logfile = logfile
186153
self.defaults_suffix = defaults_suffix
@@ -291,7 +258,8 @@ def __init__(
291258
self._completer_lock = threading.Lock()
292259

293260
self.min_completion_trigger = c["main"].as_int("min_completion_trigger")
294-
MIN_COMPLETION_TRIGGER = self.min_completion_trigger
261+
# a hack, pending a better way to handle settings and state
262+
repl_package.MIN_COMPLETION_TRIGGER = self.min_completion_trigger
295263
self.last_prompt_message = ANSI('')
296264
self.last_custom_toolbar_message = ANSI('')
297265

mycli/main_modes/repl.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313

1414
import click
1515
import prompt_toolkit
16+
from prompt_toolkit.application.current import get_app
1617
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, ThreadedAutoSuggest
1718
from prompt_toolkit.completion import DynamicCompleter
1819
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19-
from prompt_toolkit.filters import HasFocus, IsDone
20+
from prompt_toolkit.filters import Condition, HasFocus, IsDone
2021
from prompt_toolkit.formatted_text import (
2122
ANSI,
2223
)
@@ -66,6 +67,7 @@
6667

6768

6869
SUPPORT_INFO = f"Home: {HOME_URL}\nBug tracker: {ISSUES_URL}"
70+
MIN_COMPLETION_TRIGGER = 1
6971

7072

7173
def _main_module():
@@ -80,6 +82,35 @@ class ReplState:
8082
mutating: bool = False
8183

8284

85+
@Condition
86+
def complete_while_typing_filter() -> bool:
87+
"""Whether enough characters have been typed to trigger completion.
88+
89+
Written in a verbose way, with a string slice, for efficiency."""
90+
if MIN_COMPLETION_TRIGGER <= 1:
91+
return True
92+
app = get_app()
93+
text = app.current_buffer.text.lstrip()
94+
text_len = len(text)
95+
if text_len < MIN_COMPLETION_TRIGGER:
96+
return False
97+
last_word = text[-MIN_COMPLETION_TRIGGER:]
98+
if len(last_word) == text_len:
99+
return text_len >= MIN_COMPLETION_TRIGGER
100+
if text[:6].lower() in ['source', r'\.']:
101+
# Different word characters for paths; see comment below.
102+
# In fact, it might be nice if paths had a different threshold.
103+
return not bool(re.search(r'[\s!-,:-@\[-^\{\}-]', last_word))
104+
else:
105+
# This is "whitespace and all punctuation except underscore and backtick"
106+
# acting as word breaks, but it would be neat if we could complete differently
107+
# when inside a backtick, accepting all legal characters towards the trigger
108+
# limit. We would have to parse the statement, or at least go back more
109+
# characters, costing performance. This still works within a backtick! So
110+
# long as there are three trailing non-punctuation characters.
111+
return not bool(re.search(r'[\s!-/:-@\[-^\{-~]', last_word))
112+
113+
83114
def _create_history(mycli: 'MyCli') -> FileHistoryWithTimestamp | None:
84115
history_file = os.path.expanduser(os.environ.get('MYCLI_HISTFILE', mycli.config.get('history_file', '~/.mycli-history')))
85116
if dir_path_exists(history_file):
@@ -307,7 +338,7 @@ def _build_prompt_session(
307338
complete_in_thread=True,
308339
history=history,
309340
auto_suggest=ThreadedAutoSuggest(AutoSuggestFromHistory()),
310-
complete_while_typing=_main_module().complete_while_typing_filter,
341+
complete_while_typing=complete_while_typing_filter,
311342
multiline=cli_is_multiline(mycli),
312343
style=style_factory_ptoolkit(mycli.syntax_style, mycli.cli_style),
313344
include_default_pygments_style=False,

test/pytests/test_main_modes_repl.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,36 @@ def patch_repl_runtime_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
244244
monkeypatch.setattr(repl_mode, 'is_mutating', lambda status: False)
245245

246246

247+
def test_complete_while_typing_filter_covers_threshold_and_word_rules(monkeypatch: pytest.MonkeyPatch) -> None:
248+
monkeypatch.setattr(repl_mode, 'MIN_COMPLETION_TRIGGER', 3)
249+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='ab')))
250+
assert repl_mode.complete_while_typing_filter() is False
251+
252+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='abc')))
253+
assert repl_mode.complete_while_typing_filter() is True
254+
255+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='source xyz')))
256+
assert repl_mode.complete_while_typing_filter() is True
257+
258+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='source x/')))
259+
assert repl_mode.complete_while_typing_filter() is False
260+
261+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='\\. abc')))
262+
assert repl_mode.complete_while_typing_filter() is True
263+
264+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='\\. a/')))
265+
assert repl_mode.complete_while_typing_filter() is False
266+
267+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='select abc')))
268+
assert repl_mode.complete_while_typing_filter() is True
269+
270+
monkeypatch.setattr(repl_mode, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='select a!')))
271+
assert repl_mode.complete_while_typing_filter() is False
272+
273+
monkeypatch.setattr(repl_mode, 'MIN_COMPLETION_TRIGGER', 1)
274+
assert repl_mode.complete_while_typing_filter() is True
275+
276+
247277
def test_repl_main_module_and_create_history(monkeypatch: pytest.MonkeyPatch) -> None:
248278
cli = make_repl_cli()
249279
monkeypatch.setenv('MYCLI_HISTFILE', '~/override-history')

test/pytests/test_main_regression.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -532,30 +532,6 @@ def __init__(self) -> None:
532532
assert mycli.llm_prompt_section_truncate == 0
533533

534534

535-
def test_complete_while_typing_filter_covers_source_and_sql_word_rules(monkeypatch: pytest.MonkeyPatch) -> None:
536-
monkeypatch.setattr(main, 'MIN_COMPLETION_TRIGGER', 3)
537-
monkeypatch.setattr(main, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='ab')))
538-
assert main.complete_while_typing_filter() is False
539-
540-
monkeypatch.setattr(main, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='abc')))
541-
assert main.complete_while_typing_filter() is True
542-
543-
monkeypatch.setattr(main, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='source xyz')))
544-
assert main.complete_while_typing_filter() is True
545-
546-
monkeypatch.setattr(main, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='source x/')))
547-
assert main.complete_while_typing_filter() is False
548-
549-
monkeypatch.setattr(main, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='select abc')))
550-
assert main.complete_while_typing_filter() is True
551-
552-
monkeypatch.setattr(main, 'get_app', lambda: SimpleNamespace(current_buffer=SimpleNamespace(text='select a!')))
553-
assert main.complete_while_typing_filter() is False
554-
555-
monkeypatch.setattr(main, 'MIN_COMPLETION_TRIGGER', 1)
556-
assert main.complete_while_typing_filter() is True
557-
558-
559535
def test_int_or_string_click_param_type_accepts_and_rejects_values() -> None:
560536
param_type = main.IntOrStringClickParamType()
561537

0 commit comments

Comments
 (0)