Source code for xonsh.shells.ptk_shell.completer
"""Completer implementation to use with prompt_toolkit."""
import os
from prompt_toolkit.application.current import get_app
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import Completer, Completion
from xonsh.built_ins import XSH
from xonsh.completers.tools import RichCompletion
[docs]
class PromptToolkitCompleter(Completer):
"""Simple prompt_toolkit Completer object.
It just redirects requests to normal Xonsh completer.
"""
def __init__(self, completer, ctx, shell):
"""Takes instance of xonsh.completer.Completer, the xonsh execution
context, and the shell instance itself.
"""
self.completer = completer
self.ctx = ctx
self.shell = shell
self.hist_suggester = AutoSuggestFromHistory()
[docs]
def get_completions(self, document, complete_event):
"""Returns a generator for list of completions."""
env = XSH.env
should_complete = complete_event.completion_requested or env.get(
"UPDATE_COMPLETIONS_ON_KEYPRESS"
)
# Only generate completions when the user hits tab.
if not should_complete or self.completer is None:
return
# generate actual completions
line = document.current_line
endidx = document.cursor_position_col
line_ex = XSH.aliases.expand_alias(line, endidx)
begidx = line[:endidx].rfind(" ") + 1 if line[:endidx].rfind(" ") >= 0 else 0
prefix = line[begidx:endidx]
expand_offset = len(line_ex) - len(line)
multiline_text = document.text
cursor_index = document.cursor_position
if line != line_ex:
line_start = cursor_index - len(document.current_line_before_cursor)
multiline_text = (
multiline_text[:line_start]
+ line_ex
+ multiline_text[line_start + len(line) :]
)
cursor_index += expand_offset
# get normal completions
completions, plen = self.completer.complete(
prefix,
line_ex,
begidx + expand_offset,
endidx + expand_offset,
self.ctx,
multiline_text=multiline_text,
cursor_index=cursor_index,
)
# completions from auto suggest
sug_comp = None
if env.get("AUTO_SUGGEST") and env.get("AUTO_SUGGEST_IN_COMPLETIONS"):
sug_comp = self.suggestion_completion(document, line)
if sug_comp is None:
pass
elif len(completions) == 0:
plen = len(prefix)
completions = (sug_comp,)
else:
completions = set(completions)
completions.discard(sug_comp)
completions = (sug_comp,) + tuple(sorted(completions))
# reserve space, if needed.
if len(completions) <= 1:
pass
elif len(os.path.commonprefix(completions)) <= len(prefix):
self.reserve_space()
# Find common prefix (strip quoting)
c_prefix = os.path.commonprefix([a.strip("'\"") for a in completions])
# Find last split symbol, do not trim the last part
while c_prefix:
if c_prefix[-1] in r"/\.:@,":
break
c_prefix = c_prefix[:-1]
# yield completions
if sug_comp is None:
pre = min(document.cursor_position_col - begidx, len(c_prefix))
else:
pre = len(c_prefix)
for comp in completions:
# do not display quote
if isinstance(comp, RichCompletion):
# ptk doesn't render newlines. This can be removed once it is supported.
desc = (
comp.description.replace(os.linesep, " ")
if comp.description
else None
)
yield Completion(
comp,
-comp.prefix_len if comp.prefix_len is not None else -plen,
display=comp.display or comp[pre:].strip("'\""),
display_meta=desc,
style=comp.style or "",
)
elif isinstance(comp, Completion):
yield comp
else:
disp = comp[pre:].strip("'\"")
yield Completion(comp, -plen, display=disp)
[docs]
def suggestion_completion(self, document, line):
"""Provides a completion based on the current auto-suggestion."""
app = self.shell.prompter.app
sug = self.hist_suggester.get_suggestion(app.current_buffer, document)
if sug is None:
return None
comp, _, _ = sug.text.partition(" ")
_, _, prev = line.rpartition(" ")
return prev + comp
[docs]
def reserve_space(self):
"""Adjust the height for showing autocompletion menu."""
app = get_app()
render = app.renderer
window = None
try:
# this raises error sometimes when COMPLETE_WHILE_TYPE is enabled
window = app.layout.current_window
except Exception:
pass
if window and window.render_info:
h = window.render_info.content_height
r = XSH.env.get("COMPLETIONS_MENU_ROWS")
size = h + r
last_h = render._last_screen.height if render._last_screen else 0
last_h = max(render._min_available_height, last_h)
if last_h < size:
if render._last_screen:
render._last_screen.height = size