From e1f70e6f7f8be2e00e14b511c137aef0358100b4 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Sun, 8 Feb 2026 14:48:14 +0100 Subject: [PATCH] feat(repl): group completions by scope and add keywords --- src/completer/completer.py | 64 +++++++++++++++++++++++++++++++------- src/repl.py | 1 + 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/completer/completer.py b/src/completer/completer.py index 578a285..f0f03f4 100644 --- a/src/completer/completer.py +++ b/src/completer/completer.py @@ -1,29 +1,39 @@ -from typing import Optional +import os +import readline +import sys +from typing import Optional, Sequence from src.interpreter.environment import Environment from src.interpreter.interpreter import Interpreter +from src.token.keyword import KEYWORDS class Completer: def __init__(self, interpreter: Interpreter): self.interpreter: Interpreter = interpreter - self.options: list[str] = [] - self.matches: list[str] = [] + self.options: list[list[str]] = [] + self.matches: list[list[str]] = [] def complete(self, text: str, state: int) -> Optional[str]: if state == 0: self.update_matches(text) - try: - return self.matches[state] - except IndexError: - return None + return self.get_match(state) + + def get_match(self, state: int) -> Optional[str]: + for grp in self.matches: + if state < len(grp): + return grp[state] + state -= len(grp) + return None def update_matches(self, text: str): - self.options = [] + self.options = [ + list(KEYWORDS.keys()) + ] env: Optional[Environment] = self.interpreter.env while env is not None: - self.options.extend(env.values.keys()) + self.options.append(sorted(env.values.keys())) env = env.enclosing self.options = sorted(self.options) @@ -32,7 +42,37 @@ class Completer: self.matches = self.options else: self.matches = [ - opt - for opt in self.options - if opt.startswith(text) + [ + opt + for opt in grp + if opt.startswith(text) + ] + for grp in self.options ] + self.matches = list(filter(lambda grp: grp, self.matches)) + + def display_matches(self, sub: str, matches: Sequence[str], longest_len: int): + line_buf = readline.get_line_buffer() + cols = os.get_terminal_size()[0] + + for i, grp in enumerate(self.matches): + print() + self.display_group(grp, cols) + + print(">>> ", end="") + print(line_buf, end="") + sys.stdout.flush() + + def display_group(self, group: list[str], cols: int): + width: int = max(map(len, group)) + width = width * 6 // 5 + 2 + buf: str = "" + for match in group: + match = f"{match:{width}}" + if len(buf) + width > cols: + print(buf.rstrip()) + buf = "" + buf += match + + if buf: + print(buf) diff --git a/src/repl.py b/src/repl.py index 651aba5..67e158e 100644 --- a/src/repl.py +++ b/src/repl.py @@ -44,6 +44,7 @@ class REPL: def init_completer(self): readline.set_completer(self.completer.complete) readline.parse_and_bind("tab: complete") + readline.set_completion_display_matches_hook(self.completer.display_matches) def run(self): line: str