aboutsummaryrefslogtreecommitdiff
path: root/pw_console/py/pw_console/command_runner.py
diff options
context:
space:
mode:
Diffstat (limited to 'pw_console/py/pw_console/command_runner.py')
-rw-r--r--pw_console/py/pw_console/command_runner.py98
1 files changed, 78 insertions, 20 deletions
diff --git a/pw_console/py/pw_console/command_runner.py b/pw_console/py/pw_console/command_runner.py
index e2caf1212..63003cb6d 100644
--- a/pw_console/py/pw_console/command_runner.py
+++ b/pw_console/py/pw_console/command_runner.py
@@ -14,6 +14,7 @@
"""CommandRunner dialog classes."""
from __future__ import annotations
+import dataclasses
import functools
import logging
import re
@@ -24,10 +25,10 @@ from typing import (
List,
Optional,
TYPE_CHECKING,
- Tuple,
)
from prompt_toolkit.buffer import Buffer
+from prompt_toolkit.document import Document
from prompt_toolkit.filters import Condition
from prompt_toolkit.formatted_text import StyleAndTextTuples
from prompt_toolkit.formatted_text.utils import fragment_list_to_text
@@ -48,8 +49,10 @@ from prompt_toolkit.layout import (
Window,
WindowAlign,
)
-from prompt_toolkit.widgets import MenuItem
-from prompt_toolkit.widgets import TextArea
+from prompt_toolkit.lexers import PygmentsLexer
+from prompt_toolkit.widgets import MenuItem, TextArea
+
+from pygments.lexers.markup import MarkdownLexer # type: ignore
from pw_console.widgets import (
create_border,
@@ -63,9 +66,16 @@ if TYPE_CHECKING:
_LOG = logging.getLogger(__package__)
+@dataclasses.dataclass
+class CommandRunnerItem:
+ title: str
+ handler: Callable
+ description: Optional[str] = None
+
+
def flatten_menu_items(
items: List[MenuItem], prefix: str = ''
-) -> Iterator[Tuple[str, Callable]]:
+) -> Iterator[CommandRunnerItem]:
"""Flatten nested prompt_toolkit MenuItems into text and callable tuples."""
for item in items:
new_text = []
@@ -80,7 +90,7 @@ def flatten_menu_items(
# Skip this item if it's a separator or disabled.
if item.text == '-' or item.disabled:
continue
- yield (new_prefix, item.handler)
+ yield CommandRunnerItem(title=new_prefix, handler=item.handler)
def highlight_matches(
@@ -122,7 +132,7 @@ class CommandRunner:
application: ConsoleApp,
window_title: Optional[str] = None,
load_completions: Optional[
- Callable[[], List[Tuple[str, Callable]]]
+ Callable[[], List[CommandRunnerItem]]
] = None,
width: int = 80,
height: int = 10,
@@ -136,14 +146,15 @@ class CommandRunner:
self.last_focused_pane = None
# List of all possible completion items
- self.completions: List[Tuple[str, Callable]] = []
+ self.completions: List[CommandRunnerItem] = []
# Formatted text fragments of matched items
self.completion_fragments: List[StyleAndTextTuples] = []
# Current selected item tracking variables
self.selected_item: int = 0
- self.selected_item_text: str = ''
+ self.selected_item_title: str = ''
self.selected_item_handler: Optional[Callable] = None
+ self.selected_item_description: Optional[str] = None
# Previous input text
self.last_input_field_text: str = 'EMPTY'
# Previous selected item
@@ -155,7 +166,7 @@ class CommandRunner:
self.window_title: str
# Callable to fetch completion items
- self.load_completions: Callable[[], List[Tuple[str, Callable]]]
+ self.load_completions: Callable[[], List[CommandRunnerItem]]
# Command runner text input field
self.input_field = TextArea(
@@ -209,6 +220,16 @@ class CommandRunner:
height=self.height,
)
+ self.selected_item_description_text_area = TextArea(
+ focusable=False,
+ focus_on_click=False,
+ scrollbar=False,
+ style='class:help_window_content',
+ wrap_lines=False,
+ lexer=PygmentsLexer(MarkdownLexer),
+ text='empty',
+ )
+
# Main content HSplit
self.command_runner_content = HSplit(
[
@@ -221,6 +242,22 @@ class CommandRunner:
),
# Completion items below
command_items_window,
+ # Selected item description / help text.
+ ConditionalContainer(
+ create_border(
+ self.selected_item_description_text_area,
+ title=self._snippet_description_pane_title,
+ border_style='class:command-runner-border',
+ left_margin_columns=0,
+ right_margin_columns=0,
+ bottom=False,
+ left=False,
+ right=False,
+ ),
+ filter=Condition(
+ lambda: bool(self.selected_item_description)
+ ),
+ ),
],
style='class:command-runner class:theme-fg-default',
)
@@ -236,6 +273,16 @@ class CommandRunner:
filter=Condition(lambda: self.show_dialog),
)
+ def _snippet_description_pane_title(self) -> StyleAndTextTuples:
+ return [
+ # Left padding
+ ('', '━━ '),
+ # Snippet title in yellow
+ ('class:theme-fg-yellow', self.selected_item_title),
+ # right padding
+ ('', ' '),
+ ]
+
def _create_bordered_content(self) -> None:
"""Wrap self.command_runner_content in a border."""
# This should be called whenever the window_title changes.
@@ -311,7 +358,7 @@ class CommandRunner:
self,
window_title: Optional[str] = None,
load_completions: Optional[
- Callable[[], List[Tuple[str, Callable]]]
+ Callable[[], List[CommandRunnerItem]]
] = None,
) -> None:
"""Set window title and callable to fetch possible completions.
@@ -336,7 +383,7 @@ class CommandRunner:
def reload_completions(self) -> None:
self.completions = self.load_completions()
- def load_menu_items(self) -> List[Tuple[str, Callable]]:
+ def load_menu_items(self) -> List[CommandRunnerItem]:
# pylint: disable=no-self-use
return list(flatten_menu_items(self.application.menu_items))
@@ -377,17 +424,27 @@ class CommandRunner:
check_match = self._matches_orderless
i = 0
- for text, handler in self.completions:
- if not (input_text == '' or check_match(regexes, text)):
+ for item in self.completions:
+ title = item.title
+ if not (input_text == '' or check_match(regexes, item.title)):
continue
style = ''
if i == self.selected_item:
style = 'class:command-runner-selected-item'
- self.selected_item_text = text
- self.selected_item_handler = handler
- text = text.ljust(self.content_width())
+ self.selected_item_title = title
+ self.selected_item_handler = item.handler
+ self.selected_item_description = item.description
+ if self.selected_item_description:
+ self.selected_item_description_text_area.buffer.document = (
+ Document(
+ text=self.selected_item_description,
+ cursor_position=0,
+ )
+ )
+
+ title = item.title.ljust(self.content_width())
fragments: StyleAndTextTuples = highlight_matches(
- regexes, [(style, text + '\n')]
+ regexes, [(style, title + '\n')]
)
self.completion_fragments.append(fragments)
i += 1
@@ -470,8 +527,9 @@ class CommandRunner:
def _reset_selected_item(self) -> None:
self.selected_item = 0
self.last_selected_item = 0
- self.selected_item_text = ''
+ self.selected_item_title = ''
self.selected_item_handler = None
+ self.selected_item_description = None
self.last_input_field_text = 'EMPTY'
self.input_field.buffer.reset()
@@ -493,7 +551,7 @@ class CommandRunner:
'[File] > Insert Repl History',
'[File] > Open Logger',
]:
- if command_text in self.selected_item_text:
+ if command_text in self.selected_item_title:
close_dialog = False
break
@@ -509,7 +567,7 @@ class CommandRunner:
'Save/Export a copy',
'[Windows] > Floating ',
]:
- if command_text in self.selected_item_text:
+ if command_text in self.selected_item_title:
close_dialog_first = True
break