diff --git a/CHANGELOG.md b/CHANGELOG.md index 780c788d6a..c75aafff44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed `RichLog` writing at wrong width when `write` occurs before width is known (e.g. in `compose` or `on_mount`) https://github.com/Textualize/textual/pull/4978 - Fixed `RichLog.write` incorrectly shrinking width to `RichLog.min_width` when `shrink=True` (now shrinks to fit content area instead) https://github.com/Textualize/textual/pull/4978 - Fixed flicker when setting `dark` reactive on startup https://github.com/Textualize/textual/pull/4989 +- Fixed command palette not sorting search results by their match score https://github.com/Textualize/textual/pull/4994 - Fixed `DataTable` cached height issue on re-populating the table when using auto-height rows https://github.com/Textualize/textual/pull/4992 ## [0.79.1] - 2024-08-31 diff --git a/src/textual/command.py b/src/textual/command.py index 5a0ae57c0b..d6e314dc11 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -137,6 +137,16 @@ def prompt(self) -> RenderableType: """The prompt to use when displaying the discovery hit in the command palette.""" return self.display + @property + def score(self) -> float: + """A discovery hit always has a score of 0. + + The order in which discovery hits are displayed is determined by the order + in which they are yielded by the Provider. It's up to the developer to yield + DiscoveryHits in the . + """ + return 0.0 + def __lt__(self, other: object) -> bool: if isinstance(other, DiscoveryHit): assert self.text is not None @@ -312,12 +322,12 @@ async def shutdown(self) -> None: @rich.repr.auto @total_ordering class Command(Option): - """Class that holds a command in the [`CommandList`][textual.command.CommandList].""" + """Class that holds a hit in the [`CommandList`][textual.command.CommandList].""" def __init__( self, prompt: RenderableType, - command: DiscoveryHit | Hit, + hit: DiscoveryHit | Hit, id: str | None = None, disabled: bool = False, ) -> None: @@ -325,22 +335,22 @@ def __init__( Args: prompt: The prompt for the option. - command: The details of the command associated with the option. + hit: The details of the hit associated with the option. id: The optional ID for the option. disabled: The initial enabled/disabled state. Enabled by default. """ super().__init__(prompt, id, disabled) - self.command = command - """The details of the command associated with the option.""" + self.hit = hit + """The details of the hit associated with the option.""" def __lt__(self, other: object) -> bool: if isinstance(other, Command): - return self.command < other.command + return self.hit < other.hit return NotImplemented def __eq__(self, other: object) -> bool: if isinstance(other, Command): - return self.command == other.command + return self.hit == other.hit return NotImplemented @@ -891,7 +901,12 @@ def _refresh_command_list( if command_list.highlighted is not None and not clear_current else None ) - command_list.clear_options().add_options(commands) + + def sort_key(command: Command) -> float: + return -command.hit.score + + sorted_commands = sorted(commands, key=sort_key) + command_list.clear_options().add_options(sorted_commands) if highlighted is not None and highlighted.id: command_list.highlighted = command_list.get_option_index(highlighted.id) @@ -1061,8 +1076,9 @@ def _select_command(self, event: OptionList.OptionSelected) -> None: input = self.query_one(CommandInput) with self.prevent(Input.Changed): assert isinstance(event.option, Command) - input.value = str(event.option.command.text) - self._selected_command = event.option.command + hit = event.option.hit + input.value = str(hit.text) + self._selected_command = hit input.action_end() self._list_visible = False self.query_one(CommandList).clear_options() diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg index bbc56a92ea..95166b66ea 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg @@ -19,138 +19,138 @@ font-weight: 700; } - .terminal-3691012651-matrix { + .terminal-3948438059-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3691012651-title { + .terminal-3948438059-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3691012651-r1 { fill: #646464 } -.terminal-3691012651-r2 { fill: #c5c8c6 } -.terminal-3691012651-r3 { fill: #004578 } -.terminal-3691012651-r4 { fill: #dfe1e2 } -.terminal-3691012651-r5 { fill: #00ff00 } -.terminal-3691012651-r6 { fill: #000000 } -.terminal-3691012651-r7 { fill: #1e1e1e } -.terminal-3691012651-r8 { fill: #dfe1e2;font-weight: bold } -.terminal-3691012651-r9 { fill: #fea62b;font-weight: bold } + .terminal-3948438059-r1 { fill: #646464 } +.terminal-3948438059-r2 { fill: #c5c8c6 } +.terminal-3948438059-r3 { fill: #004578 } +.terminal-3948438059-r4 { fill: #dfe1e2 } +.terminal-3948438059-r5 { fill: #00ff00 } +.terminal-3948438059-r6 { fill: #000000 } +.terminal-3948438059-r7 { fill: #1e1e1e } +.terminal-3948438059-r8 { fill: #dfe1e2;font-weight: bold } +.terminal-3948438059-r9 { fill: #fea62b;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - + - - - - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎A - - -  This is a test of this code 0                                                  -  This is a test of this code 1                                                  -  This is a test of this code 2                                                  -  This is a test of this code 3                                                  -  This is a test of this code 4                                                  -  This is a test of this code 5                                                  -  This is a test of this code 6                                                  -  This is a test of this code 7                                                  -  This is a test of this code 8                                                  -  This is a test of this code 9                                                  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎A + + +  This is a test of this code 9                                                  +  This is a test of this code 8                                                  +  This is a test of this code 7                                                  +  This is a test of this code 6                                                  +  This is a test of this code 5                                                  +  This is a test of this code 4                                                  +  This is a test of this code 3                                                  +  This is a test of this code 2                                                  +  This is a test of this code 1                                                  +  This is a test of this code 0                                                  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + +