Learn cvar and command names from the server for autocomplete
Send cvarlist and cmdlist on connect and merge the answer into the autocomplete candidates. Number the server menu from 0, keep suggesting sibling commands while the command name is typed, mask the password in the startup line, ignore venv/, fix README claims.
This commit is contained in:
@@ -167,21 +167,20 @@ def suggestion_list_hint(suggestions, arg_type=None):
|
||||
return f'{shown}{more}'
|
||||
|
||||
|
||||
def compute_autocomplete(words, ends_with_space, player_names_provider):
|
||||
def compute_autocomplete(words, ends_with_space, player_names_provider, learned_names_provider=None):
|
||||
"""
|
||||
Work out what to suggest for the current input.
|
||||
player_names_provider / learned_names_provider: callables returning the
|
||||
current player names and the cvar/command names learned from the server.
|
||||
Returns (suggestions, original_word, hint_parts):
|
||||
- suggestions: values cycled with Tab
|
||||
- original_word: the word Tab replaces ('' means Tab appends a new word)
|
||||
- hint_parts: [(text, attributes)] shown below the input line
|
||||
"""
|
||||
first_word = words[0].lower()
|
||||
typing_command_name = len(words) == 1 and not ends_with_space
|
||||
|
||||
if first_word in COMMAND_ARGUMENTS:
|
||||
if len(words) == 1 and not ends_with_space:
|
||||
# Just the command: show its signature with the first argument highlighted
|
||||
return [], '', signature_hint(first_word, 0)
|
||||
|
||||
if first_word in COMMAND_ARGUMENTS and not typing_command_name:
|
||||
if ends_with_space:
|
||||
arg_position = len(words) - 1
|
||||
current_value = ''
|
||||
@@ -199,26 +198,34 @@ def compute_autocomplete(words, ends_with_space, player_names_provider):
|
||||
# No value suggestions (free text, no players known): show the signature
|
||||
return [], '', signature_hint(first_word, arg_position)
|
||||
|
||||
if COMMAND_SIGNATURES.get(first_word):
|
||||
if COMMAND_SIGNATURES.get(first_word) and not typing_command_name:
|
||||
# Command with a signature but no argument definitions
|
||||
return [], '', signature_hint(first_word, 0)
|
||||
|
||||
# Not a recognized command: suggest commands matching the current word
|
||||
# Still typing the command name, or an unknown command: suggest names matching
|
||||
# the current word. A complete name like `map` stays here on purpose because it
|
||||
# can be the prefix of others (map_restart, maplist); its signature is shown too.
|
||||
current_word = words[-1]
|
||||
if len(current_word) < 2:
|
||||
return [], '', []
|
||||
suggestions = autocomplete(current_word, max_results=5)
|
||||
if not suggestions:
|
||||
return [], current_word, []
|
||||
return suggestions, current_word, [(' '.join(suggestions), curses.A_DIM)]
|
||||
learned_names = learned_names_provider() if learned_names_provider else ()
|
||||
suggestions = autocomplete(current_word, learned_names, max_results=5)
|
||||
hint_parts = []
|
||||
if suggestions:
|
||||
hint_parts.append((' '.join(suggestions), curses.A_DIM))
|
||||
if typing_command_name and COMMAND_SIGNATURES.get(first_word):
|
||||
hint_parts.append((' ', 0))
|
||||
hint_parts.extend(signature_hint(first_word, 0))
|
||||
return suggestions, current_word, hint_parts
|
||||
|
||||
|
||||
class InputLine:
|
||||
"""The command input line: text, cursor, history and autocomplete state"""
|
||||
|
||||
def __init__(self, max_history, player_names_provider=None):
|
||||
def __init__(self, max_history, player_names_provider=None, learned_names_provider=None):
|
||||
self.max_history = max_history
|
||||
self.player_names_provider = player_names_provider
|
||||
self.learned_names_provider = learned_names_provider
|
||||
self.text = ''
|
||||
self.cursor = 0
|
||||
self.history = []
|
||||
@@ -310,7 +317,7 @@ class InputLine:
|
||||
self._clear_suggestions()
|
||||
return
|
||||
self.suggestions, self.original_word, self.hint_parts = compute_autocomplete(
|
||||
words, self.text.endswith(' '), self.player_names_provider)
|
||||
words, self.text.endswith(' '), self.player_names_provider, self.learned_names_provider)
|
||||
self.suggestion_index = -1
|
||||
|
||||
def _history_previous(self):
|
||||
@@ -384,7 +391,7 @@ def select_server(screen, servers):
|
||||
"""
|
||||
Full-screen menu listing the configured servers.
|
||||
servers: dict name -> host. Returns the chosen name, or None when the user quits.
|
||||
Keys: Up/Down or j/k move, Enter connects, 1-9 connect directly, q or Esc quit.
|
||||
Keys: Up/Down or j/k move, Enter connects, 0-9 connect directly, q or Esc quit.
|
||||
"""
|
||||
names = list(servers)
|
||||
selected = 0
|
||||
@@ -405,8 +412,8 @@ def select_server(screen, servers):
|
||||
selected = (selected + 1) % len(names)
|
||||
elif key in (curses.KEY_ENTER, 10, 13):
|
||||
return names[selected]
|
||||
elif ord('1') <= key <= ord('9') and key - ord('1') < len(names):
|
||||
return names[key - ord('1')]
|
||||
elif ord('0') <= key <= ord('9') and key - ord('0') < len(names):
|
||||
return names[key - ord('0')]
|
||||
elif key in (ord('q'), 27): # 27 = Escape
|
||||
return None
|
||||
# Any other key (including KEY_RESIZE) just redraws
|
||||
@@ -416,8 +423,7 @@ def _draw_server_menu(screen, servers, names, selected):
|
||||
screen.erase()
|
||||
safe_addstr(screen, 0, 2, 'Quake Live PyCon - select a server', curses.A_BOLD)
|
||||
for index, name in enumerate(names):
|
||||
number = index + 1
|
||||
line = f'{number:>2}. {name:<12} {servers[name]}'
|
||||
line = f'{index:>2}. {name:<12} {servers[name]}'
|
||||
attributes = curses.A_REVERSE if index == selected else 0
|
||||
safe_addstr(screen, 2 + index, 2, line, attributes)
|
||||
safe_addstr(screen, 3 + len(names), 2,
|
||||
@@ -428,7 +434,8 @@ def _draw_server_menu(screen, servers, names, selected):
|
||||
class UIManager:
|
||||
"""Manages curses windows and display"""
|
||||
|
||||
def __init__(self, screen, title, max_history=MAX_COMMAND_HISTORY, player_names_provider=None):
|
||||
def __init__(self, screen, title, max_history=MAX_COMMAND_HISTORY,
|
||||
player_names_provider=None, learned_names_provider=None):
|
||||
self.screen = screen
|
||||
self.title = title
|
||||
self.info_window = None
|
||||
@@ -445,7 +452,7 @@ class UIManager:
|
||||
# Visual rows scrolled up from the newest output; 0 = follow new output
|
||||
self.scroll_offset = 0
|
||||
|
||||
self.input_line = InputLine(max_history, player_names_provider)
|
||||
self.input_line = InputLine(max_history, player_names_provider, learned_names_provider)
|
||||
self.game_state = None # last state shown in the info window
|
||||
|
||||
self._init_curses()
|
||||
|
||||
Reference in New Issue
Block a user