pw_console: Console user guide
- Add the Pigweed Console user guide
- Help window split into 3 separate windows
- Upstream rst formatted user guide
- Downstream project specific help, if provided to embed()
- Auto-generated keybind list from all active window panes
Testing: Test Help Windows Steps 1-6
Change-Id: I25edf66ac73ba55c60a46d860623d0f2a464a839
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/54320
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/pw_console/BUILD.gn b/pw_console/BUILD.gn
index fba633e..bf0b678 100644
--- a/pw_console/BUILD.gn
+++ b/pw_console/BUILD.gn
@@ -21,6 +21,7 @@
"docs.rst",
"embedding.rst",
"internals.rst",
+ "py/pw_console/docs/user_guide.rst",
"testing.rst",
]
}
diff --git a/pw_console/docs.rst b/pw_console/docs.rst
index 65c5558..446f1e0 100644
--- a/pw_console/docs.rst
+++ b/pw_console/docs.rst
@@ -8,23 +8,15 @@
`ptpython`_ and a log message viewer in a single-window terminal based
interface. It is designed to be a replacement for `IPython's embed()`_ function.
-.. warning::
- The Pigweed Console is under heavy development. A user manual and usage
- information will be documented as features near completion.
-
-Goals
-=====
-
-``pw_console`` is a complete solution for interacting with hardware devices
-using :ref:`module-pw_rpc` over a :ref:`module-pw_hdlc` transport.
-
-The repl allows interactive RPC sending while the log viewer provides immediate
-feedback on device status.
-
Features
-^^^^^^^^
+========
-- Interactive Python repl and log viewer in a single terminal window.
+``pw_console`` aims to be a complete solution for interacting with hardware
+devices using :ref:`module-pw_rpc` over a :ref:`module-pw_hdlc` transport.
+
+- Interactive Python repl and log viewer in a single terminal window. This
+ provides interactive RPC sending while the log viewer provides immediate
+ feedback on device status.
- Easily embeddable within a project's own custom console. This should allow
users to define their own transport layer.
@@ -46,6 +38,7 @@
.. toctree::
:maxdepth: 1
+ py/pw_console/docs/user_guide
embedding
testing
internals
diff --git a/pw_console/py/pw_console/__main__.py b/pw_console/py/pw_console/__main__.py
index 049abf7..8629e71 100644
--- a/pw_console/py/pw_console/__main__.py
+++ b/pw_console/py/pw_console/__main__.py
@@ -14,6 +14,7 @@
"""Pigweed Console - Warning: This is a work in progress."""
import argparse
+import inspect
import logging
import sys
import tempfile
@@ -105,9 +106,27 @@
# Give access to adding log messages from the repl via: `LOG.warning()`
global_vars = dict(LOG=default_loggers[0])
- pw_console.console_app.embed(global_vars=global_vars,
- loggers=default_loggers,
- test_mode=args.test_mode)
+ help_text = None
+ app_title = None
+ if args.test_mode:
+ app_title = 'Console Test Mode'
+ help_text = inspect.cleandoc("""
+ Welcome to the Pigweed Console Test Mode!
+
+ Example commands:
+
+ rpcs.pw.rpc.EchoService.Echo(msg='hello!')
+
+ LOG.warning('Message appears console log window.')
+ """)
+
+ pw_console.console_app.embed(
+ global_vars=global_vars,
+ loggers=default_loggers,
+ test_mode=args.test_mode,
+ help_text=help_text,
+ app_title=app_title,
+ )
if args.logfile:
print(f'Logs saved to: {args.logfile}')
diff --git a/pw_console/py/pw_console/console_app.py b/pw_console/py/pw_console/console_app.py
index 081efcb..38e036e 100644
--- a/pw_console/py/pw_console/console_app.py
+++ b/pw_console/py/pw_console/console_app.py
@@ -152,13 +152,21 @@
'Ctrl-W', 'Quit '))
# Top level UI state toggles.
- self.show_help_window = False
self.vertical_split = False
self.load_theme()
- self.help_window = HelpWindow(self,
- preamble='Pigweed CLI v0.1',
- additional_help_text=help_text)
+ # Pigweed upstream RST user guide
+ self.user_guide_window = HelpWindow(self)
+ self.user_guide_window.load_user_guide()
+
+ # Auto-generated keybindings list for all active panes
+ self.keybind_help_window = HelpWindow(self)
+
+ # Downstream project specific help text
+ self.app_help_text = help_text if help_text else None
+ self.app_help_window = HelpWindow(self, additional_help_text=help_text)
+ self.app_help_window.generate_help_text()
+
# Used for tracking which pane was in focus before showing help window.
self.last_focused_pane = None
@@ -206,13 +214,27 @@
right=0,
height=1,
),
- # Centered floating Help Window
+ # Centered floating help windows
Float(
- content=self.help_window,
+ content=self.app_help_window,
top=2,
bottom=2,
# Callable to get width
- width=self.help_window.content_width,
+ width=self.app_help_window.content_width,
+ ),
+ Float(
+ content=self.user_guide_window,
+ top=2,
+ bottom=2,
+ # Callable to get width
+ width=self.user_guide_window.content_width,
+ ),
+ Float(
+ content=self.keybind_help_window,
+ top=2,
+ bottom=2,
+ # Callable to get width
+ width=self.keybind_help_window.content_width,
),
# Completion menu that can overlap other panes since it lives in
# the top level Float container.
@@ -382,13 +404,25 @@
)
]
+ help_menu_items = [
+ MenuItem('User Guide',
+ handler=self.user_guide_window.toggle_display),
+ MenuItem('Keyboard Shortcuts',
+ handler=self.keybind_help_window.toggle_display),
+ ]
+
+ if self.app_help_text:
+ help_menu_items.extend([
+ MenuItem('-'),
+ MenuItem(self.app_title + ' Help',
+ handler=self.app_help_window.toggle_display)
+ ])
+
help_menu = [
# Info / Help
MenuItem(
'[Help]',
- children=[
- MenuItem('Keyboard Shortcuts', handler=self.toggle_help),
- ],
+ children=help_menu_items,
),
]
@@ -627,24 +661,25 @@
'Scroll current window.': ['Scroll wheel'],
}
- self.help_window.add_custom_keybinds_help_text('Global Mouse',
- mouse_functions)
+ self.keybind_help_window.add_custom_keybinds_help_text(
+ 'Global Mouse', mouse_functions)
# Add global key bindings to the help text.
- self.help_window.add_keybind_help_text('Global', self.key_bindings)
+ self.keybind_help_window.add_keybind_help_text('Global',
+ self.key_bindings)
# Add activated plugin key bindings to the help text.
for pane in self.active_panes:
for key_bindings in pane.get_all_key_bindings():
help_section_title = pane.__class__.__name__
if isinstance(key_bindings, KeyBindings):
- self.help_window.add_keybind_help_text(
+ self.keybind_help_window.add_keybind_help_text(
help_section_title, key_bindings)
elif isinstance(key_bindings, dict):
- self.help_window.add_custom_keybinds_help_text(
+ self.keybind_help_window.add_custom_keybinds_help_text(
help_section_title, key_bindings)
- self.help_window.generate_help_text()
+ self.keybind_help_window.generate_help_text()
def _update_split_orientation(self):
if self.vertical_split:
@@ -690,20 +725,13 @@
"""Return the currently focused window."""
return self.application.layout.current_window
- def toggle_help(self):
- """Toggle visibility of the help window."""
- # Toggle state variable.
- self.show_help_window = not self.show_help_window
-
- # Set the help window in focus.
- if self.show_help_window:
- self.last_focused_pane = self.focused_window()
- self.application.layout.focus(self.help_window)
- # Restore original focus.
- else:
- if self.last_focused_pane:
- self.application.layout.focus(self.last_focused_pane)
- self.last_focused_pane = None
+ def modal_window_is_open(self):
+ if self.app_help_text:
+ return (self.app_help_window.show_window
+ or self.keybind_help_window.show_window
+ or self.user_guide_window.show_window)
+ return (self.keybind_help_window.show_window
+ or self.user_guide_window.show_window)
def exit_console(self):
"""Quit the console prompt_toolkit application UI."""
diff --git a/pw_console/py/pw_console/docs/user_guide.rst b/pw_console/py/pw_console/docs/user_guide.rst
new file mode 100644
index 0000000..6b07451
--- /dev/null
+++ b/pw_console/py/pw_console/docs/user_guide.rst
@@ -0,0 +1,465 @@
+.. _module-pw_console-user_guide:
+
+User Guide
+==========
+
+.. seealso::
+
+ This guide can be viewed online at: https://pigweed.dev/pw_console/
+
+The Pigweed Console provides a Python repl (read eval print loop) and log viewer
+in a single-window terminal based interface.
+
+.. contents::
+ :local:
+
+
+Starting the Console
+--------------------
+
+::
+
+ pw rpc -s localhost:33000 --proto-globs pw_rpc/echo.proto
+
+
+Exiting
+~~~~~~~
+
+1. Click the :guilabel:`[File]` menu and then :guilabel:`Exit`.
+2. Type :guilabel:`quit` or :guilabel:`exit` in the Python Input window.
+3. The keyboard shortcut :guilabel:`Ctrl-W` also quits.
+
+
+Interface Layout
+----------------
+
+On startup the console will display multiple windows one on top of the other.
+
+::
+
+ +-----------------------------------------------------+
+ | [File] [View] [Window] [Help] Pigweed Console |
+ +=====================================================+
+ | |
+ | |
+ | |
+ | Log Window |
+ +=====================================================+
+ | |
+ | |
+ | Python Results |
+ +-----------------------------------------------------+
+ | |
+ | Python Input |
+ +-----------------------------------------------------+
+
+
+Navigation
+----------
+
+All menus, windows, and toolbar buttons can be clicked on. Scrolling with the
+mouse wheel should work too. This requires that your terminal is able to send
+mouse events.
+
+
+Main Menu Navigation with the Keyboard
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+============================================ =====================
+Function Keys
+============================================ =====================
+Move focus between all active UI elements :guilabel:`Shift-Tab`
+
+Move focus between windows and the main menu :guilabel:`Ctrl-Up`
+ :guilabel:`Ctrl-Down`
+
+Move selection in the main menu :guilabel:`Up`
+ :guilabel:`Down`
+ :guilabel:`Left`
+ :guilabel:`Right`
+============================================ =====================
+
+
+Toolbars
+~~~~~~~~
+
+Log toolbar functions are clickable. You can also press the keyboard
+shortcut highlighted in blue:
+
+::
+
+ / → Search f → [✓] Follow t → [✓] Table w → [ ] Wrap C → Clear
+
+
+Log Window
+~~~~~~~~~~
+
+Log Window Scrolling
+^^^^^^^^^^^^^^^^^^^^
+
+============================================ =====================
+Function Keys
+============================================ =====================
+Scroll logs up :guilabel:`Mouse Wheel Up`
+ :guilabel:`Up`
+ :guilabel:`k`
+
+Scroll logs down :guilabel:`Mouse Wheel Down`
+ :guilabel:`Down`
+ :guilabel:`j`
+
+Scroll logs up one page :guilabel:`PageUp`
+Scroll logs down one page :guilabel:`PageDown`
+Jump to the beginning :guilabel:`g`
+Jump to the end :guilabel:`G`
+
+Horizontal scroll left or right :guilabel:`Left`
+ :guilabel:`Right`
+
+Horizontal scroll to the beginning :guilabel:`Home`
+ :guilabel:`0`
+ :guilabel:`^`
+============================================ =====================
+
+Log Window View Options
+^^^^^^^^^^^^^^^^^^^^^^^
+
+============================================ =====================
+Function Keys
+============================================ =====================
+Toggle line following. :guilabel:`f`
+Toggle table view. :guilabel:`t`
+Toggle line wrapping. :guilabel:`w`
+Clear log pane history. :guilabel:`C`
+============================================ =====================
+
+Log Window Management
+^^^^^^^^^^^^^^^^^^^^^^^
+
+============================================ =====================
+Function Keys
+============================================ =====================
+Duplicate this log pane. :guilabel:`Insert`
+Remove log pane. :guilabel:`Delete`
+============================================ =====================
+
+Log Searching
+^^^^^^^^^^^^^
+
+============================================ =====================
+Function Keys
+============================================ =====================
+Open the search bar :guilabel:`/`
+ :guilabel:`Ctrl-f`
+Navigate search term history :guilabel:`Up`
+ :guilabel:`Down`
+Start the search and highlight matches :guilabel:`Enter`
+Close the search bar without searching :guilabel:`Ctrl-c`
+============================================ =====================
+
+Here is a view of the search bar:
+
+::
+
+ +-------------------------------------------------------------------------------+
+ | Enter → Search Ctrl-Alt-f → Add Filter Ctrl-Alt-r → Clear Filters |
+ | Search Ctrl-t → Column:All Ctrl-v → [ ] Invert Ctrl-n → Matcher:REGEX |
+ | / |
+ +-------------------------------------------------------------------------------+
+
+Across the top are various functions with keyboard shortcuts listed. Each of
+these are clickable with the mouse. The second line shows configurable search
+parameters.
+
+**Search Parameters**
+
+- ``Column:All`` Change the part of the log message to match on. For example:
+ ``All``, ``Message`` or any extra metadata column.
+
+- ``Invert`` match. Find lines that don't match the entered text.
+
+- ``Matcher``: How the search input should be interpreted.
+
+ - ``REGEX``: Treat input text as a regex.
+
+ - ``STRING``: Treat input as a plain string. Any regex characters will be
+ escaped when search is performed.
+
+ - ``FUZZY``: input text is split on spaces using the ``.*`` regex. For
+ example if you search for ``idle run`` the resulting search regex used
+ under the hood is ``(idle)(.*?)(run)``. This would match both of these
+ lines:
+
+ .. code-block:: text
+
+ Idle task is running
+ Idle thread is running
+
+**Active Search Shortcuts**
+
+When a search is started the bar will close, log follow mode is disabled and all
+matches will be highlighted. At this point a few extra keyboard shortcuts are
+available.
+
+============================================ =====================
+Function Keys
+============================================ =====================
+Move to the next search result :guilabel:`n`
+ :guilabel:`Ctrl-g`
+ :guilabel:`Ctrl-s`
+Move to the previous search result :guilabel:`N`
+ :guilabel:`Ctrl-r`
+Removes search highlighting :guilabel:`Ctrl-l`
+Creates a filter using the active search :guilabel:`Ctrl-Alt-f`
+Deletes all active filters. :guilabel:`Ctrl-Alt-r`
+============================================ =====================
+
+
+Log Filtering
+^^^^^^^^^^^^^
+
+Log filtering allows you to limit what log lines appear in any given log
+window. Filters can be added from the currently active search or directly in the
+search bar.
+
+- With the search bar **open**:
+
+ Type something to search for then press :guilabel:`Ctrl-Alt-f` or click on
+ :guilabel:`Add Filter`.
+
+- With the search bar **closed**:
+
+ Press :guilabel:`Ctrl-Alt-f` to use the current search term as a filter.
+
+When a filter is active the ``Filters`` toolbar will appear at the bottom of the
+log window. For example, here are some logs with one active filter for
+``lorem ipsum``.
+
+::
+
+ +------------------------------------------------------------------------------+
+ | Time Lvl Module Message |
+ +------------------------------------------------------------------------------+
+ | 20210722 15:38:14 INF APP Log message # 270 Lorem ipsum dolor sit amet |
+ | 20210722 15:38:24 INF APP Log message # 280 Lorem ipsum dolor sit amet |
+ | 20210722 15:38:34 INF APP Log message # 290 Lorem ipsum dolor sit amet |
+ | 20210722 15:38:44 INF APP Log message # 300 Lorem ipsum dolor sit amet |
+ | 20210722 15:38:54 INF APP Log message # 310 Lorem ipsum dolor sit amet |
+ | 20210722 15:39:04 INF APP Log message # 320 Lorem ipsum dolor sit amet |
+ +------------------------------------------------------------------------------+
+ | Filters <lorem ipsum (X)> Ctrl-Alt-r → Clear Filters |
+ +------------------------------------------------------------------------------+
+ | Logs / → Search f → [✓] Follow t → [✓] Table w → [ ] Wrap C → Clear |
+ +------------------------------------------------------------------------------+
+
+**Stacking Filters**
+
+Adding a second filter on the above logs for ``# 2`` would update the filter
+toolbar to show:
+
+::
+
+ +------------------------------------------------------------------------------+
+ | Time Lvl Module Message |
+ +------------------------------------------------------------------------------+
+ | |
+ | |
+ | |
+ | 20210722 15:38:14 INF APP Log message # 270 Lorem ipsum dolor sit amet |
+ | 20210722 15:38:24 INF APP Log message # 280 Lorem ipsum dolor sit amet |
+ | 20210722 15:38:34 INF APP Log message # 290 Lorem ipsum dolor sit amet |
+ +------------------------------------------------------------------------------+
+ | Filters <lorem ipsum (X)> <# 2 (X)> Ctrl-Alt-r → Clear Filters |
+ +------------------------------------------------------------------------------+
+ | Logs / → Search f → [✓] Follow t → [✓] Table w → [ ] Wrap C → Clear |
+ +------------------------------------------------------------------------------+
+
+Any filter listed in the Filters toolbar and can be individually removed by
+clicking on the red ``(X)`` text.
+
+
+Python Window
+~~~~~~~~~~~~~
+
+
+Running Code in the Python Repl
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Type code and hit :guilabel:`Enter` to run.
+- If multiple lines are used, move the cursor to the end and press
+ :guilabel:`Enter` twice.
+- :guilabel:`Up` / :guilabel:`Down` Navigate command history
+- :guilabel:`Ctrl-r` Start reverse history searching
+- :guilabel:`Ctrl-c` Erase the input buffer
+
+ - If the input buffer is empty:
+ :guilabel:`Ctrl-c` cancels any currently running Python commands.
+
+- :guilabel:`F2` Open the python repl settings (from
+ `ptpython <https://github.com/prompt-toolkit/ptpython>`__). This
+ works best in vertical split mode.
+
+ - To exit: hit :guilabel:`F2` again.
+ - Navigate options with the arrow keys, Enter will close the menu.
+
+- :guilabel:`F3` Open the python repl history (from
+ `ptpython <https://github.com/prompt-toolkit/ptpython>`__).
+
+ - To exit: hit :guilabel:`F3` again.
+ - Left side shows previously entered commands
+ - Use arrow keys to navigate.
+ - :guilabel:`Space` to select as many lines you want to use
+
+ - Selected lines will be appended to the right side.
+
+ - :guilabel:`Enter` to accept the right side text, this will be inserted
+ into the repl.
+
+
+Copy & Pasting
+~~~~~~~~~~~~~~
+
+Copying Text
+^^^^^^^^^^^^
+
+At the moment there is no built-in copy/paste function. As a workaround use your
+terminal built in selection:
+
+- **Linux**
+
+ - Holding :guilabel:`Shift` and dragging the mouse in most terminals.
+
+- **Mac**
+
+ - **Apple Terminal**:
+
+ Hold :guilabel:`Fn` and drag the mouse
+
+ - **iTerm2**:
+
+ Hold :guilabel:`Cmd+Option` and drag the mouse
+
+- **Windows**
+
+ - **Git CMD** (included in `Git for Windows <https://git-scm.com/downloads>`__)
+
+ 1. Click on the Git window icon in the upper left of the title bar
+ 2. Click ``Edit`` then ``Mark``
+ 3. Drag the mouse to select text and press Enter to copy.
+
+ - **Windows Terminal**
+
+ 1. Hold :guilabel:`Shift` and drag the mouse to select text
+ 2. Press :guilabel:`Ctrl-Shift-C` to copy.
+
+Pasting Text
+^^^^^^^^^^^^
+
+Currently you must paste text using your terminal emulator's paste
+function. How to do this depends on what terminal you are using and on
+which OS. Here's how on various platforms:
+
+- **Linux**
+
+ - **XTerm**
+
+ :guilabel:`Shift-Insert` pastes text
+
+ - **Gnome Terminal**
+
+ :guilabel:`Ctrl-Shift-V` pastes text
+
+- **Windows**
+
+ - **Git CMD** (included in `Git for Windows <https://git-scm.com/downloads>`__)
+
+ 1. Click on the Git icon in the upper left of the windows title bar and open
+ ``Properties``.
+ 2. Checkmark the option ``Use Ctrl+Shift+C/V as Copy Paste`` and hit ok.
+ 3. Then use :guilabel:`Ctrl-Shift-V` to paste.
+
+ - **Windows Terminal**
+
+ - :guilabel:`Ctrl-Shift-V` pastes text.
+ - :guilabel:`Shift-RightClick` also pastes text.
+
+
+Window Management
+~~~~~~~~~~~~~~~~~
+
+Any window can be hidden by clicking the checkbox in the
+:guilabel:`[x] Show Window` submenu
+
+- To enlarge or shrink the currently focused window use :guilabel:`Ctrl-j` or
+ :guilabel:`Ctrl-k`.
+- Reset window sizes with :guilabel:`Ctrl-u`.
+
+
+- Use vertical window splitting with :guilabel:`F4`.
+- Rotate window order with :guilabel:`Ctrl-Shift-Left` and
+ :guilabel:`Ctrl-Shift-Right`.
+
+
+Color Depth
+-----------
+
+Some terminals support full 24-bit color. By default pw console will try
+to use 256 colors.
+
+To force a particular color depth: set one of these environment
+variables before launching the console.
+
+::
+
+ # 1 bit | Black and white
+ export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_1_BIT
+ # 4 bit | ANSI colors
+ export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_4_BIT
+ # 8 bit | 256 colors
+ export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_8_BIT
+ # 24 bit | True colors
+ export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_24_BIT
+
+
+Known Issues
+------------
+
+
+Python Repl Window
+~~~~~~~~~~~~~~~~~~
+
+- Any ``print()`` commands entered in the repl will not appear until the code
+ being run is completed. This is a high priority issue:
+ https://bugs.chromium.org/p/pigweed/issues/detail?id=407
+
+
+Log Window
+~~~~~~~~~~
+
+- Rendering for log lines that include ``\n`` characters is broken and hidden if
+ Table view is turned on.
+
+- Tab character rendering will not work in the log pane view. They will
+ appear as ``^I`` since prompt_toolkit can't render them. See this issue for details:
+ https://github.com/prompt-toolkit/python-prompt-toolkit/issues/556
+
+
+General
+~~~~~~~
+
+- Mouse click and colors don't work if using Windows cmd.exe. Please
+ use the newer Windows Terminal app instead: https://github.com/microsoft/terminal
+
+
+Upcoming Features
+-----------------
+
+For upcoming features see the Pigweed Console Bug Hotlist at:
+https://bugs.chromium.org/u/542633886/hotlists/Console
+
+
+Feature Requests
+~~~~~~~~~~~~~~~~
+
+Create a feature request bugs using this template:
+https://bugs.chromium.org/p/pigweed/issues/entry?owner=tonymd@google.com&labels=Type-Enhancement,Priority-Medium&summary=pw_console
diff --git a/pw_console/py/pw_console/help_window.py b/pw_console/py/pw_console/help_window.py
index 89689ea..311ff3e 100644
--- a/pw_console/py/pw_console/help_window.py
+++ b/pw_console/py/pw_console/help_window.py
@@ -21,15 +21,18 @@
from jinja2 import Template
from prompt_toolkit.document import Document
from prompt_toolkit.filters import Condition
-from prompt_toolkit.key_binding import KeyBindings
+from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
from prompt_toolkit.layout import (
ConditionalContainer,
DynamicContainer,
HSplit,
)
from prompt_toolkit.layout.dimension import Dimension
+from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.widgets import Box, Frame, TextArea
+from pygments.lexers.markup import RstLexer # type: ignore
+
_LOG = logging.getLogger(__package__)
HELP_TEMPLATE_PATH = Path(__file__).parent / "templates" / "keybind_list.jinja"
@@ -37,25 +40,55 @@
KEYBIND_TEMPLATE = tmpl.read()
+def _longest_line_length(text):
+ """Return the longest line in the given text."""
+ max_line_length = 0
+ for line in text.splitlines():
+ if len(line) > max_line_length:
+ max_line_length = len(line)
+ return max_line_length
+
+
class HelpWindow(ConditionalContainer):
"""Help window container for displaying keybindings."""
+ def _create_help_text_area(self, **kwargs):
+ help_text_area = TextArea(
+ focusable=True,
+ focus_on_click=True,
+ scrollbar=True,
+ style='class:help_window_content',
+ **kwargs,
+ )
+
+ # Additional keybindings for the text area.
+ key_bindings = KeyBindings()
+
+ @key_bindings.add('q')
+ def _close_window(_event: KeyPressEvent) -> None:
+ """Close the current dialog window."""
+ self.toggle_display()
+
+ help_text_area.control.key_bindings = key_bindings
+ return help_text_area
+
def __init__(self, application, preamble='', additional_help_text=''):
# Dict containing key = section title and value = list of key bindings.
+ self.application = application
+ self.show_window = False
self.help_text_sections = {}
- self.max_description_width = 0
- self.max_key_list_width = 0
- self.max_line_length = 0
# Generated keybinding text
self.preamble = preamble
self.additional_help_text = additional_help_text
self.help_text = ''
- self.help_text_area = TextArea(
- focusable=True,
- scrollbar=True,
- style='class:help_window_content',
- )
+ self.max_additional_help_text_width = (_longest_line_length(
+ self.additional_help_text) if additional_help_text else 0)
+ self.max_description_width = 0
+ self.max_key_list_width = 0
+ self.max_line_length = 0
+
+ self.help_text_area = self._create_help_text_area()
frame = Frame(
body=Box(
@@ -69,9 +102,26 @@
super().__init__(
HSplit([frame]),
- filter=Condition(lambda: application.show_help_window),
+ filter=Condition(lambda: self.show_window),
)
+ def toggle_display(self):
+ """Toggle visibility of this help window."""
+ # Toggle state variable.
+ self.show_window = not self.show_window
+
+ # Set the help window in focus.
+ if self.show_window:
+ self.application.last_focused_pane = (
+ self.application.focused_window())
+ self.application.layout.focus(self.help_text_area)
+ # Restore original focus.
+ else:
+ if self.application.last_focused_pane:
+ self.application.layout.focus(
+ self.application.last_focused_pane)
+ self.application.last_focused_pane = None
+
def content_width(self) -> int:
"""Return total width of help window."""
# Widths of UI elements
@@ -86,6 +136,22 @@
right_side_frame_and_padding_width +
scrollbar_padding + scrollbar_width)
+ def load_user_guide(self):
+ rstdoc = Path(__file__).parent / 'docs/user_guide.rst'
+ max_line_length = 0
+ rst_text = ''
+ with rstdoc.open() as rstfile:
+ for line in rstfile.readlines():
+ if 'https://' not in line and len(line) > max_line_length:
+ max_line_length = len(line)
+ rst_text += line
+ self.max_line_length = max_line_length
+
+ self.help_text_area = self._create_help_text_area(
+ lexer=PygmentsLexer(RstLexer),
+ text=rst_text,
+ )
+
def generate_help_text(self):
"""Generate help text based on added key bindings."""
@@ -98,6 +164,7 @@
self.help_text = template.render(
sections=self.help_text_sections,
+ max_additional_help_text_width=self.max_additional_help_text_width,
max_description_width=self.max_description_width,
max_key_list_width=self.max_key_list_width,
preamble=self.preamble,
@@ -105,10 +172,7 @@
)
# Find the longest line in the rendered template.
- self.max_line_length = 0
- for line in self.help_text.splitlines():
- if len(line) > self.max_line_length:
- self.max_line_length = len(line)
+ self.max_line_length = _longest_line_length(self.help_text)
# Replace the TextArea content.
self.help_text_area.buffer.document = Document(text=self.help_text,
diff --git a/pw_console/py/pw_console/key_bindings.py b/pw_console/py/pw_console/key_bindings.py
index ec8393f..f141920 100644
--- a/pw_console/py/pw_console/key_bindings.py
+++ b/pw_console/py/pw_console/key_bindings.py
@@ -39,15 +39,11 @@
bindings = KeyBindings()
- @bindings.add('f1')
+ @bindings.add(
+ 'f1', filter=Condition(lambda: not console_app.modal_window_is_open()))
def show_help(event):
- """Toggle help window."""
- console_app.toggle_help()
-
- @bindings.add('q', filter=Condition(lambda: console_app.show_help_window))
- def close_help_window(event):
- """Hide help window."""
- console_app.toggle_help()
+ """Toggle user guide window."""
+ console_app.user_guide_window.toggle_display()
# F2 is ptpython settings
# F3 is ptpython history
diff --git a/pw_console/py/pw_console/templates/keybind_list.jinja b/pw_console/py/pw_console/templates/keybind_list.jinja
index 654b5ac..92acdb9 100644
--- a/pw_console/py/pw_console/templates/keybind_list.jinja
+++ b/pw_console/py/pw_console/templates/keybind_list.jinja
@@ -13,7 +13,7 @@
License for the specific language governing permissions and limitations under
the License.
#}
-{% set total_width = max_description_width + max_key_list_width + 5 %}
+{% set total_width = [max_description_width + max_key_list_width + 5, max_additional_help_text_width]|max %}
{% if preamble %}
{{ preamble }}
diff --git a/pw_console/py/setup.py b/pw_console/py/setup.py
index 9fc2562..5166c86 100644
--- a/pw_console/py/setup.py
+++ b/pw_console/py/setup.py
@@ -24,6 +24,7 @@
packages=setuptools.find_packages(),
package_data={
'pw_console': [
+ 'docs/user_guide.rst',
'py.typed',
'templates/keybind_list.jinja',
'templates/repl_output.jinja',
diff --git a/pw_console/testing.rst b/pw_console/testing.rst
index e2979a7..585e983 100644
--- a/pw_console/testing.rst
+++ b/pw_console/testing.rst
@@ -185,7 +185,51 @@
| Only logs with Modules other than ``BAT`` appear.
- |checkbox|
-3. Add note to the commit message
+3. Test Help Windows
+^^^^^^^^^^^^^^^^^^^^
+
+.. list-table::
+ :widths: 5 45 45 5
+ :header-rows: 1
+
+ * - #
+ - Test Action
+ - Expected Result
+ - ✅
+
+ * - 1
+ - Click the :guilabel:`Help > User Guide`
+ - | Window appears showing the user guide with
+ | RST formatting and syntax highlighting
+ - |checkbox|
+
+ * - 2
+ - Press :guilabel:`q`
+ - Window is hidden
+ - |checkbox|
+
+ * - 3
+ - Click the :guilabel:`Help > Keyboard Shortcuts`
+ - Window appears showing the keybind list
+ - |checkbox|
+
+ * - 4
+ - Press :guilabel:`q`
+ - Window is hidden
+ - |checkbox|
+
+ * - 5
+ - Click the :guilabel:`Help > Console Test Mode Help`
+ - | Window appears showing help with content
+ | ``Welcome to the Pigweed Console Test Mode!``
+ - |checkbox|
+
+ * - 6
+ - Press :guilabel:`q`
+ - Window is hidden
+ - |checkbox|
+
+4. Add note to the commit message
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add a ``Testing:`` line to your commit message and mention the steps