Allow ssh connection for setting up zed (#12063)

Co-Authored-By: Mikayla <mikayla@zed.dev>



Release Notes:

- Magic `ssh` login feature for remote development

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
Conrad Irwin 2024-05-21 22:39:16 -06:00 committed by GitHub
parent 3382e79ef9
commit e5b9e2044e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1242 additions and 785 deletions

View file

@ -6,16 +6,19 @@ use db::kvp::KEY_VALUE_STORE;
use futures::future::join_all;
use gpui::{
actions, Action, AppContext, AsyncWindowContext, DismissEvent, Entity, EventEmitter,
ExternalPaths, FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled,
Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
ExternalPaths, FocusHandle, FocusableView, IntoElement, Model, ParentElement, Pixels, Render,
Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use itertools::Itertools;
use project::{Fs, ProjectEntryId};
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize};
use settings::Settings;
use task::{RevealStrategy, SpawnInTerminal, TaskId};
use terminal::terminal_settings::{Shell, TerminalDockPosition, TerminalSettings};
use task::{RevealStrategy, SpawnInTerminal, TaskId, TerminalWorkDir};
use terminal::{
terminal_settings::{Shell, TerminalDockPosition, TerminalSettings},
Terminal,
};
use ui::{
h_flex, ButtonCommon, Clickable, ContextMenu, FluentBuilder, IconButton, IconSize, Selectable,
Tooltip,
@ -319,14 +322,16 @@ impl TerminalPanel {
return;
};
terminal_panel.update(cx, |panel, cx| {
panel.add_terminal(
Some(action.working_directory.clone()),
None,
RevealStrategy::Always,
cx,
)
});
let terminal_work_dir = workspace
.project()
.read(cx)
.terminal_work_dir_for(Some(&action.working_directory), cx);
terminal_panel
.update(cx, |panel, cx| {
panel.add_terminal(terminal_work_dir, None, RevealStrategy::Always, cx)
})
.detach_and_log_err(cx);
}
fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext<Self>) {
@ -355,18 +360,19 @@ impl TerminalPanel {
let spawn_task = spawn_task;
let reveal = spawn_task.reveal;
let working_directory = spawn_in_terminal.cwd.clone();
let allow_concurrent_runs = spawn_in_terminal.allow_concurrent_runs;
let use_new_terminal = spawn_in_terminal.use_new_terminal;
if allow_concurrent_runs && use_new_terminal {
self.spawn_in_new_terminal(spawn_task, working_directory, cx);
self.spawn_in_new_terminal(spawn_task, cx)
.detach_and_log_err(cx);
return;
}
let terminals_for_task = self.terminals_for_task(&spawn_in_terminal.full_label, cx);
if terminals_for_task.is_empty() {
self.spawn_in_new_terminal(spawn_task, working_directory, cx);
self.spawn_in_new_terminal(spawn_task, cx)
.detach_and_log_err(cx);
return;
}
let (existing_item_index, existing_terminal) = terminals_for_task
@ -378,13 +384,7 @@ impl TerminalPanel {
!use_new_terminal,
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
);
self.replace_terminal(
working_directory,
spawn_task,
existing_item_index,
existing_terminal,
cx,
);
self.replace_terminal(spawn_task, existing_item_index, existing_terminal, cx);
} else {
self.deferred_tasks.insert(
spawn_in_terminal.id.clone(),
@ -393,14 +393,11 @@ impl TerminalPanel {
terminal_panel
.update(&mut cx, |terminal_panel, cx| {
if use_new_terminal {
terminal_panel.spawn_in_new_terminal(
spawn_task,
working_directory,
cx,
);
terminal_panel
.spawn_in_new_terminal(spawn_task, cx)
.detach_and_log_err(cx);
} else {
terminal_panel.replace_terminal(
working_directory,
spawn_task,
existing_item_index,
existing_terminal,
@ -428,14 +425,13 @@ impl TerminalPanel {
}
}
fn spawn_in_new_terminal(
pub fn spawn_in_new_terminal(
&mut self,
spawn_task: SpawnInTerminal,
working_directory: Option<PathBuf>,
cx: &mut ViewContext<Self>,
) {
) -> Task<Result<Model<Terminal>>> {
let reveal = spawn_task.reveal;
self.add_terminal(working_directory, Some(spawn_task), reveal, cx);
self.add_terminal(spawn_task.cwd.clone(), Some(spawn_task), reveal, cx)
}
/// Create a new Terminal in the current working directory or the user's home directory
@ -448,9 +444,11 @@ impl TerminalPanel {
return;
};
terminal_panel.update(cx, |this, cx| {
this.add_terminal(None, None, RevealStrategy::Always, cx)
});
terminal_panel
.update(cx, |this, cx| {
this.add_terminal(None, None, RevealStrategy::Always, cx)
})
.detach_and_log_err(cx);
}
fn terminals_for_task(
@ -482,17 +480,17 @@ impl TerminalPanel {
fn add_terminal(
&mut self,
working_directory: Option<PathBuf>,
working_directory: Option<TerminalWorkDir>,
spawn_task: Option<SpawnInTerminal>,
reveal_strategy: RevealStrategy,
cx: &mut ViewContext<Self>,
) {
) -> Task<Result<Model<Terminal>>> {
let workspace = self.workspace.clone();
self.pending_terminals_to_add += 1;
cx.spawn(|terminal_panel, mut cx| async move {
let pane = terminal_panel.update(&mut cx, |this, _| this.pane.clone())?;
workspace.update(&mut cx, |workspace, cx| {
let result = workspace.update(&mut cx, |workspace, cx| {
let working_directory = if let Some(working_directory) = working_directory {
Some(working_directory)
} else {
@ -502,35 +500,33 @@ impl TerminalPanel {
};
let window = cx.window_handle();
if let Some(terminal) = workspace.project().update(cx, |project, cx| {
project
.create_terminal(working_directory, spawn_task, window, cx)
.log_err()
}) {
let terminal = Box::new(cx.new_view(|cx| {
TerminalView::new(
terminal,
workspace.weak_handle(),
workspace.database_id(),
cx,
)
}));
pane.update(cx, |pane, cx| {
let focus = pane.has_focus(cx);
pane.add_item(terminal, true, focus, None, cx);
});
}
let terminal = workspace.project().update(cx, |project, cx| {
project.create_terminal(working_directory, spawn_task, window, cx)
})?;
let terminal_view = Box::new(cx.new_view(|cx| {
TerminalView::new(
terminal.clone(),
workspace.weak_handle(),
workspace.database_id(),
cx,
)
}));
pane.update(cx, |pane, cx| {
let focus = pane.has_focus(cx);
pane.add_item(terminal_view, true, focus, None, cx);
});
if reveal_strategy == RevealStrategy::Always {
workspace.focus_panel::<Self>(cx);
}
Ok(terminal)
})?;
terminal_panel.update(&mut cx, |this, cx| {
this.pending_terminals_to_add = this.pending_terminals_to_add.saturating_sub(1);
this.serialize(cx)
})?;
anyhow::Ok(())
result
})
.detach_and_log_err(cx);
}
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
@ -579,7 +575,6 @@ impl TerminalPanel {
fn replace_terminal(
&self,
working_directory: Option<PathBuf>,
spawn_task: SpawnInTerminal,
terminal_item_index: usize,
terminal_to_replace: View<TerminalView>,
@ -594,7 +589,7 @@ impl TerminalPanel {
let window = cx.window_handle();
let new_terminal = project.update(cx, |project, cx| {
project
.create_terminal(working_directory, Some(spawn_task), window, cx)
.create_terminal(spawn_task.cwd.clone(), Some(spawn_task), window, cx)
.log_err()
})?;
terminal_to_replace.update(cx, |terminal_to_replace, cx| {
@ -738,7 +733,8 @@ impl Panel for TerminalPanel {
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
if active && self.has_no_terminals(cx) {
self.add_terminal(None, None, RevealStrategy::Never, cx);
self.add_terminal(None, None, RevealStrategy::Never, cx)
.detach_and_log_err(cx)
}
}

View file

@ -14,6 +14,7 @@ use language::Bias;
use persistence::TERMINAL_DB;
use project::{search::SearchQuery, Fs, LocalWorktree, Metadata, Project};
use settings::SettingsStore;
use task::TerminalWorkDir;
use terminal::{
alacritty_terminal::{
index::Point,
@ -878,21 +879,26 @@ impl Item for TerminalView {
) -> Task<anyhow::Result<View<Self>>> {
let window = cx.window_handle();
cx.spawn(|pane, mut cx| async move {
let cwd = TERMINAL_DB
.get_working_directory(item_id, workspace_id)
.log_err()
.flatten()
.or_else(|| {
cx.update(|cx| {
let cwd = cx
.update(|cx| {
let from_db = TERMINAL_DB
.get_working_directory(item_id, workspace_id)
.log_err()
.flatten();
if from_db
.as_ref()
.is_some_and(|from_db| !from_db.as_os_str().is_empty())
{
project.read(cx).terminal_work_dir_for(from_db.as_ref(), cx)
} else {
let strategy = TerminalSettings::get_global(cx).working_directory.clone();
workspace.upgrade().and_then(|workspace| {
get_working_directory(workspace.read(cx), cx, strategy)
})
})
.ok()
.flatten()
}
})
.filter(|cwd| !cwd.as_os_str().is_empty());
.ok()
.flatten();
let terminal = project.update(&mut cx, |project, cx| {
project.create_terminal(cwd, None, window, cx)
@ -1043,20 +1049,24 @@ pub fn get_working_directory(
workspace: &Workspace,
cx: &AppContext,
strategy: WorkingDirectory,
) -> Option<PathBuf> {
let res = match strategy {
WorkingDirectory::CurrentProjectDirectory => current_project_directory(workspace, cx)
.or_else(|| first_project_directory(workspace, cx)),
WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx),
WorkingDirectory::AlwaysHome => None,
WorkingDirectory::Always { directory } => {
shellexpand::full(&directory) //TODO handle this better
.ok()
.map(|dir| Path::new(&dir.to_string()).to_path_buf())
.filter(|dir| dir.is_dir())
}
};
res.or_else(home_dir)
) -> Option<TerminalWorkDir> {
if workspace.project().read(cx).is_local() {
let res = match strategy {
WorkingDirectory::CurrentProjectDirectory => current_project_directory(workspace, cx)
.or_else(|| first_project_directory(workspace, cx)),
WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx),
WorkingDirectory::AlwaysHome => None,
WorkingDirectory::Always { directory } => {
shellexpand::full(&directory) //TODO handle this better
.ok()
.map(|dir| Path::new(&dir.to_string()).to_path_buf())
.filter(|dir| dir.is_dir())
}
};
res.or_else(home_dir).map(|cwd| TerminalWorkDir::Local(cwd))
} else {
workspace.project().read(cx).terminal_work_dir_for(None, cx)
}
}
///Gets the first project's home directory, or the home directory