feat(repl): group completions by scope and add keywords

This commit is contained in:
2026-02-08 14:48:14 +01:00
parent 218fb91053
commit e1f70e6f7f
2 changed files with 53 additions and 12 deletions

View File

@@ -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.environment import Environment
from src.interpreter.interpreter import Interpreter from src.interpreter.interpreter import Interpreter
from src.token.keyword import KEYWORDS
class Completer: class Completer:
def __init__(self, interpreter: Interpreter): def __init__(self, interpreter: Interpreter):
self.interpreter: Interpreter = interpreter self.interpreter: Interpreter = interpreter
self.options: list[str] = [] self.options: list[list[str]] = []
self.matches: list[str] = [] self.matches: list[list[str]] = []
def complete(self, text: str, state: int) -> Optional[str]: def complete(self, text: str, state: int) -> Optional[str]:
if state == 0: if state == 0:
self.update_matches(text) self.update_matches(text)
try: return self.get_match(state)
return self.matches[state]
except IndexError: 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 return None
def update_matches(self, text: str): def update_matches(self, text: str):
self.options = [] self.options = [
list(KEYWORDS.keys())
]
env: Optional[Environment] = self.interpreter.env env: Optional[Environment] = self.interpreter.env
while env is not None: while env is not None:
self.options.extend(env.values.keys()) self.options.append(sorted(env.values.keys()))
env = env.enclosing env = env.enclosing
self.options = sorted(self.options) self.options = sorted(self.options)
@@ -32,7 +42,37 @@ class Completer:
self.matches = self.options self.matches = self.options
else: else:
self.matches = [ self.matches = [
[
opt opt
for opt in self.options for opt in grp
if opt.startswith(text) 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)

View File

@@ -44,6 +44,7 @@ class REPL:
def init_completer(self): def init_completer(self):
readline.set_completer(self.completer.complete) readline.set_completer(self.completer.complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
readline.set_completion_display_matches_hook(self.completer.display_matches)
def run(self): def run(self):
line: str line: str