diff options
author | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
---|---|---|
committer | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
commit | 06e5c21a835c0e923506c4ff27929f34e00761c2 (patch) | |
tree | 75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py | |
parent | 03f024c4412e3aa613bb543cf1660176320ba8f4 (diff) | |
download | ydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz |
fix ya.make
Diffstat (limited to 'contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py')
-rw-r--r-- | contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py | 957 |
1 files changed, 0 insertions, 957 deletions
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py deleted file mode 100644 index 016d289466..0000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py +++ /dev/null @@ -1,957 +0,0 @@ -""" -User interface Controls for the layout. -""" -import time -from abc import ABCMeta, abstractmethod -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Hashable, - Iterable, - List, - NamedTuple, - Optional, - Union, -) - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.buffer import Buffer -from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.data_structures import Point -from prompt_toolkit.document import Document -from prompt_toolkit.filters import FilterOrBool, to_filter -from prompt_toolkit.formatted_text import ( - AnyFormattedText, - StyleAndTextTuples, - to_formatted_text, -) -from prompt_toolkit.formatted_text.utils import ( - fragment_list_to_text, - fragment_list_width, - split_lines, -) -from prompt_toolkit.lexers import Lexer, SimpleLexer -from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType -from prompt_toolkit.search import SearchState -from prompt_toolkit.selection import SelectionType -from prompt_toolkit.utils import get_cwidth - -from .processors import ( - DisplayMultipleCursors, - HighlightIncrementalSearchProcessor, - HighlightSearchProcessor, - HighlightSelectionProcessor, - Processor, - TransformationInput, - merge_processors, -) - -if TYPE_CHECKING: - from prompt_toolkit.key_binding.key_bindings import ( - KeyBindingsBase, - NotImplementedOrNone, - ) - from prompt_toolkit.utils import Event - - -__all__ = [ - "BufferControl", - "SearchBufferControl", - "DummyControl", - "FormattedTextControl", - "UIControl", - "UIContent", -] - -GetLinePrefixCallable = Callable[[int, int], AnyFormattedText] - - -class UIControl(metaclass=ABCMeta): - """ - Base class for all user interface controls. - """ - - def reset(self) -> None: - # Default reset. (Doesn't have to be implemented.) - pass - - def preferred_width(self, max_available_width: int) -> Optional[int]: - return None - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - return None - - def is_focusable(self) -> bool: - """ - Tell whether this user control is focusable. - """ - return False - - @abstractmethod - def create_content(self, width: int, height: int) -> "UIContent": - """ - Generate the content for this user control. - - Returns a :class:`.UIContent` instance. - """ - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Handle mouse events. - - When `NotImplemented` is returned, it means that the given event is not - handled by the `UIControl` itself. The `Window` or key bindings can - decide to handle this event as scrolling or changing focus. - - :param mouse_event: `MouseEvent` instance. - """ - return NotImplemented - - def move_cursor_down(self) -> None: - """ - Request to move the cursor down. - This happens when scrolling down and the cursor is completely at the - top. - """ - - def move_cursor_up(self) -> None: - """ - Request to move the cursor up. - """ - - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: - """ - The key bindings that are specific for this user control. - - Return a :class:`.KeyBindings` object if some key bindings are - specified, or `None` otherwise. - """ - - def get_invalidate_events(self) -> Iterable["Event[object]"]: - """ - Return a list of `Event` objects. This can be a generator. - (The application collects all these events, in order to bind redraw - handlers to these events.) - """ - return [] - - -class UIContent: - """ - Content generated by a user control. This content consists of a list of - lines. - - :param get_line: Callable that takes a line number and returns the current - line. This is a list of (style_str, text) tuples. - :param line_count: The number of lines. - :param cursor_position: a :class:`.Point` for the cursor position. - :param menu_position: a :class:`.Point` for the menu position. - :param show_cursor: Make the cursor visible. - """ - - def __init__( - self, - get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []), - line_count: int = 0, - cursor_position: Optional[Point] = None, - menu_position: Optional[Point] = None, - show_cursor: bool = True, - ): - - self.get_line = get_line - self.line_count = line_count - self.cursor_position = cursor_position or Point(x=0, y=0) - self.menu_position = menu_position - self.show_cursor = show_cursor - - # Cache for line heights. Maps cache key -> height - self._line_heights_cache: Dict[Hashable, int] = {} - - def __getitem__(self, lineno: int) -> StyleAndTextTuples: - "Make it iterable (iterate line by line)." - if lineno < self.line_count: - return self.get_line(lineno) - else: - raise IndexError - - def get_height_for_line( - self, - lineno: int, - width: int, - get_line_prefix: Optional[GetLinePrefixCallable], - slice_stop: Optional[int] = None, - ) -> int: - """ - Return the height that a given line would need if it is rendered in a - space with the given width (using line wrapping). - - :param get_line_prefix: None or a `Window.get_line_prefix` callable - that returns the prefix to be inserted before this line. - :param slice_stop: Wrap only "line[:slice_stop]" and return that - partial result. This is needed for scrolling the window correctly - when line wrapping. - :returns: The computed height. - """ - # Instead of using `get_line_prefix` as key, we use render_counter - # instead. This is more reliable, because this function could still be - # the same, while the content would change over time. - key = get_app().render_counter, lineno, width, slice_stop - - try: - return self._line_heights_cache[key] - except KeyError: - if width == 0: - height = 10**8 - else: - # Calculate line width first. - line = fragment_list_to_text(self.get_line(lineno))[:slice_stop] - text_width = get_cwidth(line) - - if get_line_prefix: - # Add prefix width. - text_width += fragment_list_width( - to_formatted_text(get_line_prefix(lineno, 0)) - ) - - # Slower path: compute path when there's a line prefix. - height = 1 - - # Keep wrapping as long as the line doesn't fit. - # Keep adding new prefixes for every wrapped line. - while text_width > width: - height += 1 - text_width -= width - - fragments2 = to_formatted_text( - get_line_prefix(lineno, height - 1) - ) - prefix_width = get_cwidth(fragment_list_to_text(fragments2)) - - if prefix_width >= width: # Prefix doesn't fit. - height = 10**8 - break - - text_width += prefix_width - else: - # Fast path: compute height when there's no line prefix. - try: - quotient, remainder = divmod(text_width, width) - except ZeroDivisionError: - height = 10**8 - else: - if remainder: - quotient += 1 # Like math.ceil. - height = max(1, quotient) - - # Cache and return - self._line_heights_cache[key] = height - return height - - -class FormattedTextControl(UIControl): - """ - Control that displays formatted text. This can be either plain text, an - :class:`~prompt_toolkit.formatted_text.HTML` object an - :class:`~prompt_toolkit.formatted_text.ANSI` object, a list of ``(style_str, - text)`` tuples or a callable that takes no argument and returns one of - those, depending on how you prefer to do the formatting. See - ``prompt_toolkit.layout.formatted_text`` for more information. - - (It's mostly optimized for rather small widgets, like toolbars, menus, etc...) - - When this UI control has the focus, the cursor will be shown in the upper - left corner of this control by default. There are two ways for specifying - the cursor position: - - - Pass a `get_cursor_position` function which returns a `Point` instance - with the current cursor position. - - - If the (formatted) text is passed as a list of ``(style, text)`` tuples - and there is one that looks like ``('[SetCursorPosition]', '')``, then - this will specify the cursor position. - - Mouse support: - - The list of fragments can also contain tuples of three items, looking like: - (style_str, text, handler). When mouse support is enabled and the user - clicks on this fragment, then the given handler is called. That handler - should accept two inputs: (Application, MouseEvent) and it should - either handle the event or return `NotImplemented` in case we want the - containing Window to handle this event. - - :param focusable: `bool` or :class:`.Filter`: Tell whether this control is - focusable. - - :param text: Text or formatted text to be displayed. - :param style: Style string applied to the content. (If you want to style - the whole :class:`~prompt_toolkit.layout.Window`, pass the style to the - :class:`~prompt_toolkit.layout.Window` instead.) - :param key_bindings: a :class:`.KeyBindings` object. - :param get_cursor_position: A callable that returns the cursor position as - a `Point` instance. - """ - - def __init__( - self, - text: AnyFormattedText = "", - style: str = "", - focusable: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, - show_cursor: bool = True, - modal: bool = False, - get_cursor_position: Optional[Callable[[], Optional[Point]]] = None, - ) -> None: - - self.text = text # No type check on 'text'. This is done dynamically. - self.style = style - self.focusable = to_filter(focusable) - - # Key bindings. - self.key_bindings = key_bindings - self.show_cursor = show_cursor - self.modal = modal - self.get_cursor_position = get_cursor_position - - #: Cache for the content. - self._content_cache: SimpleCache[Hashable, UIContent] = SimpleCache(maxsize=18) - self._fragment_cache: SimpleCache[int, StyleAndTextTuples] = SimpleCache( - maxsize=1 - ) - # Only cache one fragment list. We don't need the previous item. - - # Render info for the mouse support. - self._fragments: Optional[StyleAndTextTuples] = None - - def reset(self) -> None: - self._fragments = None - - def is_focusable(self) -> bool: - return self.focusable() - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.text!r})" - - def _get_formatted_text_cached(self) -> StyleAndTextTuples: - """ - Get fragments, but only retrieve fragments once during one render run. - (This function is called several times during one rendering, because - we also need those for calculating the dimensions.) - """ - return self._fragment_cache.get( - get_app().render_counter, lambda: to_formatted_text(self.text, self.style) - ) - - def preferred_width(self, max_available_width: int) -> int: - """ - Return the preferred width for this control. - That is the width of the longest line. - """ - text = fragment_list_to_text(self._get_formatted_text_cached()) - line_lengths = [get_cwidth(l) for l in text.split("\n")] - return max(line_lengths) - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - """ - Return the preferred height for this control. - """ - content = self.create_content(width, None) - if wrap_lines: - height = 0 - for i in range(content.line_count): - height += content.get_height_for_line(i, width, get_line_prefix) - if height >= max_available_height: - return max_available_height - return height - else: - return content.line_count - - def create_content(self, width: int, height: Optional[int]) -> UIContent: - # Get fragments - fragments_with_mouse_handlers = self._get_formatted_text_cached() - fragment_lines_with_mouse_handlers = list( - split_lines(fragments_with_mouse_handlers) - ) - - # Strip mouse handlers from fragments. - fragment_lines: List[StyleAndTextTuples] = [ - [(item[0], item[1]) for item in line] - for line in fragment_lines_with_mouse_handlers - ] - - # Keep track of the fragments with mouse handler, for later use in - # `mouse_handler`. - self._fragments = fragments_with_mouse_handlers - - # If there is a `[SetCursorPosition]` in the fragment list, set the - # cursor position here. - def get_cursor_position( - fragment: str = "[SetCursorPosition]", - ) -> Optional[Point]: - for y, line in enumerate(fragment_lines): - x = 0 - for style_str, text, *_ in line: - if fragment in style_str: - return Point(x=x, y=y) - x += len(text) - return None - - # If there is a `[SetMenuPosition]`, set the menu over here. - def get_menu_position() -> Optional[Point]: - return get_cursor_position("[SetMenuPosition]") - - cursor_position = (self.get_cursor_position or get_cursor_position)() - - # Create content, or take it from the cache. - key = (tuple(fragments_with_mouse_handlers), width, cursor_position) - - def get_content() -> UIContent: - return UIContent( - get_line=lambda i: fragment_lines[i], - line_count=len(fragment_lines), - show_cursor=self.show_cursor, - cursor_position=cursor_position, - menu_position=get_menu_position(), - ) - - return self._content_cache.get(key, get_content) - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Handle mouse events. - - (When the fragment list contained mouse handlers and the user clicked on - on any of these, the matching handler is called. This handler can still - return `NotImplemented` in case we want the - :class:`~prompt_toolkit.layout.Window` to handle this particular - event.) - """ - if self._fragments: - # Read the generator. - fragments_for_line = list(split_lines(self._fragments)) - - try: - fragments = fragments_for_line[mouse_event.position.y] - except IndexError: - return NotImplemented - else: - # Find position in the fragment list. - xpos = mouse_event.position.x - - # Find mouse handler for this character. - count = 0 - for item in fragments: - count += len(item[1]) - if count > xpos: - if len(item) >= 3: - # Handler found. Call it. - # (Handler can return NotImplemented, so return - # that result.) - handler = item[2] # type: ignore - return handler(mouse_event) - else: - break - - # Otherwise, don't handle here. - return NotImplemented - - def is_modal(self) -> bool: - return self.modal - - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: - return self.key_bindings - - -class DummyControl(UIControl): - """ - A dummy control object that doesn't paint any content. - - Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The - `fragment` and `char` attributes of the `Window` class can be used to - define the filling.) - """ - - def create_content(self, width: int, height: int) -> UIContent: - def get_line(i: int) -> StyleAndTextTuples: - return [] - - return UIContent( - get_line=get_line, line_count=100**100 - ) # Something very big. - - def is_focusable(self) -> bool: - return False - - -class _ProcessedLine(NamedTuple): - fragments: StyleAndTextTuples - source_to_display: Callable[[int], int] - display_to_source: Callable[[int], int] - - -class BufferControl(UIControl): - """ - Control for visualising the content of a :class:`.Buffer`. - - :param buffer: The :class:`.Buffer` object to be displayed. - :param input_processors: A list of - :class:`~prompt_toolkit.layout.processors.Processor` objects. - :param include_default_input_processors: When True, include the default - processors for highlighting of selection, search and displaying of - multiple cursors. - :param lexer: :class:`.Lexer` instance for syntax highlighting. - :param preview_search: `bool` or :class:`.Filter`: Show search while - typing. When this is `True`, probably you want to add a - ``HighlightIncrementalSearchProcessor`` as well. Otherwise only the - cursor position will move, but the text won't be highlighted. - :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable. - :param focus_on_click: Focus this buffer when it's click, but not yet focused. - :param key_bindings: a :class:`.KeyBindings` object. - """ - - def __init__( - self, - buffer: Optional[Buffer] = None, - input_processors: Optional[List[Processor]] = None, - include_default_input_processors: bool = True, - lexer: Optional[Lexer] = None, - preview_search: FilterOrBool = False, - focusable: FilterOrBool = True, - search_buffer_control: Union[ - None, "SearchBufferControl", Callable[[], "SearchBufferControl"] - ] = None, - menu_position: Optional[Callable[[], Optional[int]]] = None, - focus_on_click: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, - ): - - self.input_processors = input_processors - self.include_default_input_processors = include_default_input_processors - - self.default_input_processors = [ - HighlightSearchProcessor(), - HighlightIncrementalSearchProcessor(), - HighlightSelectionProcessor(), - DisplayMultipleCursors(), - ] - - self.preview_search = to_filter(preview_search) - self.focusable = to_filter(focusable) - self.focus_on_click = to_filter(focus_on_click) - - self.buffer = buffer or Buffer() - self.menu_position = menu_position - self.lexer = lexer or SimpleLexer() - 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[ - Hashable, Callable[[int], StyleAndTextTuples] - ] = SimpleCache(maxsize=8) - - self._last_click_timestamp: Optional[float] = None - self._last_get_processed_line: Optional[Callable[[int], _ProcessedLine]] = None - - def __repr__(self) -> str: - return f"<{self.__class__.__name__} buffer={self.buffer!r} at {id(self)!r}>" - - @property - def search_buffer_control(self) -> Optional["SearchBufferControl"]: - result: Optional[SearchBufferControl] - - if callable(self._search_buffer_control): - result = self._search_buffer_control() - else: - result = self._search_buffer_control - - assert result is None or isinstance(result, SearchBufferControl) - return result - - @property - def search_buffer(self) -> Optional[Buffer]: - control = self.search_buffer_control - if control is not None: - return control.buffer - return None - - @property - def search_state(self) -> SearchState: - """ - Return the `SearchState` for searching this `BufferControl`. This is - always associated with the search control. If one search bar is used - for searching multiple `BufferControls`, then they share the same - `SearchState`. - """ - search_buffer_control = self.search_buffer_control - if search_buffer_control: - return search_buffer_control.searcher_search_state - else: - return SearchState() - - def is_focusable(self) -> bool: - return self.focusable() - - def preferred_width(self, max_available_width: int) -> Optional[int]: - """ - This should return the preferred width. - - Note: We don't specify a preferred width according to the content, - because it would be too expensive. Calculating the preferred - width can be done by calculating the longest line, but this would - require applying all the processors to each line. This is - unfeasible for a larger document, and doing it for small - documents only would result in inconsistent behaviour. - """ - return None - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - - # Calculate the content height, if it was drawn on a screen with the - # given width. - height = 0 - content = self.create_content(width, height=1) # Pass a dummy '1' as height. - - # When line wrapping is off, the height should be equal to the amount - # of lines. - if not wrap_lines: - return content.line_count - - # When the number of lines exceeds the max_available_height, just - # return max_available_height. No need to calculate anything. - if content.line_count >= max_available_height: - return max_available_height - - for i in range(content.line_count): - height += content.get_height_for_line(i, width, get_line_prefix) - - if height >= max_available_height: - return max_available_height - - return height - - def _get_formatted_text_for_line_func( - self, document: Document - ) -> Callable[[int], StyleAndTextTuples]: - """ - Create a function that returns the fragments for a given line. - """ - # Cache using `document.text`. - def get_formatted_text_for_line() -> Callable[[int], StyleAndTextTuples]: - return self.lexer.lex_document(document) - - key = (document.text, self.lexer.invalidation_hash()) - return self._fragment_cache.get(key, get_formatted_text_for_line) - - def _create_get_processed_line_func( - self, document: Document, width: int, height: int - ) -> Callable[[int], _ProcessedLine]: - """ - Create a function that takes a line number of the current document and - returns a _ProcessedLine(processed_fragments, source_to_display, display_to_source) - tuple. - """ - # Merge all input processors together. - input_processors = self.input_processors or [] - if self.include_default_input_processors: - input_processors = self.default_input_processors + input_processors - - merged_processor = merge_processors(input_processors) - - def transform(lineno: int, fragments: StyleAndTextTuples) -> _ProcessedLine: - "Transform the fragments for a given line number." - # Get cursor position at this line. - def source_to_display(i: int) -> int: - """X position from the buffer to the x position in the - processed fragment list. By default, we start from the 'identity' - operation.""" - return i - - transformation = merged_processor.apply_transformation( - TransformationInput( - self, document, lineno, source_to_display, fragments, width, height - ) - ) - - return _ProcessedLine( - transformation.fragments, - transformation.source_to_display, - transformation.display_to_source, - ) - - def create_func() -> Callable[[int], _ProcessedLine]: - get_line = self._get_formatted_text_for_line_func(document) - cache: Dict[int, _ProcessedLine] = {} - - def get_processed_line(i: int) -> _ProcessedLine: - try: - return cache[i] - except KeyError: - processed_line = transform(i, get_line(i)) - cache[i] = processed_line - return processed_line - - return get_processed_line - - return create_func() - - def create_content( - self, width: int, height: int, preview_search: bool = False - ) -> UIContent: - """ - Create a UIContent. - """ - buffer = self.buffer - - # Trigger history loading of the buffer. We do this during the - # rendering of the UI here, because it needs to happen when an - # `Application` with its event loop is running. During the rendering of - # the buffer control is the earliest place we can achieve this, where - # we're sure the right event loop is active, and don't require user - # interaction (like in a key binding). - buffer.load_history_if_not_yet_loaded() - - # 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.) - search_control = self.search_buffer_control - preview_now = preview_search or bool( - # Only if this feature is enabled. - self.preview_search() - and - # And something was typed in the associated search field. - search_control - and search_control.buffer.text - and - # And we are searching in this control. (Many controls can point to - # the same search field, like in Pyvim.) - get_app().layout.search_target_buffer_control == self - ) - - if preview_now and search_control is not None: - ss = self.search_state - - document = buffer.document_for_search( - SearchState( - text=search_control.buffer.text, - direction=ss.direction, - ignore_case=ss.ignore_case, - ) - ) - else: - document = buffer.document - - get_processed_line = self._create_get_processed_line_func( - document, width, height - ) - self._last_get_processed_line = get_processed_line - - def translate_rowcol(row: int, col: int) -> Point: - "Return the content column for this coordinate." - return Point(x=get_processed_line(row).source_to_display(col), y=row) - - def get_line(i: int) -> StyleAndTextTuples: - "Return the fragments for a given line number." - fragments = get_processed_line(i).fragments - - # 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.) - fragments = fragments + [("", " ")] - return fragments - - content = UIContent( - get_line=get_line, - line_count=document.line_count, - cursor_position=translate_rowcol( - document.cursor_position_row, document.cursor_position_col - ), - ) - - # 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 focused buffer.) - if get_app().layout.current_control == self: - menu_position = self.menu_position() 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 mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Mouse handler for this control. - """ - buffer = self.buffer - position = mouse_event.position - - # Focus buffer when clicked. - if get_app().layout.current_control == self: - if self._last_get_processed_line: - processed_line = self._last_get_processed_line(position.y) - - # Translate coordinates back to the cursor position of the - # original input. - xpos = processed_line.display_to_source(position.x) - index = buffer.document.translate_row_col_to_index(position.y, xpos) - - # Set the cursor position. - if mouse_event.event_type == MouseEventType.MOUSE_DOWN: - buffer.exit_selection() - buffer.cursor_position = index - - elif ( - mouse_event.event_type == MouseEventType.MOUSE_MOVE - and mouse_event.button != MouseButton.NONE - ): - # Click and drag to highlight a selection - if ( - buffer.selection_state is None - and abs(buffer.cursor_position - index) > 0 - ): - buffer.start_selection(selection_type=SelectionType.CHARACTERS) - buffer.cursor_position = index - - elif mouse_event.event_type == MouseEventType.MOUSE_UP: - # When the cursor was moved to another place, select the text. - # (The >1 is actually a small but acceptable workaround for - # selecting text in Vi navigation mode. In navigation mode, - # the cursor can never be after the text, so the cursor - # will be repositioned automatically.) - if abs(buffer.cursor_position - index) > 1: - if buffer.selection_state is None: - buffer.start_selection( - selection_type=SelectionType.CHARACTERS - ) - buffer.cursor_position = index - - # Select word around cursor on double click. - # Two MOUSE_UP events in a short timespan are considered a double click. - double_click = ( - self._last_click_timestamp - and time.time() - self._last_click_timestamp < 0.3 - ) - self._last_click_timestamp = time.time() - - if double_click: - start, end = buffer.document.find_boundaries_of_current_word() - buffer.cursor_position += start - buffer.start_selection(selection_type=SelectionType.CHARACTERS) - buffer.cursor_position += end - start - else: - # Don't handle scroll events here. - return NotImplemented - - # Not focused, but focusing on click events. - else: - if ( - self.focus_on_click() - and mouse_event.event_type == MouseEventType.MOUSE_UP - ): - # Focus happens on mouseup. (If we did this on mousedown, the - # up event will be received at the point where this widget is - # focused and be handled anyway.) - get_app().layout.current_control = self - else: - return NotImplemented - - return None - - def move_cursor_down(self) -> None: - b = self.buffer - b.cursor_position += b.document.get_cursor_down_position() - - def move_cursor_up(self) -> None: - b = self.buffer - b.cursor_position += b.document.get_cursor_up_position() - - def get_key_bindings(self) -> Optional["KeyBindingsBase"]: - """ - When additional key bindings are given. Return these. - """ - return self.key_bindings - - def get_invalidate_events(self) -> Iterable["Event[object]"]: - """ - Return the Window invalidate events. - """ - # Whenever the buffer changes, the UI has to be updated. - yield self.buffer.on_text_changed - yield self.buffer.on_cursor_position_changed - - yield self.buffer.on_completions_changed - yield self.buffer.on_suggestion_set - - -class SearchBufferControl(BufferControl): - """ - :class:`.BufferControl` which is used for searching another - :class:`.BufferControl`. - - :param ignore_case: Search case insensitive. - """ - - def __init__( - self, - buffer: Optional[Buffer] = None, - input_processors: Optional[List[Processor]] = None, - lexer: Optional[Lexer] = None, - focus_on_click: FilterOrBool = False, - key_bindings: Optional["KeyBindingsBase"] = None, - ignore_case: FilterOrBool = False, - ): - - super().__init__( - buffer=buffer, - input_processors=input_processors, - lexer=lexer, - focus_on_click=focus_on_click, - key_bindings=key_bindings, - ) - - # If this BufferControl is used as a search field for one or more other - # BufferControls, then represents the search state. - self.searcher_search_state = SearchState(ignore_case=ignore_case) |