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
2 changes: 1 addition & 1 deletion crates/ui_gpui/src/app/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Gpui {
match service.list_sessions().await {
Ok(sessions) => {
*gpui.session_lifecycles.lock().unwrap() = lifecycles;
*gpui.chat_sessions.lock().unwrap() = sessions.clone();
gpui.set_chat_sessions(sessions.clone());
gpui.push_event(UiEvent::UpdateChatList { sessions });
}
Err(e) => gpui.display_error(format!("Failed to list sessions: {e:#}")),
Expand Down
2 changes: 1 addition & 1 deletion crates/ui_gpui/src/app/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ impl Gpui {
sessions.len()
);
// Update local cache
*self.chat_sessions.lock().unwrap() = sessions.clone();
self.set_chat_sessions(sessions.clone());
let _current_session_id = self.current_session_id.lock().unwrap().clone();

// Refresh all windows to trigger re-render with new chat data
Expand Down
16 changes: 16 additions & 0 deletions crates/ui_gpui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ pub struct Gpui {
// Current chat state
current_session_id: Arc<Mutex<Option<String>>>,
chat_sessions: Arc<Mutex<Vec<ChatMetadata>>>,
/// Set once the first session listing arrived, so an empty `chat_sessions`
/// can be told apart from one not loaded yet.
chat_sessions_loaded: Arc<std::sync::atomic::AtomicBool>,
/// Visits and settlement per session, mirrored from the core for the
/// sidebar. Sessions absent here have the default lifecycle.
session_lifecycles:
Expand Down Expand Up @@ -591,6 +594,7 @@ impl Gpui {

current_session_id: Arc::new(Mutex::new(None)),
chat_sessions: Arc::new(Mutex::new(Vec::new())),
chat_sessions_loaded: Arc::new(std::sync::atomic::AtomicBool::new(false)),
session_lifecycles: Arc::new(Mutex::new(HashMap::new())),
current_session_activity_state: Arc::new(Mutex::new(None)),
session_stop_requests: Arc::new(Mutex::new(std::collections::HashSet::new())),
Expand Down Expand Up @@ -889,6 +893,18 @@ impl Gpui {
self.chat_sessions.lock().unwrap().clone()
}

/// Whether the session listing arrived at least once (it may be empty).
pub fn chat_sessions_loaded(&self) -> bool {
self.chat_sessions_loaded
.load(std::sync::atomic::Ordering::Acquire)
}

fn set_chat_sessions(&self, sessions: Vec<ChatMetadata>) {
*self.chat_sessions.lock().unwrap() = sessions;
self.chat_sessions_loaded
.store(true, std::sync::atomic::Ordering::Release);
}

pub fn get_session_lifecycles(
&self,
) -> HashMap<String, code_assistant_core::session::lifecycle::SessionLifecycle> {
Expand Down
11 changes: 11 additions & 0 deletions crates/ui_gpui/src/main_screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ pub struct MainScreen {
/// Form for the viewed session's oldest open `ask_question` request.
question_prompt: Option<Entity<question_prompt::QuestionPrompt>>,

/// Whether the sidebar got the first session listing. Without it, an
/// empty listing equals the initial `sessions` and never reaches the
/// sidebar, which then shows no project folders.
sidebar_sessions_synced: bool,

// Sidebar animation
left_animator: SidebarAnimator,
right_animator: SidebarAnimator,
Expand Down Expand Up @@ -345,6 +350,7 @@ impl MainScreen {
ui_scale: initial_scale,
right_sidebar_width: px(initial_sidebar_width),
right_sidebar_resizing: false,
sidebar_sessions_synced: false,
resize_start_x: 0.0,
resize_start_width: 0.0,
context_limit_cache: None,
Expand Down Expand Up @@ -1697,11 +1703,16 @@ impl Render for MainScreen {
sidebar.set_persisted_projects(persisted_projects, cx);
});

let sessions_loaded = cx
.try_global::<Gpui>()
.is_some_and(|g| g.chat_sessions_loaded());
// Update project sidebar if needed
if self.sessions != sessions
|| self.session_lifecycles != session_lifecycles
|| self.current_session_id != current_session_id
|| (sessions_loaded && !self.sidebar_sessions_synced)
{
self.sidebar_sessions_synced = sessions_loaded;
let previous_session_id = self.current_session_id.clone();
self.sessions = sessions.clone();
self.session_lifecycles = session_lifecycles.clone();
Expand Down
1 change: 1 addition & 0 deletions crates/ui_gpui/src/main_screen/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
mod comments;
mod composer;
mod end_to_end;
mod sidebar;
mod stored_outputs;
22 changes: 22 additions & 0 deletions crates/ui_gpui/src/main_screen/tests/sidebar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! The session sidebar as the main screen feeds it.

use crate::test_support::MainScreenTest;
use code_assistant_core::ui::UiEvent;
use gpui_kit::TestAppContext;

#[gpui_kit::test]
fn saved_projects_show_before_the_first_session_exists(cx: &mut TestAppContext) {
let mut test = MainScreenTest::new(cx);
// Replace what the machine's projects.json holds.
*test.gpui.persisted_projects.lock().unwrap() = ["proj".to_string()].into();

// A fresh store: the listing arrives, and it is empty.
test.push(UiEvent::UpdateChatList {
sessions: Vec::new(),
});

let folders = test.main_screen.read_with(&test.cx, |screen, cx| {
screen.project_sidebar.read(cx).folder_projects()
});
assert_eq!(folders, vec![Some("proj".to_string())]);
}
9 changes: 9 additions & 0 deletions crates/ui_gpui/src/sidebar/projects_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@ impl SessionSidebar {
.max()
}

/// The projects of the displayed folders, in order (`None`: no project).
#[cfg(test)]
pub(crate) fn folder_projects(&self) -> Vec<Option<String>> {
self.folders
.iter()
.map(|folder| folder.project.clone())
.collect()
}

pub(super) fn render_folders(&self, cx: &mut Context<Self>) -> Vec<AnyElement> {
if self.folders.is_empty() {
return vec![render_hint("No projects yet", cx).into_any_element()];
Expand Down
Loading