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/py2/prompt_toolkit/layout/containers.py | |
parent | 03f024c4412e3aa613bb543cf1660176320ba8f4 (diff) | |
download | ydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz |
fix ya.make
Diffstat (limited to 'contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py')
-rw-r--r-- | contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py | 1665 |
1 files changed, 0 insertions, 1665 deletions
diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py deleted file mode 100644 index 0bdafe18e04..00000000000 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py +++ /dev/null @@ -1,1665 +0,0 @@ -""" -Container for the layout. -(Containers can contain other containers or user interface controls.) -""" -from __future__ import unicode_literals - -from abc import ABCMeta, abstractmethod -from six import with_metaclass -from six.moves import range - -from .controls import UIControl, TokenListControl, UIContent -from .dimension import LayoutDimension, sum_layout_dimensions, max_layout_dimensions -from .margins import Margin -from .screen import Point, WritePosition, _CHAR_CACHE -from .utils import token_list_to_text, explode_tokens -from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.filters import to_cli_filter, ViInsertMode, EmacsInsertMode -from prompt_toolkit.mouse_events import MouseEvent, MouseEventType -from prompt_toolkit.reactive import Integer -from prompt_toolkit.token import Token -from prompt_toolkit.utils import take_using_weights, get_cwidth - -__all__ = ( - 'Container', - 'HSplit', - 'VSplit', - 'FloatContainer', - 'Float', - 'Window', - 'WindowRenderInfo', - 'ConditionalContainer', - 'ScrollOffsets', - 'ColorColumn', -) - -Transparent = Token.Transparent - - -class Container(with_metaclass(ABCMeta, object)): - """ - Base class for user interface layout. - """ - @abstractmethod - def reset(self): - """ - Reset the state of this container and all the children. - (E.g. reset scroll offsets, etc...) - """ - - @abstractmethod - def preferred_width(self, cli, max_available_width): - """ - Return a :class:`~prompt_toolkit.layout.dimension.LayoutDimension` that - represents the desired width for this container. - - :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. - """ - - @abstractmethod - def preferred_height(self, cli, width, max_available_height): - """ - Return a :class:`~prompt_toolkit.layout.dimension.LayoutDimension` that - represents the desired height for this container. - - :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. - """ - - @abstractmethod - def write_to_screen(self, cli, screen, mouse_handlers, write_position): - """ - Write the actual content to the screen. - - :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. - :param screen: :class:`~prompt_toolkit.layout.screen.Screen` - :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`. - """ - - @abstractmethod - def walk(self, cli): - """ - Walk through all the layout nodes (and their children) and yield them. - """ - - -def _window_too_small(): - " Create a `Window` that displays the 'Window too small' text. " - return Window(TokenListControl.static( - [(Token.WindowTooSmall, ' Window too small... ')])) - - -class HSplit(Container): - """ - Several layouts, one stacked above/under the other. - - :param children: List of child :class:`.Container` objects. - :param window_too_small: A :class:`.Container` object that is displayed if - there is not enough space for all the children. By default, this is a - "Window too small" message. - :param get_dimensions: (`None` or a callable that takes a - `CommandLineInterface` and returns a list of `LayoutDimension` - instances.) By default the dimensions are taken from the children and - divided by the available space. However, when `get_dimensions` is specified, - this is taken instead. - :param report_dimensions_callback: When rendering, this function is called - with the `CommandLineInterface` and the list of used dimensions. (As a - list of integers.) - """ - def __init__(self, children, window_too_small=None, - get_dimensions=None, report_dimensions_callback=None): - assert all(isinstance(c, Container) for c in children) - assert window_too_small is None or isinstance(window_too_small, Container) - assert get_dimensions is None or callable(get_dimensions) - assert report_dimensions_callback is None or callable(report_dimensions_callback) - - self.children = children - self.window_too_small = window_too_small or _window_too_small() - self.get_dimensions = get_dimensions - self.report_dimensions_callback = report_dimensions_callback - - def preferred_width(self, cli, max_available_width): - if self.children: - dimensions = [c.preferred_width(cli, max_available_width) for c in self.children] - return max_layout_dimensions(dimensions) - else: - return LayoutDimension(0) - - def preferred_height(self, cli, width, max_available_height): - dimensions = [c.preferred_height(cli, width, max_available_height) for c in self.children] - return sum_layout_dimensions(dimensions) - - def reset(self): - for c in self.children: - c.reset() - - def write_to_screen(self, cli, screen, mouse_handlers, write_position): - """ - Render the prompt to a `Screen` instance. - - :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class - to which the output has to be written. - """ - sizes = self._divide_heigths(cli, write_position) - - if self.report_dimensions_callback: - self.report_dimensions_callback(cli, sizes) - - if sizes is None: - self.window_too_small.write_to_screen( - cli, screen, mouse_handlers, write_position) - else: - # Draw child panes. - ypos = write_position.ypos - xpos = write_position.xpos - width = write_position.width - - for s, c in zip(sizes, self.children): - c.write_to_screen(cli, screen, mouse_handlers, WritePosition(xpos, ypos, width, s)) - ypos += s - - def _divide_heigths(self, cli, write_position): - """ - Return the heights for all rows. - Or None when there is not enough space. - """ - if not self.children: - return [] - - # Calculate heights. - given_dimensions = self.get_dimensions(cli) if self.get_dimensions else None - - def get_dimension_for_child(c, index): - if given_dimensions and given_dimensions[index] is not None: - return given_dimensions[index] - else: - return c.preferred_height(cli, write_position.width, write_position.extended_height) - - dimensions = [get_dimension_for_child(c, index) for index, c in enumerate(self.children)] - - # Sum dimensions - sum_dimensions = sum_layout_dimensions(dimensions) - - # If there is not enough space for both. - # Don't do anything. - if sum_dimensions.min > write_position.extended_height: - return - - # Find optimal sizes. (Start with minimal size, increase until we cover - # the whole height.) - sizes = [d.min for d in dimensions] - - child_generator = take_using_weights( - items=list(range(len(dimensions))), - weights=[d.weight for d in dimensions]) - - i = next(child_generator) - - while sum(sizes) < min(write_position.extended_height, sum_dimensions.preferred): - # Increase until we meet at least the 'preferred' size. - if sizes[i] < dimensions[i].preferred: - sizes[i] += 1 - i = next(child_generator) - - if not any([cli.is_returning, cli.is_exiting, cli.is_aborting]): - while sum(sizes) < min(write_position.height, sum_dimensions.max): - # Increase until we use all the available space. (or until "max") - if sizes[i] < dimensions[i].max: - sizes[i] += 1 - i = next(child_generator) - - return sizes - - def walk(self, cli): - """ Walk through children. """ - yield self - for c in self.children: - for i in c.walk(cli): - yield i - - -class VSplit(Container): - """ - Several layouts, one stacked left/right of the other. - - :param children: List of child :class:`.Container` objects. - :param window_too_small: A :class:`.Container` object that is displayed if - there is not enough space for all the children. By default, this is a - "Window too small" message. - :param get_dimensions: (`None` or a callable that takes a - `CommandLineInterface` and returns a list of `LayoutDimension` - instances.) By default the dimensions are taken from the children and - divided by the available space. However, when `get_dimensions` is specified, - this is taken instead. - :param report_dimensions_callback: When rendering, this function is called - with the `CommandLineInterface` and the list of used dimensions. (As a - list of integers.) - """ - def __init__(self, children, window_too_small=None, - get_dimensions=None, report_dimensions_callback=None): - assert all(isinstance(c, Container) for c in children) - assert window_too_small is None or isinstance(window_too_small, Container) - assert get_dimensions is None or callable(get_dimensions) - assert report_dimensions_callback is None or callable(report_dimensions_callback) - - self.children = children - self.window_too_small = window_too_small or _window_too_small() - self.get_dimensions = get_dimensions - self.report_dimensions_callback = report_dimensions_callback - - def preferred_width(self, cli, max_available_width): - dimensions = [c.preferred_width(cli, max_available_width) for c in self.children] - return sum_layout_dimensions(dimensions) - - def preferred_height(self, cli, width, max_available_height): - sizes = self._divide_widths(cli, width) - if sizes is None: - return LayoutDimension() - else: - dimensions = [c.preferred_height(cli, s, max_available_height) - for s, c in zip(sizes, self.children)] - return max_layout_dimensions(dimensions) - - def reset(self): - for c in self.children: - c.reset() - - def _divide_widths(self, cli, width): - """ - Return the widths for all columns. - Or None when there is not enough space. - """ - if not self.children: - return [] - - # Calculate widths. - given_dimensions = self.get_dimensions(cli) if self.get_dimensions else None - - def get_dimension_for_child(c, index): - if given_dimensions and given_dimensions[index] is not None: - return given_dimensions[index] - else: - return c.preferred_width(cli, width) - - dimensions = [get_dimension_for_child(c, index) for index, c in enumerate(self.children)] - - # Sum dimensions - sum_dimensions = sum_layout_dimensions(dimensions) - - # If there is not enough space for both. - # Don't do anything. - if sum_dimensions.min > width: - return - - # Find optimal sizes. (Start with minimal size, increase until we cover - # the whole height.) - sizes = [d.min for d in dimensions] - - child_generator = take_using_weights( - items=list(range(len(dimensions))), - weights=[d.weight for d in dimensions]) - - i = next(child_generator) - - while sum(sizes) < min(width, sum_dimensions.preferred): - # Increase until we meet at least the 'preferred' size. - if sizes[i] < dimensions[i].preferred: - sizes[i] += 1 - i = next(child_generator) - - while sum(sizes) < min(width, sum_dimensions.max): - # Increase until we use all the available space. - if sizes[i] < dimensions[i].max: - sizes[i] += 1 - i = next(child_generator) - - return sizes - - def write_to_screen(self, cli, screen, mouse_handlers, write_position): - """ - Render the prompt to a `Screen` instance. - - :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class - to which the output has to be written. - """ - if not self.children: - return - - sizes = self._divide_widths(cli, write_position.width) - - if self.report_dimensions_callback: - self.report_dimensions_callback(cli, sizes) - - # If there is not enough space. - if sizes is None: - self.window_too_small.write_to_screen( - cli, screen, mouse_handlers, write_position) - return - - # Calculate heights, take the largest possible, but not larger than write_position.extended_height. - heights = [child.preferred_height(cli, width, write_position.extended_height).preferred - for width, child in zip(sizes, self.children)] - height = max(write_position.height, min(write_position.extended_height, max(heights))) - - # Draw child panes. - ypos = write_position.ypos - xpos = write_position.xpos - - for s, c in zip(sizes, self.children): - c.write_to_screen(cli, screen, mouse_handlers, WritePosition(xpos, ypos, s, height)) - xpos += s - - def walk(self, cli): - """ Walk through children. """ - yield self - for c in self.children: - for i in c.walk(cli): - yield i - - -class FloatContainer(Container): - """ - Container which can contain another container for the background, as well - as a list of floating containers on top of it. - - Example Usage:: - - FloatContainer(content=Window(...), - floats=[ - Float(xcursor=True, - ycursor=True, - layout=CompletionMenu(...)) - ]) - """ - def __init__(self, content, floats): - assert isinstance(content, Container) - assert all(isinstance(f, Float) for f in floats) - - self.content = content - self.floats = floats - - def reset(self): - self.content.reset() - - for f in self.floats: - f.content.reset() - - def preferred_width(self, cli, write_position): - return self.content.preferred_width(cli, write_position) - - def preferred_height(self, cli, width, max_available_height): - """ - Return the preferred height of the float container. - (We don't care about the height of the floats, they should always fit - into the dimensions provided by the container.) - """ - return self.content.preferred_height(cli, width, max_available_height) - - def write_to_screen(self, cli, screen, mouse_handlers, write_position): - self.content.write_to_screen(cli, screen, mouse_handlers, write_position) - - for fl in self.floats: - # When a menu_position was given, use this instead of the cursor - # position. (These cursor positions are absolute, translate again - # relative to the write_position.) - # Note: This should be inside the for-loop, because one float could - # set the cursor position to be used for the next one. - cursor_position = screen.menu_position or screen.cursor_position - cursor_position = Point(x=cursor_position.x - write_position.xpos, - y=cursor_position.y - write_position.ypos) - - fl_width = fl.get_width(cli) - fl_height = fl.get_height(cli) - - # Left & width given. - if fl.left is not None and fl_width is not None: - xpos = fl.left - width = fl_width - # Left & right given -> calculate width. - elif fl.left is not None and fl.right is not None: - xpos = fl.left - width = write_position.width - fl.left - fl.right - # Width & right given -> calculate left. - elif fl_width is not None and fl.right is not None: - xpos = write_position.width - fl.right - fl_width - width = fl_width - elif fl.xcursor: - width = fl_width - if width is None: - width = fl.content.preferred_width(cli, write_position.width).preferred - width = min(write_position.width, width) - - xpos = cursor_position.x - if xpos + width > write_position.width: - xpos = max(0, write_position.width - width) - # Only width given -> center horizontally. - elif fl_width: - xpos = int((write_position.width - fl_width) / 2) - width = fl_width - # Otherwise, take preferred width from float content. - else: - width = fl.content.preferred_width(cli, write_position.width).preferred - - if fl.left is not None: - xpos = fl.left - elif fl.right is not None: - xpos = max(0, write_position.width - width - fl.right) - else: # Center horizontally. - xpos = max(0, int((write_position.width - width) / 2)) - - # Trim. - width = min(width, write_position.width - xpos) - - # Top & height given. - if fl.top is not None and fl_height is not None: - ypos = fl.top - height = fl_height - # Top & bottom given -> calculate height. - elif fl.top is not None and fl.bottom is not None: - ypos = fl.top - height = write_position.height - fl.top - fl.bottom - # Height & bottom given -> calculate top. - elif fl_height is not None and fl.bottom is not None: - ypos = write_position.height - fl_height - fl.bottom - height = fl_height - # Near cursor - elif fl.ycursor: - ypos = cursor_position.y + 1 - - height = fl_height - if height is None: - height = fl.content.preferred_height( - cli, width, write_position.extended_height).preferred - - # Reduce height if not enough space. (We can use the - # extended_height when the content requires it.) - if height > write_position.extended_height - ypos: - if write_position.extended_height - ypos + 1 >= ypos: - # When the space below the cursor is more than - # the space above, just reduce the height. - height = write_position.extended_height - ypos - else: - # Otherwise, fit the float above the cursor. - height = min(height, cursor_position.y) - ypos = cursor_position.y - height - - # Only height given -> center vertically. - elif fl_width: - ypos = int((write_position.height - fl_height) / 2) - height = fl_height - # Otherwise, take preferred height from content. - else: - height = fl.content.preferred_height( - cli, width, write_position.extended_height).preferred - - if fl.top is not None: - ypos = fl.top - elif fl.bottom is not None: - ypos = max(0, write_position.height - height - fl.bottom) - else: # Center vertically. - ypos = max(0, int((write_position.height - height) / 2)) - - # Trim. - height = min(height, write_position.height - ypos) - - # Write float. - # (xpos and ypos can be negative: a float can be partially visible.) - if height > 0 and width > 0: - wp = WritePosition(xpos=xpos + write_position.xpos, - ypos=ypos + write_position.ypos, - width=width, height=height) - - if not fl.hide_when_covering_content or self._area_is_empty(screen, wp): - fl.content.write_to_screen(cli, screen, mouse_handlers, wp) - - def _area_is_empty(self, screen, write_position): - """ - Return True when the area below the write position is still empty. - (For floats that should not hide content underneath.) - """ - wp = write_position - Transparent = Token.Transparent - - for y in range(wp.ypos, wp.ypos + wp.height): - if y in screen.data_buffer: - row = screen.data_buffer[y] - - for x in range(wp.xpos, wp.xpos + wp.width): - c = row[x] - if c.char != ' ' or c.token != Transparent: - return False - - return True - - def walk(self, cli): - """ Walk through children. """ - yield self - - for i in self.content.walk(cli): - yield i - - for f in self.floats: - for i in f.content.walk(cli): - yield i - - -class Float(object): - """ - Float for use in a :class:`.FloatContainer`. - - :param content: :class:`.Container` instance. - :param hide_when_covering_content: Hide the float when it covers content underneath. - """ - def __init__(self, top=None, right=None, bottom=None, left=None, - width=None, height=None, get_width=None, get_height=None, - xcursor=False, ycursor=False, content=None, - hide_when_covering_content=False): - assert isinstance(content, Container) - assert width is None or get_width is None - assert height is None or get_height is None - - self.left = left - self.right = right - self.top = top - self.bottom = bottom - - self._width = width - self._height = height - - self._get_width = get_width - self._get_height = get_height - - self.xcursor = xcursor - self.ycursor = ycursor - - self.content = content - self.hide_when_covering_content = hide_when_covering_content - - def get_width(self, cli): - if self._width: - return self._width - if self._get_width: - return self._get_width(cli) - - def get_height(self, cli): - if self._height: - return self._height - if self._get_height: - return self._get_height(cli) - - def __repr__(self): - return 'Float(content=%r)' % self.content - - -class WindowRenderInfo(object): - """ - Render information, for the last render time of this control. - It stores mapping information between the input buffers (in case of a - :class:`~prompt_toolkit.layout.controls.BufferControl`) and the actual - render position on the output screen. - - (Could be used for implementation of the Vi 'H' and 'L' key bindings as - well as implementing mouse support.) - - :param ui_content: The original :class:`.UIContent` instance that contains - the whole input, without clipping. (ui_content) - :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. - :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. - :param window_width: The width of the window that displays the content, - without the margins. - :param window_height: The height of the window that displays the content. - :param configured_scroll_offsets: The scroll offsets as configured for the - :class:`Window` instance. - :param visible_line_to_row_col: Mapping that maps the row numbers on the - displayed screen (starting from zero for the first visible line) to - (row, col) tuples pointing to the row and column of the :class:`.UIContent`. - :param rowcol_to_yx: Mapping that maps (row, column) tuples representing - coordinates of the :class:`UIContent` to (y, x) absolute coordinates at - the rendered screen. - """ - def __init__(self, ui_content, horizontal_scroll, vertical_scroll, - window_width, window_height, - configured_scroll_offsets, - visible_line_to_row_col, rowcol_to_yx, - x_offset, y_offset, wrap_lines): - assert isinstance(ui_content, UIContent) - assert isinstance(horizontal_scroll, int) - assert isinstance(vertical_scroll, int) - assert isinstance(window_width, int) - assert isinstance(window_height, int) - assert isinstance(configured_scroll_offsets, ScrollOffsets) - assert isinstance(visible_line_to_row_col, dict) - assert isinstance(rowcol_to_yx, dict) - assert isinstance(x_offset, int) - assert isinstance(y_offset, int) - assert isinstance(wrap_lines, bool) - - self.ui_content = ui_content - self.vertical_scroll = vertical_scroll - self.window_width = window_width # Width without margins. - self.window_height = window_height - - self.configured_scroll_offsets = configured_scroll_offsets - self.visible_line_to_row_col = visible_line_to_row_col - self.wrap_lines = wrap_lines - - self._rowcol_to_yx = rowcol_to_yx # row/col from input to absolute y/x - # screen coordinates. - self._x_offset = x_offset - self._y_offset = y_offset - - @property - def visible_line_to_input_line(self): - return dict( - (visible_line, rowcol[0]) - for visible_line, rowcol in self.visible_line_to_row_col.items()) - - @property - def cursor_position(self): - """ - Return the cursor position coordinates, relative to the left/top corner - of the rendered screen. - """ - cpos = self.ui_content.cursor_position - y, x = self._rowcol_to_yx[cpos.y, cpos.x] - return Point(x=x - self._x_offset, y=y - self._y_offset) - - @property - def applied_scroll_offsets(self): - """ - Return a :class:`.ScrollOffsets` instance that indicates the actual - offset. This can be less than or equal to what's configured. E.g, when - the cursor is completely at the top, the top offset will be zero rather - than what's configured. - """ - if self.displayed_lines[0] == 0: - top = 0 - else: - # Get row where the cursor is displayed. - y = self.input_line_to_visible_line[self.ui_content.cursor_position.y] - top = min(y, self.configured_scroll_offsets.top) - - return ScrollOffsets( - top=top, - bottom=min(self.ui_content.line_count - self.displayed_lines[-1] - 1, - self.configured_scroll_offsets.bottom), - - # For left/right, it probably doesn't make sense to return something. - # (We would have to calculate the widths of all the lines and keep - # double width characters in mind.) - left=0, right=0) - - @property - def displayed_lines(self): - """ - List of all the visible rows. (Line numbers of the input buffer.) - The last line may not be entirely visible. - """ - return sorted(row for row, col in self.visible_line_to_row_col.values()) - - @property - def input_line_to_visible_line(self): - """ - Return the dictionary mapping the line numbers of the input buffer to - the lines of the screen. When a line spans several rows at the screen, - the first row appears in the dictionary. - """ - result = {} - for k, v in self.visible_line_to_input_line.items(): - if v in result: - result[v] = min(result[v], k) - else: - result[v] = k - return result - - def first_visible_line(self, after_scroll_offset=False): - """ - Return the line number (0 based) of the input document that corresponds - with the first visible line. - """ - if after_scroll_offset: - return self.displayed_lines[self.applied_scroll_offsets.top] - else: - return self.displayed_lines[0] - - def last_visible_line(self, before_scroll_offset=False): - """ - Like `first_visible_line`, but for the last visible line. - """ - if before_scroll_offset: - return self.displayed_lines[-1 - self.applied_scroll_offsets.bottom] - else: - return self.displayed_lines[-1] - - def center_visible_line(self, before_scroll_offset=False, - after_scroll_offset=False): - """ - Like `first_visible_line`, but for the center visible line. - """ - return (self.first_visible_line(after_scroll_offset) + - (self.last_visible_line(before_scroll_offset) - - self.first_visible_line(after_scroll_offset)) // 2 - ) - - @property - def content_height(self): - """ - The full height of the user control. - """ - return self.ui_content.line_count - - @property - def full_height_visible(self): - """ - True when the full height is visible (There is no vertical scroll.) - """ - return self.vertical_scroll == 0 and self.last_visible_line() == self.content_height - - @property - def top_visible(self): - """ - True when the top of the buffer is visible. - """ - return self.vertical_scroll == 0 - - @property - def bottom_visible(self): - """ - True when the bottom of the buffer is visible. - """ - return self.last_visible_line() == self.content_height - 1 - - @property - def vertical_scroll_percentage(self): - """ - Vertical scroll as a percentage. (0 means: the top is visible, - 100 means: the bottom is visible.) - """ - if self.bottom_visible: - return 100 - else: - return (100 * self.vertical_scroll // self.content_height) - - def get_height_for_line(self, lineno): - """ - Return the height of the given line. - (The height that it would take, if this line became visible.) - """ - if self.wrap_lines: - return self.ui_content.get_height_for_line(lineno, self.window_width) - else: - return 1 - - -class ScrollOffsets(object): - """ - Scroll offsets for the :class:`.Window` class. - - Note that left/right offsets only make sense if line wrapping is disabled. - """ - def __init__(self, top=0, bottom=0, left=0, right=0): - assert isinstance(top, Integer) - assert isinstance(bottom, Integer) - assert isinstance(left, Integer) - assert isinstance(right, Integer) - - self._top = top - self._bottom = bottom - self._left = left - self._right = right - - @property - def top(self): - return int(self._top) - - @property - def bottom(self): - return int(self._bottom) - - @property - def left(self): - return int(self._left) - - @property - def right(self): - return int(self._right) - - def __repr__(self): - return 'ScrollOffsets(top=%r, bottom=%r, left=%r, right=%r)' % ( - self.top, self.bottom, self.left, self.right) - - -class ColorColumn(object): - def __init__(self, position, token=Token.ColorColumn): - self.position = position - self.token = token - - -_in_insert_mode = ViInsertMode() | EmacsInsertMode() - - -class Window(Container): - """ - Container that holds a control. - - :param content: :class:`~prompt_toolkit.layout.controls.UIControl` instance. - :param width: :class:`~prompt_toolkit.layout.dimension.LayoutDimension` instance. - :param height: :class:`~prompt_toolkit.layout.dimension.LayoutDimension` instance. - :param get_width: callable which takes a `CommandLineInterface` and returns a `LayoutDimension`. - :param get_height: callable which takes a `CommandLineInterface` and returns a `LayoutDimension`. - :param dont_extend_width: When `True`, don't take up more width then the - preferred width reported by the control. - :param dont_extend_height: When `True`, don't take up more width then the - preferred height reported by the control. - :param left_margins: A list of :class:`~prompt_toolkit.layout.margins.Margin` - instance to be displayed on the left. For instance: - :class:`~prompt_toolkit.layout.margins.NumberredMargin` can be one of - them in order to show line numbers. - :param right_margins: Like `left_margins`, but on the other side. - :param scroll_offsets: :class:`.ScrollOffsets` instance, representing the - preferred amount of lines/columns to be always visible before/after the - cursor. When both top and bottom are a very high number, the cursor - will be centered vertically most of the time. - :param allow_scroll_beyond_bottom: A `bool` or - :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, allow - scrolling so far, that the top part of the content is not visible - anymore, while there is still empty space available at the bottom of - the window. In the Vi editor for instance, this is possible. You will - see tildes while the top part of the body is hidden. - :param wrap_lines: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` - instance. When True, don't scroll horizontally, but wrap lines instead. - :param get_vertical_scroll: Callable that takes this window - instance as input and returns a preferred vertical scroll. - (When this is `None`, the scroll is only determined by the last and - current cursor position.) - :param get_horizontal_scroll: Callable that takes this window - instance as input and returns a preferred vertical scroll. - :param always_hide_cursor: A `bool` or - :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, never - display the cursor, even when the user control specifies a cursor - position. - :param cursorline: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` - instance. When True, display a cursorline. - :param cursorcolumn: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` - instance. When True, display a cursorcolumn. - :param get_colorcolumns: A callable that takes a `CommandLineInterface` and - returns a a list of :class:`.ColorColumn` instances that describe the - columns to be highlighted. - :param cursorline_token: The token to be used for highlighting the current line, - if `cursorline` is True. - :param cursorcolumn_token: The token to be used for highlighting the current line, - if `cursorcolumn` is True. - """ - def __init__(self, content, width=None, height=None, get_width=None, - get_height=None, dont_extend_width=False, dont_extend_height=False, - left_margins=None, right_margins=None, scroll_offsets=None, - allow_scroll_beyond_bottom=False, wrap_lines=False, - get_vertical_scroll=None, get_horizontal_scroll=None, always_hide_cursor=False, - cursorline=False, cursorcolumn=False, get_colorcolumns=None, - cursorline_token=Token.CursorLine, cursorcolumn_token=Token.CursorColumn): - assert isinstance(content, UIControl) - assert width is None or isinstance(width, LayoutDimension) - assert height is None or isinstance(height, LayoutDimension) - assert get_width is None or callable(get_width) - assert get_height is None or callable(get_height) - assert width is None or get_width is None - assert height is None or get_height is None - assert scroll_offsets is None or isinstance(scroll_offsets, ScrollOffsets) - assert left_margins is None or all(isinstance(m, Margin) for m in left_margins) - assert right_margins is None or all(isinstance(m, Margin) for m in right_margins) - assert get_vertical_scroll is None or callable(get_vertical_scroll) - assert get_horizontal_scroll is None or callable(get_horizontal_scroll) - assert get_colorcolumns is None or callable(get_colorcolumns) - - self.allow_scroll_beyond_bottom = to_cli_filter(allow_scroll_beyond_bottom) - self.always_hide_cursor = to_cli_filter(always_hide_cursor) - self.wrap_lines = to_cli_filter(wrap_lines) - self.cursorline = to_cli_filter(cursorline) - self.cursorcolumn = to_cli_filter(cursorcolumn) - - self.content = content - self.dont_extend_width = dont_extend_width - self.dont_extend_height = dont_extend_height - self.left_margins = left_margins or [] - self.right_margins = right_margins or [] - self.scroll_offsets = scroll_offsets or ScrollOffsets() - self.get_vertical_scroll = get_vertical_scroll - self.get_horizontal_scroll = get_horizontal_scroll - self._width = get_width or (lambda cli: width) - self._height = get_height or (lambda cli: height) - self.get_colorcolumns = get_colorcolumns or (lambda cli: []) - self.cursorline_token = cursorline_token - self.cursorcolumn_token = cursorcolumn_token - - # Cache for the screens generated by the margin. - self._ui_content_cache = SimpleCache(maxsize=8) - self._margin_width_cache = SimpleCache(maxsize=1) - - self.reset() - - def __repr__(self): - return 'Window(content=%r)' % self.content - - def reset(self): - self.content.reset() - - #: Scrolling position of the main content. - self.vertical_scroll = 0 - self.horizontal_scroll = 0 - - # Vertical scroll 2: this is the vertical offset that a line is - # scrolled if a single line (the one that contains the cursor) consumes - # all of the vertical space. - self.vertical_scroll_2 = 0 - - #: Keep render information (mappings between buffer input and render - #: output.) - self.render_info = None - - def _get_margin_width(self, cli, margin): - """ - Return the width for this margin. - (Calculate only once per render time.) - """ - # Margin.get_width, needs to have a UIContent instance. - def get_ui_content(): - return self._get_ui_content(cli, width=0, height=0) - - def get_width(): - return margin.get_width(cli, get_ui_content) - - key = (margin, cli.render_counter) - return self._margin_width_cache.get(key, get_width) - - def preferred_width(self, cli, max_available_width): - # Calculate the width of the margin. - total_margin_width = sum(self._get_margin_width(cli, m) for m in - self.left_margins + self.right_margins) - - # Window of the content. (Can be `None`.) - preferred_width = self.content.preferred_width( - cli, max_available_width - total_margin_width) - - if preferred_width is not None: - # Include width of the margins. - preferred_width += total_margin_width - - # Merge. - return self._merge_dimensions( - dimension=self._width(cli), - preferred=preferred_width, - dont_extend=self.dont_extend_width) - - def preferred_height(self, cli, width, max_available_height): - total_margin_width = sum(self._get_margin_width(cli, m) for m in - self.left_margins + self.right_margins) - wrap_lines = self.wrap_lines(cli) - - return self._merge_dimensions( - dimension=self._height(cli), - preferred=self.content.preferred_height( - cli, width - total_margin_width, max_available_height, wrap_lines), - dont_extend=self.dont_extend_height) - - @staticmethod - def _merge_dimensions(dimension, preferred=None, dont_extend=False): - """ - Take the LayoutDimension from this `Window` class and the received - preferred size from the `UIControl` and return a `LayoutDimension` to - report to the parent container. - """ - dimension = dimension or LayoutDimension() - - # When a preferred dimension was explicitly given to the Window, - # ignore the UIControl. - if dimension.preferred_specified: - preferred = dimension.preferred - - # When a 'preferred' dimension is given by the UIControl, make sure - # that it stays within the bounds of the Window. - if preferred is not None: - if dimension.max: - preferred = min(preferred, dimension.max) - - if dimension.min: - preferred = max(preferred, dimension.min) - - # When a `dont_extend` flag has been given, use the preferred dimension - # also as the max dimension. - if dont_extend and preferred is not None: - max_ = min(dimension.max, preferred) - else: - max_ = dimension.max - - return LayoutDimension( - min=dimension.min, max=max_, - preferred=preferred, weight=dimension.weight) - - def _get_ui_content(self, cli, width, height): - """ - Create a `UIContent` instance. - """ - def get_content(): - return self.content.create_content(cli, width=width, height=height) - - key = (cli.render_counter, width, height) - return self._ui_content_cache.get(key, get_content) - - def _get_digraph_char(self, cli): - " Return `False`, or the Digraph symbol to be used. " - if cli.quoted_insert: - return '^' - if cli.vi_state.waiting_for_digraph: - if cli.vi_state.digraph_symbol1: - return cli.vi_state.digraph_symbol1 - return '?' - return False - - def write_to_screen(self, cli, screen, mouse_handlers, write_position): - """ - Write window to screen. This renders the user control, the margins and - copies everything over to the absolute position at the given screen. - """ - # Calculate margin sizes. - left_margin_widths = [self._get_margin_width(cli, m) for m in self.left_margins] - right_margin_widths = [self._get_margin_width(cli, m) for m in self.right_margins] - total_margin_width = sum(left_margin_widths + right_margin_widths) - - # Render UserControl. - ui_content = self.content.create_content( - cli, write_position.width - total_margin_width, write_position.height) - assert isinstance(ui_content, UIContent) - - # Scroll content. - wrap_lines = self.wrap_lines(cli) - scroll_func = self._scroll_when_linewrapping if wrap_lines else self._scroll_without_linewrapping - - scroll_func( - ui_content, write_position.width - total_margin_width, write_position.height, cli) - - # Write body - visible_line_to_row_col, rowcol_to_yx = self._copy_body( - cli, ui_content, screen, write_position, - sum(left_margin_widths), write_position.width - total_margin_width, - self.vertical_scroll, self.horizontal_scroll, - has_focus=self.content.has_focus(cli), - wrap_lines=wrap_lines, highlight_lines=True, - vertical_scroll_2=self.vertical_scroll_2, - always_hide_cursor=self.always_hide_cursor(cli)) - - # Remember render info. (Set before generating the margins. They need this.) - x_offset=write_position.xpos + sum(left_margin_widths) - y_offset=write_position.ypos - - self.render_info = WindowRenderInfo( - ui_content=ui_content, - horizontal_scroll=self.horizontal_scroll, - vertical_scroll=self.vertical_scroll, - window_width=write_position.width - total_margin_width, - window_height=write_position.height, - configured_scroll_offsets=self.scroll_offsets, - visible_line_to_row_col=visible_line_to_row_col, - rowcol_to_yx=rowcol_to_yx, - x_offset=x_offset, - y_offset=y_offset, - wrap_lines=wrap_lines) - - # Set mouse handlers. - def mouse_handler(cli, mouse_event): - """ Wrapper around the mouse_handler of the `UIControl` that turns - screen coordinates into line coordinates. """ - # Find row/col position first. - yx_to_rowcol = dict((v, k) for k, v in rowcol_to_yx.items()) - y = mouse_event.position.y - x = mouse_event.position.x - - # If clicked below the content area, look for a position in the - # last line instead. - max_y = write_position.ypos + len(visible_line_to_row_col) - 1 - y = min(max_y, y) - - while x >= 0: - try: - row, col = yx_to_rowcol[y, x] - except KeyError: - # Try again. (When clicking on the right side of double - # width characters, or on the right side of the input.) - x -= 1 - else: - # Found position, call handler of UIControl. - result = self.content.mouse_handler( - cli, MouseEvent(position=Point(x=col, y=row), - event_type=mouse_event.event_type)) - break - else: - # nobreak. - # (No x/y coordinate found for the content. This happens in - # case of a FillControl, that only specifies a background, but - # doesn't have a content. Report (0,0) instead.) - result = self.content.mouse_handler( - cli, MouseEvent(position=Point(x=0, y=0), - event_type=mouse_event.event_type)) - - # If it returns NotImplemented, handle it here. - if result == NotImplemented: - return self._mouse_handler(cli, mouse_event) - - return result - - mouse_handlers.set_mouse_handler_for_range( - x_min=write_position.xpos + sum(left_margin_widths), - x_max=write_position.xpos + write_position.width - total_margin_width, - y_min=write_position.ypos, - y_max=write_position.ypos + write_position.height, - handler=mouse_handler) - - # Render and copy margins. - move_x = 0 - - def render_margin(m, width): - " Render margin. Return `Screen`. " - # Retrieve margin tokens. - tokens = m.create_margin(cli, self.render_info, width, write_position.height) - - # Turn it into a UIContent object. - # already rendered those tokens using this size.) - return TokenListControl.static(tokens).create_content( - cli, width + 1, write_position.height) - - for m, width in zip(self.left_margins, left_margin_widths): - # Create screen for margin. - margin_screen = render_margin(m, width) - - # Copy and shift X. - self._copy_margin(cli, margin_screen, screen, write_position, move_x, width) - move_x += width - - move_x = write_position.width - sum(right_margin_widths) - - for m, width in zip(self.right_margins, right_margin_widths): - # Create screen for margin. - margin_screen = render_margin(m, width) - - # Copy and shift X. - self._copy_margin(cli, margin_screen, screen, write_position, move_x, width) - move_x += width - - def _copy_body(self, cli, ui_content, new_screen, write_position, move_x, - width, vertical_scroll=0, horizontal_scroll=0, - has_focus=False, wrap_lines=False, highlight_lines=False, - vertical_scroll_2=0, always_hide_cursor=False): - """ - Copy the UIContent into the output screen. - """ - xpos = write_position.xpos + move_x - ypos = write_position.ypos - line_count = ui_content.line_count - new_buffer = new_screen.data_buffer - empty_char = _CHAR_CACHE['', Token] - ZeroWidthEscape = Token.ZeroWidthEscape - - # Map visible line number to (row, col) of input. - # 'col' will always be zero if line wrapping is off. - visible_line_to_row_col = {} - rowcol_to_yx = {} # Maps (row, col) from the input to (y, x) screen coordinates. - - # Fill background with default_char first. - default_char = ui_content.default_char - - if default_char: - for y in range(ypos, ypos + write_position.height): - new_buffer_row = new_buffer[y] - for x in range(xpos, xpos + width): - new_buffer_row[x] = default_char - - # Copy content. - def copy(): - y = - vertical_scroll_2 - lineno = vertical_scroll - - while y < write_position.height and lineno < line_count: - # Take the next line and copy it in the real screen. - line = ui_content.get_line(lineno) - - col = 0 - x = -horizontal_scroll - - visible_line_to_row_col[y] = (lineno, horizontal_scroll) - new_buffer_row = new_buffer[y + ypos] - - for token, text in line: - # Remember raw VT escape sequences. (E.g. FinalTerm's - # escape sequences.) - if token == ZeroWidthEscape: - new_screen.zero_width_escapes[y + ypos][x + xpos] += text - continue - - for c in text: - char = _CHAR_CACHE[c, token] - char_width = char.width - - # Wrap when the line width is exceeded. - if wrap_lines and x + char_width > width: - visible_line_to_row_col[y + 1] = ( - lineno, visible_line_to_row_col[y][1] + x) - y += 1 - x = -horizontal_scroll # This would be equal to zero. - # (horizontal_scroll=0 when wrap_lines.) - new_buffer_row = new_buffer[y + ypos] - - if y >= write_position.height: - return y # Break out of all for loops. - - # Set character in screen and shift 'x'. - if x >= 0 and y >= 0 and x < write_position.width: - new_buffer_row[x + xpos] = char - - # When we print a multi width character, make sure - # to erase the neighbous positions in the screen. - # (The empty string if different from everything, - # so next redraw this cell will repaint anyway.) - if char_width > 1: - for i in range(1, char_width): - new_buffer_row[x + xpos + i] = empty_char - - # If this is a zero width characters, then it's - # probably part of a decomposed unicode character. - # See: https://en.wikipedia.org/wiki/Unicode_equivalence - # Merge it in the previous cell. - elif char_width == 0 and x - 1 >= 0: - prev_char = new_buffer_row[x + xpos - 1] - char2 = _CHAR_CACHE[prev_char.char + c, prev_char.token] - new_buffer_row[x + xpos - 1] = char2 - - # Keep track of write position for each character. - rowcol_to_yx[lineno, col] = (y + ypos, x + xpos) - - col += 1 - x += char_width - - lineno += 1 - y += 1 - return y - - y = copy() - - def cursor_pos_to_screen_pos(row, col): - " Translate row/col from UIContent to real Screen coordinates. " - try: - y, x = rowcol_to_yx[row, col] - except KeyError: - # Normally this should never happen. (It is a bug, if it happens.) - # But to be sure, return (0, 0) - return Point(y=0, x=0) - - # raise ValueError( - # 'Invalid position. row=%r col=%r, vertical_scroll=%r, ' - # 'horizontal_scroll=%r, height=%r' % - # (row, col, vertical_scroll, horizontal_scroll, write_position.height)) - else: - return Point(y=y, x=x) - - # Set cursor and menu positions. - if ui_content.cursor_position: - screen_cursor_position = cursor_pos_to_screen_pos( - ui_content.cursor_position.y, ui_content.cursor_position.x) - - if has_focus: - new_screen.cursor_position = screen_cursor_position - - if always_hide_cursor: - new_screen.show_cursor = False - else: - new_screen.show_cursor = ui_content.show_cursor - - self._highlight_digraph(cli, new_screen) - - if highlight_lines: - self._highlight_cursorlines( - cli, new_screen, screen_cursor_position, xpos, ypos, width, - write_position.height) - - # Draw input characters from the input processor queue. - if has_focus and ui_content.cursor_position: - self._show_input_processor_key_buffer(cli, new_screen) - - # Set menu position. - if not new_screen.menu_position and ui_content.menu_position: - new_screen.menu_position = cursor_pos_to_screen_pos( - ui_content.menu_position.y, ui_content.menu_position.x) - - # Update output screne height. - new_screen.height = max(new_screen.height, ypos + write_position.height) - - return visible_line_to_row_col, rowcol_to_yx - - def _highlight_digraph(self, cli, new_screen): - """ - When we are in Vi digraph mode, put a question mark underneath the - cursor. - """ - digraph_char = self._get_digraph_char(cli) - if digraph_char: - cpos = new_screen.cursor_position - new_screen.data_buffer[cpos.y][cpos.x] = \ - _CHAR_CACHE[digraph_char, Token.Digraph] - - def _show_input_processor_key_buffer(self, cli, new_screen): - """ - When the user is typing a key binding that consists of several keys, - display the last pressed key if the user is in insert mode and the key - is meaningful to be displayed. - E.g. Some people want to bind 'jj' to escape in Vi insert mode. But the - first 'j' needs to be displayed in order to get some feedback. - """ - key_buffer = cli.input_processor.key_buffer - - if key_buffer and _in_insert_mode(cli) and not cli.is_done: - # The textual data for the given key. (Can be a VT100 escape - # sequence.) - data = key_buffer[-1].data - - # Display only if this is a 1 cell width character. - if get_cwidth(data) == 1: - cpos = new_screen.cursor_position - new_screen.data_buffer[cpos.y][cpos.x] = \ - _CHAR_CACHE[data, Token.PartialKeyBinding] - - def _highlight_cursorlines(self, cli, new_screen, cpos, x, y, width, height): - """ - Highlight cursor row/column. - """ - cursor_line_token = (':', ) + self.cursorline_token - cursor_column_token = (':', ) + self.cursorcolumn_token - - data_buffer = new_screen.data_buffer - - # Highlight cursor line. - if self.cursorline(cli): - row = data_buffer[cpos.y] - for x in range(x, x + width): - original_char = row[x] - row[x] = _CHAR_CACHE[ - original_char.char, original_char.token + cursor_line_token] - - # Highlight cursor column. - if self.cursorcolumn(cli): - for y2 in range(y, y + height): - row = data_buffer[y2] - original_char = row[cpos.x] - row[cpos.x] = _CHAR_CACHE[ - original_char.char, original_char.token + cursor_column_token] - - # Highlight color columns - for cc in self.get_colorcolumns(cli): - assert isinstance(cc, ColorColumn) - color_column_token = (':', ) + cc.token - column = cc.position - - for y2 in range(y, y + height): - row = data_buffer[y2] - original_char = row[column] - row[column] = _CHAR_CACHE[ - original_char.char, original_char.token + color_column_token] - - def _copy_margin(self, cli, lazy_screen, new_screen, write_position, move_x, width): - """ - Copy characters from the margin screen to the real screen. - """ - xpos = write_position.xpos + move_x - ypos = write_position.ypos - - margin_write_position = WritePosition(xpos, ypos, width, write_position.height) - self._copy_body(cli, lazy_screen, new_screen, margin_write_position, 0, width) - - def _scroll_when_linewrapping(self, ui_content, width, height, cli): - """ - Scroll to make sure the cursor position is visible and that we maintain - the requested scroll offset. - - Set `self.horizontal_scroll/vertical_scroll`. - """ - scroll_offsets_bottom = self.scroll_offsets.bottom - scroll_offsets_top = self.scroll_offsets.top - - # We don't have horizontal scrolling. - self.horizontal_scroll = 0 - - # If the current line consumes more than the whole window height, - # then we have to scroll vertically inside this line. (We don't take - # the scroll offsets into account for this.) - # Also, ignore the scroll offsets in this case. Just set the vertical - # scroll to this line. - if ui_content.get_height_for_line(ui_content.cursor_position.y, width) > height - scroll_offsets_top: - # Calculate the height of the text before the cursor, with the line - # containing the cursor included, and the character belowe the - # cursor included as well. - line = explode_tokens(ui_content.get_line(ui_content.cursor_position.y)) - text_before_cursor = token_list_to_text(line[:ui_content.cursor_position.x + 1]) - text_before_height = UIContent.get_height_for_text(text_before_cursor, width) - - # Adjust scroll offset. - self.vertical_scroll = ui_content.cursor_position.y - self.vertical_scroll_2 = min(text_before_height - 1, self.vertical_scroll_2) - self.vertical_scroll_2 = max(0, text_before_height - height, self.vertical_scroll_2) - return - else: - self.vertical_scroll_2 = 0 - - # Current line doesn't consume the whole height. Take scroll offsets into account. - def get_min_vertical_scroll(): - # Make sure that the cursor line is not below the bottom. - # (Calculate how many lines can be shown between the cursor and the .) - used_height = 0 - prev_lineno = ui_content.cursor_position.y - - for lineno in range(ui_content.cursor_position.y, -1, -1): - used_height += ui_content.get_height_for_line(lineno, width) - - if used_height > height - scroll_offsets_bottom: - return prev_lineno - else: - prev_lineno = lineno - return 0 - - def get_max_vertical_scroll(): - # Make sure that the cursor line is not above the top. - prev_lineno = ui_content.cursor_position.y - used_height = 0 - - for lineno in range(ui_content.cursor_position.y - 1, -1, -1): - used_height += ui_content.get_height_for_line(lineno, width) - - if used_height > scroll_offsets_top: - return prev_lineno - else: - prev_lineno = lineno - return prev_lineno - - def get_topmost_visible(): - """ - Calculate the upper most line that can be visible, while the bottom - is still visible. We should not allow scroll more than this if - `allow_scroll_beyond_bottom` is false. - """ - prev_lineno = ui_content.line_count - 1 - used_height = 0 - for lineno in range(ui_content.line_count - 1, -1, -1): - used_height += ui_content.get_height_for_line(lineno, width) - if used_height > height: - return prev_lineno - else: - prev_lineno = lineno - return prev_lineno - - # Scroll vertically. (Make sure that the whole line which contains the - # cursor is visible. - topmost_visible = get_topmost_visible() - - # Note: the `min(topmost_visible, ...)` is to make sure that we - # don't require scrolling up because of the bottom scroll offset, - # when we are at the end of the document. - self.vertical_scroll = max(self.vertical_scroll, min(topmost_visible, get_min_vertical_scroll())) - self.vertical_scroll = min(self.vertical_scroll, get_max_vertical_scroll()) - - # Disallow scrolling beyond bottom? - if not self.allow_scroll_beyond_bottom(cli): - self.vertical_scroll = min(self.vertical_scroll, topmost_visible) - - def _scroll_without_linewrapping(self, ui_content, width, height, cli): - """ - Scroll to make sure the cursor position is visible and that we maintain - the requested scroll offset. - - Set `self.horizontal_scroll/vertical_scroll`. - """ - cursor_position = ui_content.cursor_position or Point(0, 0) - - # Without line wrapping, we will never have to scroll vertically inside - # a single line. - self.vertical_scroll_2 = 0 - - if ui_content.line_count == 0: - self.vertical_scroll = 0 - self.horizontal_scroll = 0 - return - else: - current_line_text = token_list_to_text(ui_content.get_line(cursor_position.y)) - - def do_scroll(current_scroll, scroll_offset_start, scroll_offset_end, - cursor_pos, window_size, content_size): - " Scrolling algorithm. Used for both horizontal and vertical scrolling. " - # Calculate the scroll offset to apply. - # This can obviously never be more than have the screen size. Also, when the - # cursor appears at the top or bottom, we don't apply the offset. - scroll_offset_start = int(min(scroll_offset_start, window_size / 2, cursor_pos)) - scroll_offset_end = int(min(scroll_offset_end, window_size / 2, - content_size - 1 - cursor_pos)) - - # Prevent negative scroll offsets. - if current_scroll < 0: - current_scroll = 0 - - # Scroll back if we scrolled to much and there's still space to show more of the document. - if (not self.allow_scroll_beyond_bottom(cli) and - current_scroll > content_size - window_size): - current_scroll = max(0, content_size - window_size) - - # Scroll up if cursor is before visible part. - if current_scroll > cursor_pos - scroll_offset_start: - current_scroll = max(0, cursor_pos - scroll_offset_start) - - # Scroll down if cursor is after visible part. - if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end: - current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end - - return current_scroll - - # When a preferred scroll is given, take that first into account. - if self.get_vertical_scroll: - self.vertical_scroll = self.get_vertical_scroll(self) - assert isinstance(self.vertical_scroll, int) - if self.get_horizontal_scroll: - self.horizontal_scroll = self.get_horizontal_scroll(self) - assert isinstance(self.horizontal_scroll, int) - - # Update horizontal/vertical scroll to make sure that the cursor - # remains visible. - offsets = self.scroll_offsets - - self.vertical_scroll = do_scroll( - current_scroll=self.vertical_scroll, - scroll_offset_start=offsets.top, - scroll_offset_end=offsets.bottom, - cursor_pos=ui_content.cursor_position.y, - window_size=height, - content_size=ui_content.line_count) - - self.horizontal_scroll = do_scroll( - current_scroll=self.horizontal_scroll, - scroll_offset_start=offsets.left, - scroll_offset_end=offsets.right, - cursor_pos=get_cwidth(current_line_text[:ui_content.cursor_position.x]), - window_size=width, - # We can only analyse the current line. Calculating the width off - # all the lines is too expensive. - content_size=max(get_cwidth(current_line_text), self.horizontal_scroll + width)) - - def _mouse_handler(self, cli, mouse_event): - """ - Mouse handler. Called when the UI control doesn't handle this - particular event. - """ - if mouse_event.event_type == MouseEventType.SCROLL_DOWN: - self._scroll_down(cli) - elif mouse_event.event_type == MouseEventType.SCROLL_UP: - self._scroll_up(cli) - - def _scroll_down(self, cli): - " Scroll window down. " - info = self.render_info - - if self.vertical_scroll < info.content_height - info.window_height: - if info.cursor_position.y <= info.configured_scroll_offsets.top: - self.content.move_cursor_down(cli) - - self.vertical_scroll += 1 - - def _scroll_up(self, cli): - " Scroll window up. " - info = self.render_info - - if info.vertical_scroll > 0: - # TODO: not entirely correct yet in case of line wrapping and long lines. - if info.cursor_position.y >= info.window_height - 1 - info.configured_scroll_offsets.bottom: - self.content.move_cursor_up(cli) - - self.vertical_scroll -= 1 - - def walk(self, cli): - # Only yield self. A window doesn't have children. - yield self - - -class ConditionalContainer(Container): - """ - Wrapper around any other container that can change the visibility. The - received `filter` determines whether the given container should be - displayed or not. - - :param content: :class:`.Container` instance. - :param filter: :class:`~prompt_toolkit.filters.CLIFilter` instance. - """ - def __init__(self, content, filter): - assert isinstance(content, Container) - - self.content = content - self.filter = to_cli_filter(filter) - - def __repr__(self): - return 'ConditionalContainer(%r, filter=%r)' % (self.content, self.filter) - - def reset(self): - self.content.reset() - - def preferred_width(self, cli, max_available_width): - if self.filter(cli): - return self.content.preferred_width(cli, max_available_width) - else: - return LayoutDimension.exact(0) - - def preferred_height(self, cli, width, max_available_height): - if self.filter(cli): - return self.content.preferred_height(cli, width, max_available_height) - else: - return LayoutDimension.exact(0) - - def write_to_screen(self, cli, screen, mouse_handlers, write_position): - if self.filter(cli): - return self.content.write_to_screen(cli, screen, mouse_handlers, write_position) - - def walk(self, cli): - return self.content.walk(cli) - - -# Deprecated alias for 'Container'. -Layout = Container |