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
+▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
+
+
+
+