You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
319 lines
11 KiB
319 lines
11 KiB
from __future__ import unicode_literals
|
|
|
|
from prompt_toolkit.application.current import get_app
|
|
from prompt_toolkit.buffer import Buffer
|
|
from prompt_toolkit.enums import SYSTEM_BUFFER
|
|
from prompt_toolkit.filters import Condition, has_focus, has_completions, has_validation_error, emacs_mode, vi_mode, vi_navigation_mode, has_arg, to_filter
|
|
from prompt_toolkit.formatted_text import fragment_list_len, to_formatted_text
|
|
from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings, ConditionalKeyBindings
|
|
from prompt_toolkit.key_binding.vi_state import InputMode
|
|
from prompt_toolkit.keys import Keys
|
|
from prompt_toolkit.layout.containers import Window, ConditionalContainer
|
|
from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl, FormattedTextControl, UIControl, UIContent
|
|
from prompt_toolkit.layout.dimension import Dimension
|
|
from prompt_toolkit.layout.processors import BeforeInput
|
|
from prompt_toolkit.lexers import SimpleLexer
|
|
from prompt_toolkit.search import SearchDirection
|
|
|
|
__all__ = [
|
|
'ArgToolbar',
|
|
'CompletionsToolbar',
|
|
'FormattedTextToolbar',
|
|
'SearchToolbar',
|
|
'SystemToolbar',
|
|
'ValidationToolbar',
|
|
]
|
|
|
|
|
|
class FormattedTextToolbar(Window):
|
|
def __init__(self, text, **kw):
|
|
# The style needs to be applied to the toolbar as a whole, not just the
|
|
# `FormattedTextControl`.
|
|
style = kw.pop('style', '')
|
|
|
|
super(FormattedTextToolbar, self).__init__(
|
|
FormattedTextControl(text, **kw),
|
|
style=style,
|
|
dont_extend_height=True,
|
|
height=Dimension(min=1))
|
|
|
|
|
|
class SystemToolbar(object):
|
|
"""
|
|
Toolbar for a system prompt.
|
|
|
|
:param prompt: Prompt to be displayed to the user.
|
|
"""
|
|
def __init__(self, prompt='Shell command: ', enable_global_bindings=True):
|
|
self.prompt = prompt
|
|
self.enable_global_bindings = to_filter(enable_global_bindings)
|
|
|
|
self.system_buffer = Buffer(name=SYSTEM_BUFFER)
|
|
|
|
self._bindings = self._build_key_bindings()
|
|
|
|
self.buffer_control = BufferControl(
|
|
buffer=self.system_buffer,
|
|
lexer=SimpleLexer(style='class:system-toolbar.text'),
|
|
input_processors=[BeforeInput(
|
|
lambda: self.prompt, style='class:system-toolbar')],
|
|
key_bindings=self._bindings)
|
|
|
|
self.window = Window(
|
|
self.buffer_control, height=1,
|
|
style='class:system-toolbar')
|
|
|
|
self.container = ConditionalContainer(
|
|
content=self.window,
|
|
filter=has_focus(self.system_buffer))
|
|
|
|
def _get_display_before_text(self):
|
|
return [
|
|
('class:system-toolbar', 'Shell command: '),
|
|
('class:system-toolbar.text', self.system_buffer.text),
|
|
('', '\n'),
|
|
]
|
|
|
|
def _build_key_bindings(self):
|
|
focused = has_focus(self.system_buffer)
|
|
|
|
# Emacs
|
|
emacs_bindings = KeyBindings()
|
|
handle = emacs_bindings.add
|
|
|
|
@handle('escape', filter=focused)
|
|
@handle('c-g', filter=focused)
|
|
@handle('c-c', filter=focused)
|
|
def _(event):
|
|
" Hide system prompt. "
|
|
self.system_buffer.reset()
|
|
event.app.layout.focus_last()
|
|
|
|
@handle('enter', filter=focused)
|
|
def _(event):
|
|
" Run system command. "
|
|
event.app.run_system_command(
|
|
self.system_buffer.text,
|
|
display_before_text=self._get_display_before_text())
|
|
self.system_buffer.reset(append_to_history=True)
|
|
event.app.layout.focus_last()
|
|
|
|
# Vi.
|
|
vi_bindings = KeyBindings()
|
|
handle = vi_bindings.add
|
|
|
|
@handle('escape', filter=focused)
|
|
@handle('c-c', filter=focused)
|
|
def _(event):
|
|
" Hide system prompt. "
|
|
event.app.vi_state.input_mode = InputMode.NAVIGATION
|
|
self.system_buffer.reset()
|
|
event.app.layout.focus_last()
|
|
|
|
@handle('enter', filter=focused)
|
|
def _(event):
|
|
" Run system command. "
|
|
event.app.vi_state.input_mode = InputMode.NAVIGATION
|
|
event.app.run_system_command(
|
|
self.system_buffer.text,
|
|
display_before_text=self._get_display_before_text())
|
|
self.system_buffer.reset(append_to_history=True)
|
|
event.app.layout.focus_last()
|
|
|
|
# Global bindings. (Listen to these bindings, even when this widget is
|
|
# not focussed.)
|
|
global_bindings = KeyBindings()
|
|
handle = global_bindings.add
|
|
|
|
@handle(Keys.Escape, '!', filter= ~focused & emacs_mode, is_global=True)
|
|
def _(event):
|
|
" M-'!' will focus this user control. "
|
|
event.app.layout.focus(self.window)
|
|
|
|
@handle('!', filter=~focused & vi_mode & vi_navigation_mode, is_global=True)
|
|
def _(event):
|
|
" Focus. "
|
|
event.app.vi_state.input_mode = InputMode.INSERT
|
|
event.app.layout.focus(self.window)
|
|
|
|
return merge_key_bindings([
|
|
ConditionalKeyBindings(emacs_bindings, emacs_mode),
|
|
ConditionalKeyBindings(vi_bindings, vi_mode),
|
|
ConditionalKeyBindings(global_bindings, self.enable_global_bindings),
|
|
])
|
|
|
|
def __pt_container__(self):
|
|
return self.container
|
|
|
|
|
|
class ArgToolbar(object):
|
|
def __init__(self):
|
|
def get_formatted_text():
|
|
arg = get_app().key_processor.arg or ''
|
|
if arg == '-':
|
|
arg = '-1'
|
|
|
|
return [
|
|
('class:arg-toolbar', 'Repeat: '),
|
|
('class:arg-toolbar.text', arg),
|
|
]
|
|
|
|
self.window = Window(
|
|
FormattedTextControl(get_formatted_text),
|
|
height=1)
|
|
|
|
self.container = ConditionalContainer(
|
|
content=self.window,
|
|
filter=has_arg)
|
|
|
|
def __pt_container__(self):
|
|
return self.container
|
|
|
|
|
|
class SearchToolbar(object):
|
|
"""
|
|
:param vi_mode: Display '/' and '?' instead of I-search.
|
|
:param ignore_case: Search case insensitive.
|
|
"""
|
|
def __init__(self, search_buffer=None, vi_mode=False,
|
|
text_if_not_searching='', forward_search_prompt='I-search: ',
|
|
backward_search_prompt='I-search backward: ', ignore_case=False):
|
|
assert search_buffer is None or isinstance(search_buffer, Buffer)
|
|
|
|
if search_buffer is None:
|
|
search_buffer = Buffer()
|
|
|
|
@Condition
|
|
def is_searching():
|
|
return self.control in get_app().layout.search_links
|
|
|
|
def get_before_input():
|
|
if not is_searching():
|
|
return text_if_not_searching
|
|
elif self.control.searcher_search_state.direction == SearchDirection.BACKWARD:
|
|
return ('?' if vi_mode else backward_search_prompt)
|
|
else:
|
|
return ('/' if vi_mode else forward_search_prompt)
|
|
|
|
self.search_buffer = search_buffer
|
|
|
|
self.control = SearchBufferControl(
|
|
buffer=search_buffer,
|
|
input_processors=[BeforeInput(
|
|
get_before_input,
|
|
style='class:search-toolbar.prompt')],
|
|
lexer=SimpleLexer(
|
|
style='class:search-toolbar.text'),
|
|
ignore_case=ignore_case)
|
|
|
|
self.container = ConditionalContainer(
|
|
content=Window(
|
|
self.control,
|
|
height=1,
|
|
style='class:search-toolbar'),
|
|
filter=is_searching)
|
|
|
|
def __pt_container__(self):
|
|
return self.container
|
|
|
|
|
|
class _CompletionsToolbarControl(UIControl):
|
|
def create_content(self, width, height):
|
|
complete_state = get_app().current_buffer.complete_state
|
|
if complete_state:
|
|
completions = complete_state.completions
|
|
index = complete_state.complete_index # Can be None!
|
|
|
|
# Width of the completions without the left/right arrows in the margins.
|
|
content_width = width - 6
|
|
|
|
# Booleans indicating whether we stripped from the left/right
|
|
cut_left = False
|
|
cut_right = False
|
|
|
|
# Create Menu content.
|
|
fragments = []
|
|
|
|
for i, c in enumerate(completions):
|
|
# When there is no more place for the next completion
|
|
if fragment_list_len(fragments) + len(c.display_text) >= content_width:
|
|
# If the current one was not yet displayed, page to the next sequence.
|
|
if i <= (index or 0):
|
|
fragments = []
|
|
cut_left = True
|
|
# If the current one is visible, stop here.
|
|
else:
|
|
cut_right = True
|
|
break
|
|
|
|
fragments.extend(to_formatted_text(
|
|
c.display_text,
|
|
style=('class:completion-toolbar.completion.current'
|
|
if i == index else 'class:completion-toolbar.completion')
|
|
))
|
|
fragments.append(('', ' '))
|
|
|
|
# Extend/strip until the content width.
|
|
fragments.append(('', ' ' * (content_width - fragment_list_len(fragments))))
|
|
fragments = fragments[:content_width]
|
|
|
|
# Return fragments
|
|
all_fragments = [
|
|
('', ' '),
|
|
('class:completion-toolbar.arrow', '<' if cut_left else ' '),
|
|
('', ' '),
|
|
] + fragments + [
|
|
('', ' '),
|
|
('class:completion-toolbar.arrow', '>' if cut_right else ' '),
|
|
('', ' '),
|
|
]
|
|
else:
|
|
all_fragments = []
|
|
|
|
def get_line(i):
|
|
return all_fragments
|
|
|
|
return UIContent(get_line=get_line, line_count=1)
|
|
|
|
|
|
class CompletionsToolbar(object):
|
|
def __init__(self):
|
|
self.container = ConditionalContainer(
|
|
content=Window(
|
|
_CompletionsToolbarControl(),
|
|
height=1,
|
|
style='class:completion-toolbar'),
|
|
filter=has_completions)
|
|
|
|
def __pt_container__(self):
|
|
return self.container
|
|
|
|
|
|
class ValidationToolbar(object):
|
|
def __init__(self, show_position=False):
|
|
def get_formatted_text():
|
|
buff = get_app().current_buffer
|
|
|
|
if buff.validation_error:
|
|
row, column = buff.document.translate_index_to_position(
|
|
buff.validation_error.cursor_position)
|
|
|
|
if show_position:
|
|
text = '%s (line=%s column=%s)' % (
|
|
buff.validation_error.message, row + 1, column + 1)
|
|
else:
|
|
text = buff.validation_error.message
|
|
|
|
return [('class:validation-toolbar', text)]
|
|
else:
|
|
return []
|
|
|
|
self.control = FormattedTextControl(get_formatted_text)
|
|
|
|
self.container = ConditionalContainer(
|
|
content=Window(self.control, height=1),
|
|
filter=has_validation_error)
|
|
|
|
def __pt_container__(self):
|
|
return self.container
|