Select tabs from command scope

This commit is contained in:
2026-05-07 19:16:37 -04:00
parent de68fb6ac3
commit c74ec87ed5
2 changed files with 90 additions and 11 deletions
+21 -5
View File
@@ -32,8 +32,14 @@ impl ElyShell {
let command_input = let command_input =
cx.new(|cx| InputState::new(window, cx).placeholder("Search or enter address")); cx.new(|cx| InputState::new(window, cx).placeholder("Search or enter address"));
let command_subscription = let command_subscription = cx.subscribe_in(
cx.subscribe(&command_input, |shell: &mut Self, input, event: &InputEvent, cx| { &command_input,
window,
|shell: &mut Self, input, event: &InputEvent, window, cx| {
let mut submitted_intent = None;
let mut sync_address = false;
let submitted = matches!(event, InputEvent::PressEnter { .. });
let ShellState::Ready(core) = &mut shell.state else { let ShellState::Ready(core) = &mut shell.state else {
return; return;
}; };
@@ -41,12 +47,22 @@ impl ElyShell {
let value = input.read(cx).value().to_string(); let value = input.read(cx).value().to_string();
core.set_command_query(value); core.set_command_query(value);
if matches!(event, InputEvent::PressEnter { .. }) { if submitted {
shell.last_intent = core.submit_command().ok().flatten(); submitted_intent = core.submit_command().ok().flatten();
sync_address = core.command_query().is_empty();
}
if submitted {
shell.last_intent = submitted_intent;
}
if sync_address {
shell.sync_address_input(window, cx);
} }
cx.notify(); cx.notify();
}); },
);
let state = match InitialBrowserConfig::ely_defaults().and_then(|config| { let state = match InitialBrowserConfig::ely_defaults().and_then(|config| {
BrowserCore::new(config).map_err(|error| match error { BrowserCore::new(config).map_err(|error| match error {
+69 -6
View File
@@ -1,6 +1,6 @@
use ely_domain::{ use ely_domain::{
BrowserTab, CommandIntent, DomainError, Profile, ProfileId, ProfileKind, Space, SpaceId, TabId, BrowserTab, CommandIntent, CommandScope, DomainError, Profile, ProfileId, ProfileKind, Space,
UrlText, SpaceId, TabId, UrlText,
}; };
use crate::CoreError; use crate::CoreError;
@@ -140,6 +140,11 @@ impl BrowserCore {
self.command_query = query.into(); self.command_query = query.into();
} }
#[must_use]
pub fn command_query(&self) -> &str {
&self.command_query
}
pub fn submit_command(&mut self) -> Result<Option<CommandIntent>, CoreError> { pub fn submit_command(&mut self) -> Result<Option<CommandIntent>, CoreError> {
let query = self.command_query.trim(); let query = self.command_query.trim();
if query.is_empty() { if query.is_empty() {
@@ -147,9 +152,18 @@ impl BrowserCore {
} }
let intent = CommandIntent::parse(query)?; let intent = CommandIntent::parse(query)?;
if let CommandIntent::Navigate(url) = &intent { match &intent {
self.open_tab(url.clone()); CommandIntent::Navigate(url) => {
self.command_query.clear(); self.open_tab(url.clone());
self.command_query.clear();
}
CommandIntent::ScopedSearch { scope: CommandScope::Tabs, query } => {
if let Some(tab_id) = self.find_tab_match(query) {
self.select_tab(&tab_id)?;
self.command_query.clear();
}
}
_ => {}
} }
Ok(Some(intent)) Ok(Some(intent))
@@ -199,6 +213,14 @@ impl BrowserCore {
.ok_or(CoreError::MissingActiveTab) .ok_or(CoreError::MissingActiveTab)
} }
fn find_tab_match(&self, query: &str) -> Option<TabId> {
let normalized_query = query.trim().to_lowercase();
self.tabs
.iter()
.find(|tab| tab_matches_query(tab, &normalized_query))
.map(|tab| tab.id().clone())
}
fn build_tab(&self, url: UrlText) -> BrowserTab { fn build_tab(&self, url: UrlText) -> BrowserTab {
let title = tab_title(&url); let title = tab_title(&url);
BrowserTab::new( BrowserTab::new(
@@ -219,11 +241,17 @@ fn tab_title(url: &UrlText) -> String {
url.display_host() url.display_host()
} }
fn tab_matches_query(tab: &BrowserTab, normalized_query: &str) -> bool {
tab.title().to_lowercase().contains(normalized_query)
|| tab.url().as_str().to_lowercase().contains(normalized_query)
|| tab.display_url().to_lowercase().contains(normalized_query)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::error::Error; use std::error::Error;
use ely_domain::UrlText; use ely_domain::{CommandIntent, CommandScope, UrlText};
use super::{BrowserCore, InitialBrowserConfig}; use super::{BrowserCore, InitialBrowserConfig};
use crate::CoreError; use crate::CoreError;
@@ -311,4 +339,39 @@ mod tests {
assert_eq!(wrapped_tab_id, third_tab_id); assert_eq!(wrapped_tab_id, third_tab_id);
Ok(()) Ok(())
} }
#[test]
fn tab_scoped_search_selects_matching_open_tab() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let first_tab_id = core.active_tab()?.id().clone();
core.open_tab(UrlText::parse("https://example.com")?);
let servo_tab_id = core.open_tab(UrlText::parse("https://servo.org")?);
core.select_tab(&first_tab_id)?;
core.set_command_query("@tabs servo");
let intent = core.submit_command()?;
let snapshot = core.snapshot()?;
assert!(matches!(
intent,
Some(CommandIntent::ScopedSearch { scope: CommandScope::Tabs, query }) if query == "servo"
));
assert_eq!(snapshot.active_tab_id, servo_tab_id);
assert_eq!(snapshot.command_query, "");
Ok(())
}
#[test]
fn tab_scoped_search_preserves_query_without_match() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let active_tab_id = core.active_tab()?.id().clone();
core.set_command_query("@tabs absent");
core.submit_command()?;
let snapshot = core.snapshot()?;
assert_eq!(snapshot.active_tab_id, active_tab_id);
assert_eq!(snapshot.command_query, "@tabs absent");
Ok(())
}
} }