Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/maple-agent/app/src/ui/chat/computers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ impl ChatScreen {
) {
self.sidebar
.update(cx, |sidebar, cx| sidebar.restore_browse_state(state, cx));
// The keyboard focus stayed on the computer this one replaced, which
// is no longer drawn.
self.screen_focus_pending = true;
}

/// Name updates are pushed. Refresh the small folder list periodically for
Expand Down
25 changes: 25 additions & 0 deletions apps/maple-agent/app/src/ui/chat/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub(crate) struct BrowseState {
search: String,
archived_expanded: bool,
scroll: gpui::ListOffset,
/// Application Vim's row. Other computers' sidebars name a task by its
/// computer, so it travels in that form.
vim_selected: Option<SidebarTarget>,
computers: computers::ComputerBrowseState,
}

Expand Down Expand Up @@ -582,6 +585,13 @@ impl Sidebar {
search: self.search_input.read(cx).text(),
archived_expanded: self.archived_expanded,
scroll: self.list.logical_scroll_top(),
vim_selected: self.vim_selected.clone().map(|target| match target {
SidebarTarget::Task(id) => SidebarTarget::Computer(Navigate {
computer: self.backend.remote_host().map(str::to_owned),
destination: Destination::Task(id),
}),
target => target,
}),
computers: self.computer_browse_state(),
}
}
Expand All @@ -606,6 +616,21 @@ impl Sidebar {
}
self.restore_computer_browse_state(state.computers, cx);
self.rebuild_sections();
// Stay on the row the window had, usually the task just opened
// here. This computer's own tasks are plain task rows.
if let Some(target) = state
.vim_selected
.map(|target| match target {
SidebarTarget::Computer(Navigate {
computer,
destination: Destination::Task(id),
}) if computer.as_deref() == self.backend.remote_host() => SidebarTarget::Task(id),
target => target,
})
.filter(|target| self.vim_target_row(target).is_some())
{
self.vim_selected = Some(target);
}
self.list.scroll_to(state.scroll);
cx.notify();
}
Expand Down
140 changes: 140 additions & 0 deletions apps/maple-agent/app/src/ui/chat/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5784,6 +5784,146 @@ mod state_tests {
assert!(!sidebar.update(cx, |sidebar, _| sidebar.switcher_menu_open()));
}

/// Every computer has its own Chat and sidebar, and the window swaps
/// them. Application Vim's row is window browsing: a task opened on
/// another computer stays the selected row there, also on a return
/// visit, and the keys reach the Chat swapped in.
#[gpui::test]
fn test_application_vim_row_follows_a_task_opened_on_another_computer(cx: &mut TestAppContext) {
use super::super::computers::{Destination, Navigate};
use super::super::navigation::{ChatRegion, SidebarTarget};
use std::cell::RefCell;
use std::rc::Rc;
struct Shell {
chat: Entity<ChatScreen>,
}
impl Render for Shell {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.w(px(1200.))
.h(px(800.))
.flex()
.flex_col()
.child(self.chat.clone())
}
}
cx.executor().allow_parking();
let (here, there) = {
let _guard = SETTINGS_LOCK.lock();
(
crate::backend::AgentBackend::for_test("http://127.0.0.1:9".into(), String::new())
.expect("backend"),
crate::backend::AgentBackend::for_remote_test("peer").expect("backend"),
)
};
let requests = Rc::new(RefCell::new(Vec::new()));
cx.update(crate::desktop::register_key_bindings);
let mut screen = |backend, task: &str, root: &str| {
let seen = requests.clone();
let chat = cx.new(|cx| {
let _guard = SETTINGS_LOCK.lock();
let mut chat = ChatScreen::new_without_start(Arc::new(backend), "user".into(), cx);
chat.booting = false;
chat.trust_prompts = false;
chat.sidebar
.update(cx, |sidebar, _| sidebar.reset_persisted_for_test());
chat.sessions = vec![summary_at(task, task, root)];
chat.sync_sidebar(cx);
chat.set_application_vim_enabled(true, cx);
chat
});
cx.update(|cx| {
cx.subscribe(&chat, move |_, request: &Navigate, _| {
seen.borrow_mut().push(request.clone());
})
.detach();
});
chat
};
let local = screen(here, "here", "/a");
let peer = screen(there, "there", "/b");
let mut computers = vec![
local.read_with(cx, |chat, cx| {
chat.computer_snapshot("This computer".into(), cx)
}),
peer.read_with(cx, |chat, cx| chat.computer_snapshot("Peer".into(), cx)),
];
// Rows come from these snapshots alone. With a backend, the sidebar
// also reads pages from the fixtures' empty stores, and those replies
// remove the rows whenever they happen to land.
for computer in &mut computers {
computer.backend = None;
}
let computers = Arc::new(computers);
for chat in [&local, &peer] {
chat.update(cx, |chat, cx| chat.set_computers(computers.clone(), cx));
}
let (shell, cx) = cx.add_window_view(|_, _| Shell {
chat: local.clone(),
});
cx.simulate_resize(gpui::size(px(1200.), px(800.)));
cx.update(|window, _| window.activate_window());
cx.run_until_parked();
// Put Application Vim on another computer's task row and press
// Enter, then swap Chats the way the desktop does.
let mut open = |from: &Entity<ChatScreen>, to: &Entity<ChatScreen>, row: Navigate| {
cx.update(|window, cx| {
from.update(cx, |chat, cx| {
chat.select_sidebar_from_pointer(
SidebarTarget::Computer(row.clone()),
window,
cx,
)
})
});
cx.simulate_keystrokes("enter");
assert_eq!(
requests.borrow_mut().drain(..).collect::<Vec<_>>(),
std::slice::from_ref(&row)
);
let browse = from.update(cx, |chat, cx| chat.sidebar_browse_state(cx));
from.update(cx, |chat, cx| chat.set_visible(false, cx));
shell.update(cx, |shell, cx| {
shell.chat = to.clone();
cx.notify();
});
to.update(cx, |chat, cx| {
chat.set_visible(true, cx);
chat.restore_sidebar_browse_state(browse, cx);
chat.navigate(row.destination, cx);
});
cx.run_until_parked();
};
let task = |computer: Option<&str>, id: &str| Navigate {
computer: computer.map(str::to_owned),
destination: Destination::Task(id.into()),
};
open(&local, &peer, task(Some("peer"), "there"));
open(&peer, &local, task(None, "here"));
open(&local, &peer, task(Some("peer"), "there"));
let state = |cx: &mut gpui::VisualTestContext| {
peer.update(cx, |chat, cx| {
(
chat.application_vim.region,
chat.sidebar.read(cx).vim_selected().cloned(),
)
})
};
let opened = state(cx);
// The keys reach the Chat now on screen: k leaves the open task.
cx.simulate_keystrokes("k");
let moved = state(cx);
assert_eq!(
opened,
(
ChatRegion::Sidebar,
Some(SidebarTarget::Task("there".into()))
),
"after k: {moved:?}"
);
assert_ne!(moved.1, opened.1, "k reaches the swapped-in Chat");
}

/// What menu, if any, the chat and its sidebar show.
fn open_menu(chat: &Entity<ChatScreen>, cx: &mut gpui::VisualTestContext) -> String {
chat.update(cx, |this, cx| {
Expand Down
Loading