Skip to content

Commit 3ffa60f

Browse files
authored
feat: dropdown menu (#550)
* dropdown menu * snapshot tests and feedbacks
1 parent 3897c07 commit 3ffa60f

7 files changed

Lines changed: 890 additions & 21 deletions

openhands_cli/tui/widgets/user_input/autocomplete_dropdown.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from pathlib import Path
2+
from typing import Final
23

4+
from rich.text import Text
35
from textual.app import ComposeResult
46
from textual.containers import Container
57
from textual.widgets import OptionList
@@ -23,18 +25,20 @@ class AutoCompleteDropdown(Container):
2325
and file path (@) completions.
2426
"""
2527

28+
# Min spaces between command name and description
29+
DESCRIPTION_GAP: Final[int] = 3
30+
2631
DEFAULT_CSS = """
2732
AutoCompleteDropdown {
28-
layer: autocomplete;
29-
width: auto;
30-
min-width: 30;
31-
max-width: 60;
33+
width: 100%;
3234
height: auto;
3335
max-height: 12;
3436
display: none;
3537
background: $surface;
36-
border: solid $primary;
37-
padding: 0;
38+
border-top: solid $primary;
39+
border-left: solid $primary;
40+
border-right: solid $primary;
41+
padding: 0 1;
3842
margin: 0;
3943
4044
OptionList {
@@ -106,10 +110,30 @@ def show_dropdown(self, items: list[CompletionItem]) -> None:
106110

107111
self._completion_items = items
108112
self.option_list.clear_options()
113+
114+
# Find the longest command name for alignment
115+
max_cmd_len = 0
109116
for item in items:
110-
self.option_list.add_option(
111-
Option(item.display_text, id=item.completion_value)
112-
)
117+
if (
118+
item.completion_type == CompletionType.COMMAND
119+
and " - " in item.display_text
120+
):
121+
cmd_name = item.display_text.split(" - ", 1)[0]
122+
max_cmd_len = max(max_cmd_len, len(cmd_name))
123+
124+
for item in items:
125+
prompt: str | Text = item.display_text
126+
if (
127+
item.completion_type == CompletionType.COMMAND
128+
and " - " in item.display_text
129+
):
130+
cmd_name, description = item.display_text.split(" - ", 1)
131+
prompt = Text()
132+
# Pad command name so descriptions align, with a gap between
133+
padding = max_cmd_len + self.DESCRIPTION_GAP
134+
prompt.append(cmd_name.ljust(padding), style="bold")
135+
prompt.append(description, style="dim")
136+
self.option_list.add_option(Option(prompt, id=item.completion_value))
113137

114138
self.display = True
115139
self.option_list.highlighted = 0

openhands_cli/tui/widgets/user_input/input_field.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,8 @@ class InputField(Container):
104104
width: 100%;
105105
height: auto;
106106
min-height: 3;
107-
layers: base autocomplete;
108107
109108
#single_line_input {
110-
layer: base;
111109
width: 100%;
112110
height: auto;
113111
min-height: 3;
@@ -123,7 +121,6 @@ class InputField(Container):
123121
}
124122
125123
#multiline_input {
126-
layer: base;
127124
width: 100%;
128125
height: 6;
129126
background: $background;
@@ -136,14 +133,6 @@ class InputField(Container):
136133
border: solid $primary;
137134
background: $background;
138135
}
139-
140-
AutoCompleteDropdown {
141-
layer: autocomplete;
142-
offset-x: 1;
143-
offset-y: -2;
144-
overlay: screen;
145-
constrain: inside inflect;
146-
}
147136
}
148137
"""
149138

@@ -178,9 +167,9 @@ def __init__(self, placeholder: str = "", **kwargs) -> None:
178167

179168
def compose(self) -> ComposeResult:
180169
"""Create the input widgets."""
170+
yield self.autocomplete
181171
yield self.single_line_widget
182172
yield self.multiline_widget
183-
yield self.autocomplete
184173

185174
def on_mount(self) -> None:
186175
"""Focus the input when mounted."""

0 commit comments

Comments
 (0)