diff --git a/pw_cli/py/pw_cli/log.py b/pw_cli/py/pw_cli/log.py index 5fa9a457b7..d92344870e 100644 --- a/pw_cli/py/pw_cli/log.py +++ b/pw_cli/py/pw_cli/log.py @@ -17,9 +17,8 @@ from pathlib import Path from typing import NamedTuple, Optional, Union, Iterator -import pw_cli.color -import pw_cli.env -import pw_cli.plugins +from pw_cli.color import colors as pw_cli_colors +from pw_cli.env import pigweed_environment # Log level used for captured output of a subprocess run through pw. LOGLEVEL_STDOUT = 21 @@ -126,9 +125,9 @@ def install( if not logger: logger = logging.getLogger() - colors = pw_cli.color.colors(use_color) + colors = pw_cli_colors(use_color) - env = pw_cli.env.pigweed_environment() + env = pigweed_environment() if env.PW_SUBPROCESS or hide_timestamp: # If the logger is being run in the context of a pw subprocess, the # time and date are omitted (since pw_cli.process will provide them). diff --git a/pw_console/py/BUILD.bazel b/pw_console/py/BUILD.bazel index 164353b861..a10a1c4371 100644 --- a/pw_console/py/BUILD.bazel +++ b/pw_console/py/BUILD.bazel @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations under # the License. -load("@rules_python//python:defs.bzl", "py_library", "py_test") +load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test") package(default_visibility = ["//visibility:public"]) @@ -85,6 +85,15 @@ py_library( "pw_console/window_list.py", "pw_console/window_manager.py", ], + data = [ + "pw_console/docs/user_guide.rst", + "pw_console/html/index.html", + "pw_console/html/main.js", + "pw_console/html/style.css", + "pw_console/py.typed", + "pw_console/templates/keybind_list.jinja", + "pw_console/templates/repl_output.jinja", + ], imports = ["."], deps = [ ":event_count_history", @@ -94,6 +103,17 @@ py_library( ], ) +py_binary( + name = "pw_console_test_mode", + srcs = [ + "pw_console/__main__.py", + ], + main = "pw_console/__main__.py", + deps = [ + ":pw_console", + ], +) + py_test( name = "command_runner_test", size = "small", diff --git a/pw_console/py/command_runner_test.py b/pw_console/py/command_runner_test.py index 82771d9f3a..822b46488e 100644 --- a/pw_console/py/command_runner_test.py +++ b/pw_console/py/command_runner_test.py @@ -32,7 +32,6 @@ flatten_formatted_text_tuples, join_adjacent_style_tuples, ) -from window_manager_test import target_list_and_pane, window_pane_titles def _create_console_app(log_pane_count=2): @@ -56,6 +55,30 @@ def _create_console_app(log_pane_count=2): return console_app +def window_pane_titles(window_manager): + return [ + [ + pane.pane_title() + ' - ' + pane.pane_subtitle() + for pane in window_list.active_panes + ] + for window_list in window_manager.window_lists + ] + + +def target_list_and_pane(window_manager, list_index, pane_index): + # pylint: disable=protected-access + # Bypass prompt_toolkit has_focus() + pane = window_manager.window_lists[list_index].active_panes[pane_index] + # If the pane is in focus it will be visible. + pane.show_pane = True + window_manager._get_active_window_list_and_pane = MagicMock( # type: ignore + return_value=( + window_manager.window_lists[list_index], + window_manager.window_lists[list_index].active_panes[pane_index], + ) + ) + + class TestCommandRunner(unittest.TestCase): """Tests for CommandRunner.""" diff --git a/pw_console/py/help_window_test.py b/pw_console/py/help_window_test.py index f24b8244fc..af49c0a59d 100644 --- a/pw_console/py/help_window_test.py +++ b/pw_console/py/help_window_test.py @@ -23,8 +23,11 @@ from pw_console.help_window import HelpWindow +_PW_CONSOLE_MODULE = 'pw_console' + + _jinja_env = Environment( - loader=PackageLoader('pw_console'), + loader=PackageLoader(_PW_CONSOLE_MODULE), undefined=make_logging_undefined(logger=logging.getLogger('pw_console')), trim_blocks=True, lstrip_blocks=True, diff --git a/pw_console/py/pw_console/__main__.py b/pw_console/py/pw_console/__main__.py index f9b1ffc5a2..2881b0a5cd 100644 --- a/pw_console/py/pw_console/__main__.py +++ b/pw_console/py/pw_console/__main__.py @@ -20,26 +20,21 @@ import sys from typing import Optional, Dict -import pw_cli.log -import pw_cli.argument_types +from pw_cli import log as pw_cli_log +from pw_cli import argument_types -import pw_console -import pw_console.python_logging -import pw_console.test_mode +from pw_console import PwConsoleEmbed +from pw_console.python_logging import create_temp_log_file from pw_console.log_store import LogStore from pw_console.plugins.calc_pane import CalcPane from pw_console.plugins.clock_pane import ClockPane from pw_console.plugins.twenty48_pane import Twenty48Pane +from pw_console.test_mode import FAKE_DEVICE_LOGGER_NAME _LOG = logging.getLogger(__package__) _ROOT_LOG = logging.getLogger('') -# TODO(tonymd): Remove this when no downstream projects are using it. -def create_temp_log_file(): - return pw_console.python_logging.create_temp_log_file() - - def _build_argument_parser() -> argparse.ArgumentParser: """Setup argparse.""" parser = argparse.ArgumentParser( @@ -49,7 +44,7 @@ def _build_argument_parser() -> argparse.ArgumentParser: parser.add_argument( '-l', '--loglevel', - type=pw_cli.argument_types.log_level, + type=argument_types.log_level, default=logging.DEBUG, help='Set the log level' '(debug, info, warning, error, critical)', ) @@ -83,9 +78,9 @@ def main() -> int: if not args.logfile: # Create a temp logfile to prevent logs from appearing over stdout. This # would corrupt the prompt toolkit UI. - args.logfile = pw_console.python_logging.create_temp_log_file() + args.logfile = create_temp_log_file() - pw_cli.log.install( + pw_cli_log.install( level=args.loglevel, use_color=True, hide_timestamp=False, @@ -93,7 +88,7 @@ def main() -> int: ) if args.console_debug_log_file: - pw_cli.log.install( + pw_cli_log.install( level=logging.DEBUG, use_color=True, hide_timestamp=False, @@ -108,9 +103,7 @@ def main() -> int: _ROOT_LOG.addHandler(root_log_store) _ROOT_LOG.debug('pw_console test-mode starting...') - fake_logger = logging.getLogger( - pw_console.test_mode.FAKE_DEVICE_LOGGER_NAME - ) + fake_logger = logging.getLogger(FAKE_DEVICE_LOGGER_NAME) default_loggers = { # Don't include pw_console package logs (_LOG) in the log pane UI. # Add the fake logger for test_mode. @@ -137,7 +130,7 @@ def main() -> int: """ ) - console = pw_console.PwConsoleEmbed( + console = PwConsoleEmbed( global_vars=global_vars, loggers=default_loggers, test_mode=args.test_mode, diff --git a/pw_console/py/pw_console/command_runner.py b/pw_console/py/pw_console/command_runner.py index c050589930..e2caf1212b 100644 --- a/pw_console/py/pw_console/command_runner.py +++ b/pw_console/py/pw_console/command_runner.py @@ -51,9 +51,11 @@ from prompt_toolkit.widgets import MenuItem from prompt_toolkit.widgets import TextArea -import pw_console.widgets.border -import pw_console.widgets.checkbox -import pw_console.widgets.mouse_handlers +from pw_console.widgets import ( + create_border, + mouse_handlers, + to_keybind_indicator, +) if TYPE_CHECKING: from pw_console.console_app import ConsoleApp @@ -162,7 +164,7 @@ def __init__( 'class:command-runner-setting', '> ', functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.focus_self, ), ) @@ -237,7 +239,7 @@ def __init__( def _create_bordered_content(self) -> None: """Wrap self.command_runner_content in a border.""" # This should be called whenever the window_title changes. - self.bordered_content = pw_console.widgets.border.create_border( + self.bordered_content = create_border( self.command_runner_content, title=self.window_title, border_style='class:command-runner-border', @@ -420,14 +422,10 @@ def _previous_item(self) -> None: def _get_input_field_button_fragments(self) -> StyleAndTextTuples: # Mouse handlers - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) - cancel = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.close_dialog - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) + cancel = functools.partial(mouse_handlers.on_click, self.close_dialog) select_item = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._run_selected_item + mouse_handlers.on_click, self._run_selected_item ) separator_text = ('', ' ', focus) @@ -439,7 +437,7 @@ def _get_input_field_button_fragments(self) -> StyleAndTextTuples: # Cancel button fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='Ctrl-c', description='Cancel', mouse_handler=cancel, @@ -450,7 +448,7 @@ def _get_input_field_button_fragments(self) -> StyleAndTextTuples: # Run button fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'Enter', 'Run', select_item, base_style=button_style ) ) diff --git a/pw_console/py/pw_console/console_app.py b/pw_console/py/pw_console/console_app.py index d6f8dda3e2..38c79cfca4 100644 --- a/pw_console/py/pw_console/console_app.py +++ b/pw_console/py/pw_console/console_app.py @@ -65,18 +65,21 @@ ) from pw_console.console_prefs import ConsolePrefs from pw_console.help_window import HelpWindow -import pw_console.key_bindings +from pw_console.key_bindings import create_key_bindings from pw_console.log_pane import LogPane from pw_console.log_store import LogStore from pw_console.pw_ptpython_repl import PwPtPythonRepl from pw_console.python_logging import all_loggers from pw_console.quit_dialog import QuitDialog from pw_console.repl_pane import ReplPane -import pw_console.style +from pw_console.style import generate_styles from pw_console.test_mode import start_fake_logger -import pw_console.widgets.checkbox -from pw_console.widgets import FloatingWindowPane -import pw_console.widgets.mouse_handlers +from pw_console.widgets import ( + FloatingWindowPane, + mouse_handlers, + to_checkbox_text, + to_keybind_indicator, +) from pw_console.window_manager import WindowManager _LOG = logging.getLogger(__package__) @@ -84,6 +87,8 @@ _SYSTEM_COMMAND_LOG = logging.getLogger('pw_console_system_command') +_PW_CONSOLE_MODULE = 'pw_console' + MAX_FPS = 30 MIN_REDRAW_INTERVAL = (60.0 / MAX_FPS) / 60.0 @@ -177,8 +182,12 @@ def __init__( local_vars = local_vars or global_vars jinja_templates = { - t: importlib.resources.read_text('pw_console.templates', t) - for t in importlib.resources.contents('pw_console.templates') + t: importlib.resources.read_text( + f'{_PW_CONSOLE_MODULE}.templates', t + ) + for t in importlib.resources.contents( + f'{_PW_CONSOLE_MODULE}.templates' + ) if t.endswith('.jinja') } @@ -220,11 +229,11 @@ def __init__( self.message = [('class:logo', self.app_title), ('', ' ')] self.message.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'Ctrl-p', 'Search Menu', functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.open_command_runner_main_menu, ), base_style='class:toolbar-button-inactive', @@ -291,7 +300,7 @@ def __init__( self.quit_dialog = QuitDialog(self) # Key bindings registry. - self.key_bindings = pw_console.key_bindings.create_key_bindings(self) + self.key_bindings = create_key_bindings(self) # Create help window text based global key_bindings and active panes. self._update_help_window() @@ -572,7 +581,7 @@ def start_http_server(self): if self.http_server is not None: return - html_package_path = 'pw_console.html' + html_package_path = f'{_PW_CONSOLE_MODULE}.html' self.html_files = { '/{}'.format(t): importlib.resources.read_text(html_package_path, t) for t in importlib.resources.contents(html_package_path) @@ -633,23 +642,6 @@ def _create_menu_items(self): 'Code: gruvbox-dark', self.set_code_theme('gruvbox-dark'), ), - MenuItem( - 'Code: tomorrow-night', - self.set_code_theme('tomorrow-night'), - ), - MenuItem( - 'Code: tomorrow-night-bright', - self.set_code_theme('tomorrow-night-bright'), - ), - MenuItem( - 'Code: tomorrow-night-blue', - self.set_code_theme('tomorrow-night-blue'), - ), - MenuItem( - 'Code: tomorrow-night-eighties', - self.set_code_theme('tomorrow-night-eighties'), - ), - MenuItem('Code: dracula', self.set_code_theme('dracula')), MenuItem('Code: zenburn', self.set_code_theme('zenburn')), ], ), @@ -677,7 +669,7 @@ def _create_menu_items(self): # pylint: disable=line-too-long MenuItem( '{check} Hide Date'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( + check=to_checkbox_text( self.prefs.hide_date_from_log_time, end='', ) @@ -692,7 +684,7 @@ def _create_menu_items(self): ), MenuItem( '{check} Show Source File'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( + check=to_checkbox_text( self.prefs.show_source_file, end='' ) ), @@ -706,7 +698,7 @@ def _create_menu_items(self): ), MenuItem( '{check} Show Python File'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( + check=to_checkbox_text( self.prefs.show_python_file, end='' ) ), @@ -720,7 +712,7 @@ def _create_menu_items(self): ), MenuItem( '{check} Show Python Logger'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( + check=to_checkbox_text( self.prefs.show_python_logger, end='' ) ), @@ -886,9 +878,7 @@ def _create_menu_items(self): MenuItem( # pylint: disable=line-too-long '{check} Show/Hide Window'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( - pane.show_pane, end='' - ) + check=to_checkbox_text(pane.show_pane, end='') ), # pylint: enable=line-too-long handler=functools.partial( @@ -991,7 +981,7 @@ def toggle_pref_option(self, setting_name): def load_theme(self, theme_name=None): """Regenerate styles for the current theme_name.""" - self._current_theme = pw_console.style.generate_styles(theme_name) + self._current_theme = generate_styles(theme_name) if theme_name: self.prefs.set_ui_theme(theme_name) diff --git a/pw_console/py/pw_console/embed.py b/pw_console/py/pw_console/embed.py index eb40b03cef..47cd86d606 100644 --- a/pw_console/py/pw_console/embed.py +++ b/pw_console/py/pw_console/embed.py @@ -23,7 +23,9 @@ from pw_console.console_app import ConsoleApp from pw_console.get_pw_console_app import PW_CONSOLE_APP_CONTEXTVAR from pw_console.plugin_mixin import PluginMixin -import pw_console.python_logging +from pw_console.python_logging import ( + setup_python_logging as pw_console_setup_python_logging, +) from pw_console.widgets import ( FloatingWindowPane, WindowPane, @@ -281,7 +283,7 @@ def setup_python_logging( logger. """ self.setup_python_logging_called = True - pw_console.python_logging.setup_python_logging( + pw_console_setup_python_logging( last_resort_filename, loggers_with_no_propagation ) diff --git a/pw_console/py/pw_console/filter_toolbar.py b/pw_console/py/pw_console/filter_toolbar.py index a459059ff5..9018180b08 100644 --- a/pw_console/py/pw_console/filter_toolbar.py +++ b/pw_console/py/pw_console/filter_toolbar.py @@ -28,9 +28,14 @@ ) from prompt_toolkit.mouse_events import MouseEvent, MouseEventType -import pw_console.widgets.checkbox -import pw_console.widgets.mouse_handlers -import pw_console.style +from pw_console.style import ( + get_button_style, + get_toolbar_style, +) +from pw_console.widgets import ( + mouse_handlers, + to_keybind_indicator, +) if TYPE_CHECKING: from pw_console.log_pane import LogPane @@ -54,7 +59,7 @@ def get_left_fragments(self): space = ('', ' ') fragments = [('class:filter-bar-title', ' Filters '), separator] - button_style = pw_console.style.get_button_style(self.log_pane) + button_style = get_button_style(self.log_pane) for filter_text, log_filter in self.log_pane.log_view.filters.items(): fragments.append(('class:filter-bar-delimiter', '<')) @@ -86,13 +91,13 @@ def get_left_fragments(self): def get_center_fragments(self): """Return formatted text tokens for display.""" clear_filters = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.log_pane.log_view.clear_filters, ) - button_style = pw_console.style.get_button_style(self.log_pane) + button_style = get_button_style(self.log_pane) - return pw_console.widgets.checkbox.to_keybind_indicator( + return to_keybind_indicator( 'Ctrl-Alt-r', 'Clear Filters', clear_filters, @@ -120,7 +125,7 @@ def __init__(self, log_pane: 'LogPane'): center_bar_window, ], style=functools.partial( - pw_console.style.get_toolbar_style, self.log_pane, dim=True + get_toolbar_style, self.log_pane, dim=True ), height=1, align=HorizontalAlign.LEFT, diff --git a/pw_console/py/pw_console/help_window.py b/pw_console/py/pw_console/help_window.py index 5291c13dd2..d4a8fd1af7 100644 --- a/pw_console/py/pw_console/help_window.py +++ b/pw_console/py/pw_console/help_window.py @@ -37,13 +37,22 @@ from pygments.lexers.markup import RstLexer # type: ignore from pygments.lexers.data import YamlLexer # type: ignore -import pw_console.widgets.mouse_handlers + +from pw_console.style import ( + get_pane_indicator, +) +from pw_console.widgets import ( + mouse_handlers, + to_keybind_indicator, +) if TYPE_CHECKING: from pw_console.console_app import ConsoleApp _LOG = logging.getLogger(__package__) +_PW_CONSOLE_MODULE = 'pw_console' + def _longest_line_length(text): """Return the longest line in the given text.""" @@ -120,10 +129,10 @@ def __init__( self.help_text_area: TextArea = self._create_help_text_area() close_mouse_handler = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.toggle_display + mouse_handlers.on_click, self.toggle_display ) copy_mouse_handler = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.copy_all_text + mouse_handlers.on_click, self.copy_all_text ) toolbar_padding = 1 @@ -132,7 +141,7 @@ def __init__( buttons = [] buttons.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'Ctrl-c', 'Copy All', copy_mouse_handler, @@ -141,7 +150,7 @@ def __init__( ) buttons.append(('', ' ')) buttons.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'q', 'Close', close_mouse_handler, @@ -154,7 +163,7 @@ def __init__( content=FormattedTextControl( # [('', toolbar_title)] functools.partial( - pw_console.style.get_pane_indicator, + get_pane_indicator, self, toolbar_title, ) @@ -261,7 +270,7 @@ def content_width(self) -> int: def load_user_guide(self): rstdoc_text = importlib.resources.read_text( - 'pw_console.docs', 'user_guide.rst' + f'{_PW_CONSOLE_MODULE}.docs', 'user_guide.rst' ) max_line_length = 0 rst_text = '' diff --git a/pw_console/py/pw_console/key_bindings.py b/pw_console/py/pw_console/key_bindings.py index 6592e28935..3db2592173 100644 --- a/pw_console/py/pw_console/key_bindings.py +++ b/pw_console/py/pw_console/key_bindings.py @@ -24,7 +24,6 @@ from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous from prompt_toolkit.key_binding.key_bindings import Binding -import pw_console.pw_ptpython_repl __all__ = ('create_key_bindings',) diff --git a/pw_console/py/pw_console/log_pane.py b/pw_console/py/pw_console/log_pane.py index ce46f2cd71..49c2421dcf 100644 --- a/pw_console/py/pw_console/log_pane.py +++ b/pw_console/py/pw_console/log_pane.py @@ -53,8 +53,6 @@ ) from prompt_toolkit.mouse_events import MouseEvent, MouseEventType, MouseButton -import pw_console.widgets.checkbox -import pw_console.style from pw_console.log_view import LogView from pw_console.log_pane_toolbars import ( LineInfoBar, @@ -65,13 +63,22 @@ from pw_console.log_store import LogStore from pw_console.search_toolbar import SearchToolbar from pw_console.filter_toolbar import FilterToolbar + +from pw_console.style import ( + get_pane_style, +) from pw_console.widgets import ( ToolbarButton, WindowPane, WindowPaneHSplit, WindowPaneToolbar, + create_border, + mouse_handlers, + to_checkbox_text, + to_keybind_indicator, ) + if TYPE_CHECKING: from pw_console.console_app import ConsoleApp @@ -384,7 +391,7 @@ def __init__(self, log_pane: 'LogPane'): ) super().__init__( - pw_console.widgets.border.create_border( + create_border( HSplit( [ info_bar_window, @@ -426,9 +433,7 @@ def copy_url_to_clipboard(self) -> None: def get_message_fragments(self): """Return FormattedText with the last action message.""" # Mouse handlers - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) # Separator should have the focus mouse handler so clicking on any # whitespace focuses the input field. separator_text = ('', ' ', focus) @@ -443,9 +448,7 @@ def get_message_fragments(self): def get_info_fragments(self): """Return FormattedText with current URL info.""" # Mouse handlers - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) # Separator should have the focus mouse handler so clicking on any # whitespace focuses the input field. separator_text = ('', ' ', focus) @@ -464,14 +467,10 @@ def get_info_fragments(self): def get_action_fragments(self): """Return FormattedText with the action buttons.""" # Mouse handlers - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) - cancel = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.close_dialog - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) + cancel = functools.partial(mouse_handlers.on_click, self.close_dialog) copy = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.copy_url_to_clipboard, ) @@ -486,7 +485,7 @@ def get_action_fragments(self): # Action buttons fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key=None, description='Stop', mouse_handler=cancel, @@ -496,7 +495,7 @@ def get_action_fragments(self): fragments.append(separator_text) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key=None, description='Copy to Clipboard', mouse_handler=copy, @@ -624,7 +623,7 @@ def __init__( dont_extend_width=False, # Needed for log lines ANSI sequences that don't specify foreground # or background colors. - style=functools.partial(pw_console.style.get_pane_style, self), + style=functools.partial(get_pane_style, self), ) # Root level container @@ -644,9 +643,7 @@ def __init__( align=VerticalAlign.BOTTOM, height=lambda: self.height, width=lambda: self.width, - style=functools.partial( - pw_console.style.get_pane_style, self - ), + style=functools.partial(get_pane_style, self), ), floats=[ Float(top=0, right=0, height=1, content=LineInfoBar(self)), @@ -815,31 +812,25 @@ def get_window_menu_options( ('-', None), ( '{check} Line wrapping'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( - self.wrap_lines, end='' - ) + check=to_checkbox_text(self.wrap_lines, end='') ), self.toggle_wrap_lines, ), ( '{check} Table view'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( - self._table_view, end='' - ) + check=to_checkbox_text(self._table_view, end='') ), self.toggle_table_view, ), ( '{check} Follow'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( - self.log_view.follow, end='' - ) + check=to_checkbox_text(self.log_view.follow, end='') ), self.toggle_follow, ), ( '{check} Open in web browser'.format( - check=pw_console.widgets.checkbox.to_checkbox_text( + check=to_checkbox_text( self.log_view.websocket_running, end='' ) ), diff --git a/pw_console/py/pw_console/log_pane_saveas_dialog.py b/pw_console/py/pw_console/log_pane_saveas_dialog.py index 385281781a..b3adb090f4 100644 --- a/pw_console/py/pw_console/log_pane_saveas_dialog.py +++ b/pw_console/py/pw_console/log_pane_saveas_dialog.py @@ -36,10 +36,12 @@ Validator, ) -import pw_console.widgets.checkbox -import pw_console.widgets.border -import pw_console.widgets.mouse_handlers -import pw_console.style +from pw_console.widgets import ( + create_border, + mouse_handlers, + to_checkbox_with_keybind_indicator, + to_keybind_indicator, +) if TYPE_CHECKING: from pw_console.log_pane import LogPane @@ -89,7 +91,7 @@ def __init__(self, log_pane: 'LogPane'): 'class:saveas-dialog-setting', 'File: ', functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.focus_self, ), ) @@ -139,7 +141,7 @@ def _close_saveas_dialog(_event: KeyPressEvent) -> None: self.input_field.control.key_bindings = key_bindings super().__init__( - pw_console.widgets.border.create_border( + create_border( HSplit( [ settings_bar_window, @@ -219,15 +221,13 @@ def _saveas_accept_handler(self, buff: Buffer) -> bool: def get_settings_fragments(self): """Return FormattedText with current save settings.""" # Mouse handlers - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) toggle_table_formatting = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self._toggle_table_formatting, ) toggle_selected_lines = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self._toggle_selected_lines, ) @@ -243,7 +243,7 @@ def get_settings_fragments(self): # Table checkbox fragments.extend( - pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator( + to_checkbox_with_keybind_indicator( checked=self._export_with_table_formatting, key='', # No key shortcut help text description='Table Formatting', @@ -257,7 +257,7 @@ def get_settings_fragments(self): # Selected lines checkbox fragments.extend( - pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator( + to_checkbox_with_keybind_indicator( checked=self._export_with_selected_lines_only, key='', # No key shortcut help text description='Selected Lines Only', @@ -274,15 +274,9 @@ def get_settings_fragments(self): def get_action_fragments(self): """Return FormattedText with the save action buttons.""" # Mouse handlers - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) - cancel = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.close_dialog - ) - save = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.save_action - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) + cancel = functools.partial(mouse_handlers.on_click, self.close_dialog) + save = functools.partial(mouse_handlers.on_click, self.save_action) # Separator should have the focus mouse handler so clicking on any # whitespace focuses the input field. @@ -294,7 +288,7 @@ def get_action_fragments(self): fragments = [separator_text] # Cancel button fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='Ctrl-c', description='Cancel', mouse_handler=cancel, @@ -307,7 +301,7 @@ def get_action_fragments(self): # Save button fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='Enter', description='Save', mouse_handler=save, diff --git a/pw_console/py/pw_console/log_pane_selection_dialog.py b/pw_console/py/pw_console/log_pane_selection_dialog.py index 8be14433fd..ec0b1c9b74 100644 --- a/pw_console/py/pw_console/log_pane_selection_dialog.py +++ b/pw_console/py/pw_console/log_pane_selection_dialog.py @@ -25,10 +25,12 @@ WindowAlign, ) -import pw_console.style -import pw_console.widgets.checkbox -import pw_console.widgets.border -import pw_console.widgets.mouse_handlers +from pw_console.widgets import ( + create_border, + mouse_handlers, + to_checkbox_with_keybind_indicator, + to_keybind_indicator, +) if TYPE_CHECKING: from pw_console.log_pane import LogPane @@ -61,7 +63,7 @@ def __init__(self, log_pane: 'LogPane'): ) super().__init__( - pw_console.widgets.border.create_border( + create_border( selection_bar_window, (LogPaneSelectionDialog.DIALOG_HEIGHT - 1), border_style='class:selection-dialog-border', @@ -105,31 +107,29 @@ def get_fragments(self): """Return formatted text tuples for both rows of the selection dialog.""" - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_log_pane - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_log_pane) one_space = ('', ' ', focus) two_spaces = ('', ' ', focus) select_all = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._select_all + mouse_handlers.on_click, self._select_all ) select_none = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._select_none + mouse_handlers.on_click, self._select_none ) copy_selection = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._copy_selection + mouse_handlers.on_click, self._copy_selection ) saveas_file = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._saveas_file + mouse_handlers.on_click, self._saveas_file ) toggle_markdown = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self._toggle_markdown_flag, ) toggle_table = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._toggle_table_flag + mouse_handlers.on_click, self._toggle_table_flag ) button_style = 'class:toolbar-button-inactive' @@ -149,7 +149,7 @@ def get_fragments(self): # Table and Markdown options fragments.extend( - pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator( + to_checkbox_with_keybind_indicator( self._table_flag, key='', description='Table', @@ -159,7 +159,7 @@ def get_fragments(self): ) fragments.extend( - pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator( + to_checkbox_with_keybind_indicator( self._markdown_flag, key='', description='Markdown', @@ -175,7 +175,7 @@ def get_fragments(self): fragments.append(one_space) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='Ctrl-c', description='Cancel', mouse_handler=select_none, @@ -185,7 +185,7 @@ def get_fragments(self): fragments.append(two_spaces) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='Ctrl-a', description='Select All', mouse_handler=select_all, @@ -196,7 +196,7 @@ def get_fragments(self): fragments.append(one_space) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='', description='Save as File', mouse_handler=saveas_file, @@ -206,7 +206,7 @@ def get_fragments(self): fragments.append(two_spaces) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='', description='Copy', mouse_handler=copy_selection, diff --git a/pw_console/py/pw_console/log_pane_toolbars.py b/pw_console/py/pw_console/log_pane_toolbars.py index 2f54b9aba9..35778f4a11 100644 --- a/pw_console/py/pw_console/log_pane_toolbars.py +++ b/pw_console/py/pw_console/log_pane_toolbars.py @@ -27,9 +27,7 @@ HorizontalAlign, ) -import pw_console.widgets.checkbox -import pw_console.widgets.mouse_handlers -import pw_console.style +from pw_console.style import get_toolbar_style if TYPE_CHECKING: from pw_console.log_pane import LogPane @@ -60,7 +58,7 @@ def __init__(self, log_pane: 'LogPane'): [info_bar_window], height=1, style=functools.partial( - pw_console.style.get_toolbar_style, self.log_pane, dim=True + get_toolbar_style, self.log_pane, dim=True ), align=HorizontalAlign.RIGHT, ), @@ -90,9 +88,7 @@ def __init__(self, log_pane: 'LogPane'): VSplit( [table_header_bar_window], height=1, - style=functools.partial( - pw_console.style.get_toolbar_style, log_pane, dim=True - ), + style=functools.partial(get_toolbar_style, log_pane, dim=True), align=HorizontalAlign.LEFT, ), filter=Condition( diff --git a/pw_console/py/pw_console/log_store.py b/pw_console/py/pw_console/log_store.py index 072a92bb85..8a8c4e83c6 100644 --- a/pw_console/py/pw_console/log_store.py +++ b/pw_console/py/pw_console/log_store.py @@ -19,11 +19,11 @@ from datetime import datetime from typing import Dict, List, Optional, TYPE_CHECKING -import pw_cli.color +from pw_cli.color import colors as pw_cli_colors from pw_console.console_prefs import ConsolePrefs from pw_console.log_line import LogLine -import pw_console.text_formatting +from pw_console.text_formatting import strip_ansi from pw_console.widgets.table import TableView if TYPE_CHECKING: @@ -127,7 +127,7 @@ def register_viewer(self, viewer: 'LogView') -> None: def set_formatting(self) -> None: """Setup log formatting.""" # Copy of pw_cli log formatter - colors = pw_cli.color.colors(True) + colors = pw_cli_colors(True) timestamp_prefix = colors.black_on_white('%(asctime)s') + ' ' timestamp_format = '%Y%m%d %H:%M:%S' format_string = timestamp_prefix + '%(levelname)s %(message)s' @@ -190,10 +190,8 @@ def _update_log_prefix_width(self, record: logging.LogRecord): ) # Delete ANSI escape sequences. - ansi_stripped_time_and_level = ( - pw_console.text_formatting.strip_ansi( - formatted_time_and_level - ) + ansi_stripped_time_and_level = strip_ansi( + formatted_time_and_level ) self.channel_formatted_prefix_widths[record.name] = len( @@ -211,7 +209,7 @@ def _append_log(self, record: logging.LogRecord): """Add a new log event.""" # Format incoming log line. formatted_log = self.format(record) - ansi_stripped_log = pw_console.text_formatting.strip_ansi(formatted_log) + ansi_stripped_log = strip_ansi(formatted_log) # Save this log. self.logs.append( LogLine( diff --git a/pw_console/py/pw_console/pigweed_code_style.py b/pw_console/py/pw_console/pigweed_code_style.py index 7fa5453b60..689ee67711 100644 --- a/pw_console/py/pw_console/pigweed_code_style.py +++ b/pw_console/py/pw_console/pigweed_code_style.py @@ -13,37 +13,93 @@ # the License. """Brighter PigweedCode pygments style.""" -import copy import re from prompt_toolkit.styles.style_transformation import get_opposite_color from pygments.style import Style # type: ignore -from pygments.token import Comment, Keyword, Name, Generic # type: ignore -from pygments_style_dracula.dracula import DraculaStyle # type: ignore - -_style_list = copy.copy(DraculaStyle.styles) - -# Darker Prompt -_style_list[Generic.Prompt] = '#bfbfbf' -# Lighter comments -_style_list[Comment] = '#778899' -_style_list[Comment.Hashbang] = '#778899' -_style_list[Comment.Multiline] = '#778899' -_style_list[Comment.Preproc] = '#ff79c6' -_style_list[Comment.Single] = '#778899' -_style_list[Comment.Special] = '#778899' -# Lighter output -_style_list[Generic.Output] = '#f8f8f2' -_style_list[Generic.Emph] = '#f8f8f2' -# Remove 'italic' from these -_style_list[Keyword.Declaration] = '#8be9fd' -_style_list[Name.Builtin] = '#8be9fd' -_style_list[Name.Label] = '#8be9fd' -_style_list[Name.Variable] = '#8be9fd' -_style_list[Name.Variable.Class] = '#8be9fd' -_style_list[Name.Variable.Global] = '#8be9fd' -_style_list[Name.Variable.Instance] = '#8be9fd' +from pygments.token import Token # type: ignore + +_style_list = { + Token.Comment: '#778899', # Lighter comments + Token.Comment.Hashbang: '#778899', + Token.Comment.Multiline: '#778899', + Token.Comment.Preproc: '#ff79c6', + Token.Comment.PreprocFile: '', + Token.Comment.Single: '#778899', + Token.Comment.Special: '#778899', + Token.Error: '#f8f8f2', + Token.Escape: '', + Token.Generic.Deleted: '#8b080b', + Token.Generic.Emph: '#f8f8f2', + Token.Generic.Error: '#f8f8f2', + Token.Generic.Heading: '#f8f8f2 bold', + Token.Generic.Inserted: '#f8f8f2 bold', + Token.Generic.Output: '#f8f8f2', + Token.Generic.Prompt: '#bfbfbf', # Darker Prompt + Token.Generic.Strong: '#f8f8f2', + Token.Generic.Subheading: '#f8f8f2 bold', + Token.Generic.Traceback: '#f8f8f2', + Token.Generic: '#f8f8f2', + Token.Keyword.Constant: '#ff79c6', + Token.Keyword.Declaration: '#8be9fd', + Token.Keyword.Namespace: '#ff79c6', + Token.Keyword.Pseudo: '#ff79c6', + Token.Keyword.Reserved: '#ff79c6', + Token.Keyword.Type: '#8be9fd', + Token.Keyword: '#ff79c6', + Token.Literal.Date: '#f8f8f2', + Token.Literal.Number.Bin: '#bd93f9', + Token.Literal.Number.Float: '#bd93f9', + Token.Literal.Number.Hex: '#bd93f9', + Token.Literal.Number.Integer.Long: '#bd93f9', + Token.Literal.Number.Integer: '#bd93f9', + Token.Literal.Number.Oct: '#bd93f9', + Token.Literal.Number: '#bd93f9', + Token.Literal.String.Affix: '', + Token.Literal.String.Backtick: '#f1fa8c', + Token.Literal.String.Char: '#f1fa8c', + Token.Literal.String.Delimiter: '', + Token.Literal.String.Doc: '#f1fa8c', + Token.Literal.String.Double: '#f1fa8c', + Token.Literal.String.Escape: '#f1fa8c', + Token.Literal.String.Heredoc: '#f1fa8c', + Token.Literal.String.Interpol: '#f1fa8c', + Token.Literal.String.Other: '#f1fa8c', + Token.Literal.String.Regex: '#f1fa8c', + Token.Literal.String.Single: '#f1fa8c', + Token.Literal.String.Symbol: '#f1fa8c', + Token.Literal.String: '#f1fa8c', + Token.Literal: '#f8f8f2', + Token.Name.Attribute: '#50fa7b', + Token.Name.Builtin: '#8be9fd', + Token.Name.Builtin.Pseudo: '#f8f8f2', + Token.Name.Class: '#50fa7b', + Token.Name.Constant: '#f8f8f2', + Token.Name.Decorator: '#f8f8f2', + Token.Name.Entity: '#f8f8f2', + Token.Name.Exception: '#f8f8f2', + Token.Name.Function.Magic: '', + Token.Name.Function: '#50fa7b', + Token.Name.Label: '#8be9fd', + Token.Name.Namespace: '#f8f8f2', + Token.Name.Other: '#f8f8f2', + Token.Name.Property: '', + Token.Name.Tag: '#ff79c6', + Token.Name.Variable: '#8be9fd', + Token.Name.Variable.Class: '#8be9fd', + Token.Name.Variable.Global: '#8be9fd', + Token.Name.Variable.Instance: '#8be9fd', + Token.Name.Variable.Magic: '', + Token.Name: '#f8f8f2', + Token.Operator.Word: '#ff79c6', + Token.Operator: '#ff79c6', + Token.Other: '#f8f8f2', + Token.Punctuation: '#f8f8f2', + Token.Text.Whitespace: '#f8f8f2', + Token.Text: '#f8f8f2', + Token: '', +} _COLOR_REGEX = re.compile(r'#(?P[0-9a-fA-F]{6}) *(?P.*?)$') diff --git a/pw_console/py/pw_console/plugins/twenty48_pane.py b/pw_console/py/pw_console/plugins/twenty48_pane.py index 9c47aeaa20..104e921812 100644 --- a/pw_console/py/pw_console/plugins/twenty48_pane.py +++ b/pw_console/py/pw_console/plugins/twenty48_pane.py @@ -32,8 +32,8 @@ from prompt_toolkit.mouse_events import MouseEvent, MouseEventType from prompt_toolkit.widgets import MenuItem -import pw_console.widgets.border from pw_console.widgets import ( + create_border, FloatingWindowPane, ToolbarButton, WindowPaneToolbar, @@ -453,7 +453,7 @@ def __init__(self, include_resize_handle: bool = True, **kwargs): # self.container is the root container that contains objects to be # rendered in the UI, one on top of the other. self.container = self._create_pane_container( - pw_console.widgets.border.create_border( + create_border( HSplit( [ # Vertical split content @@ -490,7 +490,7 @@ def __init__(self, include_resize_handle: bool = True, **kwargs): self.bottom_toolbar, ] # Wrap the dialog content in a border - self.bordered_dialog_content = pw_console.widgets.border.create_border( + self.bordered_dialog_content = create_border( HSplit(self.dialog_content), title='2048', border_style='class:command-runner-border', diff --git a/pw_console/py/pw_console/pw_ptpython_repl.py b/pw_console/py/pw_console/pw_ptpython_repl.py index 5f100978fb..1b2c7d87c7 100644 --- a/pw_console/py/pw_console/pw_ptpython_repl.py +++ b/pw_console/py/pw_console/pw_ptpython_repl.py @@ -22,6 +22,7 @@ import shlex import subprocess from typing import Iterable, Optional, TYPE_CHECKING +from unittest.mock import patch from prompt_toolkit.buffer import Buffer from prompt_toolkit.layout.controls import BufferControl @@ -40,8 +41,13 @@ CompletionVisualisation, Dimension, ) +import pygments.plugin -import pw_console.text_formatting +from pw_console.pigweed_code_style import ( + PigweedCodeStyle, + PigweedCodeLightStyle, +) +from pw_console.text_formatting import remove_formatting if TYPE_CHECKING: from pw_console.repl_pane import ReplPane @@ -49,6 +55,21 @@ _LOG = logging.getLogger(__package__) _SYSTEM_COMMAND_LOG = logging.getLogger('pw_console_system_command') +_original_find_plugin_styles = pygments.plugin.find_plugin_styles + + +def _wrapped_find_plugin_styles(): + """Patch pygment find_plugin_styles to also include Pigweed codes styles + + This allows using these themes without requiring Python entrypoints. + """ + for style in [ + ('pigweed-code', PigweedCodeStyle), + ('pigweed-code-light', PigweedCodeLightStyle), + ]: + yield style + yield from _original_find_plugin_styles() + class MissingPtpythonBufferControl(Exception): """Exception for a missing ptpython BufferControl object.""" @@ -64,6 +85,9 @@ class PwPtPythonRepl( """A ptpython repl class with changes to code execution and output related methods.""" + @patch( + 'pygments.styles.find_plugin_styles', new=_wrapped_find_plugin_styles + ) def __init__( self, *args, @@ -180,16 +204,12 @@ def set_repl_pane(self, repl_pane): def _save_result(self, formatted_text): """Save the last repl execution result.""" - unformatted_result = pw_console.text_formatting.remove_formatting( - formatted_text - ) + unformatted_result = remove_formatting(formatted_text) self._last_result = unformatted_result def _save_exception(self, formatted_text): """Save the last repl exception.""" - unformatted_result = pw_console.text_formatting.remove_formatting( - formatted_text - ) + unformatted_result = remove_formatting(formatted_text) self._last_exception = unformatted_result def clear_last_result(self): @@ -238,9 +258,7 @@ def user_code_complete_callback(self, input_text, future): if result_object is not None: # Use ptpython formatted results: formatted_result = self._format_result_output(result_object) - result_text = pw_console.text_formatting.remove_formatting( - formatted_result - ) + result_text = remove_formatting(formatted_result) # Job is finished, append the last result. self.repl_pane.append_result_to_executed_code( diff --git a/pw_console/py/pw_console/quit_dialog.py b/pw_console/py/pw_console/quit_dialog.py index 5e26dcd3d5..6195dd2fb8 100644 --- a/pw_console/py/pw_console/quit_dialog.py +++ b/pw_console/py/pw_console/quit_dialog.py @@ -30,9 +30,11 @@ WindowAlign, ) -import pw_console.widgets.border -import pw_console.widgets.checkbox -import pw_console.widgets.mouse_handlers +from pw_console.widgets import ( + create_border, + mouse_handlers, + to_keybind_indicator, +) if TYPE_CHECKING: from pw_console.console_app import ConsoleApp @@ -91,7 +93,7 @@ def _cancel(_event: KeyPressEvent) -> None: ) super().__init__( - pw_console.widgets.border.create_border( + create_border( HSplit( [action_bar_window], height=QuitDialog.DIALOG_HEIGHT, @@ -136,14 +138,10 @@ def get_action_fragments(self): """Return FormattedText with action buttons.""" # Mouse handlers - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) - cancel = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.close_dialog - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) + cancel = functools.partial(mouse_handlers.on_click, self.close_dialog) quit_action = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.quit_action + mouse_handlers.on_click, self.quit_action ) # Separator should have the focus mouse handler so clicking on any @@ -158,7 +156,7 @@ def get_action_fragments(self): # Cancel button fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='n / Ctrl-c', description='Cancel', mouse_handler=cancel, @@ -171,7 +169,7 @@ def get_action_fragments(self): # Save button fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='y / Ctrl-d', description='Quit', mouse_handler=quit_action, diff --git a/pw_console/py/pw_console/repl_pane.py b/pw_console/py/pw_console/repl_pane.py index 9fb55b9c06..9547215c92 100644 --- a/pw_console/py/pw_console/repl_pane.py +++ b/pw_console/py/pw_console/repl_pane.py @@ -54,14 +54,13 @@ from pw_console.progress_bar.progress_bar_state import TASKS_CONTEXTVAR from pw_console.pw_ptpython_repl import PwPtPythonRepl +from pw_console.style import get_pane_style from pw_console.widgets import ( ToolbarButton, WindowPane, WindowPaneHSplit, WindowPaneToolbar, ) -import pw_console.mouse -import pw_console.style if TYPE_CHECKING: from pw_console.console_app import ConsoleApp @@ -198,9 +197,7 @@ def _copy_selection(_event: KeyPressEvent) -> None: # Repl pane dimensions height=lambda: self.height, width=lambda: self.width, - style=functools.partial( - pw_console.style.get_pane_style, self - ), + style=functools.partial(get_pane_style, self), ), floats=[], ), diff --git a/pw_console/py/pw_console/search_toolbar.py b/pw_console/py/pw_console/search_toolbar.py index cd4af8b138..c140a074ee 100644 --- a/pw_console/py/pw_console/search_toolbar.py +++ b/pw_console/py/pw_console/search_toolbar.py @@ -37,9 +37,11 @@ from prompt_toolkit.validation import DynamicValidator from pw_console.log_view import RegexValidator, SearchMatcher - -# import pw_console.widgets.checkbox -import pw_console.widgets.mouse_handlers +from pw_console.widgets import ( + mouse_handlers, + to_checkbox_with_keybind_indicator, + to_keybind_indicator, +) if TYPE_CHECKING: from pw_console.log_pane import LogPane @@ -64,7 +66,7 @@ def __init__(self, log_pane: 'LogPane'): 'class:search-bar-setting', '/', functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.focus_self, ), ) @@ -275,14 +277,12 @@ def _search_accept_handler(self, buff: Buffer) -> bool: def get_search_help_fragments(self): """Return FormattedText with search general help keybinds.""" - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) start_search = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._start_search + mouse_handlers.on_click, self._start_search ) close_search = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.cancel_search + mouse_handlers.on_click, self.cancel_search ) # Search toolbar is darker than pane toolbars, use the darker button @@ -298,14 +298,14 @@ def get_search_help_fragments(self): fragments.extend(separator_text) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'Enter', 'Search', start_search, base_style=button_style ) ) fragments.extend(separator_text) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'Ctrl-c', 'Cancel', close_search, base_style=button_style ) ) @@ -314,17 +314,15 @@ def get_search_help_fragments(self): def get_search_settings_fragments(self): """Return FormattedText with current search settings and keybinds.""" - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_self - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_self) next_field = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._next_field + mouse_handlers.on_click, self._next_field ) toggle_invert = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._invert_search + mouse_handlers.on_click, self._invert_search ) next_matcher = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.log_pane.log_view.select_next_search_matcher, ) @@ -348,7 +346,7 @@ def get_search_settings_fragments(self): ), ] fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'Ctrl-t', 'Column:', next_field, @@ -359,7 +357,7 @@ def get_search_settings_fragments(self): fragments.extend(separator_text) fragments.extend( - pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator( + to_checkbox_with_keybind_indicator( self._search_invert, 'Ctrl-v', 'Invert', @@ -378,7 +376,7 @@ def get_search_settings_fragments(self): ) ] fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( 'Ctrl-n', 'Matcher:', next_matcher, @@ -397,9 +395,7 @@ def get_search_matcher(self): def get_match_count_fragments(self): """Return formatted text for the match count indicator.""" - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_log_pane - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_log_pane) two_spaces = ('', ' ', focus) # Check if this line is a search match @@ -429,26 +425,22 @@ def get_match_count_fragments(self): def get_button_fragments(self) -> StyleAndTextTuples: """Return formatted text for the action buttons.""" - focus = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.focus_log_pane - ) + focus = functools.partial(mouse_handlers.on_click, self.focus_log_pane) one_space = ('', ' ', focus) two_spaces = ('', ' ', focus) - cancel = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self.cancel_search - ) + cancel = functools.partial(mouse_handlers.on_click, self.cancel_search) create_filter = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._create_filter + mouse_handlers.on_click, self._create_filter ) next_match = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._next_match + mouse_handlers.on_click, self._next_match ) previous_match = functools.partial( - pw_console.widgets.mouse_handlers.on_click, self._previous_match + mouse_handlers.on_click, self._previous_match ) toggle_search_follow = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self._toggle_search_follow, ) @@ -456,7 +448,7 @@ def get_button_fragments(self) -> StyleAndTextTuples: fragments = [] fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='n', description='Next', mouse_handler=next_match, @@ -466,7 +458,7 @@ def get_button_fragments(self) -> StyleAndTextTuples: fragments.append(two_spaces) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='N', description='Previous', mouse_handler=previous_match, @@ -476,7 +468,7 @@ def get_button_fragments(self) -> StyleAndTextTuples: fragments.append(two_spaces) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='Ctrl-c', description='Cancel', mouse_handler=cancel, @@ -486,7 +478,7 @@ def get_button_fragments(self) -> StyleAndTextTuples: fragments.append(two_spaces) fragments.extend( - pw_console.widgets.checkbox.to_keybind_indicator( + to_keybind_indicator( key='Ctrl-Alt-f', description='Add Filter', mouse_handler=create_filter, @@ -496,7 +488,7 @@ def get_button_fragments(self) -> StyleAndTextTuples: fragments.append(two_spaces) fragments.extend( - pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator( + to_checkbox_with_keybind_indicator( checked=self.log_view.follow_search_match, key='', description='Jump to new matches', diff --git a/pw_console/py/pw_console/widgets/__init__.py b/pw_console/py/pw_console/widgets/__init__.py index 71ec193274..133df8201f 100644 --- a/pw_console/py/pw_console/widgets/__init__.py +++ b/pw_console/py/pw_console/widgets/__init__.py @@ -14,6 +14,7 @@ """Pigweed Console Reusable UI widgets.""" # pylint: disable=unused-import +from pw_console.widgets.border import create_border from pw_console.widgets.checkbox import ( ToolbarButton, to_checkbox, diff --git a/pw_console/py/pw_console/widgets/table.py b/pw_console/py/pw_console/widgets/table.py index 05bf43059f..e486fd2b92 100644 --- a/pw_console/py/pw_console/widgets/table.py +++ b/pw_console/py/pw_console/widgets/table.py @@ -20,7 +20,7 @@ from pw_console.console_prefs import ConsolePrefs from pw_console.log_line import LogLine -import pw_console.text_formatting +from pw_console.text_formatting import strip_ansi class TableView: @@ -117,9 +117,7 @@ def update_metadata_column_widths(self, log: LogLine) -> None: self.column_widths[field_name] = len(value_string) # Update log level character width. - ansi_stripped_level = pw_console.text_formatting.strip_ansi( - log.record.levelname - ) + ansi_stripped_level = strip_ansi(log.record.levelname) if len(ansi_stripped_level) > self.column_widths['level']: self.column_widths['level'] = len(ansi_stripped_level) @@ -192,9 +190,7 @@ def formatted_row(self, log: LogLine) -> StyleAndTextTuples: if name == 'level' and hasattr(log.record, 'levelname'): # Remove any existing ANSI formatting and apply our colors. - level_text = pw_console.text_formatting.strip_ansi( - log.record.levelname - ) + level_text = strip_ansi(log.record.levelname) level_style = self.prefs.column_style( 'level', level_text, @@ -224,7 +220,7 @@ def formatted_row(self, log: LogLine) -> StyleAndTextTuples: # Grab the message to appear after the justified columns with ANSI # escape sequences removed. - message_text = pw_console.text_formatting.strip_ansi(log.record.message) + message_text = strip_ansi(log.record.message) message = log.metadata.fields.get( 'msg', message_text.rstrip(), # Remove any trailing line breaks diff --git a/pw_console/py/pw_console/widgets/window_pane.py b/pw_console/py/pw_console/widgets/window_pane.py index 6657e01ae0..4ed28df4d6 100644 --- a/pw_console/py/pw_console/widgets/window_pane.py +++ b/pw_console/py/pw_console/widgets/window_pane.py @@ -30,10 +30,7 @@ from prompt_toolkit.widgets import MenuItem from pw_console.get_pw_console_app import get_pw_console_app - -import pw_console.widgets.checkbox -import pw_console.widgets.mouse_handlers -import pw_console.style +from pw_console.style import get_pane_style if TYPE_CHECKING: from pw_console.console_app import ConsoleApp @@ -216,7 +213,7 @@ def _create_pane_container(self, *content) -> ConditionalContainer: # Window pane dimensions height=lambda: self.height, width=lambda: self.width, - style=functools.partial(pw_console.style.get_pane_style, self), + style=functools.partial(get_pane_style, self), ), filter=Condition(lambda: self.show_pane), ) diff --git a/pw_console/py/pw_console/widgets/window_pane_toolbar.py b/pw_console/py/pw_console/widgets/window_pane_toolbar.py index c18b8f0940..4302cccd6b 100644 --- a/pw_console/py/pw_console/widgets/window_pane_toolbar.py +++ b/pw_console/py/pw_console/widgets/window_pane_toolbar.py @@ -28,13 +28,17 @@ from prompt_toolkit.mouse_events import MouseEvent, MouseEventType from pw_console.get_pw_console_app import get_pw_console_app -import pw_console.style +from pw_console.style import ( + get_pane_indicator, + get_button_style, + get_toolbar_style, +) from pw_console.widgets import ( ToolbarButton, + mouse_handlers, to_checkbox_with_keybind_indicator, to_keybind_indicator, ) -import pw_console.widgets.mouse_handlers _LOG = logging.getLogger(__package__) @@ -74,7 +78,7 @@ def get_left_text_tokens(self): # No title was set, fetch the parent window pane title if available. parent_pane_title = self.parent_window_pane.pane_title() title = parent_pane_title if parent_pane_title else title - return pw_console.style.get_pane_indicator( + return get_pane_indicator( self.focus_check_container, f' {title} ', self.focus_mouse_handler ) @@ -82,9 +86,7 @@ def get_center_text_tokens(self): """Return formatted text tokens for display in the center part of the toolbar.""" - button_style = pw_console.style.get_button_style( - self.focus_check_container - ) + button_style = get_button_style(self.focus_check_container) # FormattedTextTuple contents: (Style, Text, Mouse handler) separator_text = [('', ' ')] # 2 spaces of separaton between keybinds. @@ -98,7 +100,7 @@ def get_center_text_tokens(self): on_click_handler = None if button.mouse_handler: on_click_handler = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, button.mouse_handler, ) @@ -162,7 +164,7 @@ def get_right_text_tokens(self): return fragments def get_resize_handle(self): - return pw_console.style.get_pane_indicator( + return get_pane_indicator( self.focus_check_container, '─══─', hide_indicator=True ) @@ -211,7 +213,7 @@ def empty_subtitle() -> str: self.focus_mouse_handler = None if self.focus_action_callable: self.focus_mouse_handler = functools.partial( - pw_console.widgets.mouse_handlers.on_click, + mouse_handlers.on_click, self.focus_action_callable, ) @@ -237,8 +239,8 @@ def empty_subtitle() -> str: dont_extend_width=True, ) - get_toolbar_style = functools.partial( - pw_console.style.get_toolbar_style, self.focus_check_container + wrapped_get_toolbar_style = functools.partial( + get_toolbar_style, self.focus_check_container ) sections = [ @@ -261,7 +263,7 @@ def empty_subtitle() -> str: self.toolbar_vsplit = VSplit( sections, height=WindowPaneToolbar.TOOLBAR_HEIGHT, - style=get_toolbar_style, + style=wrapped_get_toolbar_style, ) self.container = self._create_toolbar_container(self.toolbar_vsplit) diff --git a/pw_console/py/pw_console/window_list.py b/pw_console/py/pw_console/window_list.py index ee2e6affcb..2069f2d1bf 100644 --- a/pw_console/py/pw_console/window_list.py +++ b/pw_console/py/pw_console/window_list.py @@ -31,8 +31,7 @@ ) from prompt_toolkit.mouse_events import MouseEvent, MouseEventType, MouseButton -import pw_console.style -import pw_console.widgets.mouse_handlers +from pw_console.widgets import mouse_handlers as pw_console_mouse_handlers if TYPE_CHECKING: # pylint: disable=ungrouped-imports @@ -80,9 +79,7 @@ def write_to_screen( # Is resize mode active? if self.parent_window_list.resize_mode: # Ignore future mouse_handler updates. - new_mouse_handlers = ( - pw_console.widgets.mouse_handlers.EmptyMouseHandler() - ) + new_mouse_handlers = mouse_handlers.EmptyMouseHandler() # Set existing mouse_handlers to the parent_window_list's # mouse_handler. This will handle triggering resize events. mouse_handlers.set_mouse_handler_for_range( @@ -261,7 +258,7 @@ def get_pane_titles(self, omit_subtitles=False, use_menu_title=True): text, # Mouse handler functools.partial( - pw_console.widgets.mouse_handlers.on_click, + pw_console_mouse_handlers.on_click, functools.partial(self.switch_to_tab, pane_index), ), ) diff --git a/pw_console/py/pw_console/window_manager.py b/pw_console/py/pw_console/window_manager.py index 8413ab94a1..006b570a37 100644 --- a/pw_console/py/pw_console/window_manager.py +++ b/pw_console/py/pw_console/window_manager.py @@ -35,9 +35,10 @@ from pw_console.console_prefs import ConsolePrefs, error_unknown_window from pw_console.log_pane import LogPane -import pw_console.widgets.checkbox -from pw_console.widgets import WindowPaneToolbar -import pw_console.widgets.mouse_handlers +from pw_console.widgets import ( + WindowPaneToolbar, + to_checkbox_text, +) from pw_console.window_list import WindowList, DisplayMode _LOG = logging.getLogger(__package__) @@ -94,9 +95,7 @@ def write_to_screen( # Is resize mode active? if self.parent_window_manager.resize_mode: # Ignore future mouse_handler updates. - new_mouse_handlers = ( - pw_console.widgets.mouse_handlers.EmptyMouseHandler() - ) + new_mouse_handlers = mouse_handlers.EmptyMouseHandler() # Set existing mouse_handlers to the parent_window_managers's # mouse_handler. This will handle triggering resize events. mouse_handlers.set_mouse_handler_for_range( @@ -148,9 +147,7 @@ def write_to_screen( # Is resize mode active? if self.parent_window_manager.resize_mode: # Ignore future mouse_handler updates. - new_mouse_handlers = ( - pw_console.widgets.mouse_handlers.EmptyMouseHandler() - ) + new_mouse_handlers = mouse_handlers.EmptyMouseHandler() # Set existing mouse_handlers to the parent_window_managers's # mouse_handler. This will handle triggering resize events. mouse_handlers.set_mouse_handler_for_range( @@ -1048,7 +1045,6 @@ def create_window_menu_items(self) -> List[MenuItem]: root_menu_items = [] for window_list_index, window_list in enumerate(self.window_lists): menu_items = [] - checkbox = pw_console.widgets.checkbox menu_items.append( MenuItem( 'Column {index} View Modes'.format( @@ -1058,7 +1054,7 @@ def create_window_menu_items(self) -> List[MenuItem]: MenuItem( '{check} {display_mode} Windows'.format( display_mode=display_mode.value, - check=checkbox.to_checkbox_text( + check=to_checkbox_text( window_list.display_mode == display_mode, end='', ), @@ -1080,9 +1076,7 @@ def create_window_menu_items(self) -> List[MenuItem]: children=[ MenuItem( '{check} Show/Hide Window'.format( - check=checkbox.to_checkbox_text( - pane.show_pane, end='' - ) + check=to_checkbox_text(pane.show_pane, end='') ), handler=functools.partial(self.toggle_pane, pane), ), diff --git a/pw_console/py/setup.cfg b/pw_console/py/setup.cfg index b27dfbe47f..97b921a15b 100644 --- a/pw_console/py/setup.cfg +++ b/pw_console/py/setup.cfg @@ -27,8 +27,6 @@ install_requires = prompt-toolkit>=3.0.26 ptpython>=3.0.20 pygments - pygments-style-dracula - pygments-style-tomorrow pyperclip pyserial pyyaml @@ -38,9 +36,6 @@ install_requires = [options.entry_points] console_scripts = pw-console = pw_console.__main__:main -pygments.styles = - pigweed-code = pw_console.pigweed_code_style:PigweedCodeStyle - pigweed-code-light = pw_console.pigweed_code_style:PigweedCodeLightStyle [options.package_data] pw_console = diff --git a/pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list b/pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list index d1e23a71ca..49cfbe942b 100644 --- a/pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list +++ b/pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list @@ -52,8 +52,6 @@ pyasn1-modules==0.2.8 pycparser==2.21 pyelftools==0.27 Pygments==2.11.2 -pygments-style-dracula==1.2.5.1 -pygments-style-tomorrow==1.0.0.1 pylint==2.9.3 pyparsing==3.0.6 pyperclip==1.8.2