Add active tab reorder commands
This commit is contained in:
@@ -167,6 +167,10 @@ impl BrowserCore {
|
||||
self.open_new_tab()?;
|
||||
Ok(true)
|
||||
}
|
||||
"move-tab-up" | "move tab up" | "tab-up" | "tab up" => self.move_active_tab_up(),
|
||||
"move-tab-down" | "move tab down" | "tab-down" | "tab down" => {
|
||||
self.move_active_tab_down()
|
||||
}
|
||||
"split-right" | "split right" => {
|
||||
self.split_active_tab_right()?;
|
||||
Ok(true)
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
use ely_domain::{BrowserTab, SpaceId};
|
||||
use ely_domain::{BrowserTab, SpaceId, TabId};
|
||||
|
||||
use super::BrowserCore;
|
||||
|
||||
impl BrowserCore {
|
||||
pub fn move_active_tab_up(&mut self) -> Result<bool, crate::CoreError> {
|
||||
self.move_active_tab_by(TabMoveDirection::Up)
|
||||
}
|
||||
|
||||
pub fn move_active_tab_down(&mut self) -> Result<bool, crate::CoreError> {
|
||||
self.move_active_tab_by(TabMoveDirection::Down)
|
||||
}
|
||||
|
||||
pub(super) fn next_tab_sort_key(&self, space_id: &SpaceId) -> u64 {
|
||||
self.tabs
|
||||
.iter()
|
||||
@@ -34,6 +42,45 @@ impl BrowserCore {
|
||||
self.tabs[index] = tab;
|
||||
}
|
||||
}
|
||||
|
||||
fn move_active_tab_by(
|
||||
&mut self,
|
||||
direction: TabMoveDirection,
|
||||
) -> Result<bool, crate::CoreError> {
|
||||
let active_tab = self.active_tab()?.clone();
|
||||
let space_id = active_tab.space_id().clone();
|
||||
let mut tab_ids = sorted_tabs(self.tabs.iter().filter(|tab| tab.space_id() == &space_id))
|
||||
.into_iter()
|
||||
.map(|tab| tab.id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
let Some(active_index) = tab_ids.iter().position(|tab_id| tab_id == active_tab.id()) else {
|
||||
return Err(crate::CoreError::MissingActiveTab);
|
||||
};
|
||||
let Some(target_index) = direction.target_index(active_index, tab_ids.len()) else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
tab_ids.swap(active_index, target_index);
|
||||
self.apply_tab_order(&space_id, &tab_ids)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn apply_tab_order(
|
||||
&mut self,
|
||||
space_id: &SpaceId,
|
||||
tab_ids: &[TabId],
|
||||
) -> Result<(), crate::CoreError> {
|
||||
for (sort_key, tab_id) in tab_ids.iter().enumerate() {
|
||||
let tab = self
|
||||
.tabs
|
||||
.iter_mut()
|
||||
.find(|tab| tab.id() == tab_id && tab.space_id() == space_id)
|
||||
.ok_or_else(|| crate::CoreError::TabNotFound { id: tab_id.clone() })?;
|
||||
tab.set_sort_key(sort_key as u64);
|
||||
}
|
||||
self.sort_tabs_within_space(space_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sorted_tabs<'a>(tabs: impl Iterator<Item = &'a BrowserTab>) -> Vec<BrowserTab> {
|
||||
@@ -45,3 +92,18 @@ pub(super) fn sorted_tabs<'a>(tabs: impl Iterator<Item = &'a BrowserTab>) -> Vec
|
||||
fn compare_tabs(left: &BrowserTab, right: &BrowserTab) -> std::cmp::Ordering {
|
||||
left.sort_key().cmp(&right.sort_key()).then_with(|| left.id().cmp(right.id()))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TabMoveDirection {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl TabMoveDirection {
|
||||
fn target_index(self, active_index: usize, tab_count: usize) -> Option<usize> {
|
||||
match self {
|
||||
Self::Up => active_index.checked_sub(1),
|
||||
Self::Down => (active_index + 1 < tab_count).then_some(active_index + 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::error::Error;
|
||||
|
||||
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||
use ely_domain::{CommandIntent, UrlText};
|
||||
|
||||
#[test]
|
||||
fn move_active_tab_commands_reorder_visible_tabs() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let first_tab_id = core.active_tab()?.id().clone();
|
||||
let second_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
|
||||
let third_tab_id = core.open_tab(UrlText::parse("https://servo.org")?);
|
||||
|
||||
core.set_command_query(">move-tab-up");
|
||||
let up_intent = core.submit_command()?;
|
||||
let up_snapshot = core.snapshot()?;
|
||||
let up_order = up_snapshot.tabs.iter().map(|tab| tab.id().clone()).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(up_intent, Some(CommandIntent::Command("move-tab-up".to_string())));
|
||||
assert_eq!(up_order, vec![first_tab_id.clone(), third_tab_id.clone(), second_tab_id.clone()]);
|
||||
assert_eq!(up_snapshot.active_tab_id, third_tab_id);
|
||||
assert_eq!(up_snapshot.command_query, "");
|
||||
|
||||
core.set_command_query(">move-tab-down");
|
||||
let down_intent = core.submit_command()?;
|
||||
let down_snapshot = core.snapshot()?;
|
||||
let down_order = down_snapshot.tabs.iter().map(|tab| tab.id().clone()).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(down_intent, Some(CommandIntent::Command("move-tab-down".to_string())));
|
||||
assert_eq!(down_order, vec![first_tab_id, second_tab_id, third_tab_id]);
|
||||
assert_eq!(down_snapshot.command_query, "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_active_tab_command_preserves_query_at_boundary() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let active_tab_id = core.active_tab()?.id().clone();
|
||||
core.open_tab(UrlText::parse("https://example.com")?);
|
||||
core.select_tab(&active_tab_id)?;
|
||||
|
||||
core.set_command_query(">move-tab-up");
|
||||
let intent = core.submit_command()?;
|
||||
let snapshot = core.snapshot()?;
|
||||
let ordered_ids = snapshot.tabs.iter().map(|tab| tab.id().clone()).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(intent, Some(CommandIntent::Command("move-tab-up".to_string())));
|
||||
assert_eq!(snapshot.command_query, ">move-tab-up");
|
||||
assert_eq!(ordered_ids[0], active_tab_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_active_tab_stays_within_active_space() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let work_space_id = core.snapshot()?.active_space_id;
|
||||
let work_tab_id = core.active_tab()?.id().clone();
|
||||
let work_example_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
|
||||
let research_space_id = core.create_space("Research", "R", 0xf54e00)?;
|
||||
let research_tab_id = core.active_tab()?.id().clone();
|
||||
let research_servo_tab_id = core.open_tab(UrlText::parse("https://servo.org")?);
|
||||
|
||||
core.move_active_tab_up()?;
|
||||
let research_snapshot = core.snapshot()?;
|
||||
let research_order =
|
||||
research_snapshot.tabs.iter().map(|tab| tab.id().clone()).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(research_snapshot.active_space_id, research_space_id);
|
||||
assert_eq!(research_order, vec![research_servo_tab_id, research_tab_id]);
|
||||
|
||||
core.select_space(&work_space_id)?;
|
||||
let work_snapshot = core.snapshot()?;
|
||||
let work_order = work_snapshot.tabs.iter().map(|tab| tab.id().clone()).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(work_order, vec![work_tab_id, work_example_tab_id]);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user