186 lines
6.0 KiB
Python
186 lines
6.0 KiB
Python
|
from __future__ import unicode_literals
|
||
|
|
||
|
from prompt_toolkit.completion import Completer, Completion, WordCompleter
|
||
|
from prompt_toolkit.document import Document
|
||
|
|
||
|
from .aliases import ALIASES
|
||
|
from .commands import COMMANDS_TO_HANDLERS, get_option_flags_for_command
|
||
|
from .utils import wrap_argument
|
||
|
|
||
|
from pymux.arrangement import LayoutTypes
|
||
|
from pymux.key_mappings import PYMUX_TO_PROMPT_TOOLKIT_KEYS
|
||
|
|
||
|
from functools import partial
|
||
|
|
||
|
|
||
|
__all__ = (
|
||
|
'create_command_completer',
|
||
|
)
|
||
|
|
||
|
|
||
|
def create_command_completer(pymux):
|
||
|
return ShlexCompleter(partial(get_completions_for_parts, pymux=pymux))
|
||
|
|
||
|
|
||
|
class CommandCompleter(Completer):
|
||
|
"""
|
||
|
Completer for command names.
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
# Completer for full command names.
|
||
|
self._command_completer = WordCompleter(
|
||
|
sorted(COMMANDS_TO_HANDLERS.keys()),
|
||
|
ignore_case=True, WORD=True, match_middle=True)
|
||
|
|
||
|
# Completer for aliases.
|
||
|
self._aliases_completer = WordCompleter(
|
||
|
sorted(ALIASES.keys()),
|
||
|
ignore_case=True, WORD=True, match_middle=True)
|
||
|
|
||
|
def get_completions(self, document, complete_event):
|
||
|
# First, complete on full command names.
|
||
|
found = False
|
||
|
|
||
|
for c in self._command_completer.get_completions(document, complete_event):
|
||
|
found = True
|
||
|
yield c
|
||
|
|
||
|
# When no matches are found, complete aliases instead.
|
||
|
# The completion however, inserts the full name.
|
||
|
if not found:
|
||
|
for c in self._aliases_completer.get_completions(document, complete_event):
|
||
|
full_name = ALIASES.get(c.display)
|
||
|
|
||
|
yield Completion(full_name,
|
||
|
start_position=c.start_position,
|
||
|
display='%s (%s)' % (c.display, full_name))
|
||
|
|
||
|
|
||
|
_command_completer = CommandCompleter()
|
||
|
_layout_type_completer = WordCompleter(sorted(LayoutTypes._ALL), WORD=True)
|
||
|
_keys_completer = WordCompleter(sorted(PYMUX_TO_PROMPT_TOOLKIT_KEYS.keys()),
|
||
|
ignore_case=True, WORD=True)
|
||
|
|
||
|
|
||
|
def get_completions_for_parts(parts, last_part, complete_event, pymux):
|
||
|
completer = None
|
||
|
|
||
|
# Resolve aliases.
|
||
|
if len(parts) > 0:
|
||
|
parts = [ALIASES.get(parts[0], parts[0])] + parts[1:]
|
||
|
|
||
|
if len(parts) == 0:
|
||
|
# New command.
|
||
|
completer = _command_completer
|
||
|
|
||
|
elif len(parts) >= 1 and last_part.startswith('-'):
|
||
|
flags = get_option_flags_for_command(parts[0])
|
||
|
completer = WordCompleter(sorted(flags), WORD=True)
|
||
|
|
||
|
elif len(parts) == 1 and parts[0] in ('set-option', 'set-window-option'):
|
||
|
options = pymux.options if parts[0] == 'set-option' else pymux.window_options
|
||
|
|
||
|
completer = WordCompleter(sorted(options.keys()), sentence=True)
|
||
|
|
||
|
elif len(parts) == 2 and parts[0] in ('set-option', 'set-window-option'):
|
||
|
options = pymux.options if parts[0] == 'set-option' else pymux.window_options
|
||
|
|
||
|
option = options.get(parts[1])
|
||
|
if option:
|
||
|
completer = WordCompleter(sorted(option.get_all_values(pymux)), sentence=True)
|
||
|
|
||
|
elif len(parts) == 1 and parts[0] == 'select-layout':
|
||
|
completer = _layout_type_completer
|
||
|
|
||
|
elif len(parts) == 1 and parts[0] == 'send-keys':
|
||
|
completer = _keys_completer
|
||
|
|
||
|
elif parts[0] == 'bind-key':
|
||
|
if len(parts) == 1:
|
||
|
completer = _keys_completer
|
||
|
|
||
|
elif len(parts) == 2:
|
||
|
completer = _command_completer
|
||
|
|
||
|
# Recursive, for bind-key options.
|
||
|
if parts and parts[0] == 'bind-key' and len(parts) > 2:
|
||
|
for c in get_completions_for_parts(parts[2:], last_part, complete_event, pymux):
|
||
|
yield c
|
||
|
|
||
|
if completer:
|
||
|
for c in completer.get_completions(Document(last_part), complete_event):
|
||
|
yield c
|
||
|
|
||
|
|
||
|
class ShlexCompleter(Completer):
|
||
|
"""
|
||
|
Completer that can be used when the input is parsed with shlex.
|
||
|
"""
|
||
|
def __init__(self, get_completions_for_parts):
|
||
|
assert callable(get_completions_for_parts)
|
||
|
self.get_completions_for_parts = get_completions_for_parts
|
||
|
|
||
|
def get_completions(self, document, complete_event):
|
||
|
text = document.text_before_cursor
|
||
|
|
||
|
parts, part_start_pos = self.parse(text)
|
||
|
|
||
|
for c in self.get_completions_for_parts(parts[:-1], parts[-1], complete_event):
|
||
|
yield Completion(wrap_argument(parts[-1][:c.start_position] + c.text),
|
||
|
start_position=part_start_pos - len(document.text),
|
||
|
display=c.display,
|
||
|
display_meta=c.display_meta)
|
||
|
|
||
|
@classmethod
|
||
|
def parse(cls, text):
|
||
|
"""
|
||
|
Parse the given text. Returns a tuple:
|
||
|
(list_of_parts, start_pos_of_the_last_part).
|
||
|
"""
|
||
|
OUTSIDE, IN_DOUBLE, IN_SINGLE = 0, 1, 2
|
||
|
|
||
|
iterator = enumerate(text)
|
||
|
state = OUTSIDE
|
||
|
parts = []
|
||
|
current_part = ''
|
||
|
part_start_pos = 0
|
||
|
|
||
|
for i, c in iterator: # XXX: correctly handle empty strings.
|
||
|
if state == OUTSIDE:
|
||
|
if c.isspace():
|
||
|
# New part.
|
||
|
if current_part:
|
||
|
parts.append(current_part)
|
||
|
part_start_pos = i + 1
|
||
|
current_part = ''
|
||
|
elif c == '"':
|
||
|
state = IN_DOUBLE
|
||
|
elif c == "'":
|
||
|
state = IN_SINGLE
|
||
|
else:
|
||
|
current_part += c
|
||
|
|
||
|
elif state == IN_SINGLE:
|
||
|
if c == "'":
|
||
|
state = OUTSIDE
|
||
|
elif c == "\\":
|
||
|
next(iterator)
|
||
|
current_part += c
|
||
|
else:
|
||
|
current_part += c
|
||
|
|
||
|
elif state == IN_DOUBLE:
|
||
|
if c == '"':
|
||
|
state = OUTSIDE
|
||
|
elif c == "\\":
|
||
|
next(iterator)
|
||
|
current_part += c
|
||
|
else:
|
||
|
current_part += c
|
||
|
|
||
|
parts.append(current_part)
|
||
|
return parts, part_start_pos
|
||
|
|
||
|
|
||
|
# assert ShlexCompleter.parse('"hello" world') == (['hello', 'world'], 8)
|