def apply_transformation( self, transformation_input: TransformationInput) -> Transformation: buffer_control, document, lineno, source_to_display, fragments, _, _ = transformation_input.unpack( ) # When the application is in the 'done' state, don't highlight. if get_app().is_done: return Transformation(fragments) # Get the highlight positions. key = (get_app().render_counter, document.text, document.cursor_position) positions = self._positions_cache.get( key, lambda: self._get_positions_to_highlight(document)) # Apply if positions were found at this line. if positions: for row, col in positions: if row == lineno: col = source_to_display(col) fragments = explode_text_fragments(fragments) style, text, *_ = fragments[col] if col == document.cursor_position_col: style += ' class:matching-bracket.cursor ' else: style += ' class:matching-bracket.other ' fragments[col] = (style, text) return Transformation(fragments)
def request_absolute_cursor_position(self) -> None: """ Get current cursor position. We do this to calculate the minimum available height that we can consume for rendering the prompt. This is the available space below te cursor. For vt100: Do CPR request. (answer will arrive later.) For win32: Do API call. (Answer comes immediately.) """ # Only do this request when the cursor is at the top row. (after a # clear or reset). We will rely on that in `report_absolute_cursor_row`. assert self._cursor_pos.y == 0 # In full-screen mode, always use the total height as min-available-height. if self.full_screen: self._min_available_height = self.output.get_size().rows # For Win32, we have an API call to get the number of rows below the # cursor. elif is_windows(): self._min_available_height = self.output.get_rows_below_cursor_position( ) # Use CPR. else: if self.cpr_support == CPR_Support.NOT_SUPPORTED: return def do_cpr() -> None: # Asks for a cursor position report (CPR). self._waiting_for_cpr_futures.append(Future()) self.output.ask_for_cpr() if self.cpr_support == CPR_Support.SUPPORTED: do_cpr() # If we don't know whether CPR is supported, only do a request if # none is pending, and test it, using a timer. elif self.cpr_support == CPR_Support.UNKNOWN and not self.waiting_for_cpr: do_cpr() async def timer() -> None: await sleep(self.CPR_TIMEOUT) # Not set in the meantime -> not supported. if self.cpr_support == CPR_Support.UNKNOWN: self.cpr_support = CPR_Support.NOT_SUPPORTED if self.cpr_not_supported_callback: # Make sure to call this callback in the main thread. self.cpr_not_supported_callback() get_app().create_background_task(timer())
def completion_is_selected() -> bool: """ True when the user selected a completion. """ complete_state = get_app().current_buffer.complete_state return (complete_state is not None and complete_state.current_completion is not None)
def _start_timeout(self) -> None: """ Start auto flush timeout. Similar to Vim's `timeoutlen` option. Start a background thread with a timer. When this timeout expires and no key was pressed in the meantime, we flush all data in the queue and call the appropriate key binding handlers. """ app = get_app() timeout = app.timeoutlen if timeout is None: return counter = self._keys_pressed async def wait() -> None: " Wait for timeout. " await sleep(timeout) if len(self.key_buffer) > 0 and counter == self._keys_pressed: # (No keys pressed in the meantime.) flush_keys() def flush_keys() -> None: " Flush keys. " self.feed(_Flush) self.process_keys() # Automatically flush keys. # (_daemon needs to be set, otherwise, this will hang the # application for .5 seconds before exiting.) app.create_background_task(wait())
def emacs_insert_mode() -> bool: app = get_app() if (app.editing_mode != EditingMode.EMACS or app.current_buffer.selection_state or app.current_buffer.read_only()): return False return True
def create_content(self, width: int, height: int) -> UIContent: """ Create a UIContent object for this control. """ complete_state = get_app().current_buffer.complete_state if complete_state: completions = complete_state.completions index = complete_state.complete_index # Can be None! # Calculate width of completions menu. menu_width = self._get_menu_width(width, complete_state) menu_meta_width = self._get_menu_meta_width(width - menu_width, complete_state) show_meta = self._show_meta(complete_state) def get_line(i: int) -> StyleAndTextTuples: c = completions[i] is_current_completion = (i == index) result = _get_menu_item_fragments( c, is_current_completion, menu_width, space_after=True) if show_meta: result += self._get_menu_item_meta_fragments(c, is_current_completion, menu_meta_width) return result return UIContent(get_line=get_line, cursor_position=Point(x=0, y=index or 0), line_count=len(completions)) return UIContent()
def control_is_searchable() -> bool: " When the current UIControl is searchable. " from prompt_toolkit_dev.layout.controls import BufferControl control = get_app().layout.current_control return (isinstance(control, BufferControl) and control.search_buffer_control is not None)
def _get_menu_fragments(self) -> StyleAndTextTuples: focused = get_app().layout.has_focus(self.window) # This is called during the rendering. When we discover that this # widget doesn't have the focus anymore. Reset menu state. if not focused: self.selected_menu = [0] # Generate text fragments for the main menu. def one_item(i: int, item: MenuItem) -> Iterable[OneStyleAndTextTuple]: def mouse_handler(mouse_event: MouseEvent) -> None: if mouse_event.event_type == MouseEventType.MOUSE_UP: # Toggle focus. app = get_app() if app.layout.has_focus(self.window): if self.selected_menu == [i]: app.layout.focus_last() else: app.layout.focus(self.window) self.selected_menu = [i] yield ('class:menu-bar', ' ', mouse_handler) if i == self.selected_menu[0] and focused: yield ('[SetMenuPosition]', '', mouse_handler) style = 'class:menu-bar.selected-item' else: style = 'class:menu-bar' yield style, item.text, mouse_handler result: StyleAndTextTuples = [] for i, item in enumerate(self.menu_items): result.extend(one_item(i, item)) return result
def vi_recording_macro() -> bool: " When recording a Vi macro. " app = get_app() if app.editing_mode != EditingMode.VI: return False return app.vi_state.recording_register is not None
def mouse_handler(mouse_event: MouseEvent) -> None: if mouse_event.event_type == MouseEventType.MOUSE_UP: app = get_app() if item.handler: app.layout.focus_last() item.handler() else: self.selected_menu = self.selected_menu[:level + 1] + [i]
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_height( self, width: int, max_available_height: int, wrap_lines: bool, get_line_prefix: Optional[GetLinePrefixCallable]) -> Optional[int]: complete_state = get_app().current_buffer.complete_state if complete_state: return len(complete_state.completions) else: return 0
def preferred_width(self, max_available_width: int) -> Optional[int]: complete_state = get_app().current_buffer.complete_state if complete_state: menu_width = self._get_menu_width(500, complete_state) menu_meta_width = self._get_menu_meta_width(500, complete_state) return menu_width + menu_meta_width else: return 0
def get_formatted_text() -> StyleAndTextTuples: arg = get_app().key_processor.arg or '' if arg == '-': arg = '-1' return [ ('class:arg-toolbar', 'Repeat: '), ('class:arg-toolbar.text', arg), ]
def _get_main_buffer( self, buffer_control: 'BufferControl') -> Optional['BufferControl']: from prompt_toolkit_dev.layout.controls import BufferControl prev_control = get_app().layout.search_target_buffer_control if isinstance(prev_control, BufferControl) and \ prev_control.search_buffer_control == buffer_control: return prev_control return None
def test() -> bool: # Consider focused when any window inside this container is # focused. current_window = get_app().layout.current_window for c in walk(cast(Container, value)): if isinstance(c, Window) and c == current_window: return True return False
def mouse_handler(mouse_event: MouseEvent) -> None: if mouse_event.event_type == MouseEventType.MOUSE_UP: # Toggle focus. app = get_app() if app.layout.has_focus(self.window): if self.selected_menu == [i]: app.layout.focus_last() else: app.layout.focus(self.window) self.selected_menu = [i]
def renderer_height_is_known() -> bool: """ Only True when the renderer knows it's real height. (On VT100 terminals, we have to wait for a CPR response, before we can be sure of the available height between the cursor position and the bottom of the terminal. And usually it's nicer to wait with drawing bottom toolbars until we receive the height, in order to avoid flickering -- first drawing somewhere in the middle, and then again at the bottom.) """ return get_app().renderer.height_is_known
def _get_text_fragments(self) -> StyleAndTextTuples: style = 'class:completion-menu.multi-column-meta' state = get_app().current_buffer.complete_state if state and state.current_completion and state.current_completion.display_meta_text: return to_formatted_text( cast(StyleAndTextTuples, [('', ' ')]) + state.current_completion.display_meta + [('', ' ')], style=style) return []
def _get_text_fragments(self) -> StyleAndTextTuples: app = get_app() if app.key_processor.arg is None: return [] else: arg = app.key_processor.arg return [ ('class:prompt.arg', '(arg: '), ('class:prompt.arg.text', str(arg)), ('class:prompt.arg', ') '), ]
def vi_replace_mode() -> bool: from prompt_toolkit_dev.key_binding.vi_state import InputMode app = get_app() if (app.editing_mode != EditingMode.VI or app.vi_state.operator_func or app.vi_state.waiting_for_digraph or app.current_buffer.selection_state or app.vi_state.temporary_navigation_mode or app.current_buffer.read_only()): return False return app.vi_state.input_mode == InputMode.REPLACE
def filter() -> bool: " Only handle key bindings if this menu is visible. " app = get_app() complete_state = app.current_buffer.complete_state # There need to be completions, and one needs to be selected. if complete_state is None or complete_state.complete_index is None: return False # This menu needs to be visible. return any( window.content == self for window in app.layout.visible_windows)
def preferred_height( self, width: int, max_available_height: int, wrap_lines: bool, get_line_prefix: Optional[GetLinePrefixCallable]) -> Optional[int]: """ Preferred height: as much as needed in order to display all the completions. """ complete_state = get_app().current_buffer.complete_state if complete_state is None: return 0 column_width = self._get_column_width(complete_state) column_count = max(1, (width - self._required_margin) // column_width) return int(math.ceil(len(complete_state.completions) / float(column_count)))
def preferred_width(self, max_available_width: int) -> Optional[int]: """ Report the width of the longest meta text as the preferred width of this control. It could be that we use less width, but this way, we're sure that the layout doesn't change when we select another completion (E.g. that completions are suddenly shown in more or fewer columns.) """ app = get_app() if app.current_buffer.complete_state: state = app.current_buffer.complete_state return 2 + max(get_cwidth(c.display_meta_text) for c in state.completions) else: return 0
def move(right: bool = False) -> None: buff = get_app().current_buffer complete_state = buff.complete_state if complete_state is not None and \ complete_state.complete_index is not None: # Calculate new complete index. new_index = complete_state.complete_index if right: new_index += self._rendered_rows else: new_index -= self._rendered_rows if 0 <= new_index < len(complete_state.completions): buff.go_to_completion(new_index)
def _call_handler(self, handler: Binding, key_sequence: List[KeyPress]) -> None: app = get_app() was_recording_emacs = app.emacs_state.is_recording was_recording_vi = bool(app.vi_state.recording_register) was_temporary_navigation_mode = app.vi_state.temporary_navigation_mode arg = self.arg self.arg = None event = KeyPressEvent( weakref.ref(self), arg=arg, key_sequence=key_sequence, previous_key_sequence=self._previous_key_sequence, is_repeat=(handler == self._previous_handler)) # Save the state of the current buffer. if handler.save_before(event): event.app.current_buffer.save_to_undo_stack() # Call handler. from prompt_toolkit_dev.buffer import EditReadOnlyBuffer try: handler.call(event) self._fix_vi_cursor_position(event) except EditReadOnlyBuffer: # When a key binding does an attempt to change a buffer which is # read-only, we can ignore that. We sound a bell and go on. app.output.bell() if was_temporary_navigation_mode: self._leave_vi_temp_navigation_mode(event) self._previous_key_sequence = key_sequence self._previous_handler = handler # Record the key sequence in our macro. (Only if we're in macro mode # before and after executing the key.) if handler.record_in_macro(): if app.emacs_state.is_recording and was_recording_emacs: recording = app.emacs_state.current_recording if recording is not None: # Should always be true, given that # `was_recording_emacs` is set. recording.extend(key_sequence) if app.vi_state.recording_register and was_recording_vi: for k in key_sequence: app.vi_state.current_recording += k.data
def __init__(self, key_processor_ref: 'weakref.ReferenceType[KeyProcessor]', arg: Optional[str], key_sequence: List[KeyPress], previous_key_sequence: List[KeyPress], is_repeat: bool) -> None: self._key_processor_ref = key_processor_ref self.key_sequence = key_sequence self.previous_key_sequence = previous_key_sequence #: True when the previous key sequence was handled by the same handler. self.is_repeat = is_repeat self._arg = arg self._app = get_app()
def vi_navigation_mode() -> bool: """ Active when the set for Vi navigation key bindings are active. """ from prompt_toolkit_dev.key_binding.vi_state import InputMode app = get_app() if (app.editing_mode != EditingMode.VI or app.vi_state.operator_func or app.vi_state.waiting_for_digraph or app.current_buffer.selection_state): return False return (app.vi_state.input_mode == InputMode.NAVIGATION or app.vi_state.temporary_navigation_mode or app.current_buffer.read_only())
def get_formatted_text() -> StyleAndTextTuples: buff = get_app().current_buffer if buff.validation_error: row, column = buff.document.translate_index_to_position( buff.validation_error.cursor_position) if show_position: text = '%s (line=%s column=%s)' % ( buff.validation_error.message, row + 1, column + 1) else: text = buff.validation_error.message return [('class:validation-toolbar', text)] else: return []
async def show_dialog_as_float(dialog): " Coroutine. " float_ = Float(content=dialog) root_container.floats.insert(0, float_) app = get_app() focused_before = app.layout.current_window app.layout.focus(dialog) result = await dialog.future app.layout.focus(focused_before) if float_ in root_container.floats: root_container.floats.remove(float_) return result