Wire ↑↓↵ keyboard navigation in the command overlay
The footer chips already advertised ↑↓ to navigate and ↵ to open; they now match reality. - chrome::command_match exposes COMMAND_ACTIONS / matching_actions alongside the tab/history/bookmark match helpers and a CommandSelection enum + visible_command_rows that returns the flat ordered list of activatable rows. command_overlay drops its private copies of the action const and matcher and consumes them from the shared module so render and key handling share one source of truth. - ElyShell tracks command_selected_index with command_select_next / command_select_prev (cyclic, no notify when index doesn't change) and activate_selected_command which dispatches the right shell call for the currently selected CommandSelection variant and dismisses the overlay. Dismissing command mode resets the index to 0. - The shell root captures key_down via on_command_overlay_key_down. When the live snapshot's command_query starts with '>', up / down / enter run the matching helper and the event stops propagating so the omnibar input doesn't move its caret. - Each rendered row receives a `selected` flag. The selected row gets the design's tinted bg + accent-bar on the left edge so the active result is unambiguous at any keyboard step.
This commit is contained in:
@@ -1,8 +1,94 @@
|
||||
use ely_browser_core::BrowserSnapshot;
|
||||
use ely_domain::{BookmarkEntry, BrowserTab, HistoryEntry};
|
||||
use ely_domain::{BookmarkEntry, BrowserTab, HistoryEntry, TabId, UrlText};
|
||||
use gpui_component::IconName;
|
||||
|
||||
pub(crate) const RESULT_LIMIT: usize = 4;
|
||||
|
||||
pub(crate) struct CommandActionEntry {
|
||||
pub title: &'static str,
|
||||
pub hint: &'static str,
|
||||
pub icon: IconName,
|
||||
pub route: &'static str,
|
||||
pub keys: &'static str,
|
||||
}
|
||||
|
||||
pub(crate) const COMMAND_ACTIONS: &[CommandActionEntry] = &[
|
||||
CommandActionEntry {
|
||||
title: "Switch workspace",
|
||||
hint: "Cycle to the next space",
|
||||
icon: IconName::LayoutDashboard,
|
||||
route: "ely://settings/spaces",
|
||||
keys: "⌘⇧W",
|
||||
},
|
||||
CommandActionEntry {
|
||||
title: "Open Settings",
|
||||
hint: "Appearance, sync, plugins…",
|
||||
icon: IconName::Settings,
|
||||
route: "ely://settings",
|
||||
keys: "⌘,",
|
||||
},
|
||||
CommandActionEntry {
|
||||
title: "Open Plugins",
|
||||
hint: "Marketplace and installed plugins",
|
||||
icon: IconName::Asterisk,
|
||||
route: "ely://plugins",
|
||||
keys: "",
|
||||
},
|
||||
CommandActionEntry {
|
||||
title: "Open Bookmarks",
|
||||
hint: "Manage your saved pages",
|
||||
icon: IconName::BookOpen,
|
||||
route: "ely://bookmarks",
|
||||
keys: "",
|
||||
},
|
||||
];
|
||||
|
||||
pub(crate) fn matching_actions(needle: &str) -> Vec<&'static CommandActionEntry> {
|
||||
COMMAND_ACTIONS
|
||||
.iter()
|
||||
.filter(|action| {
|
||||
needle.is_empty()
|
||||
|| action.title.to_lowercase().contains(needle)
|
||||
|| action.hint.to_lowercase().contains(needle)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum CommandSelection {
|
||||
SelectTab(TabId),
|
||||
OpenUrl(UrlText),
|
||||
OpenRoute(&'static str),
|
||||
}
|
||||
|
||||
pub(crate) fn visible_command_rows(
|
||||
snapshot: &BrowserSnapshot,
|
||||
needle: &str,
|
||||
) -> Vec<CommandSelection> {
|
||||
let mut rows = Vec::new();
|
||||
rows.extend(
|
||||
matching_tabs(snapshot, needle)
|
||||
.iter()
|
||||
.map(|tab| CommandSelection::SelectTab(tab.id().clone())),
|
||||
);
|
||||
rows.extend(
|
||||
matching_history(snapshot, needle)
|
||||
.iter()
|
||||
.map(|entry| CommandSelection::OpenUrl(entry.url().clone())),
|
||||
);
|
||||
rows.extend(
|
||||
matching_bookmarks(snapshot, needle)
|
||||
.iter()
|
||||
.map(|bookmark| CommandSelection::OpenUrl(bookmark.url().clone())),
|
||||
);
|
||||
rows.extend(
|
||||
matching_actions(needle)
|
||||
.iter()
|
||||
.map(|action| CommandSelection::OpenRoute(action.route)),
|
||||
);
|
||||
rows
|
||||
}
|
||||
|
||||
pub(crate) fn matching_tabs<'a>(
|
||||
snapshot: &'a BrowserSnapshot,
|
||||
needle: &str,
|
||||
|
||||
@@ -3,20 +3,22 @@ use ely_design_system::colors;
|
||||
use ely_domain::{BookmarkEntry, BrowserTab, HistoryEntry};
|
||||
use gpui::{
|
||||
AnyElement, BoxShadow, Context, FontWeight, InteractiveElement, IntoElement, ParentElement,
|
||||
SharedString, StatefulInteractiveElement, Styled, div, hsla, point, px, rgb, rgba,
|
||||
SharedString, StatefulInteractiveElement, Styled, div, hsla, point,
|
||||
prelude::FluentBuilder, px, rgb, rgba,
|
||||
};
|
||||
use gpui_component::IconName;
|
||||
|
||||
use crate::shell::ElyShell;
|
||||
use crate::shell::chrome::command_footer::{render_command_footer, render_kbd};
|
||||
use crate::shell::chrome::command_match::{
|
||||
matching_bookmarks, matching_history, matching_tabs,
|
||||
CommandActionEntry, matching_actions, matching_bookmarks, matching_history, matching_tabs,
|
||||
};
|
||||
use crate::shell::chrome::render_glyph_for;
|
||||
|
||||
const COMMAND_PREFIX: &str = ">";
|
||||
|
||||
pub(crate) fn render_command_overlay(
|
||||
shell: &ElyShell,
|
||||
snapshot: &BrowserSnapshot,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> Option<AnyElement> {
|
||||
@@ -25,13 +27,15 @@ pub(crate) fn render_command_overlay(
|
||||
return None;
|
||||
}
|
||||
let needle = query[COMMAND_PREFIX.len()..].trim().to_lowercase();
|
||||
let selected_index = shell.command_selected_index;
|
||||
|
||||
Some(render_overlay(snapshot, &needle, cx))
|
||||
Some(render_overlay(snapshot, &needle, selected_index, cx))
|
||||
}
|
||||
|
||||
fn render_overlay(
|
||||
snapshot: &BrowserSnapshot,
|
||||
needle: &str,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
@@ -42,13 +46,14 @@ fn render_overlay(
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.pt(px(80.0))
|
||||
.child(render_panel(snapshot, needle, cx))
|
||||
.child(render_panel(snapshot, needle, selected_index, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_panel(
|
||||
snapshot: &BrowserSnapshot,
|
||||
needle: &str,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
let query_label = if needle.is_empty() {
|
||||
@@ -67,7 +72,7 @@ fn render_panel(
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(render_header(query_label.clone(), needle.is_empty()))
|
||||
.child(render_results(snapshot, needle, cx))
|
||||
.child(render_results(snapshot, needle, selected_index, cx))
|
||||
.child(render_command_footer())
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -112,6 +117,7 @@ fn render_header(query_label: String, is_empty: bool) -> AnyElement {
|
||||
fn render_results(
|
||||
snapshot: &BrowserSnapshot,
|
||||
needle: &str,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
let tabs = matching_tabs(snapshot, needle);
|
||||
@@ -119,21 +125,37 @@ fn render_results(
|
||||
let bookmarks = matching_bookmarks(snapshot, needle);
|
||||
let actions = matching_actions(needle);
|
||||
|
||||
let mut offset = 0usize;
|
||||
let mut sections: Vec<AnyElement> = Vec::new();
|
||||
if !tabs.is_empty() {
|
||||
sections.push(render_section("Open tabs", render_tab_rows(tabs, cx)));
|
||||
let count = tabs.len();
|
||||
sections.push(render_section(
|
||||
"Open tabs",
|
||||
render_tab_rows(tabs, offset, selected_index, cx),
|
||||
));
|
||||
offset += count;
|
||||
}
|
||||
if !history.is_empty() {
|
||||
sections.push(render_section("History", render_history_rows(history, cx)));
|
||||
let count = history.len();
|
||||
sections.push(render_section(
|
||||
"History",
|
||||
render_history_rows(history, offset, selected_index, cx),
|
||||
));
|
||||
offset += count;
|
||||
}
|
||||
if !bookmarks.is_empty() {
|
||||
let count = bookmarks.len();
|
||||
sections.push(render_section(
|
||||
"Bookmarks",
|
||||
render_bookmark_rows(bookmarks, cx),
|
||||
render_bookmark_rows(bookmarks, offset, selected_index, cx),
|
||||
));
|
||||
offset += count;
|
||||
}
|
||||
if !actions.is_empty() {
|
||||
sections.push(render_section("Actions", render_action_rows(actions, cx)));
|
||||
sections.push(render_section(
|
||||
"Actions",
|
||||
render_action_rows(actions, offset, selected_index, cx),
|
||||
));
|
||||
}
|
||||
|
||||
if sections.is_empty() {
|
||||
@@ -170,7 +192,12 @@ fn render_section(label: &'static str, body: AnyElement) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_tab_rows(tabs: Vec<&BrowserTab>, cx: &mut Context<ElyShell>) -> AnyElement {
|
||||
fn render_tab_rows(
|
||||
tabs: Vec<&BrowserTab>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
@@ -182,6 +209,7 @@ fn render_tab_rows(tabs: Vec<&BrowserTab>, cx: &mut Context<ElyShell>) -> AnyEle
|
||||
.clone()
|
||||
.unwrap_or_else(|| tab.display_url());
|
||||
let initial = title.chars().next().unwrap_or('?').to_string();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row_with_glyph(
|
||||
CommandRowContent {
|
||||
@@ -189,6 +217,7 @@ fn render_tab_rows(tabs: Vec<&BrowserTab>, cx: &mut Context<ElyShell>) -> AnyEle
|
||||
title,
|
||||
hint: Some(host_label),
|
||||
keys: None,
|
||||
selected: is_selected,
|
||||
},
|
||||
host.as_deref(),
|
||||
&initial,
|
||||
@@ -204,6 +233,8 @@ fn render_tab_rows(tabs: Vec<&BrowserTab>, cx: &mut Context<ElyShell>) -> AnyEle
|
||||
|
||||
fn render_history_rows(
|
||||
entries: Vec<&HistoryEntry>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
@@ -217,6 +248,7 @@ fn render_history_rows(
|
||||
.clone()
|
||||
.unwrap_or_else(|| entry.url().as_str().to_string());
|
||||
let initial = title.chars().next().unwrap_or('?').to_string();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row_with_glyph(
|
||||
CommandRowContent {
|
||||
@@ -224,6 +256,7 @@ fn render_history_rows(
|
||||
title,
|
||||
hint: Some(display),
|
||||
keys: None,
|
||||
selected: is_selected,
|
||||
},
|
||||
host.as_deref(),
|
||||
&initial,
|
||||
@@ -237,58 +270,10 @@ fn render_history_rows(
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
struct CommandAction {
|
||||
title: &'static str,
|
||||
hint: &'static str,
|
||||
icon: IconName,
|
||||
route: &'static str,
|
||||
keys: &'static str,
|
||||
}
|
||||
|
||||
const COMMAND_ACTIONS: &[CommandAction] = &[
|
||||
CommandAction {
|
||||
title: "Switch workspace",
|
||||
hint: "Cycle to the next space",
|
||||
icon: IconName::LayoutDashboard,
|
||||
route: "ely://settings/spaces",
|
||||
keys: "⌘⇧W",
|
||||
},
|
||||
CommandAction {
|
||||
title: "Open Settings",
|
||||
hint: "Appearance, sync, plugins…",
|
||||
icon: IconName::Settings,
|
||||
route: "ely://settings",
|
||||
keys: "⌘,",
|
||||
},
|
||||
CommandAction {
|
||||
title: "Open Plugins",
|
||||
hint: "Marketplace and installed plugins",
|
||||
icon: IconName::Asterisk,
|
||||
route: "ely://plugins",
|
||||
keys: "",
|
||||
},
|
||||
CommandAction {
|
||||
title: "Open Bookmarks",
|
||||
hint: "Manage your saved pages",
|
||||
icon: IconName::BookOpen,
|
||||
route: "ely://bookmarks",
|
||||
keys: "",
|
||||
},
|
||||
];
|
||||
|
||||
fn matching_actions(needle: &str) -> Vec<&'static CommandAction> {
|
||||
COMMAND_ACTIONS
|
||||
.iter()
|
||||
.filter(|action| {
|
||||
needle.is_empty()
|
||||
|| action.title.to_lowercase().contains(needle)
|
||||
|| action.hint.to_lowercase().contains(needle)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_action_rows(
|
||||
actions: Vec<&'static CommandAction>,
|
||||
actions: Vec<&'static CommandActionEntry>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
@@ -302,6 +287,7 @@ fn render_action_rows(
|
||||
Some(action.keys.to_string())
|
||||
};
|
||||
let icon = action.icon.clone();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row(
|
||||
CommandRowContent {
|
||||
@@ -309,6 +295,7 @@ fn render_action_rows(
|
||||
title: action.title.to_string(),
|
||||
hint: Some(action.hint.to_string()),
|
||||
keys,
|
||||
selected: is_selected,
|
||||
},
|
||||
icon,
|
||||
cx,
|
||||
@@ -326,6 +313,7 @@ struct CommandRowContent {
|
||||
title: String,
|
||||
hint: Option<String>,
|
||||
keys: Option<String>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
fn render_row<F>(
|
||||
@@ -373,19 +361,34 @@ fn render_row_inner<F>(
|
||||
where
|
||||
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
||||
{
|
||||
let CommandRowContent { id, title, hint, keys } = content;
|
||||
let CommandRowContent { id, title, hint, keys, selected } = content;
|
||||
let bg = if selected { ROW_SELECTED_BG } else { 0x00000000 };
|
||||
|
||||
div()
|
||||
.id(SharedString::from(id))
|
||||
.relative()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap(px(12.0))
|
||||
.px(px(16.0))
|
||||
.py(px(8.0))
|
||||
.bg(rgba(bg))
|
||||
.cursor_pointer()
|
||||
.hover(|style| style.bg(rgba(ROW_HOVER_BG)))
|
||||
.active(|style| style.opacity(0.85))
|
||||
.on_click(cx.listener(move |shell, _, window, cx| handler(shell, window, cx)))
|
||||
.when(selected, |el| {
|
||||
el.child(
|
||||
div()
|
||||
.absolute()
|
||||
.left_0()
|
||||
.top(px(6.0))
|
||||
.bottom(px(6.0))
|
||||
.w(px(2.0))
|
||||
.rounded(px(2.0))
|
||||
.bg(rgb(colors::ACCENT)),
|
||||
)
|
||||
})
|
||||
.child(leading)
|
||||
.child(
|
||||
div()
|
||||
@@ -431,6 +434,8 @@ fn render_empty_state() -> AnyElement {
|
||||
|
||||
fn render_bookmark_rows(
|
||||
entries: Vec<&BookmarkEntry>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
@@ -444,6 +449,7 @@ fn render_bookmark_rows(
|
||||
.clone()
|
||||
.unwrap_or_else(|| bookmark.url().as_str().to_string());
|
||||
let initial = title.chars().next().unwrap_or('?').to_string();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row_with_glyph(
|
||||
CommandRowContent {
|
||||
@@ -451,6 +457,7 @@ fn render_bookmark_rows(
|
||||
title,
|
||||
hint: Some(display),
|
||||
keys: None,
|
||||
selected: is_selected,
|
||||
},
|
||||
host.as_deref(),
|
||||
&initial,
|
||||
@@ -467,6 +474,7 @@ fn render_bookmark_rows(
|
||||
const PANEL_BG: u32 = 0xfffffff5;
|
||||
const BACKDROP_BG: u32 = 0x140f0a3d;
|
||||
const ROW_HOVER_BG: u32 = 0xc9644214;
|
||||
const ROW_SELECTED_BG: u32 = 0xc964421f;
|
||||
const ROW_ICON_BG: u32 = 0xffffffd9;
|
||||
const BADGE_BG: u32 = 0x281e140f;
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ pub struct ElyShell {
|
||||
pub(crate) translucency_slider: Entity<SliderState>,
|
||||
pub(crate) workspace_picker_open: bool,
|
||||
pub(crate) sidebar_hover_expanded: bool,
|
||||
pub(crate) command_selected_index: usize,
|
||||
download_action_error: Option<String>,
|
||||
download_clear_confirmation: bool,
|
||||
download_security_confirmation: Option<PendingDownloadFileAction>,
|
||||
@@ -179,6 +180,7 @@ impl ElyShell {
|
||||
translucency_slider,
|
||||
workspace_picker_open: false,
|
||||
sidebar_hover_expanded: false,
|
||||
command_selected_index: 0,
|
||||
download_action_error: None,
|
||||
download_clear_confirmation: false,
|
||||
download_security_confirmation: None,
|
||||
|
||||
@@ -81,6 +81,73 @@ impl ElyShell {
|
||||
self.command_input.update(cx, |input, cx| {
|
||||
input.set_value("", window, cx);
|
||||
});
|
||||
self.command_selected_index = 0;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn command_select_next(&mut self, total_rows: usize, cx: &mut Context<Self>) {
|
||||
if total_rows == 0 {
|
||||
self.command_selected_index = 0;
|
||||
return;
|
||||
}
|
||||
let next = (self.command_selected_index + 1) % total_rows;
|
||||
if next != self.command_selected_index {
|
||||
self.command_selected_index = next;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn command_select_prev(&mut self, total_rows: usize, cx: &mut Context<Self>) {
|
||||
if total_rows == 0 {
|
||||
self.command_selected_index = 0;
|
||||
return;
|
||||
}
|
||||
let prev = if self.command_selected_index == 0 {
|
||||
total_rows - 1
|
||||
} else {
|
||||
self.command_selected_index - 1
|
||||
};
|
||||
if prev != self.command_selected_index {
|
||||
self.command_selected_index = prev;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn activate_selected_command(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = match &self.state {
|
||||
ShellState::Ready(core) => match core.snapshot() {
|
||||
Ok(snapshot) => snapshot,
|
||||
Err(_) => return,
|
||||
},
|
||||
ShellState::StartupError(_) => return,
|
||||
};
|
||||
let needle_owned = snapshot.command_query.as_str();
|
||||
let Some(stripped) = needle_owned.strip_prefix('>') else {
|
||||
return;
|
||||
};
|
||||
let needle = stripped.trim().to_lowercase();
|
||||
let rows = crate::shell::chrome::command_match::visible_command_rows(
|
||||
&snapshot, &needle,
|
||||
);
|
||||
if rows.is_empty() {
|
||||
return;
|
||||
}
|
||||
let index = self.command_selected_index.min(rows.len() - 1);
|
||||
match rows[index].clone() {
|
||||
crate::shell::chrome::command_match::CommandSelection::SelectTab(tab_id) => {
|
||||
self.select_tab(&tab_id, window, cx);
|
||||
}
|
||||
crate::shell::chrome::command_match::CommandSelection::OpenUrl(url) => {
|
||||
self.open_internal_tab(url.as_str(), window, cx);
|
||||
}
|
||||
crate::shell::chrome::command_match::CommandSelection::OpenRoute(route) => {
|
||||
self.open_internal_tab(route, window, cx);
|
||||
}
|
||||
}
|
||||
self.dismiss_command_mode(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ use ely_browser_core::BrowserSnapshot;
|
||||
use ely_design_system::{colors, spacing};
|
||||
use ely_domain::{BrowserTab, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX};
|
||||
use gpui::{
|
||||
AnyElement, Context, InteractiveElement, IntoElement, MouseMoveEvent, ParentElement, Render,
|
||||
SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px,
|
||||
rgb, rgba,
|
||||
AnyElement, Context, InteractiveElement, IntoElement, KeyDownEvent, MouseMoveEvent,
|
||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window, div,
|
||||
prelude::FluentBuilder, px, rgb, rgba,
|
||||
};
|
||||
|
||||
use super::chrome::command_match::visible_command_rows;
|
||||
use super::chrome::{
|
||||
panel_bg, panel_shadow, render_command_overlay,
|
||||
render_topbar as render_topbar_chrome, render_wallpaper,
|
||||
@@ -67,6 +68,7 @@ impl ElyShell {
|
||||
.on_action(cx.listener(Self::on_zoom_in))
|
||||
.on_action(cx.listener(Self::on_zoom_out))
|
||||
.on_mouse_move(cx.listener(Self::on_window_mouse_move))
|
||||
.capture_key_down(cx.listener(Self::on_command_overlay_key_down))
|
||||
.text_color(rgb(colors::INK))
|
||||
.child(render_wallpaper(snapshot.appearance.wallpaper()))
|
||||
.child(
|
||||
@@ -88,7 +90,7 @@ impl ElyShell {
|
||||
.when(hover_expanded, |el| {
|
||||
el.child(self.render_hidden_sidebar_overlay(&snapshot, cx))
|
||||
})
|
||||
.children(render_command_overlay(&snapshot, cx))
|
||||
.children(render_command_overlay(self, &snapshot, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -167,6 +169,40 @@ impl ElyShell {
|
||||
self.render_expanded_sidebar(snapshot, sidebar_width, cx)
|
||||
}
|
||||
|
||||
fn on_command_overlay_key_down(
|
||||
&mut self,
|
||||
event: &KeyDownEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let key = event.keystroke.key.as_str();
|
||||
if !matches!(key, "up" | "down" | "enter") {
|
||||
return;
|
||||
}
|
||||
|
||||
let total_rows = match &self.state {
|
||||
ShellState::Ready(core) => match core.snapshot() {
|
||||
Ok(snapshot) => {
|
||||
let Some(stripped) = snapshot.command_query.strip_prefix('>') else {
|
||||
return;
|
||||
};
|
||||
let needle = stripped.trim().to_lowercase();
|
||||
visible_command_rows(&snapshot, &needle).len()
|
||||
}
|
||||
Err(_) => return,
|
||||
},
|
||||
ShellState::StartupError(_) => return,
|
||||
};
|
||||
|
||||
match key {
|
||||
"up" => self.command_select_prev(total_rows, cx),
|
||||
"down" => self.command_select_next(total_rows, cx),
|
||||
"enter" => self.activate_selected_command(window, cx),
|
||||
_ => {}
|
||||
}
|
||||
cx.stop_propagation();
|
||||
}
|
||||
|
||||
fn on_window_mouse_move(
|
||||
&mut self,
|
||||
event: &MouseMoveEvent,
|
||||
|
||||
Reference in New Issue
Block a user