def __init__(self, process): assert isinstance(process, Process) self.process = process self.chosen_name = None # Displayed the clock instead of this pane content. self.clock_mode = False # Give unique ID. Pane._pane_counter += 1 self.pane_id = Pane._pane_counter # Prompt_toolkit buffer, for displaying scrollable text. # (In copy mode, or help mode.) # Note: Because the scroll_buffer can only contain text, we also use the # copy_token_list, that contains a token list with color information. self.scroll_buffer = Buffer(read_only=True) self.copy_token_list = [] self.display_scroll_buffer = False self.scroll_buffer_title = '' # Search buffer, for use in copy mode. (Each pane gets its own search buffer.) self.search_buffer = Buffer() self.is_searching = False self.search_state = SearchState(ignore_case=False)
def current_search_state(self): """ Return the current `SearchState`. (The one for the focused `BufferControl`.) """ ui_control = self.layout.current_control if isinstance(ui_control, BufferControl): return ui_control.search_state else: return SearchState() # Dummy search state. (Don't return None!)
def _enter_scroll_buffer(self, title, document, token_list): # Suspend child process. self.process.suspend() self.scroll_buffer.set_document(document, bypass_readonly=True) self.copy_token_list = token_list self.display_scroll_buffer = True self.scroll_buffer_title = title # Reset search state. self.search_state = SearchState(ignore_case=False)
def create_content(self, cli, width, height): """ Create a UIContent. """ buffer = self._buffer(cli) # Get the document to be shown. If we are currently searching (the # search buffer has focus, and the preview_search filter is enabled), # then use the search document, which has possibly a different # text/cursor position.) def preview_now(): """ True when we should preview a search. """ return bool( self.preview_search(cli) and cli.buffers[self.search_buffer_name].text) if preview_now(): if self.get_search_state: ss = self.get_search_state(cli) else: ss = cli.search_state document = buffer.document_for_search( SearchState(text=cli.current_buffer.text, direction=ss.direction, ignore_case=ss.ignore_case)) else: document = buffer.document get_processed_line = self._create_get_processed_line_func( cli, document) self._last_get_processed_line = get_processed_line def translate_rowcol(row, col): " Return the content column for this coordinate. " return Point(y=row, x=get_processed_line(row).source_to_display(col)) def get_line(i): " Return the tokens for a given line number. " tokens = get_processed_line(i).tokens # Add a space at the end, because that is a possible cursor # position. (When inserting after the input.) We should do this on # all the lines, not just the line containing the cursor. (Because # otherwise, line wrapping/scrolling could change when moving the # cursor around.) tokens = tokens + [(self.default_char.token, ' ')] return tokens content = UIContent(get_line=get_line, line_count=document.line_count, cursor_position=translate_rowcol( document.cursor_position_row, document.cursor_position_col), default_char=self.default_char) # If there is an auto completion going on, use that start point for a # pop-up menu position. (But only when this buffer has the focus -- # there is only one place for a menu, determined by the focussed buffer.) if cli.current_buffer_name == self.buffer_name: menu_position = self.menu_position( cli) if self.menu_position else None if menu_position is not None: assert isinstance(menu_position, int) menu_row, menu_col = buffer.document.translate_index_to_position( menu_position) content.menu_position = translate_rowcol(menu_row, menu_col) elif buffer.complete_state: # Position for completion menu. # Note: We use 'min', because the original cursor position could be # behind the input string when the actual completion is for # some reason shorter than the text we had before. (A completion # can change and shorten the input.) menu_row, menu_col = buffer.document.translate_index_to_position( min( buffer.cursor_position, buffer.complete_state. original_document.cursor_position)) content.menu_position = translate_rowcol(menu_row, menu_col) else: content.menu_position = None return content
def create_screen(self, cli, width, height): buffer = self._buffer(cli) # Get the document to be shown. If we are currently searching (the # search buffer has focus, and the preview_search filter is enabled), # then use the search document, which has possibly a different # text/cursor position.) def preview_now(): """ True when we should preview a search. """ return bool( self.preview_search(cli) and cli.is_searching and cli.current_buffer.text) if preview_now(): document = buffer.document_for_search( SearchState(text=cli.current_buffer.text, direction=cli.search_state.direction, ignore_case=cli.search_state.ignore_case)) else: document = buffer.document # Wrap. wrap_width = width if self.wrap_lines(cli) else None def _create_screen(): screen = Screen(self.default_char, initial_width=width) # Get tokens # Note: we add the space character at the end, because that's where # the cursor can also be. input_tokens, source_to_display, display_to_source = self._get_input_tokens( cli, document) input_tokens += [(self.default_char.token, ' ')] indexes_to_pos = screen.write_data(input_tokens, width=wrap_width) pos_to_indexes = dict((v, k) for k, v in indexes_to_pos.items()) def cursor_position_to_xy(cursor_position): """ Turn a cursor position in the buffer into x/y coordinates on the screen. """ # First get the real token position by applying all transformations. cursor_position = source_to_display(cursor_position) # Then look up into the table. try: return indexes_to_pos[cursor_position] except KeyError: # This can fail with KeyError, but only if one of the # processors is returning invalid key locations. raise # return 0, 0 def xy_to_cursor_position(x, y): """ Turn x/y screen coordinates back to the original cursor position in the buffer. """ # Look up reverse in table. while x > 0 or y > 0: try: index = pos_to_indexes[x, y] break except KeyError: # No match found -> mouse click outside of region # containing text. Look to the left or up. if x: x -= 1 elif y: y -= 1 else: # Nobreak. index = 0 # Transform. return display_to_source(index) return screen, cursor_position_to_xy, xy_to_cursor_position # Build a key for the caching. If any of these parameters changes, we # have to recreate a new screen. key = ( # When the text changes, we obviously have to recreate a new screen. document.text, # When the width changes, line wrapping will be different. # (None when disabled.) wrap_width, # Include invalidation_hashes from all processors. tuple( p.invalidation_hash(cli, document) for p in self.input_processors), ) # Get from cache, or create if this doesn't exist yet. screen, cursor_position_to_xy, self._xy_to_cursor_position = self._screen_lru_cache.get( key, _create_screen) x, y = cursor_position_to_xy(document.cursor_position) screen.cursor_position = Point(y=y, x=x) # If there is an auto completion going on, use that start point for a # pop-up menu position. (But only when this buffer has the focus -- # there is only one place for a menu, determined by the focussed buffer.) if cli.current_buffer_name == self.buffer_name: menu_position = self.menu_position( cli) if self.menu_position else None if menu_position is not None: assert isinstance(menu_position, int) x, y = cursor_position_to_xy(menu_position) screen.menu_position = Point(y=y, x=x) elif buffer.complete_state: # Position for completion menu. # Note: We use 'min', because the original cursor position could be # behind the input string when the actual completion is for # some reason shorter than the text we had before. (A completion # can change and shorten the input.) x, y = cursor_position_to_xy( min( buffer.cursor_position, buffer.complete_state. original_document.cursor_position)) screen.menu_position = Point(y=y, x=x) else: screen.menu_position = None return screen
def create_screen(self, cli, width, height): buffer = self._buffer(cli) # Get the document to be shown. If we are currently searching (the # search buffer has focus, and the preview_search filter is enabled), # then use the search document, which has possibly a different # text/cursor position.) def preview_now(): """ True when we should preview a search. """ return bool( self.preview_search(cli) and cli.is_searching and cli.current_buffer.text) if preview_now(): document = buffer.document_for_search( SearchState(text=cli.current_buffer.text, direction=cli.search_state.direction, ignore_case=cli.search_state.ignore_case)) else: document = buffer.document def _create_screen(): screen = Screen(width) # Get tokens # Note: we add the space character at the end, because that's where # the cursor can also be. input_tokens, cursor_transform_functions = self._get_input_tokens( cli, document) input_tokens += [(Token, ' ')] indexes_to_pos = screen.write_data(input_tokens, screen.width, margin=self._margin( cli, buffer)) def cursor_position_to_xy(cursor_position): # First get the real token position by applying all # transformations from the input processors. for f in cursor_transform_functions: cursor_position = f(cursor_position) # Then look up into the table. try: return indexes_to_pos[cursor_position] except KeyError: # This can fail with KeyError, but only if one of the # processors is returning invalid key locations. raise # return 0, 0 return screen, cursor_position_to_xy # Build a key for the caching. If any of these parameters changes, we # have to recreate a new screen. key = ( # When the text changes, we obviously have to recreate a new screen. document.text, # When the width changes, line wrapping will be different. # TODO: allow to disable line wrapping. + in that case, remove 'width' width, # When line numbers are enabled/disabled. self.show_line_numbers(cli), # Include invalidation_hashes from all processors. tuple( p.invalidation_hash(cli, document) for p in self.input_processors), ) # Get from cache, or create if this doesn't exist yet. screen, cursor_position_to_xy = self._screen_lru_cache.get( key, _create_screen) x, y = cursor_position_to_xy(document.cursor_position) screen.cursor_position = Point(y=y, x=x) # If there is an auto completion going on, use that start point for a # pop-up menu position. (But only when this buffer has the focus -- # there is only one place for a menu, determined by the focussed buffer.) if cli.current_buffer_name == self.buffer_name: menu_position = self.menu_position( cli) if self.menu_position else None if menu_position is not None: assert isinstance(menu_position, int) x, y = cursor_position_to_xy(menu_position) screen.menu_position = Point(y=y, x=x) elif buffer.complete_state: # Position for completion menu. # Note: We use 'min', because the original cursor position could be # behind the input string when the actual completion is for # some reason shorter than the text we had before. (A completion # can change and shorten the input.) x, y = cursor_position_to_xy( min( buffer.cursor_position, buffer.complete_state. original_document.cursor_position)) screen.menu_position = Point(y=y, x=x) else: screen.menu_position = None return screen
def __init__(self, buffer=None, input_processor=None, lexer=None, preview_search=False, focusable=True, search_buffer_control=None, get_search_buffer_control=None, get_search_state=None, menu_position=None, focus_on_click=False, key_bindings=None): from prompt_toolkit.key_binding.key_bindings import KeyBindingsBase assert buffer is None or isinstance(buffer, Buffer) assert input_processor is None or isinstance(input_processor, Processor) assert menu_position is None or callable(menu_position) assert lexer is None or isinstance(lexer, Lexer) assert search_buffer_control is None or isinstance( search_buffer_control, BufferControl) assert get_search_buffer_control is None or callable( get_search_buffer_control) assert not (search_buffer_control and get_search_buffer_control) assert get_search_state is None or callable(get_search_state) assert key_bindings is None or isinstance(key_bindings, KeyBindingsBase) # Default search state. if get_search_state is None: search_state = SearchState() def get_search_state(): return search_state # Default input processor (display search and selection by default.) if input_processor is None: input_processor = merge_processors([ HighlightSearchProcessor(), HighlightSelectionProcessor(), DisplayMultipleCursors(), ]) self.preview_search = to_filter(preview_search) self.focusable = to_filter(focusable) self.get_search_state = get_search_state self.focus_on_click = to_filter(focus_on_click) self.input_processor = input_processor self.buffer = buffer or Buffer() self.menu_position = menu_position self.lexer = lexer or SimpleLexer() self.get_search_buffer_control = get_search_buffer_control self.key_bindings = key_bindings self._search_buffer_control = search_buffer_control #: Cache for the lexer. #: Often, due to cursor movement, undo/redo and window resizing #: operations, it happens that a short time, the same document has to be #: lexed. This is a fairly easy way to cache such an expensive operation. self._fragment_cache = SimpleCache(maxsize=8) self._xy_to_cursor_position = None self._last_click_timestamp = None self._last_get_processed_line = None