recent_projects: Move SSH server entry to initialize once instead of every render (#31650)

Currently, `RemoteEntry::SshConfig` for `ssh_config_servers` initializes
on every render. This leads to side effects like a new focus handle
being created on every render, which leads to breaking navigating
up/down for `ssh_config_servers` items.

This PR fixes it by moving the logic of remote entry
for`ssh_config_servers` into `default_mode`, and only rebuilding it when
`ssh_config_servers` actually changes.

Before:


https://github.com/user-attachments/assets/8c7187d3-16b5-4f96-aa73-fe4f8227b7d0

After:


https://github.com/user-attachments/assets/21588628-8b1c-43fb-bcb8-0b93c70a1e2b

Release Notes:

- Fixed issue navigating SSH config servers in Remote Projects with
keyboard.
This commit is contained in:
Smit Barmase 2025-05-29 09:24:39 +05:30 committed by GitHub
parent 5173a1a968
commit ea8a3be91b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -289,15 +289,18 @@ struct DefaultState {
scrollbar: ScrollbarState, scrollbar: ScrollbarState,
add_new_server: NavigableEntry, add_new_server: NavigableEntry,
servers: Vec<RemoteEntry>, servers: Vec<RemoteEntry>,
handle: ScrollHandle,
} }
impl DefaultState { impl DefaultState {
fn new(cx: &mut App) -> Self { fn new(ssh_config_servers: &BTreeSet<SharedString>, cx: &mut App) -> Self {
let handle = ScrollHandle::new(); let handle = ScrollHandle::new();
let scrollbar = ScrollbarState::new(handle.clone()); let scrollbar = ScrollbarState::new(handle.clone());
let add_new_server = NavigableEntry::new(&handle, cx); let add_new_server = NavigableEntry::new(&handle, cx);
let servers = SshSettings::get_global(cx)
let ssh_settings = SshSettings::get_global(cx);
let read_ssh_config = ssh_settings.read_ssh_config;
let mut servers: Vec<RemoteEntry> = ssh_settings
.ssh_connections() .ssh_connections()
.map(|connection| { .map(|connection| {
let open_folder = NavigableEntry::new(&handle, cx); let open_folder = NavigableEntry::new(&handle, cx);
@ -316,11 +319,25 @@ impl DefaultState {
}) })
.collect(); .collect();
if read_ssh_config {
let mut extra_servers_from_config = ssh_config_servers.clone();
for server in &servers {
if let RemoteEntry::Project { connection, .. } = server {
extra_servers_from_config.remove(&connection.host);
}
}
servers.extend(extra_servers_from_config.into_iter().map(|host| {
RemoteEntry::SshConfig {
open_folder: NavigableEntry::new(&handle, cx),
host,
}
}));
}
Self { Self {
scrollbar, scrollbar,
add_new_server, add_new_server,
servers, servers,
handle,
} }
} }
} }
@ -340,8 +357,8 @@ enum Mode {
} }
impl Mode { impl Mode {
fn default_mode(cx: &mut App) -> Self { fn default_mode(ssh_config_servers: &BTreeSet<SharedString>, cx: &mut App) -> Self {
Self::Default(DefaultState::new(cx)) Self::Default(DefaultState::new(ssh_config_servers, cx))
} }
} }
impl RemoteServerProjects { impl RemoteServerProjects {
@ -404,7 +421,7 @@ impl RemoteServerProjects {
}); });
Self { Self {
mode: Mode::default_mode(cx), mode: Mode::default_mode(&BTreeSet::new(), cx),
focus_handle, focus_handle,
workspace, workspace,
retained_connections: Vec::new(), retained_connections: Vec::new(),
@ -481,7 +498,7 @@ impl RemoteServerProjects {
telemetry::event!("SSH Server Created"); telemetry::event!("SSH Server Created");
this.retained_connections.push(client); this.retained_connections.push(client);
this.add_ssh_server(connection_options, cx); this.add_ssh_server(connection_options, cx);
this.mode = Mode::default_mode(cx); this.mode = Mode::default_mode(&this.ssh_config_servers, cx);
this.focus_handle(cx).focus(window); this.focus_handle(cx).focus(window);
cx.notify() cx.notify()
}) })
@ -648,7 +665,7 @@ impl RemoteServerProjects {
} }
} }
}); });
self.mode = Mode::default_mode(cx); self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
self.focus_handle.focus(window); self.focus_handle.focus(window);
} }
} }
@ -668,7 +685,7 @@ impl RemoteServerProjects {
cx.notify(); cx.notify();
} }
_ => { _ => {
self.mode = Mode::default_mode(cx); self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
self.focus_handle(cx).focus(window); self.focus_handle(cx).focus(window);
cx.notify(); cx.notify();
} }
@ -1229,7 +1246,10 @@ impl RemoteServerProjects {
.ok(); .ok();
remote_servers remote_servers
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.mode = Mode::default_mode(cx); this.mode = Mode::default_mode(
&this.ssh_config_servers,
cx,
);
cx.notify(); cx.notify();
}) })
.ok(); .ok();
@ -1281,7 +1301,7 @@ impl RemoteServerProjects {
.id("ssh-options-copy-server-address") .id("ssh-options-copy-server-address")
.track_focus(&entries[3].focus_handle) .track_focus(&entries[3].focus_handle)
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| { .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
this.mode = Mode::default_mode(cx); this.mode = Mode::default_mode(&this.ssh_config_servers, cx);
cx.focus_self(window); cx.focus_self(window);
cx.notify(); cx.notify();
})) }))
@ -1297,7 +1317,8 @@ impl RemoteServerProjects {
) )
.child(Label::new("Go Back")) .child(Label::new("Go Back"))
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.mode = Mode::default_mode(cx); this.mode =
Mode::default_mode(&this.ssh_config_servers, cx);
cx.focus_self(window); cx.focus_self(window);
cx.notify() cx.notify()
})), })),
@ -1358,7 +1379,8 @@ impl RemoteServerProjects {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let ssh_settings = SshSettings::get_global(cx); let ssh_settings = SshSettings::get_global(cx);
let read_ssh_config = ssh_settings.read_ssh_config; let mut should_rebuild = false;
if ssh_settings if ssh_settings
.ssh_connections .ssh_connections
.as_ref() .as_ref()
@ -1373,32 +1395,34 @@ impl RemoteServerProjects {
.ne(connections.iter()) .ne(connections.iter())
}) })
{ {
self.mode = Mode::default_mode(cx); should_rebuild = true;
};
if !should_rebuild && ssh_settings.read_ssh_config {
let current_ssh_hosts: BTreeSet<SharedString> = state
.servers
.iter()
.filter_map(|server| match server {
RemoteEntry::SshConfig { host, .. } => Some(host.clone()),
_ => None,
})
.collect();
let mut expected_ssh_hosts = self.ssh_config_servers.clone();
for server in &state.servers {
if let RemoteEntry::Project { connection, .. } = server {
expected_ssh_hosts.remove(&connection.host);
}
}
should_rebuild = current_ssh_hosts != expected_ssh_hosts;
}
if should_rebuild {
self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
if let Mode::Default(new_state) = &self.mode { if let Mode::Default(new_state) = &self.mode {
state = new_state.clone(); state = new_state.clone();
} }
} }
let mut extra_servers_from_config = if read_ssh_config {
self.ssh_config_servers.clone()
} else {
BTreeSet::new()
};
let mut servers = state.servers.clone();
for server in &servers {
if let RemoteEntry::Project { connection, .. } = server {
extra_servers_from_config.remove(&connection.host);
}
}
servers.extend(
extra_servers_from_config
.into_iter()
.map(|host| RemoteEntry::SshConfig {
open_folder: NavigableEntry::new(&state.handle, cx),
host,
}),
);
let scroll_state = state.scrollbar.parent_entity(&cx.entity()); let scroll_state = state.scrollbar.parent_entity(&cx.entity());
let connect_button = div() let connect_button = div()
.id("ssh-connect-new-server-container") .id("ssh-connect-new-server-container")
@ -1455,7 +1479,7 @@ impl RemoteServerProjects {
) )
.into_any_element(), .into_any_element(),
) )
.children(servers.iter().enumerate().map(|(ix, connection)| { .children(state.servers.iter().enumerate().map(|(ix, connection)| {
self.render_ssh_connection(ix, connection.clone(), window, cx) self.render_ssh_connection(ix, connection.clone(), window, cx)
.into_any_element() .into_any_element()
})), })),
@ -1464,7 +1488,7 @@ impl RemoteServerProjects {
) )
.entry(state.add_new_server.clone()); .entry(state.add_new_server.clone());
for server in &servers { for server in &state.servers {
match server { match server {
RemoteEntry::Project { RemoteEntry::Project {
open_folder, open_folder,
@ -1556,7 +1580,7 @@ impl RemoteServerProjects {
}, },
cx, cx,
); );
self.mode = Mode::default_mode(cx); self.mode = Mode::default_mode(&self.ssh_config_servers, cx);
new_ix.load(atomic::Ordering::Acquire) new_ix.load(atomic::Ordering::Acquire)
} }
} }