def enter(self, buffer: Buffer) -> bool: """The User has Accepted the LineBuffer. Read the Buffer, reset the line, and then forward the text to the Execute Function. """ self.handler.completion = "" command: str = buffer.text buffer.reset(append_to_history=not command.startswith(" ")) self.execute(command) return False
def _accept_handler(self, buffer: Buffer) -> None: """Visit the tile at the given coordinates.""" # The validator has already checked that these coordinates are valid. coordinates = buffer.document.text tile = self.maze_grid.get_from_user_string(coordinates) tile.visit() if self.maze_grid.check_complete(): self.multi_screen.app.exit() else: buffer.reset()
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 BufferTest(unittest.TestCase): def setUp(self): self.buffer = Buffer() def test_initial(self): self.assertEqual(self.buffer.text, '') self.assertEqual(self.buffer.cursor_position, 0) def test_insert_text(self): self.buffer.insert_text('some_text') self.assertEqual(self.buffer.text, 'some_text') self.assertEqual(self.buffer.cursor_position, len('some_text')) def test_cursor_movement(self): self.buffer.insert_text('some_text') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.cursor_right() self.buffer.insert_text('A') self.assertEqual(self.buffer.text, 'some_teAxt') self.assertEqual(self.buffer.cursor_position, len('some_teA')) def test_backspace(self): self.buffer.insert_text('some_text') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.delete_before_cursor() self.assertEqual(self.buffer.text, 'some_txt') self.assertEqual(self.buffer.cursor_position, len('some_t')) def test_cursor_up(self): # Cursor up to a line thats longer. self.buffer.insert_text('long line1\nline2') self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) # Going up when already at the top. self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) # Going up to a line that's shorter. self.buffer.reset() self.buffer.insert_text('line1\nlong line2') self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) def test_cursor_down(self): self.buffer.insert_text('line1\nline2') self.buffer.cursor_position = 3 # Normally going down self.buffer.cursor_down() self.assertEqual(self.buffer.document.cursor_position, len('line1\nlin')) # Going down to a line that's storter. self.buffer.reset() self.buffer.insert_text('long line1\na\nb') self.buffer.cursor_position = 3 self.buffer.cursor_down() self.assertEqual(self.buffer.document.cursor_position, len('long line1\na')) def test_join_next_line(self): self.buffer.insert_text('line1\nline2\nline3') self.buffer.cursor_up() self.buffer.join_next_line() self.assertEqual(self.buffer.text, 'line1\nline2 line3') # Test when there is no '\n' in the text self.buffer.reset() self.buffer.insert_text('line1') self.buffer.cursor_position = 0 self.buffer.join_next_line() self.assertEqual(self.buffer.text, 'line1') def test_newline(self): self.buffer.insert_text('hello world') self.buffer.newline() self.assertEqual(self.buffer.text, 'hello world\n') def test_swap_characters_before_cursor(self): self.buffer.insert_text('hello world') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.swap_characters_before_cursor() self.assertEqual(self.buffer.text, 'hello wrold')
class PythonInput(object): """ Prompt for reading Python input. :: python_input = PythonInput(...) python_code = python_input.run() """ def __init__( self, get_globals=None, get_locals=None, history_filename=None, vi_mode=False, input=None, output=None, color_depth=None, # For internal use. extra_key_bindings=None, _completer=None, _validator=None, _lexer=None, _extra_buffer_processors=None, _extra_layout_body=None, _extra_toolbars=None, _input_buffer_height=None): self.get_globals = get_globals or (lambda: {}) self.get_locals = get_locals or self.get_globals self._completer = _completer or PythonCompleter( self.get_globals, self.get_locals) self._validator = _validator or PythonValidator( self.get_compiler_flags) self._lexer = _lexer or PygmentsLexer(PythonLexer) if history_filename: self.history = ThreadedHistory(FileHistory(history_filename)) else: self.history = InMemoryHistory() self._input_buffer_height = _input_buffer_height self._extra_layout_body = _extra_layout_body or [] self._extra_toolbars = _extra_toolbars or [] self._extra_buffer_processors = _extra_buffer_processors or [] self.extra_key_bindings = extra_key_bindings or KeyBindings() # Settings. self.show_signature = False self.show_docstring = False self.show_meta_enter_message = True self.completion_visualisation = CompletionVisualisation.MULTI_COLUMN self.completion_menu_scroll_offset = 1 self.show_line_numbers = False self.show_status_bar = True self.wrap_lines = True self.complete_while_typing = True self.paste_mode = False # When True, don't insert whitespace after newline. self.confirm_exit = True # Ask for confirmation when Control-D is pressed. self.accept_input_on_enter = 2 # Accept when pressing Enter 'n' times. # 'None' means that meta-enter is always required. self.enable_open_in_editor = True self.enable_system_bindings = True self.enable_input_validation = True self.enable_auto_suggest = False self.enable_mouse_support = False self.enable_history_search = False # When True, like readline, going # back in history will filter the # history on the records starting # with the current input. self.enable_syntax_highlighting = True self.highlight_matching_parenthesis = False self.show_sidebar = False # Currently show the sidebar. self.show_sidebar_help = True # When the sidebar is visible, also show the help text. self.show_exit_confirmation = False # Currently show 'Do you really want to exit?' self.terminal_title = None # The title to be displayed in the terminal. (None or string.) self.exit_message = 'Do you really want to exit?' self.insert_blank_line_after_output = True # (For the REPL.) # The buffers. self.default_buffer = self._create_buffer() self.search_buffer = Buffer() self.docstring_buffer = Buffer(read_only=True) # Tokens to be shown at the prompt. self.prompt_style = 'classic' # The currently active style. self.all_prompt_styles = { # Styles selectable from the menu. 'ipython': IPythonPrompt(self), 'classic': ClassicPrompt(), } self.get_input_prompt = lambda: \ self.all_prompt_styles[self.prompt_style].in_prompt() self.get_output_prompt = lambda: \ self.all_prompt_styles[self.prompt_style].out_prompt() #: Load styles. self.code_styles = get_all_code_styles() self.ui_styles = get_all_ui_styles() self._current_code_style_name = 'default' self._current_ui_style_name = 'default' if is_windows(): self._current_code_style_name = 'win32' self._current_style = self._generate_style() # Options to be configurable from the sidebar. self.options = self._create_options() self.selected_option_index = 0 #: Incremeting integer counting the current statement. self.current_statement_index = 1 # Code signatures. (This is set asynchronously after a timeout.) self.signatures = [] # Boolean indicating whether we have a signatures thread running. # (Never run more than one at the same time.) self._get_signatures_thread_running = False self.output = output or create_output() self.input = input or create_input(sys.stdin) self.app = self._create_application(color_depth) if vi_mode: self.app.editing_mode = EditingMode.VI def _accept_handler(self, buff): app = get_app() app.exit(result=buff.text) app.pre_run_callables.append(buff.reset) @property def option_count(self): " Return the total amount of options. (In all categories together.) " return sum(len(category.options) for category in self.options) @property def selected_option(self): " Return the currently selected option. " i = 0 for category in self.options: for o in category.options: if i == self.selected_option_index: return o else: i += 1 def get_compiler_flags(self): """ Give the current compiler flags by looking for _Feature instances in the globals. """ flags = 0 for value in self.get_globals().values(): if isinstance(value, __future__._Feature): flags |= value.compiler_flag return flags @property def add_key_binding(self): """ Shortcut for adding new key bindings. (Mostly useful for a .ptpython/config.py file, that receives a PythonInput/Repl instance as input.) :: @python_input.add_key_binding(Keys.ControlX, filter=...) def handler(event): ... """ def add_binding_decorator(*k, **kw): return self.extra_key_bindings.add(*k, **kw) return add_binding_decorator def install_code_colorscheme(self, name, style_dict): """ Install a new code color scheme. """ assert isinstance(name, six.text_type) assert isinstance(style_dict, dict) self.code_styles[name] = style_dict def use_code_colorscheme(self, name): """ Apply new colorscheme. (By name.) """ assert name in self.code_styles self._current_code_style_name = name self._current_style = self._generate_style() def install_ui_colorscheme(self, name, style_dict): """ Install a new UI color scheme. """ assert isinstance(name, six.text_type) assert isinstance(style_dict, dict) self.ui_styles[name] = style_dict def use_ui_colorscheme(self, name): """ Apply new colorscheme. (By name.) """ assert name in self.ui_styles self._current_ui_style_name = name self._current_style = self._generate_style() def _use_color_depth(self, depth): get_app()._color_depth = depth def _generate_style(self): """ Create new Style instance. (We don't want to do this on every key press, because each time the renderer receives a new style class, he will redraw everything.) """ return generate_style(self.code_styles[self._current_code_style_name], self.ui_styles[self._current_ui_style_name]) def _create_options(self): """ Create a list of `Option` instances for the options sidebar. """ def enable(attribute, value=True): setattr(self, attribute, value) # Return `True`, to be able to chain this in the lambdas below. return True def disable(attribute): setattr(self, attribute, False) return True def simple_option(title, description, field_name, values=None): " Create Simple on/of option. " values = values or ['off', 'on'] def get_current_value(): return values[bool(getattr(self, field_name))] def get_values(): return { values[1]: lambda: enable(field_name), values[0]: lambda: disable(field_name), } return Option(title=title, description=description, get_values=get_values, get_current_value=get_current_value) return [ OptionCategory('Input', [ simple_option(title='Editing mode', description='Vi or emacs key bindings.', field_name='vi_mode', values=[EditingMode.EMACS, EditingMode.VI]), simple_option( title='Paste mode', description="When enabled, don't indent automatically.", field_name='paste_mode'), Option( title='Complete while typing', description= "Generate autocompletions automatically while typing. " 'Don\'t require pressing TAB. (Not compatible with "History search".)', get_current_value=lambda: ['off', 'on'][ self.complete_while_typing], get_values=lambda: { 'on': lambda: enable('complete_while_typing') and disable( 'enable_history_search'), 'off': lambda: disable('complete_while_typing'), }), Option( title='History search', description= 'When pressing the up-arrow, filter the history on input starting ' 'with the current text. (Not compatible with "Complete while typing".)', get_current_value=lambda: ['off', 'on'][ self.enable_history_search], get_values=lambda: { 'on': lambda: enable('enable_history_search') and disable( 'complete_while_typing'), 'off': lambda: disable('enable_history_search'), }), simple_option( title='Mouse support', description= 'Respond to mouse clicks and scrolling for positioning the cursor, ' 'selecting text and scrolling through windows.', field_name='enable_mouse_support'), simple_option(title='Confirm on exit', description='Require confirmation when exiting.', field_name='confirm_exit'), simple_option( title='Input validation', description= 'In case of syntax errors, move the cursor to the error ' 'instead of showing a traceback of a SyntaxError.', field_name='enable_input_validation'), simple_option( title='Auto suggestion', description='Auto suggest inputs by looking at the history. ' 'Pressing right arrow or Ctrl-E will complete the entry.', field_name='enable_auto_suggest'), Option( title='Accept input on enter', description= 'Amount of ENTER presses required to execute input when the cursor ' 'is at the end of the input. (Note that META+ENTER will always execute.)', get_current_value=lambda: str(self.accept_input_on_enter or 'meta-enter'), get_values=lambda: { '2': lambda: enable('accept_input_on_enter', 2), '3': lambda: enable('accept_input_on_enter', 3), '4': lambda: enable('accept_input_on_enter', 4), 'meta-enter': lambda: enable('accept_input_on_enter', None), }), ]), OptionCategory('Display', [ Option( title='Completions', description= 'Visualisation to use for displaying the completions. (Multiple columns, one column, a toolbar or nothing.)', get_current_value=lambda: self.completion_visualisation, get_values=lambda: { CompletionVisualisation.NONE: lambda: enable('completion_visualisation', CompletionVisualisation.NONE), CompletionVisualisation.POP_UP: lambda: enable('completion_visualisation', CompletionVisualisation.POP_UP), CompletionVisualisation.MULTI_COLUMN: lambda: enable('completion_visualisation', CompletionVisualisation.MULTI_COLUMN), CompletionVisualisation.TOOLBAR: lambda: enable('completion_visualisation', CompletionVisualisation.TOOLBAR), }), Option(title='Prompt', description= "Visualisation of the prompt. ('>>>' or 'In [1]:')", get_current_value=lambda: self.prompt_style, get_values=lambda: dict( (s, partial(enable, 'prompt_style', s)) for s in self.all_prompt_styles)), simple_option( title='Blank line after output', description='Insert a blank line after the output.', field_name='insert_blank_line_after_output'), simple_option(title='Show signature', description='Display function signatures.', field_name='show_signature'), simple_option(title='Show docstring', description='Display function docstrings.', field_name='show_docstring'), simple_option( title='Show line numbers', description= 'Show line numbers when the input consists of multiple lines.', field_name='show_line_numbers'), simple_option( title='Show Meta+Enter message', description= 'Show the [Meta+Enter] message when this key combination is required to execute commands. ' + '(This is the case when a simple [Enter] key press will insert a newline.', field_name='show_meta_enter_message'), simple_option( title='Wrap lines', description='Wrap lines instead of scrolling horizontally.', field_name='wrap_lines'), simple_option( title='Show status bar', description= 'Show the status bar at the bottom of the terminal.', field_name='show_status_bar'), simple_option( title='Show sidebar help', description= 'When the sidebar is visible, also show this help text.', field_name='show_sidebar_help'), simple_option( title='Highlight parenthesis', description= 'Highlight matching parenthesis, when the cursor is on or right after one.', field_name='highlight_matching_parenthesis'), ]), OptionCategory('Colors', [ simple_option(title='Syntax highlighting', description='Use colors for syntax highligthing', field_name='enable_syntax_highlighting'), Option(title='Code', description='Color scheme to use for the Python code.', get_current_value=lambda: self._current_code_style_name, get_values=lambda: dict( (name, partial(self.use_code_colorscheme, name)) for name in self.code_styles)), Option( title='User interface', description='Color scheme to use for the user interface.', get_current_value=lambda: self._current_ui_style_name, get_values=lambda: dict( (name, partial(self.use_ui_colorscheme, name)) for name in self.ui_styles)), Option( title='Color depth', description= 'Monochrome (1 bit), 16 ANSI colors (4 bit),\n256 colors (8 bit), or 24 bit.', get_current_value=lambda: COLOR_DEPTHS[get_app(). color_depth], get_values=lambda: dict( (name, partial(self._use_color_depth, depth)) for depth, name in COLOR_DEPTHS.items())), ]), ] def _create_application(self, color_depth): """ Create an `Application` instance. """ return Application( input=self.input, output=self.output, layout=create_layout( self, lexer=DynamicLexer( lambda: self._lexer if self.enable_syntax_highlighting else SimpleLexer()), input_buffer_height=self._input_buffer_height, extra_buffer_processors=self._extra_buffer_processors, extra_body=self._extra_layout_body, extra_toolbars=self._extra_toolbars), key_bindings=merge_key_bindings([ load_python_bindings(self), load_sidebar_bindings(self), load_confirm_exit_bindings(self), # Extra key bindings should not be active when the sidebar is visible. ConditionalKeyBindings( self.extra_key_bindings, Condition(lambda: not self.show_sidebar)) ]), color_depth=color_depth, paste_mode=Condition(lambda: self.paste_mode), mouse_support=Condition(lambda: self.enable_mouse_support), style=DynamicStyle(lambda: self._current_style), include_default_pygments_style=False, reverse_vi_search_direction=True) def _create_buffer(self): """ Create the `Buffer` for the Python input. """ python_buffer = Buffer( name=DEFAULT_BUFFER, complete_while_typing=Condition( lambda: self.complete_while_typing), enable_history_search=Condition( lambda: self.enable_history_search), tempfile_suffix='.py', history=self.history, completer=ThreadedCompleter(self._completer), validator=ConditionalValidator( self._validator, Condition(lambda: self.enable_input_validation)), auto_suggest=ConditionalAutoSuggest( ThreadedAutoSuggest(AutoSuggestFromHistory()), Condition(lambda: self.enable_auto_suggest)), accept_handler=self._accept_handler, on_text_changed=self._on_input_timeout) return python_buffer @property def editing_mode(self): return self.app.editing_mode @editing_mode.setter def editing_mode(self, value): self.app.editing_mode = value @property def vi_mode(self): return self.editing_mode == EditingMode.VI @vi_mode.setter def vi_mode(self, value): if value: self.editing_mode = EditingMode.VI else: self.editing_mode = EditingMode.EMACS def _on_input_timeout(self, buff): """ When there is no input activity, in another thread, get the signature of the current code. """ assert isinstance(buff, Buffer) app = self.app # Never run multiple get-signature threads. if self._get_signatures_thread_running: return self._get_signatures_thread_running = True document = buff.document def run(): script = get_jedi_script_from_document(document, self.get_locals(), self.get_globals()) # Show signatures in help text. if script: try: signatures = script.call_signatures() except ValueError: # e.g. in case of an invalid \\x escape. signatures = [] except Exception: # Sometimes we still get an exception (TypeError), because # of probably bugs in jedi. We can silence them. # See: https://github.com/davidhalter/jedi/issues/492 signatures = [] else: # Try to access the params attribute just once. For Jedi # signatures containing the keyword-only argument star, # this will crash when retrieving it the first time with # AttributeError. Every following time it works. # See: https://github.com/jonathanslenders/ptpython/issues/47 # https://github.com/davidhalter/jedi/issues/598 try: if signatures: signatures[0].params except AttributeError: pass else: signatures = [] self._get_signatures_thread_running = False # Set signatures and redraw if the text didn't change in the # meantime. Otherwise request new signatures. if buff.text == document.text: self.signatures = signatures # Set docstring in docstring buffer. if signatures: string = signatures[0].docstring() if not isinstance(string, six.text_type): string = string.decode('utf-8') self.docstring_buffer.reset( document=Document(string, cursor_position=0)) else: self.docstring_buffer.reset() app.invalidate() else: self._on_input_timeout(buff) get_event_loop().run_in_executor(run) def on_reset(self): self.signatures = [] def enter_history(self): """ Display the history. """ app = get_app() app.vi_state.input_mode = InputMode.NAVIGATION def done(f): result = f.result() if result is not None: self.default_buffer.text = result app.vi_state.input_mode = InputMode.INSERT history = History(self, self.default_buffer.document) future = run_coroutine_in_terminal(history.app.run_async) future.add_done_callback(done)
class Editor(object): """ The main class. Containing the whole editor. :param config_directory: Place where configuration is stored. :param input: (Optionally) `prompt_toolkit.input.Input` object. :param output: (Optionally) `prompt_toolkit.output.Output` object. """ def __init__(self, config_directory='~/.pyvim', input=None, output=None): self.input = input self.output = output # Vi options. self.show_line_numbers = True self.highlight_search = True self.paste_mode = False self.show_ruler = True self.show_wildmenu = True self.expand_tab = True # Insect spaces instead of tab characters. self.tabstop = 4 # Number of spaces that a tab character represents. self.incsearch = True # Show matches while typing search string. self.ignore_case = False # Ignore case while searching. self.enable_mouse_support = True self.display_unprintable_characters = True # ':set list' self.enable_jedi = True # ':set jedi', for Python Jedi completion. self.scroll_offset = 0 # ':set scrolloff' self.relative_number = False # ':set relativenumber' self.wrap_lines = True # ':set wrap' self.cursorline = False # ':set cursorline' self.cursorcolumn = False # ':set cursorcolumn' self.colorcolumn = [] # ':set colorcolumn'. List of integers. # Ensure config directory exists. self.config_directory = os.path.abspath( os.path.expanduser(config_directory)) if not os.path.exists(self.config_directory): os.mkdir(self.config_directory) self.window_arrangement = WindowArrangement(self) self.message = None # Load styles. (Mapping from name to Style class.) self.styles = generate_built_in_styles() self.current_style = get_editor_style_by_name('vim') # I/O backends. self.io_backends = [ DirectoryIO(), HttpIO(), GZipFileIO(), # Should come before FileIO. FileIO(), ] # Create history and search buffers. def handle_action(buff): ' When enter is pressed in the Vi command line. ' text = buff.text # Remember: leave_command_mode resets the buffer. # First leave command mode. We want to make sure that the working # pane is focussed again before executing the command handlers. self.leave_command_mode(append_to_history=True) # Execute command. handle_command(self, text) commands_history = FileHistory( os.path.join(self.config_directory, 'commands_history')) self.command_buffer = Buffer(accept_handler=handle_action, enable_history_search=True, completer=create_command_completer(self), history=commands_history, multiline=False) search_buffer_history = FileHistory( os.path.join(self.config_directory, 'search_history')) self.search_buffer = Buffer(history=search_buffer_history, enable_history_search=True, multiline=False) # Create key bindings registry. self.key_bindings = create_key_bindings(self) # Create layout and CommandLineInterface instance. self.editor_layout = EditorLayout(self, self.window_arrangement) self.application = self._create_application() # Hide message when a key is pressed. def key_pressed(_): self.message = None self.application.key_processor.before_key_press += key_pressed # Command line previewer. self.previewer = CommandPreviewer(self) def load_initial_files(self, locations, in_tab_pages=False, hsplit=False, vsplit=False): """ Load a list of files. """ assert in_tab_pages + hsplit + vsplit <= 1 # Max one of these options. # When no files were given, open at least one empty buffer. locations2 = locations or [None] # First file self.window_arrangement.open_buffer(locations2[0]) for f in locations2[1:]: if in_tab_pages: self.window_arrangement.create_tab(f) elif hsplit: self.window_arrangement.hsplit(location=f) elif vsplit: self.window_arrangement.vsplit(location=f) else: self.window_arrangement.open_buffer(f) self.window_arrangement.active_tab_index = 0 if locations and len(locations) > 1: self.show_message('%i files loaded.' % len(locations)) def _create_application(self): """ Create CommandLineInterface instance. """ # Create Application. application = Application( input=self.input, output=self.output, editing_mode=EditingMode.VI, layout=self.editor_layout.layout, key_bindings=self.key_bindings, # get_title=lambda: get_terminal_title(self), style=DynamicStyle(lambda: self.current_style), paste_mode=Condition(lambda: self.paste_mode), # ignore_case=Condition(lambda: self.ignore_case), # TODO include_default_pygments_style=False, mouse_support=Condition(lambda: self.enable_mouse_support), full_screen=True, enable_page_navigation_bindings=True) # Handle command line previews. # (e.g. when typing ':colorscheme blue', it should already show the # preview before pressing enter.) def preview(_): if self.application.layout.has_focus(self.command_buffer): self.previewer.preview(self.command_buffer.text) self.command_buffer.on_text_changed += preview return application @property def current_editor_buffer(self): """ Return the `EditorBuffer` that is currently active. """ # For each buffer name on the focus stack. for current_buffer_name in self.application.buffers.focus_stack: if current_buffer_name is not None: # Find/return the EditorBuffer with this name. for b in self.window_arrangement.editor_buffers: if b.buffer_name == current_buffer_name: return b @property def add_key_binding(self): """ Shortcut for adding new key bindings. (Mostly useful for a pyvimrc file, that receives this Editor instance as input.) """ return self.key_bindings.add def show_message(self, message): """ Set a warning message. The layout will render it as a "pop-up" at the bottom. """ self.message = message def use_colorscheme(self, name='default'): """ Apply new colorscheme. (By name.) """ try: self.current_style = get_editor_style_by_name(name) except pygments.util.ClassNotFound: pass def sync_with_prompt_toolkit(self): """ Update the prompt-toolkit Layout and FocusStack. """ # After executing a command, make sure that the layout of # prompt-toolkit matches our WindowArrangement. self.editor_layout.update() # Make sure that the focus stack of prompt-toolkit has the current # page. window = self.window_arrangement.active_pt_window if window: self.application.layout.focus(window) def show_help(self): """ Show help in new window. """ self.window_arrangement.hsplit(text=HELP_TEXT) self.sync_with_prompt_toolkit() # Show new window. def run(self): """ Run the event loop for the interface. This starts the interaction. """ # Make sure everything is in sync, before starting. self.sync_with_prompt_toolkit() def pre_run(): # Start in navigation mode. self.application.vi_state.input_mode = InputMode.NAVIGATION # Run eventloop of prompt_toolkit. self.application.run(pre_run=pre_run) def enter_command_mode(self): """ Go into command mode. """ self.application.layout.focus(self.command_buffer) self.application.vi_state.input_mode = InputMode.INSERT self.previewer.save() def leave_command_mode(self, append_to_history=False): """ Leave command mode. Focus document window again. """ self.previewer.restore() self.application.layout.focus_last() self.application.vi_state.input_mode = InputMode.NAVIGATION self.command_buffer.reset(append_to_history=append_to_history)
class SystemToolbar: """ Toolbar for a system prompt. :param prompt: Prompt to be displayed to the user. """ def __init__( self, prompt: AnyFormattedText = "Shell command: ", enable_global_bindings: FilterOrBool = True, ) -> None: 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) -> StyleAndTextTuples: return [ ("class:system-toolbar", "Shell command: "), ("class:system-toolbar.text", self.system_buffer.text), ("", "\n"), ] def _build_key_bindings(self) -> KeyBindingsBase: 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 _cancel(event: E) -> None: " Hide system prompt. " self.system_buffer.reset() event.app.layout.focus_last() @handle("enter", filter=focused) async def _accept(event: E) -> None: " Run system command. " await 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 _cancel_vi(event: E) -> None: " Hide system prompt. " event.app.vi_state.input_mode = InputMode.NAVIGATION self.system_buffer.reset() event.app.layout.focus_last() @handle("enter", filter=focused) async def _accept_vi(event: E) -> None: " 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 _focus_me(event: E) -> None: " M-'!' will focus this user control. " event.app.layout.focus(self.window) @handle("!", filter=~focused & vi_mode & vi_navigation_mode, is_global=True) def _focus_me_vi(event: E) -> None: " 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) -> Container: return self.container
class BufferTest(unittest.TestCase): def setUp(self): self.buffer = Buffer() def test_initial(self): self.assertEqual(self.buffer.text, '') self.assertEqual(self.buffer.cursor_position, 0) def test_insert_text(self): self.buffer.insert_text('some_text') self.assertEqual(self.buffer.text, 'some_text') self.assertEqual(self.buffer.cursor_position, len('some_text')) def test_cursor_movement(self): self.buffer.insert_text('some_text') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.cursor_right() self.buffer.insert_text('A') self.assertEqual(self.buffer.text, 'some_teAxt') self.assertEqual(self.buffer.cursor_position, len('some_teA')) def test_backspace(self): self.buffer.insert_text('some_text') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.delete_before_cursor() self.assertEqual(self.buffer.text, 'some_txt') self.assertEqual(self.buffer.cursor_position, len('some_t')) def test_cursor_up(self): # Cursor up to a line thats longer. self.buffer.insert_text('long line1\nline2') self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) # Going up when already at the top. self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) # Going up to a line that's shorter. self.buffer.reset() self.buffer.insert_text('line1\nlong line2') self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) def test_cursor_down(self): self.buffer.insert_text('line1\nline2') self.buffer.cursor_position = 3 # Normally going down self.buffer.cursor_down() self.assertEqual(self.buffer.document.cursor_position, len('line1\nlin')) # Going down to a line that's storter. self.buffer.reset() self.buffer.insert_text('long line1\na\nb') self.buffer.cursor_position = 3 self.buffer.cursor_down() self.assertEqual(self.buffer.document.cursor_position, len('long line1\na')) def test_join_next_line(self): self.buffer.insert_text('line1\nline2\nline3') self.buffer.cursor_up() self.buffer.join_next_line() self.assertEqual(self.buffer.text, 'line1\nline2line3') # Test when there is no '\n' in the text self.buffer.reset() self.buffer.insert_text('line1') self.buffer.cursor_position = 0 self.buffer.join_next_line() self.assertEqual(self.buffer.text, 'line1') def test_newline(self): self.buffer.insert_text('hello world') self.buffer.newline() self.assertEqual(self.buffer.text, 'hello world\n') def test_swap_characters_before_cursor(self): self.buffer.insert_text('hello world') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.swap_characters_before_cursor() self.assertEqual(self.buffer.text, 'hello wrold')
show_window = Window(content=BufferControl(buffer=show_buffer, focus_on_click=True), ) control_window = Window(content=BufferControl(buffer=control_buffer, focus_on_click=True), height=5) root_container = HSplit([ # Display the text 'Hello world' on the right. show_window, # A vertical line in the middle. We explicitly specify the width, to # make sure that the layout engine will not try to divide the whole # width by three for all these windows. The window will simply fill its # content by repeating this character. Window(char='─', height=1), # One window that holds the BufferControl with the default buffer on # the left. control_window, ]) t = '' for i in range(99): t += '<style bg="blue" fg="white">' + str(i) + '</style>' + '\n' show_buffer.reset(Document(HTML(t, ))) layout = Layout(root_container) app = Application(layout=layout, full_screen=True, key_bindings=kb) app.run()
class PythonInput(object): """ Prompt for reading Python input. :: python_input = PythonInput(...) python_code = python_input.app.run() """ def __init__(self, get_globals=None, get_locals=None, history_filename=None, vi_mode=False, input=None, output=None, color_depth=None, # For internal use. extra_key_bindings=None, _completer=None, _validator=None, _lexer=None, _extra_buffer_processors=None, _extra_layout_body=None, _extra_toolbars=None, _input_buffer_height=None): self.get_globals = get_globals or (lambda: {}) self.get_locals = get_locals or self.get_globals self._completer = _completer or PythonCompleter(self.get_globals, self.get_locals) self._validator = _validator or PythonValidator(self.get_compiler_flags) self._lexer = _lexer or PygmentsLexer(PythonLexer) if history_filename: self.history = ThreadedHistory(FileHistory(history_filename)) else: self.history = InMemoryHistory() self._input_buffer_height = _input_buffer_height self._extra_layout_body = _extra_layout_body or [] self._extra_toolbars = _extra_toolbars or [] self._extra_buffer_processors = _extra_buffer_processors or [] self.extra_key_bindings = extra_key_bindings or KeyBindings() # Settings. self.show_signature = False self.show_docstring = False self.show_meta_enter_message = True self.completion_visualisation = CompletionVisualisation.MULTI_COLUMN self.completion_menu_scroll_offset = 1 self.show_line_numbers = False self.show_status_bar = True self.wrap_lines = True self.complete_while_typing = True self.paste_mode = False # When True, don't insert whitespace after newline. self.confirm_exit = True # Ask for confirmation when Control-D is pressed. self.accept_input_on_enter = 2 # Accept when pressing Enter 'n' times. # 'None' means that meta-enter is always required. self.enable_open_in_editor = True self.enable_system_bindings = True self.enable_input_validation = True self.enable_auto_suggest = False self.enable_mouse_support = False self.enable_history_search = False # When True, like readline, going # back in history will filter the # history on the records starting # with the current input. self.enable_syntax_highlighting = True self.swap_light_and_dark = False self.highlight_matching_parenthesis = False self.show_sidebar = False # Currently show the sidebar. self.show_sidebar_help = True # When the sidebar is visible, also show the help text. self.show_exit_confirmation = False # Currently show 'Do you really want to exit?' self.terminal_title = None # The title to be displayed in the terminal. (None or string.) self.exit_message = 'Do you really want to exit?' self.insert_blank_line_after_output = True # (For the REPL.) # The buffers. self.default_buffer = self._create_buffer() self.search_buffer = Buffer() self.docstring_buffer = Buffer(read_only=True) # Tokens to be shown at the prompt. self.prompt_style = 'classic' # The currently active style. self.all_prompt_styles = { # Styles selectable from the menu. 'ipython': IPythonPrompt(self), 'classic': ClassicPrompt(), } self.get_input_prompt = lambda: \ self.all_prompt_styles[self.prompt_style].in_prompt() self.get_output_prompt = lambda: \ self.all_prompt_styles[self.prompt_style].out_prompt() #: Load styles. self.code_styles = get_all_code_styles() self.ui_styles = get_all_ui_styles() self._current_code_style_name = 'default' self._current_ui_style_name = 'default' if is_windows(): self._current_code_style_name = 'win32' self._current_style = self._generate_style() self.color_depth = color_depth or ColorDepth.default() self.max_brightness = 1.0 self.min_brightness = 0.0 # Options to be configurable from the sidebar. self.options = self._create_options() self.selected_option_index = 0 #: Incremeting integer counting the current statement. self.current_statement_index = 1 # Code signatures. (This is set asynchronously after a timeout.) self.signatures = [] # Boolean indicating whether we have a signatures thread running. # (Never run more than one at the same time.) self._get_signatures_thread_running = False self.output = output or create_output() self.input = input or create_input(sys.stdin) self.style_transformation = merge_style_transformations([ ConditionalStyleTransformation( SwapLightAndDarkStyleTransformation(), filter=Condition(lambda: self.swap_light_and_dark)), AdjustBrightnessStyleTransformation( lambda: self.min_brightness, lambda: self.max_brightness), ]) self.ptpython_layout = PtPythonLayout( self, lexer=DynamicLexer( lambda: self._lexer if self.enable_syntax_highlighting else SimpleLexer()), input_buffer_height=self._input_buffer_height, extra_buffer_processors=self._extra_buffer_processors, extra_body=self._extra_layout_body, extra_toolbars=self._extra_toolbars) self.app = self._create_application() if vi_mode: self.app.editing_mode = EditingMode.VI def _accept_handler(self, buff): app = get_app() app.exit(result=buff.text) app.pre_run_callables.append(buff.reset) return True # Keep text, we call 'reset' later on. @property def option_count(self): " Return the total amount of options. (In all categories together.) " return sum(len(category.options) for category in self.options) @property def selected_option(self): " Return the currently selected option. " i = 0 for category in self.options: for o in category.options: if i == self.selected_option_index: return o else: i += 1 def get_compiler_flags(self): """ Give the current compiler flags by looking for _Feature instances in the globals. """ flags = 0 for value in self.get_globals().values(): if isinstance(value, __future__._Feature): flags |= value.compiler_flag return flags @property def add_key_binding(self): """ Shortcut for adding new key bindings. (Mostly useful for a .ptpython/config.py file, that receives a PythonInput/Repl instance as input.) :: @python_input.add_key_binding(Keys.ControlX, filter=...) def handler(event): ... """ def add_binding_decorator(*k, **kw): return self.extra_key_bindings.add(*k, **kw) return add_binding_decorator def install_code_colorscheme(self, name, style_dict): """ Install a new code color scheme. """ assert isinstance(name, six.text_type) assert isinstance(style_dict, dict) self.code_styles[name] = style_dict def use_code_colorscheme(self, name): """ Apply new colorscheme. (By name.) """ assert name in self.code_styles self._current_code_style_name = name self._current_style = self._generate_style() def install_ui_colorscheme(self, name, style_dict): """ Install a new UI color scheme. """ assert isinstance(name, six.text_type) assert isinstance(style_dict, dict) self.ui_styles[name] = style_dict def use_ui_colorscheme(self, name): """ Apply new colorscheme. (By name.) """ assert name in self.ui_styles self._current_ui_style_name = name self._current_style = self._generate_style() def _use_color_depth(self, depth): self.color_depth = depth def _set_min_brightness(self, value): self.min_brightness = value self.max_brightness = max(self.max_brightness, value) def _set_max_brightness(self, value): self.max_brightness = value self.min_brightness = min(self.min_brightness, value) def _generate_style(self): """ Create new Style instance. (We don't want to do this on every key press, because each time the renderer receives a new style class, he will redraw everything.) """ return generate_style(self.code_styles[self._current_code_style_name], self.ui_styles[self._current_ui_style_name]) def _create_options(self): """ Create a list of `Option` instances for the options sidebar. """ def enable(attribute, value=True): setattr(self, attribute, value) # Return `True`, to be able to chain this in the lambdas below. return True def disable(attribute): setattr(self, attribute, False) return True def simple_option(title, description, field_name, values=None): " Create Simple on/of option. " values = values or ['off', 'on'] def get_current_value(): return values[bool(getattr(self, field_name))] def get_values(): return { values[1]: lambda: enable(field_name), values[0]: lambda: disable(field_name), } return Option(title=title, description=description, get_values=get_values, get_current_value=get_current_value) brightness_values = [1.0 / 20 * value for value in range(0, 21)] return [ OptionCategory('Input', [ simple_option(title='Editing mode', description='Vi or emacs key bindings.', field_name='vi_mode', values=[EditingMode.EMACS, EditingMode.VI]), simple_option(title='Paste mode', description="When enabled, don't indent automatically.", field_name='paste_mode'), Option(title='Complete while typing', description="Generate autocompletions automatically while typing. " 'Don\'t require pressing TAB. (Not compatible with "History search".)', get_current_value=lambda: ['off', 'on'][self.complete_while_typing], get_values=lambda: { 'on': lambda: enable('complete_while_typing') and disable('enable_history_search'), 'off': lambda: disable('complete_while_typing'), }), Option(title='History search', description='When pressing the up-arrow, filter the history on input starting ' 'with the current text. (Not compatible with "Complete while typing".)', get_current_value=lambda: ['off', 'on'][self.enable_history_search], get_values=lambda: { 'on': lambda: enable('enable_history_search') and disable('complete_while_typing'), 'off': lambda: disable('enable_history_search'), }), simple_option(title='Mouse support', description='Respond to mouse clicks and scrolling for positioning the cursor, ' 'selecting text and scrolling through windows.', field_name='enable_mouse_support'), simple_option(title='Confirm on exit', description='Require confirmation when exiting.', field_name='confirm_exit'), simple_option(title='Input validation', description='In case of syntax errors, move the cursor to the error ' 'instead of showing a traceback of a SyntaxError.', field_name='enable_input_validation'), simple_option(title='Auto suggestion', description='Auto suggest inputs by looking at the history. ' 'Pressing right arrow or Ctrl-E will complete the entry.', field_name='enable_auto_suggest'), Option(title='Accept input on enter', description='Amount of ENTER presses required to execute input when the cursor ' 'is at the end of the input. (Note that META+ENTER will always execute.)', get_current_value=lambda: str(self.accept_input_on_enter or 'meta-enter'), get_values=lambda: { '2': lambda: enable('accept_input_on_enter', 2), '3': lambda: enable('accept_input_on_enter', 3), '4': lambda: enable('accept_input_on_enter', 4), 'meta-enter': lambda: enable('accept_input_on_enter', None), }), ]), OptionCategory('Display', [ Option(title='Completions', description='Visualisation to use for displaying the completions. (Multiple columns, one column, a toolbar or nothing.)', get_current_value=lambda: self.completion_visualisation, get_values=lambda: { CompletionVisualisation.NONE: lambda: enable('completion_visualisation', CompletionVisualisation.NONE), CompletionVisualisation.POP_UP: lambda: enable('completion_visualisation', CompletionVisualisation.POP_UP), CompletionVisualisation.MULTI_COLUMN: lambda: enable('completion_visualisation', CompletionVisualisation.MULTI_COLUMN), CompletionVisualisation.TOOLBAR: lambda: enable('completion_visualisation', CompletionVisualisation.TOOLBAR), }), Option(title='Prompt', description="Visualisation of the prompt. ('>>>' or 'In [1]:')", get_current_value=lambda: self.prompt_style, get_values=lambda: dict((s, partial(enable, 'prompt_style', s)) for s in self.all_prompt_styles)), simple_option(title='Blank line after output', description='Insert a blank line after the output.', field_name='insert_blank_line_after_output'), simple_option(title='Show signature', description='Display function signatures.', field_name='show_signature'), simple_option(title='Show docstring', description='Display function docstrings.', field_name='show_docstring'), simple_option(title='Show line numbers', description='Show line numbers when the input consists of multiple lines.', field_name='show_line_numbers'), simple_option(title='Show Meta+Enter message', description='Show the [Meta+Enter] message when this key combination is required to execute commands. ' + '(This is the case when a simple [Enter] key press will insert a newline.', field_name='show_meta_enter_message'), simple_option(title='Wrap lines', description='Wrap lines instead of scrolling horizontally.', field_name='wrap_lines'), simple_option(title='Show status bar', description='Show the status bar at the bottom of the terminal.', field_name='show_status_bar'), simple_option(title='Show sidebar help', description='When the sidebar is visible, also show this help text.', field_name='show_sidebar_help'), simple_option(title='Highlight parenthesis', description='Highlight matching parenthesis, when the cursor is on or right after one.', field_name='highlight_matching_parenthesis'), ]), OptionCategory('Colors', [ simple_option(title='Syntax highlighting', description='Use colors for syntax highligthing', field_name='enable_syntax_highlighting'), simple_option(title='Swap light/dark colors', description='Swap light and dark colors.', field_name='swap_light_and_dark'), Option(title='Code', description='Color scheme to use for the Python code.', get_current_value=lambda: self._current_code_style_name, get_values=lambda: dict( (name, partial(self.use_code_colorscheme, name)) for name in self.code_styles) ), Option(title='User interface', description='Color scheme to use for the user interface.', get_current_value=lambda: self._current_ui_style_name, get_values=lambda: dict( (name, partial(self.use_ui_colorscheme, name)) for name in self.ui_styles) ), Option(title='Color depth', description='Monochrome (1 bit), 16 ANSI colors (4 bit),\n256 colors (8 bit), or 24 bit.', get_current_value=lambda: COLOR_DEPTHS[self.color_depth], get_values=lambda: dict( (name, partial(self._use_color_depth, depth)) for depth, name in COLOR_DEPTHS.items()) ), Option(title='Min brightness', description='Minimum brightness for the color scheme (default=0.0).', get_current_value=lambda: '%.2f' % self.min_brightness, get_values=lambda: dict( ('%.2f' % value, partial(self._set_min_brightness, value)) for value in brightness_values) ), Option(title='Max brightness', description='Maximum brightness for the color scheme (default=1.0).', get_current_value=lambda: '%.2f' % self.max_brightness, get_values=lambda: dict( ('%.2f' % value, partial(self._set_max_brightness, value)) for value in brightness_values) ), ]), ] def _create_application(self): """ Create an `Application` instance. """ return Application( input=self.input, output=self.output, layout=self.ptpython_layout.layout, key_bindings=merge_key_bindings([ load_python_bindings(self), load_auto_suggest_bindings(), load_sidebar_bindings(self), load_confirm_exit_bindings(self), ConditionalKeyBindings( load_open_in_editor_bindings(), Condition(lambda: self.enable_open_in_editor)), # Extra key bindings should not be active when the sidebar is visible. ConditionalKeyBindings( self.extra_key_bindings, Condition(lambda: not self.show_sidebar)) ]), color_depth=lambda: self.color_depth, paste_mode=Condition(lambda: self.paste_mode), mouse_support=Condition(lambda: self.enable_mouse_support), style=DynamicStyle(lambda: self._current_style), style_transformation=self.style_transformation, include_default_pygments_style=False, reverse_vi_search_direction=True) def _create_buffer(self): """ Create the `Buffer` for the Python input. """ python_buffer = Buffer( name=DEFAULT_BUFFER, complete_while_typing=Condition(lambda: self.complete_while_typing), enable_history_search=Condition(lambda: self.enable_history_search), tempfile_suffix='.py', history=self.history, completer=ThreadedCompleter(self._completer), validator=ConditionalValidator( self._validator, Condition(lambda: self.enable_input_validation)), auto_suggest=ConditionalAutoSuggest( ThreadedAutoSuggest(AutoSuggestFromHistory()), Condition(lambda: self.enable_auto_suggest)), accept_handler=self._accept_handler, on_text_changed=self._on_input_timeout) return python_buffer @property def editing_mode(self): return self.app.editing_mode @editing_mode.setter def editing_mode(self, value): self.app.editing_mode = value @property def vi_mode(self): return self.editing_mode == EditingMode.VI @vi_mode.setter def vi_mode(self, value): if value: self.editing_mode = EditingMode.VI else: self.editing_mode = EditingMode.EMACS def _on_input_timeout(self, buff): """ When there is no input activity, in another thread, get the signature of the current code. """ assert isinstance(buff, Buffer) app = self.app # Never run multiple get-signature threads. if self._get_signatures_thread_running: return self._get_signatures_thread_running = True document = buff.document def run(): script = get_jedi_script_from_document(document, self.get_locals(), self.get_globals()) # Show signatures in help text. if script: try: signatures = script.call_signatures() except ValueError: # e.g. in case of an invalid \\x escape. signatures = [] except Exception: # Sometimes we still get an exception (TypeError), because # of probably bugs in jedi. We can silence them. # See: https://github.com/davidhalter/jedi/issues/492 signatures = [] else: # Try to access the params attribute just once. For Jedi # signatures containing the keyword-only argument star, # this will crash when retrieving it the first time with # AttributeError. Every following time it works. # See: https://github.com/jonathanslenders/ptpython/issues/47 # https://github.com/davidhalter/jedi/issues/598 try: if signatures: signatures[0].params except AttributeError: pass else: signatures = [] self._get_signatures_thread_running = False # Set signatures and redraw if the text didn't change in the # meantime. Otherwise request new signatures. if buff.text == document.text: self.signatures = signatures # Set docstring in docstring buffer. if signatures: string = signatures[0].docstring() if not isinstance(string, six.text_type): string = string.decode('utf-8') self.docstring_buffer.reset( document=Document(string, cursor_position=0)) else: self.docstring_buffer.reset() app.invalidate() else: self._on_input_timeout(buff) get_event_loop().run_in_executor(run) def on_reset(self): self.signatures = [] def enter_history(self): """ Display the history. """ app = get_app() app.vi_state.input_mode = InputMode.NAVIGATION def done(f): result = f.result() if result is not None: self.default_buffer.text = result app.vi_state.input_mode = InputMode.INSERT history = History(self, self.default_buffer.document) future = run_coroutine_in_terminal(history.app.run_async) future.add_done_callback(done)
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 Editor(object): """ The main class. Containing the whole editor. :param config_directory: Place where configuration is stored. :param input: (Optionally) `prompt_toolkit.input.Input` object. :param output: (Optionally) `prompt_toolkit.output.Output` object. """ def __init__(self, config_directory='~/.pyvim', input=None, output=None): self.input = input self.output = output # Vi options. self.show_line_numbers = True self.highlight_search = True self.paste_mode = False self.show_ruler = True self.show_wildmenu = True self.expand_tab = True # Insect spaces instead of tab characters. self.tabstop = 4 # Number of spaces that a tab character represents. self.incsearch = True # Show matches while typing search string. self.ignore_case = False # Ignore case while searching. self.enable_mouse_support = True self.display_unprintable_characters = True # ':set list' self.enable_jedi = True # ':set jedi', for Python Jedi completion. self.scroll_offset = 0 # ':set scrolloff' self.relative_number = False # ':set relativenumber' self.wrap_lines = True # ':set wrap' self.break_indent = False # ':set breakindent' self.cursorline = False # ':set cursorline' self.cursorcolumn = False # ':set cursorcolumn' self.colorcolumn = [] # ':set colorcolumn'. List of integers. # Ensure config directory exists. self.config_directory = os.path.abspath(os.path.expanduser(config_directory)) if not os.path.exists(self.config_directory): os.mkdir(self.config_directory) self.window_arrangement = WindowArrangement(self) self.message = None # Load styles. (Mapping from name to Style class.) self.styles = generate_built_in_styles() self.current_style = get_editor_style_by_name('vim') # I/O backends. self.io_backends = [ DirectoryIO(), HttpIO(), GZipFileIO(), # Should come before FileIO. FileIO(), ] # Create history and search buffers. def handle_action(buff): ' When enter is pressed in the Vi command line. ' text = buff.text # Remember: leave_command_mode resets the buffer. # First leave command mode. We want to make sure that the working # pane is focussed again before executing the command handlers. self.leave_command_mode(append_to_history=True) # Execute command. handle_command(self, text) commands_history = FileHistory(os.path.join(self.config_directory, 'commands_history')) self.command_buffer = Buffer( accept_handler=handle_action, enable_history_search=True, completer=create_command_completer(self), history=commands_history, multiline=False) search_buffer_history = FileHistory(os.path.join(self.config_directory, 'search_history')) self.search_buffer = Buffer( history=search_buffer_history, enable_history_search=True, multiline=False) # Create key bindings registry. self.key_bindings = create_key_bindings(self) # Create layout and CommandLineInterface instance. self.editor_layout = EditorLayout(self, self.window_arrangement) self.application = self._create_application() # Hide message when a key is pressed. def key_pressed(_): self.message = None self.application.key_processor.before_key_press += key_pressed # Command line previewer. self.previewer = CommandPreviewer(self) def load_initial_files(self, locations, in_tab_pages=False, hsplit=False, vsplit=False): """ Load a list of files. """ assert in_tab_pages + hsplit + vsplit <= 1 # Max one of these options. # When no files were given, open at least one empty buffer. locations2 = locations or [None] # First file self.window_arrangement.open_buffer(locations2[0]) for f in locations2[1:]: if in_tab_pages: self.window_arrangement.create_tab(f) elif hsplit: self.window_arrangement.hsplit(location=f) elif vsplit: self.window_arrangement.vsplit(location=f) else: self.window_arrangement.open_buffer(f) self.window_arrangement.active_tab_index = 0 if locations and len(locations) > 1: self.show_message('%i files loaded.' % len(locations)) def _create_application(self): """ Create CommandLineInterface instance. """ # Create Application. application = Application( input=self.input, output=self.output, editing_mode=EditingMode.VI, layout=self.editor_layout.layout, key_bindings=self.key_bindings, # get_title=lambda: get_terminal_title(self), style=DynamicStyle(lambda: self.current_style), paste_mode=Condition(lambda: self.paste_mode), # ignore_case=Condition(lambda: self.ignore_case), # TODO include_default_pygments_style=False, mouse_support=Condition(lambda: self.enable_mouse_support), full_screen=True, enable_page_navigation_bindings=True) # Handle command line previews. # (e.g. when typing ':colorscheme blue', it should already show the # preview before pressing enter.) def preview(_): if self.application.layout.has_focus(self.command_buffer): self.previewer.preview(self.command_buffer.text) self.command_buffer.on_text_changed += preview return application @property def current_editor_buffer(self): """ Return the `EditorBuffer` that is currently active. """ current_buffer = self.application.current_buffer # Find/return the EditorBuffer with this name. for b in self.window_arrangement.editor_buffers: if b.buffer == current_buffer: return b @property def add_key_binding(self): """ Shortcut for adding new key bindings. (Mostly useful for a pyvimrc file, that receives this Editor instance as input.) """ return self.key_bindings.add def show_message(self, message): """ Set a warning message. The layout will render it as a "pop-up" at the bottom. """ self.message = message def use_colorscheme(self, name='default'): """ Apply new colorscheme. (By name.) """ try: self.current_style = get_editor_style_by_name(name) except pygments.util.ClassNotFound: pass def sync_with_prompt_toolkit(self): """ Update the prompt-toolkit Layout and FocusStack. """ # After executing a command, make sure that the layout of # prompt-toolkit matches our WindowArrangement. self.editor_layout.update() # Make sure that the focus stack of prompt-toolkit has the current # page. window = self.window_arrangement.active_pt_window if window: self.application.layout.focus(window) def show_help(self): """ Show help in new window. """ self.window_arrangement.hsplit(text=HELP_TEXT) self.sync_with_prompt_toolkit() # Show new window. def run(self): """ Run the event loop for the interface. This starts the interaction. """ # Make sure everything is in sync, before starting. self.sync_with_prompt_toolkit() def pre_run(): # Start in navigation mode. self.application.vi_state.input_mode = InputMode.NAVIGATION # Run eventloop of prompt_toolkit. self.application.run(pre_run=pre_run) def enter_command_mode(self): """ Go into command mode. """ self.application.layout.focus(self.command_buffer) self.application.vi_state.input_mode = InputMode.INSERT self.previewer.save() def leave_command_mode(self, append_to_history=False): """ Leave command mode. Focus document window again. """ self.previewer.restore() self.application.layout.focus_last() self.application.vi_state.input_mode = InputMode.NAVIGATION self.command_buffer.reset(append_to_history=append_to_history)