def load_basic_system_bindings(registry, filter=Always()): """ Basic system bindings (For both Emacs and Vi mode.) """ assert isinstance(filter, CLIFilter) handle = create_handle_decorator(registry, filter) suspend_supported = Condition( lambda cli: suspend_to_background_supported()) @handle(Keys.ControlZ, filter=suspend_supported) def _(event): """ Suspend process to background. """ event.cli.suspend_to_background()
def load_basic_system_bindings(): """ Basic system bindings (For both Emacs and Vi mode.) """ registry = Registry() suspend_supported = Condition( lambda cli: suspend_to_background_supported()) @registry.add_binding(Keys.ControlZ, filter=suspend_supported) def _(event): """ Suspend process to background. """ event.cli.suspend_to_background() return registry
def init_prompt_toolkit_cli(self): if self.simple_prompt or ('JUPYTER_CONSOLE_TEST' in os.environ): # Simple restricted interface for tests so we can find prompts with # pexpect. Multi-line input not supported. async def prompt(): prompt = 'In [%d]: ' % self.execution_count raw = await async_input(prompt) return raw self.prompt_for_code = prompt self.print_out_prompt = \ lambda: print('Out[%d]: ' % self.execution_count, end='') return kb = KeyBindings() insert_mode = vi_insert_mode | emacs_insert_mode @kb.add("enter", filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode)) def _(event): b = event.current_buffer d = b.document if not (d.on_last_line or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()): b.newline() return # Pressing enter flushes any pending display. This also ensures # the displayed execution_count is correct. self.handle_iopub() more, indent = self.check_complete(d.text) if (not more) and b.accept_handler: b.validate_and_handle() else: b.insert_text('\n' + indent) @kb.add("c-c", filter=has_focus(DEFAULT_BUFFER)) def _(event): event.current_buffer.reset() @kb.add("c-\\", filter=has_focus(DEFAULT_BUFFER)) def _(event): raise EOFError @kb.add("c-z", filter=Condition(lambda: suspend_to_background_supported())) def _(event): event.cli.suspend_to_background() @kb.add("c-o", filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)) def _(event): event.current_buffer.insert_text("\n") # Pre-populate history from IPython's history database history = InMemoryHistory() last_cell = u"" for _, _, cell in self.history_manager.get_tail( self.history_load_length, include_latest=True): # Ignore blank lines and consecutive duplicates cell = cell.rstrip() if cell and (cell != last_cell): history.append_string(cell) style_overrides = { Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', Token.OutPrompt: '#ff2200', Token.OutPromptNum: '#ff0000 bold', Token.RemotePrompt: '#999900', } if self.highlighting_style: style_cls = get_style_by_name(self.highlighting_style) else: style_cls = get_style_by_name('default') # The default theme needs to be visible on both a dark background # and a light background, because we can't tell what the terminal # looks like. These tweaks to the default theme help with that. style_overrides.update({ Token.Number: '#007700', Token.Operator: 'noinherit', Token.String: '#BB6622', Token.Name.Function: '#2080D0', Token.Name.Class: 'bold #2080D0', Token.Name.Namespace: 'bold #2080D0', }) style_overrides.update(self.highlighting_style_overrides) style = merge_styles([ style_from_pygments_cls(style_cls), style_from_pygments_dict(style_overrides), ]) editing_mode = getattr(EditingMode, self.editing_mode.upper()) langinfo = self.kernel_info.get('language_info', {}) lexer = langinfo.get('pygments_lexer', langinfo.get('name', 'text')) # If enabled in the settings, highlight matching brackets # when the DEFAULT_BUFFER has the focus input_processors = [ ConditionalProcessor( processor=HighlightMatchingBracketProcessor(chars='[](){}'), filter=has_focus(DEFAULT_BUFFER) & ~is_done & Condition(lambda: self.highlight_matching_brackets)) ] # Tell prompt_toolkit to use the asyncio event loop. # Obsolete in prompt_toolkit.v3 if not PTK3: use_asyncio_event_loop() self.pt_cli = PromptSession( message=(lambda: PygmentsTokens(self.get_prompt_tokens())), multiline=True, complete_style=self.pt_complete_style, editing_mode=editing_mode, lexer=PygmentsLexer(get_pygments_lexer(lexer)), prompt_continuation=( lambda width, lineno, is_soft_wrap: PygmentsTokens( self.get_continuation_tokens(width))), key_bindings=kb, history=history, completer=JupyterPTCompleter(self.Completer), enable_history_search=True, style=style, input_processors=input_processors, color_depth=(ColorDepth.TRUE_COLOR if self.true_color else None), )
def init_prompt_toolkit_cli(self): if self.simple_prompt or ('JUPYTER_CONSOLE_TEST' in os.environ): # Simple restricted interface for tests so we can find prompts with # pexpect. Multi-line input not supported. def prompt(): return cast_unicode_py2( input('In [%d]: ' % self.execution_count)) self.prompt_for_code = prompt self.print_out_prompt = \ lambda: print('Out[%d]: ' % self.execution_count, end='') return kbmanager = KeyBindingManager.for_prompt() insert_mode = ViInsertMode() | EmacsInsertMode() # Ctrl+J == Enter, seemingly @kbmanager.registry.add_binding(Keys.ControlJ, filter=(HasFocus(DEFAULT_BUFFER) & ~HasSelection() & insert_mode)) def _(event): b = event.current_buffer d = b.document if not (d.on_last_line or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()): b.newline() return # Pressing enter flushes any pending display. This also ensures # the displayed execution_count is correct. self.handle_iopub() more, indent = self.check_complete(d.text) if (not more) and b.accept_action.is_returnable: b.accept_action.validate_and_handle(event.cli, b) else: b.insert_text('\n' + indent) @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER)) def _(event): event.current_buffer.reset() @kbmanager.registry.add_binding(Keys.ControlBackslash, filter=HasFocus(DEFAULT_BUFFER)) def _(event): raise EOFError @kbmanager.registry.add_binding( Keys.ControlZ, filter=Condition(lambda cli: suspend_to_background_supported())) def _(event): event.cli.suspend_to_background() # Pre-populate history from IPython's history database history = InMemoryHistory() last_cell = u"" for _, _, cell in self.history_manager.get_tail( self.history_load_length, include_latest=True): # Ignore blank lines and consecutive duplicates cell = cast_unicode_py2(cell.rstrip()) if cell and (cell != last_cell): history.append(cell) style_overrides = { Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', Token.OutPrompt: '#ff2200', Token.OutPromptNum: '#ff0000 bold', } if self.highlighting_style: style_cls = get_style_by_name(self.highlighting_style) else: style_cls = get_style_by_name('default') # The default theme needs to be visible on both a dark background # and a light background, because we can't tell what the terminal # looks like. These tweaks to the default theme help with that. style_overrides.update({ Token.Number: '#007700', Token.Operator: 'noinherit', Token.String: '#BB6622', Token.Name.Function: '#2080D0', Token.Name.Class: 'bold #2080D0', Token.Name.Namespace: 'bold #2080D0', }) style_overrides.update(self.highlighting_style_overrides) style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls, style_dict=style_overrides) editing_mode = getattr(EditingMode, self.editing_mode.upper()) langinfo = self.kernel_info.get('language_info', {}) lexer = langinfo.get('pygments_lexer', langinfo.get('name', 'text')) app = create_prompt_application( multiline=True, editing_mode=editing_mode, lexer=PygmentsLexer(get_pygments_lexer(lexer)), get_prompt_tokens=self.get_prompt_tokens, get_continuation_tokens=self.get_continuation_tokens, key_bindings_registry=kbmanager.registry, history=history, completer=JupyterPTCompleter(self.Completer), enable_history_search=True, style=style, ) self._eventloop = create_eventloop() self.pt_cli = CommandLineInterface( app, eventloop=self._eventloop, output=create_output(true_color=self.true_color), )
def create_key_bindings(pager): kb = KeyBindings() handle = kb.add @Condition def has_colon(): return pager.in_colon_mode @Condition def default_focus(): app = get_app() return app.layout.current_window == pager.current_source_info.window @Condition def displaying_help(): return pager.displaying_help for c in '01234556789': @handle(c, filter=default_focus) def _(event, c=c): event.append_to_arg_count(c) @handle('q', filter=default_focus) @handle('Q', filter=default_focus | has_colon) @handle('Z', 'Z', filter=default_focus) def _(event): " Quit. " if pager.displaying_help: pager.quit_help() else: event.app.exit() @handle(' ', filter=default_focus) @handle('f', filter=default_focus) @handle(Keys.ControlF, filter=default_focus) @handle(Keys.ControlV, filter=default_focus) def _(event): " Page down." scroll_page_down(event) @handle('b', filter=default_focus) @handle(Keys.ControlB, filter=default_focus) @handle(Keys.Escape, 'v', filter=default_focus) def _(event): " Page up." scroll_page_up(event) @handle('d', filter=default_focus) @handle(Keys.ControlD, filter=default_focus) def _(event): " Half page down." scroll_half_page_down(event) @handle('u', filter=default_focus) @handle(Keys.ControlU, filter=default_focus) def _(event): " Half page up." scroll_half_page_up(event) @handle('e', filter=default_focus) @handle('j', filter=default_focus) @handle(Keys.ControlE, filter=default_focus) @handle(Keys.ControlN, filter=default_focus) @handle(Keys.ControlJ, filter=default_focus) @handle(Keys.ControlM, filter=default_focus) @handle(Keys.Down, filter=default_focus) def _(event): " Scoll one line down." if event.arg > 1: # When an argument is given, go this amount of lines down. event.current_buffer.auto_down(count=event.arg) else: scroll_one_line_down(event) @handle('y', filter=default_focus) @handle('k', filter=default_focus) @handle(Keys.ControlY, filter=default_focus) @handle(Keys.ControlK, filter=default_focus) @handle(Keys.ControlP, filter=default_focus) @handle(Keys.Up, filter=default_focus) def _(event): " Scoll one line up." if event.arg > 1: event.current_buffer.auto_up(count=event.arg) else: scroll_one_line_up(event) @handle(Keys.Escape, 'u') def _(event): " Toggle search highlighting. " pager.highlight_search = not pager.highlight_search @handle('=', filter=default_focus) @handle(Keys.ControlG, filter=default_focus) @handle('f', filter=has_colon) def _(event): " Print the current file name. " pager.message = ' {} '.format(pager.current_source.get_name()) @handle('h', filter=default_focus & ~displaying_help) @handle('H', filter=default_focus & ~displaying_help) def _(event): " Display Help. " pager.display_help() @handle('g', filter=default_focus) @handle('<', filter=default_focus) @handle(Keys.Escape, '<', filter=default_focus) def _(event): " Go to the first line of the file. " event.current_buffer.cursor_position = 0 @handle('G', filter=default_focus) @handle('>', filter=default_focus) @handle(Keys.Escape, '>', filter=default_focus) def _(event): " Go to the last line of the file. " b = event.current_buffer b.cursor_position = len(b.text) @handle('m', Keys.Any, filter=default_focus) def _(event): " Mark current position. " source_info = pager.current_source_info source_info.marks[event.data] = (event.current_buffer.cursor_position, source_info.window.vertical_scroll) @handle("'", Keys.Any, filter=default_focus) def _(event): " Go to a previously marked position. " go_to_mark(event, event.data) @handle(Keys.ControlX, Keys.ControlX, filter=default_focus) def _(event): " Same as '. " go_to_mark(event, '.') def go_to_mark(event, mark): b = event.current_buffer source_info = pager.current_source_info try: if mark == '^': # Start of file. cursor_pos, vertical_scroll = 0, 0 elif mark == '$': # End of file - mark. cursor_pos, vertical_scroll = len(b.text), 0 else: # Custom mark. cursor_pos, vertical_scroll = source_info.marks[mark] except KeyError: pass # TODO: show warning. else: b.cursor_position = cursor_pos source_info.window.vertical_scroll = vertical_scroll @handle('F', filter=default_focus) def _(event): " Forward forever, like 'tail -f'. " pager.forward_forever = True @handle('r', filter=default_focus) @handle('R', filter=default_focus) def _(event): event.app.renderer.clear() def search_buffer_is_empty(): " Returns True when the search buffer is empty. " return pager.search_buffer.text == '' @handle('backspace', filter=has_focus(pager.search_buffer) & Condition(search_buffer_is_empty)) def _(event): " Cancel search when backspace is pressed. " stop_search() @handle(Keys.Left, filter=default_focus) @handle(Keys.Escape, '(', filter=default_focus) def _(event): " Scroll half page to the left. " w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: info = w.render_info amount = info.window_width // 2 # Move cursor horizontally. value = b.cursor_position - min( amount, len(b.document.current_line_before_cursor)) b.cursor_position = value # Scroll. w.horizontal_scroll = max(0, w.horizontal_scroll - amount) @handle(Keys.Right, filter=default_focus) @handle(Keys.Escape, ')', filter=default_focus) def _(event): " Scroll half page to the right. " w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: info = w.render_info amount = info.window_width // 2 # Move the cursor first to a visible line that is long enough to # have the cursor visible after scrolling. (Otherwise, the Window # will scroll back.) xpos = w.horizontal_scroll + amount for line in info.displayed_lines: if len(b.document.lines[line]) >= xpos: b.cursor_position = b.document.translate_row_col_to_index( line, xpos) break # Scroll. w.horizontal_scroll = max(0, w.horizontal_scroll + amount) @handle(':', filter=default_focus & ~displaying_help) def _(event): pager.in_colon_mode = True @handle('n', filter=has_colon) def _(event): " Go to next file. " pager.focus_next_source() @handle('p', filter=has_colon) def _(event): " Go to previous file. " pager.focus_previous_source() @handle('e', filter=has_colon) @handle(Keys.ControlX, Keys.ControlV, filter=default_focus) def _(event): event.app.layout.focus(pager.layout.examine_control) pager.in_colon_mode = False @handle('d', filter=has_colon) def _(event): pager.remove_current_source() @handle('backspace', filter=has_colon) @handle('q', filter=has_colon) def _(event): pager.in_colon_mode = False @handle(Keys.Any, filter=has_colon) def _(event): pager.in_colon_mode = False pager.message = 'No command.' @handle(Keys.ControlC, filter=has_focus('EXAMINE')) @handle(Keys.ControlG, filter=has_focus('EXAMINE')) def _(event): " Cancel 'Examine' input. " event.app.layout.focus(pager.current_source_info.window) @handle(Keys.ControlZ, filter=Condition(lambda: suspend_to_background_supported())) def _(event): " Suspend to bakground. " event.app.suspend_to_background() return kb
def create_key_bindings(pager: "Pager") -> KeyBindings: kb = KeyBindings() handle = kb.add @Condition def has_colon() -> bool: return pager.in_colon_mode @Condition def default_focus() -> bool: app = get_app() return app.layout.current_window == pager.current_source_info.window @Condition def displaying_help() -> bool: return pager.displaying_help for c in "01234556789": @handle(c, filter=default_focus) def _handle_arg(event: E, c: str = c) -> None: event.append_to_arg_count(c) @handle("q", filter=default_focus, eager=True) # Eager because q-any is a registered Vi key binding. @handle("Q", filter=default_focus | has_colon) @handle("Z", "Z", filter=default_focus) def _quit(event: E) -> None: " Quit. " if pager.displaying_help: pager.quit_help() else: event.app.exit() @handle(" ", filter=default_focus) @handle("f", filter=default_focus) @handle("c-f", filter=default_focus) @handle("c-v", filter=default_focus) def _pagedown(event: E) -> None: " Page down." scroll_page_down(event) @handle("b", filter=default_focus) @handle("c-b", filter=default_focus) @handle("escape", "v", filter=default_focus) def _pageup(event: E) -> None: " Page up." scroll_page_up(event) @handle("d", filter=default_focus) @handle("c-d", filter=default_focus) def _halfdown(event: E) -> None: " Half page down." scroll_half_page_down(event) @handle("u", filter=default_focus) @handle("c-u", filter=default_focus) def _halfup(event: E) -> None: " Half page up." scroll_half_page_up(event) @handle("e", filter=default_focus) @handle("j", filter=default_focus) @handle("c-e", filter=default_focus) @handle("c-n", filter=default_focus) @handle("c-j", filter=default_focus) @handle("c-m", filter=default_focus) @handle("down", filter=default_focus) def _down(event: E) -> None: " Scoll one line down." if event.arg > 1: # When an argument is given, go this amount of lines down. event.current_buffer.auto_down(count=event.arg) else: scroll_one_line_down(event) @handle("y", filter=default_focus) @handle("k", filter=default_focus) @handle("c-y", filter=default_focus) @handle("c-k", filter=default_focus) @handle("c-p", filter=default_focus) @handle("up", filter=default_focus) def _up(event: E) -> None: " Scoll one line up." if event.arg > 1: event.current_buffer.auto_up(count=event.arg) else: scroll_one_line_up(event) @handle(Keys.Escape, "u") def _toggle_highlighting(event: E) -> None: " Toggle search highlighting. " pager.highlight_search = not pager.highlight_search @handle("=", filter=default_focus) @handle(Keys.ControlG, filter=default_focus) @handle("f", filter=has_colon) def _print_filename(event: E) -> None: " Print the current file name. " pager.message = " {} ".format(pager.current_source.get_name()) @handle("h", filter=default_focus & ~displaying_help) @handle("H", filter=default_focus & ~displaying_help) def _help(event: E) -> None: " Display Help. " pager.display_help() @handle("g", filter=default_focus) @handle("<", filter=default_focus) @handle("escape", "<", filter=default_focus) def _firstline(event: E) -> None: " Go to the first line of the file. " event.current_buffer.cursor_position = 0 @handle("G", filter=default_focus) @handle(">", filter=default_focus) @handle("escape", ">", filter=default_focus) def _lastline(event: E) -> None: " Go to the last line of the file. " b = event.current_buffer b.cursor_position = len(b.text) @handle("m", Keys.Any, filter=default_focus) def _mark(event: E) -> None: " Mark current position. " source_info = pager.current_source_info source_info.marks[event.data] = ( event.current_buffer.cursor_position, source_info.window.vertical_scroll, ) @handle("'", Keys.Any, filter=default_focus) def _goto_mark(event: E) -> None: " Go to a previously marked position. " go_to_mark(event, event.data) @handle("c-x", Keys.ControlX, filter=default_focus) def _gotomark_dot(event: E) -> None: " Same as '. " go_to_mark(event, ".") def go_to_mark(event: E, mark: str) -> None: b = event.current_buffer source_info = pager.current_source_info try: if mark == "^": # Start of file. cursor_pos, vertical_scroll = 0, 0 elif mark == "$": # End of file - mark. cursor_pos, vertical_scroll = len(b.text), 0 else: # Custom mark. cursor_pos, vertical_scroll = source_info.marks[mark] except KeyError: pass # TODO: show warning. else: b.cursor_position = cursor_pos source_info.window.vertical_scroll = vertical_scroll @handle("F", filter=default_focus) def _follow(event: E) -> None: " Forward forever, like 'tail -f'. " pager.forward_forever = True @handle("r", filter=default_focus) @handle("R", filter=default_focus) def _repaint(event: E) -> None: event.app.renderer.clear() @Condition def search_buffer_is_empty() -> bool: " Returns True when the search buffer is empty. " return pager.search_buffer.text == "" @handle( "backspace", filter=has_focus(pager.search_buffer) & search_buffer_is_empty, ) def _cancel_search(event: E) -> None: " Cancel search when backspace is pressed. " stop_search() @Condition def line_wrapping_enable() -> bool: return pager.current_source_info.wrap_lines @handle("left", filter=default_focus & ~line_wrapping_enable) @handle("escape", "(", filter=default_focus & ~line_wrapping_enable) def _left(event: E) -> None: " Scroll half page to the left. " w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: info = w.render_info amount = info.window_width // 2 # Move cursor horizontally. value = b.cursor_position - min( amount, len(b.document.current_line_before_cursor)) b.cursor_position = value # Scroll. w.horizontal_scroll = max(0, w.horizontal_scroll - amount) @handle("right", filter=default_focus & ~line_wrapping_enable) @handle("escape", ")", filter=default_focus & ~line_wrapping_enable) def _right(event: E) -> None: " Scroll half page to the right. " w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: info = w.render_info amount = info.window_width // 2 # Move the cursor first to a visible line that is long enough to # have the cursor visible after scrolling. (Otherwise, the Window # will scroll back.) xpos = w.horizontal_scroll + amount for line in info.displayed_lines: if len(b.document.lines[line]) >= xpos: b.cursor_position = b.document.translate_row_col_to_index( line, xpos) break # Scroll. w.horizontal_scroll = max(0, w.horizontal_scroll + amount) @handle(":", filter=default_focus & ~displaying_help) def _colon(event: E) -> None: pager.in_colon_mode = True @handle("n", filter=has_colon) def _next_file(event: E) -> None: " Go to next file. " pager.focus_next_source() @handle("p", filter=has_colon) def _previous_file(event: E) -> None: " Go to previous file. " pager.focus_previous_source() @handle("e", filter=has_colon) @handle(Keys.ControlX, Keys.ControlV, filter=default_focus) def _examine(event: E) -> None: event.app.layout.focus(pager.layout.examine_control) pager.in_colon_mode = False @handle("d", filter=has_colon) def _remove_source(event: E) -> None: pager.remove_current_source() @handle("backspace", filter=has_colon) @handle("q", filter=has_colon, eager=True) def _cancel_colon(event: E) -> None: pager.in_colon_mode = False @handle(Keys.Any, filter=has_colon) def _any(event: E) -> None: pager.in_colon_mode = False pager.message = "No command." @handle("c-c", filter=has_focus("EXAMINE")) @handle("c-g", filter=has_focus("EXAMINE")) def _cancel_examine(event: E) -> None: " Cancel 'Examine' input. " event.app.layout.focus(pager.current_source_info.window) @handle("c-z", filter=Condition(lambda: suspend_to_background_supported())) def _suspend(event: E) -> None: " Suspend to bakground. " event.app.suspend_to_background() @handle("w") def _suspend(event: E) -> None: " Enable/disable line wrapping. " source_info = pager.current_source_info source_info.wrap_lines = not source_info.wrap_lines return kb
def create_key_bindings(pager): registry = load_key_bindings(enable_search=True, enable_extra_page_navigation=True, enable_system_bindings=True) handle = registry.add_binding @Condition def has_colon(cli): return pager.in_colon_mode @Condition def default_focus(cli): return (cli.current_buffer_name.startswith('source') and not pager.in_colon_mode) @Condition def displaying_help(cli): return pager.displaying_help for c in '01234556789': @handle(c, filter=default_focus) def _(event, c=c): event.append_to_arg_count(c) @handle('q', filter=default_focus | has_colon) @handle('Q', filter=default_focus | has_colon) @handle('Z', 'Z', filter=default_focus) def _(event): " Quit. " if pager.displaying_help: pager.quit_help() else: event.cli.set_return_value(None) @handle(' ', filter=default_focus) @handle('f', filter=default_focus) @handle(Keys.ControlF, filter=default_focus) @handle(Keys.ControlV, filter=default_focus) def _(event): " Page down." scroll_page_down(event) @handle('b', filter=default_focus) @handle(Keys.ControlB, filter=default_focus) @handle(Keys.Escape, 'v', filter=default_focus) def _(event): " Page up." scroll_page_up(event) @handle('d', filter=default_focus) @handle(Keys.ControlD, filter=default_focus) def _(event): " Half page down." scroll_half_page_down(event) @handle('u', filter=default_focus) @handle(Keys.ControlU, filter=default_focus) def _(event): " Half page up." scroll_half_page_up(event) @handle('e', filter=default_focus) @handle('j', filter=default_focus) @handle(Keys.ControlE, filter=default_focus) @handle(Keys.ControlN, filter=default_focus) @handle(Keys.ControlJ, filter=default_focus) @handle(Keys.ControlM, filter=default_focus) @handle(Keys.Down, filter=default_focus) def _(event): " Scoll one line down." if event.arg > 1: # When an argument is given, go this amount of lines down. event.current_buffer.auto_down(count=event.arg) else: scroll_one_line_down(event) @handle('y', filter=default_focus) @handle('k', filter=default_focus) @handle(Keys.ControlY, filter=default_focus) @handle(Keys.ControlK, filter=default_focus) @handle(Keys.ControlP, filter=default_focus) @handle(Keys.Up, filter=default_focus) def _(event): " Scoll one line up." if event.arg > 1: event.current_buffer.auto_up(count=event.arg) else: scroll_one_line_up(event) @handle('/', filter=default_focus) def _(event): " Start searching forward. " event.cli.search_state.direction = IncrementalSearchDirection.FORWARD event.cli.vi_state.input_mode = InputMode.INSERT event.cli.push_focus(SEARCH_BUFFER) @handle('?', filter=default_focus) def _(event): " Start searching backwards. " event.cli.search_state.direction = IncrementalSearchDirection.BACKWARD event.cli.vi_state.input_mode = InputMode.INSERT event.cli.push_focus(SEARCH_BUFFER) @handle('n', filter=default_focus) def _(event): " Search next. " event.current_buffer.apply_search(event.cli.search_state, include_current_position=False, count=event.arg) @handle('N', filter=default_focus) def _(event): " Search previous. " event.current_buffer.apply_search(~event.cli.search_state, include_current_position=False, count=event.arg) @handle(Keys.Escape, 'u') def _(event): " Toggle search highlighting. " pager.highlight_search = not pager.highlight_search @handle('=', filter=default_focus) @handle(Keys.ControlG, filter=default_focus) @handle('f', filter=has_colon) def _(event): " Print the current file name. " pager.message = ' {} '.format(pager.source.get_name()) @handle('h', filter=default_focus & ~displaying_help) @handle('H', filter=default_focus & ~displaying_help) def _(event): " Display Help. " pager.display_help() @handle('g', filter=default_focus) @handle('<', filter=default_focus) @handle(Keys.Escape, '<', filter=default_focus) def _(event): " Go to the first line of the file. " event.current_buffer.cursor_position = 0 @handle('G', filter=default_focus) @handle('>', filter=default_focus) @handle(Keys.Escape, '>', filter=default_focus) def _(event): " Go to the last line of the file. " b = event.current_buffer b.cursor_position = len(b.text) @handle('m', Keys.Any, filter=default_focus) def _(event): " Mark current position. " pager.marks[event.data] = (event.current_buffer.cursor_position, pager.layout.buffer_window.vertical_scroll) @handle("'", Keys.Any, filter=default_focus) def _(event): " Go to a previously marked position. " go_to_mark(event, event.data) @handle(Keys.ControlX, Keys.ControlX, filter=default_focus) def _(event): " Same as '. " go_to_mark(event, '.') def go_to_mark(event, mark): b = event.current_buffer try: if mark == '^': # Start of file. cursor_pos, vertical_scroll = 0, 0 elif mark == '$': # End of file - mark. cursor_pos, vertical_scroll = len(b.text), 0 else: # Custom mark. cursor_pos, vertical_scroll = pager.marks[mark] except KeyError: pass # TODO: show warning. else: b.cursor_position = cursor_pos pager.layout.buffer_window.vertical_scroll = vertical_scroll @handle('F', filter=default_focus) def _(event): " Forward forever, like 'tail -f'. " pager.forward_forever = True @handle(Keys.ControlR, filter=default_focus) @handle('r', filter=default_focus) @handle('R', filter=default_focus) def _(event): event.cli.renderer.clear() def search_buffer_is_empty(cli): " Returns True when the search buffer is empty. " return cli.buffers[SEARCH_BUFFER].text == '' @handle(Keys.Backspace, filter=HasFocus(SEARCH_BUFFER) & Condition(search_buffer_is_empty)) def _(event): " Cancel search when backspace is pressed. " event.cli.vi_state.input_mode = InputMode.NAVIGATION event.cli.pop_focus() event.cli.buffers[SEARCH_BUFFER].reset() @handle(Keys.Left, filter=default_focus) @handle(Keys.Escape, '(', filter=default_focus) def _(event): " Scroll half page to the left. " w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w and w.render_info: info = w.render_info amount = info.window_width // 2 # Move cursor horizontally. value = b.cursor_position - min( amount, len(b.document.current_line_before_cursor)) b.cursor_position = value # Scroll. w.horizontal_scroll = max(0, w.horizontal_scroll - amount) @handle(Keys.Right, filter=default_focus) @handle(Keys.Escape, ')', filter=default_focus) def _(event): " Scroll half page to the right. " w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w and w.render_info: info = w.render_info amount = info.window_width // 2 # Move the cursor first to a visible line that is long enough to # have the cursor visible after scrolling. (Otherwise, the Window # will scroll back.) xpos = w.horizontal_scroll + amount for line in info.displayed_lines: if len(b.document.lines[line]) >= xpos: b.cursor_position = b.document.translate_row_col_to_index( line, xpos) break # Scroll. w.horizontal_scroll = max(0, w.horizontal_scroll + amount) @handle(':', filter=default_focus & ~displaying_help) def _(event): pager.in_colon_mode = True @handle('n', filter=has_colon) def _(event): " Go to next file. " pager.focus_next_source() @handle('p', filter=has_colon) def _(event): " Go to previous file. " pager.focus_previous_source() @handle('e', filter=has_colon) @handle(Keys.ControlX, Keys.ControlV, filter=default_focus) def _(event): pager.buffers.focus(event.cli, 'EXAMINE') pager.in_colon_mode = False @handle('d', filter=has_colon) def _(event): pager.remove_current_source() @handle(Keys.Any, filter=has_colon) @handle(Keys.Backspace, filter=has_colon) def _(event): pager.in_colon_mode = False pager.message = 'No command.' @handle(Keys.ControlC, filter=HasFocus('EXAMINE')) @handle(Keys.ControlG, filter=HasFocus('EXAMINE')) def _(event): " Cancel 'Examine' input. " pager.buffers.focus(event.cli, pager.source_info[pager.source].buffer_name) @handle(Keys.ControlZ, filter=Condition(lambda cli: suspend_to_background_supported())) def _(event): " Suspend to bakground. " event.cli.suspend_to_background() return registry