def adjust_cursor_position(self, x=None, y=None): d_items = self.displayed_items() if y is not None: if y < len(d_items): self.cursor_position = Point(1, y) if len(d_items) == 0: self.cursor_position = Point(1, 0) elif self.cursor_position.y >= len(d_items): self.cursor_position = Point(1, len(d_items) - 1)
def update_cursor(self): """This function updates the cursor according to the current index in the list. """ try: index = self.indices.index(self.current_index) line = sum(self.options_headers_linecount[i] for i in self.indices[0:index]) self.cursor = Point(0, line) except Exception: self.cursor = Point(0, 0)
def reset(self): # Reset position self._cursor_pos = Point(x=0, y=0) # Remember the last screen instance between renderers. This way, # we can create a `diff` between two screens and only output the # difference. It's also to remember the last height. (To show for # instance a toolbar at the bottom position.) self._last_screen = None self._last_size = None self._last_char = None self._last_style = None # When the style changes, we have to do a full # redraw as well. #: Space from the top of the layout, until the bottom of the terminal. #: We don't know this until a `report_absolute_cursor_row` call. self._min_available_height = 0 # In case of Windown, also make sure to scroll to the current cursor # position. if sys.platform == 'win32': self.output.scroll_buffer_to_prompt() # Quit alternate screen. if self._in_alternate_screen: self.output.quit_alternate_screen() self.output.flush() self._in_alternate_screen = False
def __init__(self, content): lines = [content.get_line(lineno) for lineno in range(content.line_count)] * 3 super(InfiniteContent, self).__init__( get_line=lambda i: lines[i], line_count=len(lines), cursor_position=Point( x=content.cursor_position.x, y=content.cursor_position.y + content.line_count), default_char=content.default_char)
def __init__(self, checklist, session): super().__init__(text=self.checklist_text, get_cursor_position=lambda: self.cursor_position, key_bindings=self.build_key_bindings()) self.checklist = checklist self.session = session self.cursor_position = Point(1, 0) self.undo_stack = [] self.show_completed = False
def __init__(self, message, choices, pointer_index=0): self.message = message self.pointer_index = pointer_index self.answered = False self.selected = [] self._init_choices(choices) super(IssuesController, self).__init__( self.get_formatted_choices, show_cursor=False, get_cursor_position=lambda: Point(1, self.pointer_index))
def create_content(self, width, height): # Report dimensions to the process. self.process.set_size(width, height) # The first time that this user control is rendered. Keep track of the # 'app' object and start the process. if not self._running: self.process.start() self._running = True if not self.process.screen: return UIContent() pt_screen = self.process.screen.pt_screen data_buffer = pt_screen.data_buffer cursor_y = pt_screen.cursor_position.y # Prompt_toolkit needs the amount of characters before the cursor in a # UIControl. This doesn't correspond with the xpos in case of double # width characters. That's why we compute the wcwidth. cursor_row = data_buffer[pt_screen.cursor_position.y] text_before_cursor = ''.join( cursor_row[x].char for x in range(0, pt_screen.cursor_position.x)) cursor_x = len(text_before_cursor) def get_line(number): row = data_buffer[number] empty = True if row: max_column = max(row) empty = False else: max_column = 0 if number == cursor_y: max_column = max(max_column, cursor_x) empty = False if empty: return [('', ' ')] else: cells = [row[i] for i in range(max_column + 1)] return [(cell.style, cell.char) for cell in cells] if data_buffer: line_count = max( data_buffer ) + 1 # TODO: substract all empty lines from the beginning. (If we need to. Not sure.) else: line_count = 1 return UIContent(get_line, line_count=line_count, show_cursor=pt_screen.show_cursor, cursor_position=Point(x=cursor_x, y=cursor_y))
def create_content(self, width, height): menu_width = self.preferred_width(width) def get_line(i): item = self._get_items()[i] is_selected = (i == self._selection) return self._menu_item_fragment(item, is_selected, menu_width) return UIContent(get_line=get_line, cursor_position=Point(x=0, y=self._selection or 0), line_count=len(self._get_items()))
def mouse_handler(cli, mouse_event): """ Wrapper around the mouse_handler of the `UIControl` that turns absolute coordinates into relative coordinates. """ position = mouse_event.position # Call the mouse handler of the UIControl first. self._mouse_handler( cli, MouseEvent(position=Point(x=position.x - write_position.xpos, y=position.y - write_position.ypos + vertical_scroll), event_type=mouse_event.event_type))
def move_cursor(self, count): y = self.model.line = max( 0, min(self.model.line_count() - 1, self.model.line + count)) self.main.content.content.cursor_position = Point(y, 0) if not self.main.render_info: return w = self.main info = w.render_info if y > w.vertical_scroll + info.window_height or y < w.vertical_scroll: w.vertical_scroll = max(0, y - info.window_height // 2)
def reset(self, _scroll=False, leave_alternate_screen=True): # Reset position self._cursor_pos = Point(x=0, y=0) # Remember the last screen instance between renderers. This way, # we can create a `diff` between two screens and only output the # difference. It's also to remember the last height. (To show for # instance a toolbar at the bottom position.) self._last_screen = None self._last_size = None self._last_token = None # When the style hash changes, we have to do a full redraw as well as # clear the `_attrs_for_token` dictionary. self._last_style_hash = None self._attrs_for_token = None # Default MouseHandlers. (Just empty.) self.mouse_handlers = MouseHandlers() # Remember the last title. Only set the title when it changes. self._last_title = None #: Space from the top of the layout, until the bottom of the terminal. #: We don't know this until a `report_absolute_cursor_row` call. self._min_available_height = 0 # In case of Windown, also make sure to scroll to the current cursor # position. (Only when rendering the first time.) if is_windows() and _scroll: self.output.scroll_buffer_to_prompt() # Quit alternate screen. if self._in_alternate_screen and leave_alternate_screen: self.output.quit_alternate_screen() self._in_alternate_screen = False # Disable mouse support. if self._mouse_support_enabled: self.output.disable_mouse_support() self._mouse_support_enabled = False # Disable bracketed paste. if self._bracketed_paste_enabled: self.output.disable_bracketed_paste() self._bracketed_paste_enabled = False # Flush output. `disable_mouse_support` needs to write to stdout. self.output.flush()
def _copy_body(self, cli, temp_screen, new_screen, write_position, vertical_scroll, width): """ Copy characters from the temp screen that we got from the `UIControl` to the real screen. """ xpos = write_position.xpos ypos = write_position.ypos height = write_position.height temp_buffer = temp_screen.data_buffer new_buffer = new_screen.data_buffer temp_screen_height = temp_screen.height vertical_scroll = self.process.screen.line_offset y = 0 # Now copy the region we need to the real screen. for y in range(0, height): # We keep local row variables. (Don't look up the row in the dict # for each iteration of the nested loop.) new_row = new_buffer[y + ypos] if y >= temp_screen_height and y >= write_position.height: # Break out of for loop when we pass after the last row of the # temp screen. (We use the 'y' position for calculation of new # screen's height.) break else: temp_row = temp_buffer[y + vertical_scroll] # Copy row content, except for transparent tokens. # (This is useful in case of floats.) for x in range(0, width): new_row[x + xpos] = temp_row[x] if self.has_focus(cli): new_screen.cursor_position = Point( y=temp_screen.cursor_position.y + ypos - vertical_scroll, x=temp_screen.cursor_position.x + xpos) new_screen.show_cursor = temp_screen.show_cursor # Update height of the output screen. (new_screen.write_data is not # called, so the screen is not aware of its height.) new_screen.height = max(new_screen.height, ypos + y + 1)
def _(event): """ Handling of mouse events for Windows. """ assert is_windows() # This key binding should only exist for Windows. # Parse data. event_type, x, y = event.data.split(';') x = int(x) y = int(y) # Make coordinates absolute to the visible part of the terminal. screen_buffer_info = event.app.renderer.output.get_win32_screen_buffer_info() rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.app.renderer._cursor_pos.y y -= rows_above_cursor # Call the mouse event handler. handler = event.app.renderer.mouse_handlers.mouse_handlers[x, y] handler(MouseEvent(position=Point(x=x, y=y), event_type=event_type))
def scroll(self, count, half): info = self.main.render_info if not info: return if half is not None: count *= info.window_height if half: count = count // 2 self.main.vertical_scroll = max( 0, min(self.model.line_count() - 1, self.main.vertical_scroll + count)) if self.model.line < self.main.vertical_scroll or \ self.model.line > self.main.vertical_scroll + info.window_height: y = self.model.line = self.main.vertical_scroll self.main.content.content.cursor_position = Point(y, 0)
def create_content(self, app, width, height): self.process.set_size(width, height) if not self.process.screen: return UIContent() pt_screen = self.process.screen.pt_screen data_buffer = pt_screen.data_buffer cursor_y = pt_screen.cursor_position.y cursor_x = pt_screen.cursor_position.x def get_line(number): row = data_buffer[number] empty = True if row: max_column = max(row) empty = False else: max_column = 0 if number == cursor_y: max_column = max(max_column, cursor_x) empty = False if empty: return [(Token, ' ')] else: cells = [row[i] for i in range(max_column + 1)] return [(cell.token, cell.char) for cell in cells] if data_buffer: line_count = max(data_buffer) + 1 else: line_count = 1 return UIContent(get_line, line_count=line_count, cursor_position=Point(x=pt_screen.cursor_position.x, y=pt_screen.cursor_position.y))
def __init__(self, options: Sequence[Option], default_index: int = 0, header_filter: Callable[[Option], str] = str, match_filter: Callable[[Option], str] = str, custom_filter: Optional[Callable[[str], bool]] = None, search_buffer: Buffer = Buffer(multiline=False), cpu_count: int = os.cpu_count()): self.search_buffer = search_buffer self.last_query_text = '' # type: str self.search_buffer.on_text_changed += self.update self.header_filter = header_filter self.match_filter = match_filter self.current_index = default_index # type: Optional[int] self.entries_left_offset = 0 self.cpu_count = cpu_count self.options_headers_linecount = [] # type: List[int] self._indices_to_lines = [] # type: List[int] self.options_headers = [] # type: FormattedText self.options_matchers = [] # type: List[str] self.indices = [] # type: List[int] self._options = [] # type: Sequence[Option] self.marks = [] # type: List[int] self.max_entry_height = 1 # type: int # options are processed here also through the setter # ################################################## self.set_options(options) self.cursor = Point(0, 0) # type: Point # ################################################## self.content = FormattedTextControl( text=self.get_tokens, focusable=False, key_bindings=None, get_cursor_position=lambda: self.cursor, ) self.content_window = Window( content=self.content, wrap_lines=False, allow_scroll_beyond_bottom=True, scroll_offsets=ScrollOffsets(bottom=self.max_entry_height), cursorline=False, cursorcolumn=False, # right_margins=[NumberedMargin()], # left_margins=[NumberedMargin()], align=WindowAlign.LEFT, height=None, get_line_prefix=self.get_line_prefix # get_line_prefix=lambda line, b: [('bg:red', ' ')] ) self.update() super(OptionsList, self).__init__(content=self.content_window, filter=(custom_filter if custom_filter is not None else has_focus(self.search_buffer)))
def output_screen_diff(output, screen, current_pos, previous_screen=None, last_char=None, is_done=False, style=None): # XXX: drop is_done """ Create diff of this screen with the previous screen. """ #: Remember the last printed character. last_char = [last_char] # nonlocal background_turned_on = [False] # Nonlocal #: Variable for capturing the output. write = output.write def move_cursor(new): current_x, current_y = current_pos.x, current_pos.y if new.y > current_y: # Use newlines instead of CURSOR_DOWN, because this meight add new lines. # CURSOR_DOWN will never create new lines at the bottom. # Also reset attributes, otherwise the newline could draw a # background color. output.reset_attributes() write('\r\n' * (new.y - current_y)) current_x = 0 output.cursor_forward(new.x) last_char[0] = None # Forget last char after resetting attributes. return new elif new.y < current_y: output.cursor_up(current_y - new.y) if current_x >= screen.width - 1: write('\r') output.cursor_forward(new.x) elif new.x < current_x or current_x >= screen.width - 1: output.cursor_backward(current_x - new.x) elif new.x > current_x: output.cursor_forward(new.x - current_x) return new style_for_token = _StyleForTokenCache(style) def output_char(char): """ Write the output of this character. """ # If the last printed character has the same token, it also has the # same style, so we don't output it. if last_char[0] and last_char[0].token == char.token: write(char.char) else: style = style_for_token[char.token] if style: output.set_attributes(style['color'], style['bgcolor'], bold=style.get('bold', False), underline=style.get('underline', False)) # If we print something with a background color, remember that. background_turned_on[0] = bool(style['bgcolor']) else: # Reset previous style and output. output.reset_attributes() write(char.char) last_char[0] = char # Disable autowrap if not previous_screen: output.disable_autowrap() output.reset_attributes() # When the previous screen has a different size, redraw everything anyway. # Also when we are done. (We meight take up less rows, so clearing is important.) if is_done or not previous_screen or previous_screen.width != screen.width: # XXX: also consider height?? current_pos = move_cursor(Point(0, 0)) output.reset_attributes() output.erase_down() previous_screen = Screen(screen.width) # Get height of the screen. # (current_height changes as we loop over _buffer, so remember the current value.) current_height = screen.current_height # Loop over the rows. row_count = max(screen.current_height, previous_screen.current_height) c = 0 # Column counter. for y, r in enumerate(range(0, row_count)): new_row = screen._buffer[r] previous_row = previous_screen._buffer[r] new_max_line_len = max(new_row.keys()) if new_row else 0 previous_max_line_len = max(previous_row.keys()) if previous_row else 0 # Loop over the columns. c = 0 while c < new_max_line_len + 1: new_char = new_row[c] old_char = previous_row[c] char_width = (new_char.width or 1) # When the old and new character at this position are different, # draw the output. (Because of the performance, we don't call # `Char.__ne__`, but inline the same expression.) if new_char.char != old_char.char or new_char.token != old_char.token: current_pos = move_cursor(Point(y=y, x=c)) output_char(new_char) current_pos = current_pos._replace(x=current_pos.x + char_width) c += char_width # If the new line is shorter, trim it if previous_screen and new_max_line_len < previous_max_line_len: current_pos = move_cursor(Point(y=y, x=new_max_line_len + 1)) output.reset_attributes() output.erase_end_of_line() last_char[0] = None # Forget last char after resetting attributes. # Move cursor: if is_done: current_pos = move_cursor(Point(y=current_height, x=0)) output.erase_down() else: current_pos = move_cursor(screen.cursor_position) if is_done: output.reset_attributes() output.enable_autowrap() # If the last printed character has a background color, always reset. # (Many terminals give weird artifacs on resize events when there is an # active background color.) if background_turned_on[0]: output.reset_attributes() last_char[0] = None return current_pos, last_char[0]
def test_basic(): ol = OptionsList(['hello', 'world', '<bye']) assert (ol.get_selection() == ['hello']) assert (len(ol.marks) == 0) assert (len(ol.indices) == 3) ol.move_down() assert (ol.get_selection() == ['world']) ol.move_up() assert (ol.get_selection() == ['hello']) ol.mark_current_selection() assert (ol.marks == [0]) ol.move_down() ol.move_down() ol.mark_current_selection() assert (ol.marks == [0, 2]) ol.toggle_mark_current_selection() assert (ol.marks == [0]) # fg:red because it failed assert (ol.get_tokens() == [('', 'hello\n'), ('', 'world\n'), ('fg:red', '<bye\n')]) assert (ol.get_line_prefix(2, None) == [ ('class:options_list.selected_margin', '|') ]) assert (ol.get_line_prefix(1, None) == [ ('class:options_list.unselected_margin', ' ') ]) assert (ol.get_line_prefix(0, None) == [ ('class:options_list.marked_margin', '#') ]) assert (ol.search_regex == re.compile('.*', re.I)) ol.search_buffer.text = 'l' assert (ol.search_regex == re.compile('.*l', re.I)) assert (ol.indices == [0, 1]) ol.search_buffer.text = 'l ' assert (ol.search_regex == re.compile('.*l.*', re.I)) assert (ol.indices == [0, 1]) assert (len(ol.get_options()) == 3) ol.deselect() assert (ol.current_index is None) ol.set_options([str(i) for i in range(1000)]) assert (len(ol.marks) == 0) assert (len(ol.indices) == 1000) ol.go_top() assert (ol.get_selection() == ['0']) ol.move_up() assert (ol.get_selection() == ['999']) ol.move_down() assert (ol.get_selection() == ['0']) ol.go_bottom() assert (ol.get_selection() == ['999']) ol.search_buffer.text = 'asdfadsf' assert (ol.indices == []) ol.update_cursor() assert (ol.cursor == Point(0, 0)) # when there is nothing selected and appearing it's ok to # move up and down ol.move_down() ol.move_up() ol.go_top() ol.search_buffer.text = '99' assert (len(ol.indices) == 19) ol.update()
def output_screen_diff(output, screen, current_pos, previous_screen=None, last_char=None, is_done=False, style=None): # XXX: drop is_done """ Create diff of this screen with the previous screen. This is some performance-critical code which is heavily optimized. Don't change things without profiling first. """ #: Remember the last printed character. last_char = [last_char] # nonlocal background_turned_on = [False] # Nonlocal #: Variable for capturing the output. write = output.write # Create locals for the most used output methods. # (Save expensive attribute lookups.) _output_set_attributes = output.set_attributes _output_reset_attributes = output.reset_attributes _output_cursor_forward = output.cursor_forward _output_cursor_up = output.cursor_up _output_cursor_backward = output.cursor_backward def reset_attributes(): " Wrapper around Output.reset_attributes. " _output_reset_attributes() last_char[0] = None # Forget last char after resetting attributes. def move_cursor(new): " Move cursor to this `new` point. Returns the given Point. " current_x, current_y = current_pos.x, current_pos.y if new.y > current_y: # Use newlines instead of CURSOR_DOWN, because this meight add new lines. # CURSOR_DOWN will never create new lines at the bottom. # Also reset attributes, otherwise the newline could draw a # background color. reset_attributes() write('\r\n' * (new.y - current_y)) current_x = 0 _output_cursor_forward(new.x) return new elif new.y < current_y: _output_cursor_up(current_y - new.y) if current_x >= screen.width - 1: write('\r') _output_cursor_forward(new.x) elif new.x < current_x or current_x >= screen.width - 1: _output_cursor_backward(current_x - new.x) elif new.x > current_x: _output_cursor_forward(new.x - current_x) return new style_for_token = _StyleForTokenCache(style) def output_char(char): """ Write the output of this character. """ # If the last printed character has the same token, it also has the # same style, so we don't output it. if last_char[0] and last_char[0].token == char.token: write(char.char) else: style = style_for_token[char.token] if style: _output_set_attributes(style['color'], style['bgcolor'], bold=style.get('bold', False), underline=style.get('underline', False)) # If we print something with a background color, remember that. background_turned_on[0] = bool(style['bgcolor']) else: # Reset previous style and output. reset_attributes() write(char.char) last_char[0] = char # Disable autowrap if not previous_screen: output.disable_autowrap() reset_attributes() # When the previous screen has a different size, redraw everything anyway. # Also when we are done. (We meight take up less rows, so clearing is important.) if is_done or not previous_screen or previous_screen.width != screen.width: # XXX: also consider height?? current_pos = move_cursor(Point(0, 0)) reset_attributes() output.erase_down() previous_screen = Screen(screen.width) # Get height of the screen. # (current_height changes as we loop over _buffer, so remember the current value.) current_height = screen.current_height # Loop over the rows. row_count = max(screen.current_height, previous_screen.current_height) c = 0 # Column counter. for y, r in enumerate(range(0, row_count)): new_row = screen._buffer[r] previous_row = previous_screen._buffer[r] new_max_line_len = max(new_row.keys()) if new_row else 0 previous_max_line_len = max(previous_row.keys()) if previous_row else 0 # Loop over the columns. c = 0 while c < new_max_line_len + 1: new_char = new_row[c] old_char = previous_row[c] char_width = (new_char.width or 1) # When the old and new character at this position are different, # draw the output. (Because of the performance, we don't call # `Char.__ne__`, but inline the same expression.) if new_char.char != old_char.char or new_char.token != old_char.token: current_pos = move_cursor(Point(y=y, x=c)) output_char(new_char) current_pos = current_pos._replace(x=current_pos.x + char_width) c += char_width # If the new line is shorter, trim it if previous_screen and new_max_line_len < previous_max_line_len: current_pos = move_cursor(Point(y=y, x=new_max_line_len + 1)) reset_attributes() output.erase_end_of_line() # Correctly reserve vertical space as required by the layout. # When this is a new screen (drawn for the first time), or for some reason # higher than the previous one. Move the cursor once to the bottom of the # output. That way, we're sure that the terminal scrolls up, even when the # lower lines of the canvas just contain whitespace. # The most obvious reason that we actually want this behaviour is the avoid # the artifact of the input scrolling when the completion menu is shown. # (If the scrolling is actually wanted, the layout can still be build in a # way to behave that way by setting a dynamic height.) if screen.current_height > previous_screen.current_height: current_pos = move_cursor(Point(y=screen.current_height - 1, x=0)) # Move cursor: if is_done: current_pos = move_cursor(Point(y=current_height, x=0)) output.erase_down() else: current_pos = move_cursor(screen.cursor_position) if is_done: reset_attributes() output.enable_autowrap() # If the last printed character has a background color, always reset. # (Many terminals give weird artifacs on resize events when there is an # active background color.) if background_turned_on[0]: reset_attributes() return current_pos, last_char[0]
def _(event): """ Handling of incoming mouse event. """ # Typical: "Esc[MaB*" # Urxvt: "Esc[96;14;13M" # Xterm SGR: "Esc[<64;85;12M" # Parse incoming packet. if event.data[2] == 'M': # Typical. mouse_event, x, y = map(ord, event.data[3:]) mouse_event = { 32: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, }.get(mouse_event) # Handle situations where `PosixStdinReader` used surrogateescapes. if x >= 0xdc00: x-= 0xdc00 if y >= 0xdc00: y-= 0xdc00 x -= 32 y -= 32 else: # Urxvt and Xterm SGR. # When the '<' is not present, we are not using the Xterm SGR mode, # but Urxvt instead. data = event.data[2:] if data[:1] == '<': sgr = True data = data[1:] else: sgr = False # Extract coordinates. mouse_event, x, y = map(int, data[:-1].split(';')) m = data[-1] # Parse event type. if sgr: mouse_event = { (0, 'M'): MouseEventType.MOUSE_DOWN, (0, 'm'): MouseEventType.MOUSE_UP, (64, 'M'): MouseEventType.SCROLL_UP, (65, 'M'): MouseEventType.SCROLL_DOWN, }.get((mouse_event, m)) else: mouse_event = { 32: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, }.get(mouse_event) x -= 1 y -= 1 # Only handle mouse events when we know the window height. if event.cli.renderer.height_is_known and mouse_event is not None: # Take region above the layout into account. The reported # coordinates are absolute to the visible part of the terminal. try: y -= event.cli.renderer.rows_above_layout except HeightIsUnknownError: return # Call the mouse handler from the renderer. handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] handler(event.cli, MouseEvent(position=Point(x=x, y=y), event_type=mouse_event))
def __init__(self, options, default_index=0, header_filter=lambda x: x, match_filter=lambda x: x, custom_filter=None, search_buffer=Buffer(multiline=False), cpu_count=multiprocessing.cpu_count()): assert (isinstance(options, list)) assert (callable(header_filter)) assert (callable(match_filter)) assert (isinstance(default_index, int)) self.search_buffer = search_buffer self.last_query_text = '' self.search_buffer.on_text_changed += self.update self.header_filter = header_filter self.match_filter = match_filter self.current_index = default_index self.entries_left_offset = 0 self.pool = multiprocessing.Pool(cpu_count) self.options_headers_linecount = [] self._indices_to_lines = [] self._options = [] self.marks = [] self.max_entry_height = 1 # Options are processed here also through the setter self.options = options self.cursor = Point(0, 0) self.content = FormattedTextControl( text=self.get_tokens, focusable=False, key_bindings=None, get_cursor_position=lambda: self.cursor, ) self.content_window = Window( content=self.content, wrap_lines=False, allow_scroll_beyond_bottom=True, scroll_offsets=ScrollOffsets(bottom=self.max_entry_height), cursorline=False, cursorcolumn=False, # right_margins=[NumberedMargin()], # left_margins=[NumberedMargin()], align=WindowAlign.LEFT, height=None, get_line_prefix=self.get_line_prefix # get_line_prefix=lambda line, b: [('bg:red', ' ')] ) self.update() super(OptionsList, self).__init__(content=self.content_window, filter=(custom_filter if custom_filter is not None else has_focus(self.search_buffer)))
def _output_screen_diff(app, output, screen, current_pos, color_depth, previous_screen=None, last_style=None, is_done=False, full_screen=False, attrs_for_style_string=None, size=None, previous_width=0): # XXX: drop is_done """ Render the diff between this screen and the previous screen. This takes two `Screen` instances. The one that represents the output like it was during the last rendering and one that represents the current output raster. Looking at these two `Screen` instances, this function will render the difference by calling the appropriate methods of the `Output` object that only paint the changes to the terminal. This is some performance-critical code which is heavily optimized. Don't change things without profiling first. :param current_pos: Current cursor position. :param last_style: The style string, used for drawing the last drawn character. (Color/attributes.) :param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance. :param width: The width of the terminal. :param previous_width: The width of the terminal during the last rendering. """ width, height = size.columns, size.rows #: Remember the last printed character. last_style = [last_style] # nonlocal #: Variable for capturing the output. write = output.write write_raw = output.write_raw # Create locals for the most used output methods. # (Save expensive attribute lookups.) _output_set_attributes = output.set_attributes _output_reset_attributes = output.reset_attributes _output_cursor_forward = output.cursor_forward _output_cursor_up = output.cursor_up _output_cursor_backward = output.cursor_backward # Hide cursor before rendering. (Avoid flickering.) output.hide_cursor() def reset_attributes(): " Wrapper around Output.reset_attributes. " _output_reset_attributes() last_style[0] = None # Forget last char after resetting attributes. def move_cursor(new): " Move cursor to this `new` point. Returns the given Point. " current_x, current_y = current_pos.x, current_pos.y if new.y > current_y: # Use newlines instead of CURSOR_DOWN, because this might add new lines. # CURSOR_DOWN will never create new lines at the bottom. # Also reset attributes, otherwise the newline could draw a # background color. reset_attributes() write('\r\n' * (new.y - current_y)) current_x = 0 _output_cursor_forward(new.x) return new elif new.y < current_y: _output_cursor_up(current_y - new.y) if current_x >= width - 1: write('\r') _output_cursor_forward(new.x) elif new.x < current_x or current_x >= width - 1: _output_cursor_backward(current_x - new.x) elif new.x > current_x: _output_cursor_forward(new.x - current_x) return new def output_char(char): """ Write the output of this character. """ # If the last printed character has the same style, don't output the # style again. the_last_style = last_style[0] # Either `None` or a style string. if the_last_style == char.style: write(char.char) else: # Look up `Attr` for this style string. Only set attributes if different. # (Two style strings can still have the same formatting.) # Note that an empty style string can have formatting that needs to # be applied, because of style transformations. new_attrs = attrs_for_style_string[char.style] if not the_last_style or new_attrs != attrs_for_style_string[ the_last_style]: _output_set_attributes(new_attrs, color_depth) write(char.char) last_style[0] = char.style # Render for the first time: reset styling. if not previous_screen: reset_attributes() # Disable autowrap. (When entering a the alternate screen, or anytime when # we have a prompt. - In the case of a REPL, like IPython, people can have # background threads, and it's hard for debugging if their output is not # wrapped.) if not previous_screen or not full_screen: output.disable_autowrap() # When the previous screen has a different size, redraw everything anyway. # Also when we are done. (We might take up less rows, so clearing is important.) if is_done or not previous_screen or previous_width != width: # XXX: also consider height?? current_pos = move_cursor(Point(x=0, y=0)) reset_attributes() output.erase_down() previous_screen = Screen() # Get height of the screen. # (height changes as we loop over data_buffer, so remember the current value.) # (Also make sure to clip the height to the size of the output.) current_height = min(screen.height, height) # Loop over the rows. row_count = min(max(screen.height, previous_screen.height), height) c = 0 # Column counter. for y in range(row_count): new_row = screen.data_buffer[y] previous_row = previous_screen.data_buffer[y] zero_width_escapes_row = screen.zero_width_escapes[y] new_max_line_len = min(width - 1, max(new_row.keys()) if new_row else 0) previous_max_line_len = min( width - 1, max(previous_row.keys()) if previous_row else 0) # Loop over the columns. c = 0 while c < new_max_line_len + 1: new_char = new_row[c] old_char = previous_row[c] char_width = (new_char.width or 1) # When the old and new character at this position are different, # draw the output. (Because of the performance, we don't call # `Char.__ne__`, but inline the same expression.) if new_char.char != old_char.char or new_char.style != old_char.style: current_pos = move_cursor(Point(x=c, y=y)) # Send injected escape sequences to output. if c in zero_width_escapes_row: write_raw(zero_width_escapes_row[c]) output_char(new_char) current_pos = Point(x=current_pos.x + char_width, y=current_pos.y) c += char_width # If the new line is shorter, trim it. if previous_screen and new_max_line_len < previous_max_line_len: current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y)) reset_attributes() output.erase_end_of_line() # Correctly reserve vertical space as required by the layout. # When this is a new screen (drawn for the first time), or for some reason # higher than the previous one. Move the cursor once to the bottom of the # output. That way, we're sure that the terminal scrolls up, even when the # lower lines of the canvas just contain whitespace. # The most obvious reason that we actually want this behaviour is the avoid # the artifact of the input scrolling when the completion menu is shown. # (If the scrolling is actually wanted, the layout can still be build in a # way to behave that way by setting a dynamic height.) if current_height > previous_screen.height: current_pos = move_cursor(Point(x=0, y=current_height - 1)) # Move cursor: if is_done: current_pos = move_cursor(Point(x=0, y=current_height)) output.erase_down() else: current_pos = move_cursor( screen.get_cursor_position(app.layout.current_window)) if is_done or not full_screen: output.enable_autowrap() # Always reset the color attributes. This is important because a background # thread could print data to stdout and we want that to be displayed in the # default colors. (Also, if a background color has been set, many terminals # give weird artifacts on resize events.) reset_attributes() if screen.show_cursor or is_done: output.show_cursor() return current_pos, last_style[0]
def _output_screen_diff(output, screen, current_pos, previous_screen=None, last_char=None, is_done=False, attrs_for_token=None, size=None, previous_width=0): # XXX: drop is_done """ Render the diff between this screen and the previous screen. This takes two `Screen` instances. The one that represents the output like it was during the last rendering and one that represents the current output raster. Looking at these two `Screen` instances, this function will render the difference by calling the appropriate methods of the `Output` object that only paint the changes to the terminal. This is some performance-critical code which is heavily optimized. Don't change things without profiling first. :param current_pos: Current cursor position. :param last_char: `Char` instance that represents the output attributes of the last drawn character. (Color/attributes.) :param attrs_for_token: :class:`._TokenToAttrsCache` instance. :param width: The width of the terminal. :param prevous_width: The width of the terminal during the last rendering. """ width, height = size.columns, size.rows #: Remember the last printed character. last_char = [last_char] # nonlocal background_turned_on = [False] # Nonlocal #: Variable for capturing the output. write = output.write write_raw = output.write_raw # Create locals for the most used output methods. # (Save expensive attribute lookups.) _output_set_attributes = output.set_attributes _output_reset_attributes = output.reset_attributes _output_cursor_forward = output.cursor_forward _output_cursor_up = output.cursor_up _output_cursor_backward = output.cursor_backward # Hide cursor before rendering. (Avoid flickering.) output.hide_cursor() def reset_attributes(): " Wrapper around Output.reset_attributes. " _output_reset_attributes() last_char[0] = None # Forget last char after resetting attributes. def move_cursor(new): " Move cursor to this `new` point. Returns the given Point. " current_x, current_y = current_pos.x, current_pos.y if new.y > current_y: # Use newlines instead of CURSOR_DOWN, because this meight add new lines. # CURSOR_DOWN will never create new lines at the bottom. # Also reset attributes, otherwise the newline could draw a # background color. reset_attributes() write('\r\n' * (new.y - current_y)) current_x = 0 _output_cursor_forward(new.x) return new elif new.y < current_y: _output_cursor_up(current_y - new.y) if current_x >= width - 1: write('\r') _output_cursor_forward(new.x) elif new.x < current_x or current_x >= width - 1: _output_cursor_backward(current_x - new.x) elif new.x > current_x: _output_cursor_forward(new.x - current_x) return new def output_char(char): """ Write the output of this character. """ # If the last printed character has the same token, it also has the # same style, so we don't output it. if last_char[0] and last_char[0].token == char.token: write(char.char) else: attrs = attrs_for_token[char.token] _output_set_attributes(attrs) # If we print something with a background color, remember that. background_turned_on[0] = bool(attrs.bgcolor) write(char.char) last_char[0] = char # Disable autowrap if not previous_screen: output.disable_autowrap() reset_attributes() # When the previous screen has a different size, redraw everything anyway. # Also when we are done. (We meight take up less rows, so clearing is important.) if is_done or not previous_screen or previous_width != width: # XXX: also consider height?? current_pos = move_cursor(Point(0, 0)) reset_attributes() output.erase_down() previous_screen = Screen() # Get height of the screen. # (height changes as we loop over data_buffer, so remember the current value.) # (Also make sure to clip the height to the size of the output.) current_height = min(screen.height, height) # Loop over the rows. row_count = min(max(screen.height, previous_screen.height), height) c = 0 # Column counter. for y in range(row_count): new_row = screen.data_buffer[y] previous_row = previous_screen.data_buffer[y] zero_width_escapes_row = screen.zero_width_escapes[y] new_max_line_len = min(width - 1, max(new_row.keys()) if new_row else 0) previous_max_line_len = min( width - 1, max(previous_row.keys()) if previous_row else 0) # Loop over the columns. c = 0 while c < new_max_line_len + 1: new_char = new_row[c] old_char = previous_row[c] char_width = (new_char.width or 1) # When the old and new character at this position are different, # draw the output. (Because of the performance, we don't call # `Char.__ne__`, but inline the same expression.) if new_char.char != old_char.char or new_char.token != old_char.token: current_pos = move_cursor(Point(y=y, x=c)) # Send injected escape sequences to output. if c in zero_width_escapes_row: write_raw(zero_width_escapes_row[c]) output_char(new_char) current_pos = current_pos._replace(x=current_pos.x + char_width) c += char_width # If the new line is shorter, trim it. if previous_screen and new_max_line_len < previous_max_line_len: current_pos = move_cursor(Point(y=y, x=new_max_line_len + 1)) reset_attributes() output.erase_end_of_line() # Correctly reserve vertical space as required by the layout. # When this is a new screen (drawn for the first time), or for some reason # higher than the previous one. Move the cursor once to the bottom of the # output. That way, we're sure that the terminal scrolls up, even when the # lower lines of the canvas just contain whitespace. # The most obvious reason that we actually want this behaviour is the avoid # the artifact of the input scrolling when the completion menu is shown. # (If the scrolling is actually wanted, the layout can still be build in a # way to behave that way by setting a dynamic height.) if current_height > previous_screen.height: current_pos = move_cursor(Point(y=current_height - 1, x=0)) # Move cursor: if is_done: current_pos = move_cursor(Point(y=current_height, x=0)) output.erase_down() else: current_pos = move_cursor(screen.cursor_position) if is_done: reset_attributes() output.enable_autowrap() # If the last printed character has a background color, always reset. # (Many terminals give weird artifacs on resize events when there is an # active background color.) if background_turned_on[0]: reset_attributes() if screen.show_cursor or is_done: output.show_cursor() return current_pos, last_char[0]
def move_cursor(self, y): content.cursor_position = Point(y, 0)
def __call__(self): global selected y = selected return Point(0, y)