def test_split_lines_3(): " Edge cases: inputs ending with newlines. " # -1- lines = list(split_lines([ ('class:a', 'line1\nline2\n') ])) assert lines == [ [('class:a', 'line1')], [('class:a', 'line2')], [('class:a', '')], ] # -2- lines = list(split_lines([ ('class:a', '\n'), ])) assert lines == [ [], [('class:a', '')], ] # -3- lines = list(split_lines([ ('class:a', ''), ])) assert lines == [ [('class:a', '')], ]
def test_split_lines_3(): " Edge cases: inputs ending with newlines. " # -1- lines = list(split_lines([("class:a", "line1\nline2\n")])) assert lines == [ [("class:a", "line1")], [("class:a", "line2")], [("class:a", "")], ] # -2- lines = list(split_lines([("class:a", "\n"),])) assert lines == [ [], [("class:a", "")], ] # -3- lines = list(split_lines([("class:a", ""),])) assert lines == [ [("class:a", "")], ]
def test_split_lines_3(): " Edge cases: inputs ending with newlines. " # -1- lines = list(split_lines([('class:a', 'line1\nline2\n')])) assert lines == [ [('class:a', 'line1')], [('class:a', 'line2')], [('class:a', '')], ] # -2- lines = list(split_lines([ ('class:a', '\n'), ])) assert lines == [ [], [('class:a', '')], ] # -3- lines = list(split_lines([ ('class:a', ''), ])) assert lines == [ [('class:a', '')], ]
def create_content(self, width, height): # Get fragments fragments_with_mouse_handlers = self._get_formatted_text_cached() fragment_lines_with_mouse_handlers = list( split_lines(fragments_with_mouse_handlers)) # Strip mouse handlers from fragments. fragment_lines = [[tuple(item[:2]) for item in line] for line in fragment_lines_with_mouse_handlers] # Keep track of the fragments with mouse handler, for later use in # `mouse_handler`. self._fragments = fragments_with_mouse_handlers cursor_position = self.get_cursor_position() # Create content, or take it from the cache. key = (tuple(fragments_with_mouse_handlers), width, cursor_position) def get_content(): return UIContent(get_line=lambda i: fragment_lines[i], line_count=len(fragment_lines), cursor_position=cursor_position, show_cursor=self.show_cursor) return self._content_cache.get(key, get_content)
def test_split_lines(): lines = list(split_lines([("class:a", "line1\nline2\nline3")])) assert lines == [ [("class:a", "line1")], [("class:a", "line2")], [("class:a", "line3")], ]
def test_split_lines(): lines = list(split_lines([('class:a', 'line1\nline2\nline3')])) assert lines == [ [('class:a', 'line1')], [('class:a', 'line2')], [('class:a', 'line3')], ]
def show_result(self, result: object) -> None: """ Show __repr__ for an `eval` result. """ out_prompt = to_formatted_text(self.get_output_prompt()) # If the repr is valid Python code, use the Pygments lexer. result_repr = repr(result) try: compile(result_repr, "", "eval") except SyntaxError: formatted_result_repr = to_formatted_text(result_repr) else: formatted_result_repr = to_formatted_text( PygmentsTokens(list(_lex_python_result(result_repr)))) # If __pt_repr__ is present, take this. This can return # prompt_toolkit formatted text. if hasattr(result, "__pt_repr__"): try: formatted_result_repr = to_formatted_text( getattr(result, "__pt_repr__")()) if isinstance(formatted_result_repr, list): formatted_result_repr = FormattedText( formatted_result_repr) except: pass # Align every line to the prompt. line_sep = "\n" + " " * fragment_list_width(out_prompt) indented_repr: StyleAndTextTuples = [] lines = list(split_lines(formatted_result_repr)) for i, fragment in enumerate(lines): indented_repr.extend(fragment) # Add indentation separator between lines, not after the last line. if i != len(lines) - 1: indented_repr.append(("", line_sep)) # Write output tokens. if self.enable_syntax_highlighting: formatted_output = merge_formatted_text( [out_prompt, indented_repr]) else: formatted_output = FormattedText(out_prompt + [( "", fragment_list_to_text(formatted_result_repr))]) print_formatted_text( formatted_output, style=self._current_style, style_transformation=self.style_transformation, include_default_pygments_style=False, output=self.app.output, ) self.app.output.flush()
def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: lines = list(split_lines(self._get_text_fragments(document.text))) def get_line(lineno: int) -> StyleAndTextTuples: try: return lines[lineno] except IndexError: return [] return get_line
def lex_document(self, document): lines = list(split_lines(self._get_text_fragments(document.text))) def get_line(lineno): try: return lines[lineno] except IndexError: return [] return get_line
def create_content(self, width: int, height: Optional[int]) -> UIContent: # Get fragments fragments_with_mouse_handlers = self._get_formatted_text_cached() fragment_lines_with_mouse_handlers = list( split_lines(fragments_with_mouse_handlers) ) # Strip mouse handlers from fragments. fragment_lines: List[StyleAndTextTuples] = [ [(item[0], item[1]) for item in line] for line in fragment_lines_with_mouse_handlers ] # Keep track of the fragments with mouse handler, for later use in # `mouse_handler`. self._fragments = fragments_with_mouse_handlers # If there is a `[SetCursorPosition]` in the fragment list, set the # cursor position here. def get_cursor_position( fragment: str = "[SetCursorPosition]", ) -> Optional[Point]: for y, line in enumerate(fragment_lines): x = 0 for style_str, text, *_ in line: if fragment in style_str: return Point(x=x, y=y) x += len(text) return None # If there is a `[SetMenuPosition]`, set the menu over here. def get_menu_position() -> Optional[Point]: return get_cursor_position("[SetMenuPosition]") cursor_position = (self.get_cursor_position or get_cursor_position)() # Create content, or take it from the cache. key = (tuple(fragments_with_mouse_handlers), width, cursor_position) def get_content() -> UIContent: return UIContent( get_line=lambda i: fragment_lines[i], line_count=len(fragment_lines), show_cursor=self.show_cursor, cursor_position=cursor_position, menu_position=get_menu_position(), ) return self._content_cache.get(key, get_content)
def show_result(self, result: object) -> None: if hasattr(result, "__pt_repr__"): out_prompt = to_formatted_text(self.get_output_prompt()) try: formatted_result_repr = to_formatted_text( getattr(result, "__pt_repr__")()) if isinstance(formatted_result_repr, list): formatted_result_repr = FormattedText( formatted_result_repr) except: pass line_sep = "\n" + " " * fragment_list_width(out_prompt) indented_repr: StyleAndTextTuples = [] lines = list(split_lines(formatted_result_repr)) for i, fragment in enumerate(lines): indented_repr.extend(fragment) # Add indentation separator between lines, not after the last line. if i != len(lines) - 1: indented_repr.append(("", line_sep)) # Write output tokens. if self.enable_syntax_highlighting: formatted_output = merge_formatted_text( [out_prompt, indented_repr]) else: formatted_output = FormattedText(out_prompt + [( "", fragment_list_to_text(formatted_result_repr))]) if self.enable_pager: self.print_paginated_formatted_text( to_formatted_text(formatted_output)) else: self.print_formatted_text(to_formatted_text(formatted_output)) self.app.output.flush() if self.insert_blank_line_after_output: self.app.output.write("\n") else: if self._formatter: result = self._formatter(result) self.console.print(result)
def create_line_generator(start_lineno: int, column: int = 0) -> LineGenerator: """ Create a generator that yields the lexed lines. Each iteration it yields a (line_number, [(style_str, text), ...]) tuple. """ def get_text_fragments() -> Iterable[Tuple[str, str]]: text = '\n'.join(document.lines[start_lineno:])[column:] # We call `get_text_fragments_unprocessed`, because `get_tokens` will # still replace \r\n and \r by \n. (We don't want that, # Pygments should return exactly the same amount of text, as we # have given as input.) for _, t, v in self.pygments_lexer.get_tokens_unprocessed(text): # Turn Pygments `Token` object into prompt_toolkit style # strings. yield _token_cache[t], v yield from enumerate(split_lines(list(get_text_fragments())), start_lineno)
def create_line_generator(start_lineno, column=0): """ Create a generator that yields the lexed lines. Each iteration it yields a (line_number, [(token, text), ...]) tuple. """ def get_text_fragments(): text = '\n'.join(document.lines[start_lineno:])[column:] # We call `get_text_fragments_unprocessed`, because `get_tokens` will # still replace \r\n and \r by \n. (We don't want that, # Pygments should return exactly the same amount of text, as we # have given as input.) for _, t, v in self.pygments_lexer.get_tokens_unprocessed(text): # Turn Pygments `Token` object into prompt_toolkit `Token` # objects. yield _token_cache[t], v return enumerate(split_lines(get_text_fragments()), start_lineno)
def create_content(self, width: int, height: Optional[int]) -> UIContent: # Get fragments fragments_with_mouse_handlers = self._get_formatted_text_cached() fragment_lines_with_mouse_handlers = list(split_lines(fragments_with_mouse_handlers)) # Strip mouse handlers from fragments. fragment_lines: List[StyleAndTextTuples] = [ [(item[0], item[1]) for item in line] for line in fragment_lines_with_mouse_handlers ] # Keep track of the fragments with mouse handler, for later use in # `mouse_handler`. self._fragments = fragments_with_mouse_handlers # If there is a `[SetCursorPosition]` in the fragment list, set the # cursor position here. def get_cursor_position(fragment: str = '[SetCursorPosition]') -> Optional[Point]: for y, line in enumerate(fragment_lines): x = 0 for style_str, text, *_ in line: if fragment in style_str: return Point(x=x, y=y) x += len(text) return None # If there is a `[SetMenuPosition]`, set the menu over here. def get_menu_position() -> Optional[Point]: return get_cursor_position('[SetMenuPosition]') cursor_position = (self.get_cursor_position or get_cursor_position)() # Create content, or take it from the cache. key = (tuple(fragments_with_mouse_handlers), width, cursor_position) def get_content() -> UIContent: return UIContent(get_line=lambda i: fragment_lines[i], line_count=len(fragment_lines), show_cursor=self.show_cursor, cursor_position=cursor_position, menu_position=get_menu_position()) return self._content_cache.get(key, get_content)
def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": """ Handle mouse events. (When the fragment list contained mouse handlers and the user clicked on on any of these, the matching handler is called. This handler can still return `NotImplemented` in case we want the :class:`~prompt_toolkit.layout.Window` to handle this particular event.) """ if self._fragments: # Read the generator. fragments_for_line = list(split_lines(self._fragments)) try: fragments = fragments_for_line[mouse_event.position.y] except IndexError: return NotImplemented else: # Find position in the fragment list. xpos = mouse_event.position.x # Find mouse handler for this character. count = 0 for item in fragments: count += len(item[1]) if count >= xpos: if len(item) >= 3: # Handler found. Call it. # (Handler can return NotImplemented, so return # that result.) handler = item[2] # type: ignore return handler(mouse_event) else: break # Otherwise, don't handle here. return NotImplemented
def mouse_handler(self, mouse_event: MouseEvent) -> 'NotImplementedOrNone': """ Handle mouse events. (When the fragment list contained mouse handlers and the user clicked on on any of these, the matching handler is called. This handler can still return `NotImplemented` in case we want the :class:`~prompt_toolkit.layout.Window` to handle this particular event.) """ if self._fragments: # Read the generator. fragments_for_line = list(split_lines(self._fragments)) try: fragments = fragments_for_line[mouse_event.position.y] except IndexError: return NotImplemented else: # Find position in the fragment list. xpos = mouse_event.position.x # Find mouse handler for this character. count = 0 for item in fragments: count += len(item[1]) if count >= xpos: if len(item) >= 3: # Handler found. Call it. # (Handler can return NotImplemented, so return # that result.) handler = item[2] # type: ignore return handler(mouse_event) else: break # Otherwise, don't handle here. return NotImplemented
def print_paginated_formatted_text( self, formatted_text: StyleAndTextTuples, end: str = "\n", ) -> None: """ Print formatted text, using --MORE-- style pagination. (Avoid filling up the terminal's scrollback buffer.) """ pager_prompt = self.create_pager_prompt() size = self.app.output.get_size() abort = False print_all = False # Max number of lines allowed in the buffer before painting. max_rows = size.rows - 1 # Page buffer. rows_in_buffer = 0 columns_in_buffer = 0 page: StyleAndTextTuples = [] def flush_page() -> None: nonlocal page, columns_in_buffer, rows_in_buffer self.print_formatted_text(page, end="") page = [] columns_in_buffer = 0 rows_in_buffer = 0 def show_pager() -> None: nonlocal abort, max_rows, print_all # Run pager prompt in another thread. # Same as for the input. This prevents issues with nested event # loops. pager_result = None def in_thread() -> None: nonlocal pager_result pager_result = pager_prompt.prompt() th = threading.Thread(target=in_thread) th.start() th.join() if pager_result == PagerResult.ABORT: print("...") abort = True elif pager_result == PagerResult.NEXT_LINE: max_rows = 1 elif pager_result == PagerResult.NEXT_PAGE: max_rows = size.rows - 1 elif pager_result == PagerResult.PRINT_ALL: print_all = True # Loop over lines. Show --MORE-- prompt when page is filled. formatted_text = formatted_text + [("", end)] lines = list(split_lines(formatted_text)) for lineno, line in enumerate(lines): for style, text, *_ in line: for c in text: width = get_cwidth(c) # (Soft) wrap line if it doesn't fit. if columns_in_buffer + width > size.columns: # Show pager first if we get too many lines after # wrapping. if rows_in_buffer + 1 >= max_rows and not print_all: page.append(("", "\n")) flush_page() show_pager() if abort: return rows_in_buffer += 1 columns_in_buffer = 0 columns_in_buffer += width page.append((style, c)) if rows_in_buffer + 1 >= max_rows and not print_all: page.append(("", "\n")) flush_page() show_pager() if abort: return else: # Add line ending between lines (if `end="\n"` was given, one # more empty line is added in `split_lines` automatically to # take care of the final line ending). if lineno != len(lines) - 1: page.append(("", "\n")) rows_in_buffer += 1 columns_in_buffer = 0 flush_page()
def show_result(self, result: object) -> None: """ Show __repr__ for an `eval` result. Note: this can raise `KeyboardInterrupt` if either calling `__repr__`, `__pt_repr__` or formatting the output with "Black" takes to long and the user presses Control-C. """ out_prompt = to_formatted_text(self.get_output_prompt()) # If the repr is valid Python code, use the Pygments lexer. try: result_repr = repr(result) except KeyboardInterrupt: raise # Don't catch here. except BaseException as e: # Calling repr failed. self._handle_exception(e) return try: compile(result_repr, "", "eval") except SyntaxError: formatted_result_repr = to_formatted_text(result_repr) else: # Syntactically correct. Format with black and syntax highlight. if self.enable_output_formatting: # Inline import. Slightly speed up start-up time if black is # not used. import black result_repr = black.format_str( result_repr, mode=black.FileMode( line_length=self.app.output.get_size().columns), ) formatted_result_repr = to_formatted_text( PygmentsTokens(list(_lex_python_result(result_repr)))) # If __pt_repr__ is present, take this. This can return prompt_toolkit # formatted text. try: if hasattr(result, "__pt_repr__"): formatted_result_repr = to_formatted_text( getattr(result, "__pt_repr__")()) if isinstance(formatted_result_repr, list): formatted_result_repr = FormattedText( formatted_result_repr) except KeyboardInterrupt: raise # Don't catch here. except: # For bad code, `__getattr__` can raise something that's not an # `AttributeError`. This happens already when calling `hasattr()`. pass # Align every line to the prompt. line_sep = "\n" + " " * fragment_list_width(out_prompt) indented_repr: StyleAndTextTuples = [] lines = list(split_lines(formatted_result_repr)) for i, fragment in enumerate(lines): indented_repr.extend(fragment) # Add indentation separator between lines, not after the last line. if i != len(lines) - 1: indented_repr.append(("", line_sep)) # Write output tokens. if self.enable_syntax_highlighting: formatted_output = merge_formatted_text( [out_prompt, indented_repr]) else: formatted_output = FormattedText(out_prompt + [( "", fragment_list_to_text(formatted_result_repr))]) if self.enable_pager: self.print_paginated_formatted_text( to_formatted_text(formatted_output)) else: self.print_formatted_text(to_formatted_text(formatted_output)) self.app.output.flush() if self.insert_blank_line_after_output: self.app.output.write("\n")