mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-28 09:54:33 +00:00
WIP: ssh remoting: Add upload_binary
field to SshConnections (#19748)
Some checks are pending
CI / Check Postgres and Protobuf migrations, mergability (push) Waiting to run
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Waiting to run
CI / (Linux) Run Clippy and tests (push) Waiting to run
CI / (Linux) Build Remote Server (push) Waiting to run
CI / (Windows) Run Clippy and tests (push) Waiting to run
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run
Docs / Check formatting (push) Waiting to run
Some checks are pending
CI / Check Postgres and Protobuf migrations, mergability (push) Waiting to run
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Waiting to run
CI / (Linux) Run Clippy and tests (push) Waiting to run
CI / (Linux) Build Remote Server (push) Waiting to run
CI / (Windows) Run Clippy and tests (push) Waiting to run
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run
Docs / Check formatting (push) Waiting to run
This removes the old `remote_server { "download_binary_on_host": bool }` field and replaces it with a `upload_binary: bool` on every `ssh_connection`. @ConradIrwin it compiles, it connects, but I haven't tested it really yet Release Notes: - N/A --------- Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
1acebb3c47
commit
fc8a72cdd8
11 changed files with 204 additions and 235 deletions
|
@ -11,7 +11,7 @@ use ui::{
|
|||
};
|
||||
use workspace::{notifications::DetachAndPromptErr, ModalView, OpenOptions, Workspace};
|
||||
|
||||
use crate::{open_ssh_project, SshSettings};
|
||||
use crate::open_ssh_project;
|
||||
|
||||
enum Host {
|
||||
RemoteProject,
|
||||
|
@ -102,16 +102,6 @@ impl DisconnectedOverlay {
|
|||
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
|
||||
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let nickname = cx
|
||||
.update(|cx| {
|
||||
SshSettings::get_global(cx).nickname_for(
|
||||
&connection_options.host,
|
||||
connection_options.port,
|
||||
&connection_options.username,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
open_ssh_project(
|
||||
connection_options,
|
||||
paths,
|
||||
|
@ -120,7 +110,6 @@ impl DisconnectedOverlay {
|
|||
replace_window: Some(window),
|
||||
..Default::default()
|
||||
},
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
pub mod disconnected_overlay;
|
||||
mod remote_servers;
|
||||
mod ssh_connections;
|
||||
use remote::SshConnectionOptions;
|
||||
pub use ssh_connections::open_ssh_project;
|
||||
|
||||
use disconnected_overlay::DisconnectedOverlay;
|
||||
|
@ -331,23 +330,12 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let args = SshSettings::get_global(cx).args_for(
|
||||
&ssh_project.host,
|
||||
ssh_project.port,
|
||||
&ssh_project.user,
|
||||
);
|
||||
let nickname = SshSettings::get_global(cx).nickname_for(
|
||||
&ssh_project.host,
|
||||
ssh_project.port,
|
||||
&ssh_project.user,
|
||||
);
|
||||
let connection_options = SshConnectionOptions {
|
||||
host: ssh_project.host.clone(),
|
||||
username: ssh_project.user.clone(),
|
||||
port: ssh_project.port,
|
||||
password: None,
|
||||
args,
|
||||
};
|
||||
let connection_options = SshSettings::get_global(cx)
|
||||
.connection_options_for(
|
||||
ssh_project.host.clone(),
|
||||
ssh_project.port,
|
||||
ssh_project.user.clone(),
|
||||
);
|
||||
|
||||
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
|
||||
|
||||
|
@ -357,7 +345,6 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
paths,
|
||||
app_state,
|
||||
open_options,
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -197,11 +197,7 @@ impl ProjectPicker {
|
|||
picker
|
||||
});
|
||||
let connection_string = connection.connection_string().into();
|
||||
let nickname = SshSettings::get_global(cx).nickname_for(
|
||||
&connection.host,
|
||||
connection.port,
|
||||
&connection.username,
|
||||
);
|
||||
let nickname = connection.nickname.clone().map(|nick| nick.into());
|
||||
let _path_task = cx
|
||||
.spawn({
|
||||
let workspace = workspace.clone();
|
||||
|
@ -414,7 +410,7 @@ impl RemoteServerProjects {
|
|||
return;
|
||||
}
|
||||
};
|
||||
let ssh_prompt = cx.new_view(|cx| SshPrompt::new(&connection_options, None, cx));
|
||||
let ssh_prompt = cx.new_view(|cx| SshPrompt::new(&connection_options, cx));
|
||||
|
||||
let connection = connect_over_ssh(
|
||||
connection_options.remote_server_identifier(),
|
||||
|
@ -491,12 +487,11 @@ impl RemoteServerProjects {
|
|||
return;
|
||||
};
|
||||
|
||||
let nickname = ssh_connection.nickname.clone();
|
||||
let connection_options = ssh_connection.into();
|
||||
workspace.update(cx, |_, cx| {
|
||||
cx.defer(move |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |cx| {
|
||||
SshConnectionModal::new(&connection_options, Vec::new(), nickname, cx)
|
||||
SshConnectionModal::new(&connection_options, Vec::new(), cx)
|
||||
});
|
||||
let prompt = workspace
|
||||
.active_modal::<SshConnectionModal>(cx)
|
||||
|
@ -584,9 +579,7 @@ impl RemoteServerProjects {
|
|||
self.create_ssh_server(state.address_editor.clone(), cx);
|
||||
}
|
||||
Mode::EditNickname(state) => {
|
||||
let text = Some(state.editor.read(cx).text(cx))
|
||||
.filter(|text| !text.is_empty())
|
||||
.map(SharedString::from);
|
||||
let text = Some(state.editor.read(cx).text(cx)).filter(|text| !text.is_empty());
|
||||
let index = state.index;
|
||||
self.update_settings_file(cx, move |setting, _| {
|
||||
if let Some(connections) = setting.ssh_connections.as_mut() {
|
||||
|
@ -633,7 +626,7 @@ impl RemoteServerProjects {
|
|||
) -> impl IntoElement {
|
||||
let (main_label, aux_label) = if let Some(nickname) = ssh_connection.nickname.clone() {
|
||||
let aux_label = SharedString::from(format!("({})", ssh_connection.host));
|
||||
(nickname, Some(aux_label))
|
||||
(nickname.into(), Some(aux_label))
|
||||
} else {
|
||||
(ssh_connection.host.clone(), None)
|
||||
};
|
||||
|
@ -746,13 +739,11 @@ impl RemoteServerProjects {
|
|||
let project = project.clone();
|
||||
let server = server.clone();
|
||||
cx.spawn(|remote_server_projects, mut cx| async move {
|
||||
let nickname = server.nickname.clone();
|
||||
let result = open_ssh_project(
|
||||
server.into(),
|
||||
project.paths.into_iter().map(PathBuf::from).collect(),
|
||||
app_state,
|
||||
OpenOptions::default(),
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await;
|
||||
|
@ -861,6 +852,7 @@ impl RemoteServerProjects {
|
|||
projects: vec![],
|
||||
nickname: None,
|
||||
args: connection_options.args.unwrap_or_default(),
|
||||
upload_binary_over_ssh: None,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -953,7 +945,7 @@ impl RemoteServerProjects {
|
|||
SshConnectionHeader {
|
||||
connection_string: connection_string.clone(),
|
||||
paths: Default::default(),
|
||||
nickname: connection.nickname.clone(),
|
||||
nickname: connection.nickname.clone().map(|s| s.into()),
|
||||
}
|
||||
.render(cx),
|
||||
)
|
||||
|
@ -1135,13 +1127,14 @@ impl RemoteServerProjects {
|
|||
};
|
||||
|
||||
let connection_string = connection.host.clone();
|
||||
let nickname = connection.nickname.clone().map(|s| s.into());
|
||||
|
||||
v_flex()
|
||||
.child(
|
||||
SshConnectionHeader {
|
||||
connection_string,
|
||||
paths: Default::default(),
|
||||
nickname: connection.nickname.clone(),
|
||||
nickname,
|
||||
}
|
||||
.render(cx),
|
||||
)
|
||||
|
|
|
@ -26,15 +26,9 @@ use ui::{
|
|||
};
|
||||
use workspace::{AppState, ModalView, Workspace};
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RemoteServerSettings {
|
||||
pub download_on_host: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SshSettings {
|
||||
pub ssh_connections: Option<Vec<SshConnection>>,
|
||||
pub remote_server: Option<RemoteServerSettings>,
|
||||
}
|
||||
|
||||
impl SshSettings {
|
||||
|
@ -42,39 +36,31 @@ impl SshSettings {
|
|||
self.ssh_connections.clone().into_iter().flatten()
|
||||
}
|
||||
|
||||
pub fn args_for(
|
||||
pub fn connection_options_for(
|
||||
&self,
|
||||
host: &str,
|
||||
host: String,
|
||||
port: Option<u16>,
|
||||
user: &Option<String>,
|
||||
) -> Option<Vec<String>> {
|
||||
self.ssh_connections()
|
||||
.filter_map(|conn| {
|
||||
if conn.host == host && &conn.username == user && conn.port == port {
|
||||
Some(conn.args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
||||
pub fn nickname_for(
|
||||
&self,
|
||||
host: &str,
|
||||
port: Option<u16>,
|
||||
user: &Option<String>,
|
||||
) -> Option<SharedString> {
|
||||
self.ssh_connections()
|
||||
.filter_map(|conn| {
|
||||
if conn.host == host && &conn.username == user && conn.port == port {
|
||||
Some(conn.nickname)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.flatten()
|
||||
username: Option<String>,
|
||||
) -> SshConnectionOptions {
|
||||
for conn in self.ssh_connections() {
|
||||
if conn.host == host && conn.username == username && conn.port == port {
|
||||
return SshConnectionOptions {
|
||||
nickname: conn.nickname,
|
||||
upload_binary_over_ssh: conn.upload_binary_over_ssh.unwrap_or_default(),
|
||||
args: Some(conn.args),
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
password: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
SshConnectionOptions {
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,13 +71,20 @@ pub struct SshConnection {
|
|||
pub username: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub port: Option<u16>,
|
||||
pub projects: Vec<SshProject>,
|
||||
/// Name to use for this server in UI.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nickname: Option<SharedString>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub projects: Vec<SshProject>,
|
||||
/// Name to use for this server in UI.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nickname: Option<String>,
|
||||
// By default Zed will download the binary to the host directly.
|
||||
// If this is set to true, Zed will download the binary to your local machine,
|
||||
// and then upload it over the SSH connection. Useful if your SSH server has
|
||||
// limited outbound internet access.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub upload_binary_over_ssh: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<SshConnection> for SshConnectionOptions {
|
||||
|
@ -102,6 +95,8 @@ impl From<SshConnection> for SshConnectionOptions {
|
|||
port: val.port,
|
||||
password: None,
|
||||
args: Some(val.args),
|
||||
nickname: val.nickname,
|
||||
upload_binary_over_ssh: val.upload_binary_over_ssh.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +109,6 @@ pub struct SshProject {
|
|||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RemoteSettingsContent {
|
||||
pub ssh_connections: Option<Vec<SshConnection>>,
|
||||
pub remote_server: Option<RemoteServerSettings>,
|
||||
}
|
||||
|
||||
impl Settings for SshSettings {
|
||||
|
@ -153,10 +147,10 @@ pub struct SshConnectionModal {
|
|||
impl SshPrompt {
|
||||
pub(crate) fn new(
|
||||
connection_options: &SshConnectionOptions,
|
||||
nickname: Option<SharedString>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let connection_string = connection_options.connection_string().into();
|
||||
let nickname = connection_options.nickname.clone().map(|s| s.into());
|
||||
|
||||
Self {
|
||||
connection_string,
|
||||
|
@ -276,11 +270,10 @@ impl SshConnectionModal {
|
|||
pub(crate) fn new(
|
||||
connection_options: &SshConnectionOptions,
|
||||
paths: Vec<PathBuf>,
|
||||
nickname: Option<SharedString>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
prompt: cx.new_view(|cx| SshPrompt::new(connection_options, nickname, cx)),
|
||||
prompt: cx.new_view(|cx| SshPrompt::new(connection_options, cx)),
|
||||
finished: false,
|
||||
paths,
|
||||
}
|
||||
|
@ -451,13 +444,17 @@ impl remote::SshClientDelegate for SshClientDelegate {
|
|||
fn get_server_binary(
|
||||
&self,
|
||||
platform: SshPlatform,
|
||||
upload_binary_over_ssh: bool,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> oneshot::Receiver<Result<(ServerBinary, SemanticVersion)>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let this = self.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
tx.send(this.get_server_binary_impl(platform, &mut cx).await)
|
||||
.ok();
|
||||
tx.send(
|
||||
this.get_server_binary_impl(platform, upload_binary_over_ssh, &mut cx)
|
||||
.await,
|
||||
)
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
rx
|
||||
|
@ -492,19 +489,14 @@ impl SshClientDelegate {
|
|||
async fn get_server_binary_impl(
|
||||
&self,
|
||||
platform: SshPlatform,
|
||||
upload_binary_via_ssh: bool,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<(ServerBinary, SemanticVersion)> {
|
||||
let (version, release_channel, download_binary_on_host) = cx.update(|cx| {
|
||||
let (version, release_channel) = cx.update(|cx| {
|
||||
let version = AppVersion::global(cx);
|
||||
let channel = ReleaseChannel::global(cx);
|
||||
|
||||
let ssh_settings = SshSettings::get_global(cx);
|
||||
let download_binary_on_host = ssh_settings
|
||||
.remote_server
|
||||
.as_ref()
|
||||
.and_then(|server| server.download_on_host)
|
||||
.unwrap_or(false);
|
||||
(version, channel, download_binary_on_host)
|
||||
(version, channel)
|
||||
})?;
|
||||
|
||||
// In dev mode, build the remote server binary from source
|
||||
|
@ -529,33 +521,7 @@ impl SshClientDelegate {
|
|||
cx,
|
||||
);
|
||||
|
||||
if download_binary_on_host {
|
||||
let (request_url, request_body) = AutoUpdater::get_remote_server_release_url(
|
||||
platform.os,
|
||||
platform.arch,
|
||||
release_channel,
|
||||
current_version,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
anyhow!(
|
||||
"Failed to get remote server binary download url (version: {}, os: {}, arch: {}): {}",
|
||||
version,
|
||||
platform.os,
|
||||
platform.arch,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok((
|
||||
ServerBinary::ReleaseUrl {
|
||||
url: request_url,
|
||||
body: request_body,
|
||||
},
|
||||
version,
|
||||
))
|
||||
} else {
|
||||
if upload_binary_via_ssh {
|
||||
let binary_path = AutoUpdater::download_remote_server_release(
|
||||
platform.os,
|
||||
platform.arch,
|
||||
|
@ -575,6 +541,32 @@ impl SshClientDelegate {
|
|||
})?;
|
||||
|
||||
Ok((ServerBinary::LocalBinary(binary_path), version))
|
||||
} else {
|
||||
let (request_url, request_body) = AutoUpdater::get_remote_server_release_url(
|
||||
platform.os,
|
||||
platform.arch,
|
||||
release_channel,
|
||||
current_version,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
anyhow!(
|
||||
"Failed to get remote server binary download url (version: {}, os: {}, arch: {}): {}",
|
||||
version,
|
||||
platform.os,
|
||||
platform.arch,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok((
|
||||
ServerBinary::ReleaseUrl {
|
||||
url: request_url,
|
||||
body: request_body,
|
||||
},
|
||||
version,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -715,7 +707,6 @@ pub async fn open_ssh_project(
|
|||
paths: Vec<PathBuf>,
|
||||
app_state: Arc<AppState>,
|
||||
open_options: workspace::OpenOptions,
|
||||
nickname: Option<SharedString>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let window = if let Some(window) = open_options.replace_window {
|
||||
|
@ -740,12 +731,11 @@ pub async fn open_ssh_project(
|
|||
let (cancel_tx, cancel_rx) = oneshot::channel();
|
||||
let delegate = window.update(cx, {
|
||||
let connection_options = connection_options.clone();
|
||||
let nickname = nickname.clone();
|
||||
let paths = paths.clone();
|
||||
move |workspace, cx| {
|
||||
cx.activate_window();
|
||||
workspace.toggle_modal(cx, |cx| {
|
||||
SshConnectionModal::new(&connection_options, paths, nickname.clone(), cx)
|
||||
SshConnectionModal::new(&connection_options, paths, cx)
|
||||
});
|
||||
|
||||
let ui = workspace
|
||||
|
|
|
@ -64,6 +64,9 @@ pub struct SshConnectionOptions {
|
|||
pub port: Option<u16>,
|
||||
pub password: Option<String>,
|
||||
pub args: Option<Vec<String>>,
|
||||
|
||||
pub nickname: Option<String>,
|
||||
pub upload_binary_over_ssh: bool,
|
||||
}
|
||||
|
||||
impl SshConnectionOptions {
|
||||
|
@ -141,8 +144,10 @@ impl SshConnectionOptions {
|
|||
host: hostname.to_string(),
|
||||
username: username.clone(),
|
||||
port,
|
||||
password: None,
|
||||
args: Some(args),
|
||||
password: None,
|
||||
nickname: None,
|
||||
upload_binary_over_ssh: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -236,6 +241,7 @@ pub trait SshClientDelegate: Send + Sync {
|
|||
fn get_server_binary(
|
||||
&self,
|
||||
platform: SshPlatform,
|
||||
upload_binary_over_ssh: bool,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> oneshot::Receiver<Result<(ServerBinary, SemanticVersion)>>;
|
||||
fn set_status(&self, status: Option<&str>, cx: &mut AsyncAppContext);
|
||||
|
@ -1705,7 +1711,10 @@ impl SshRemoteConnection {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let (binary, version) = delegate.get_server_binary(platform, cx).await??;
|
||||
let upload_binary_over_ssh = self.socket.connection_options.upload_binary_over_ssh;
|
||||
let (binary, version) = delegate
|
||||
.get_server_binary(platform, upload_binary_over_ssh, cx)
|
||||
.await??;
|
||||
|
||||
let mut remote_version = None;
|
||||
if cfg!(not(debug_assertions)) {
|
||||
|
@ -2336,6 +2345,7 @@ mod fake {
|
|||
fn get_server_binary(
|
||||
&self,
|
||||
_: SshPlatform,
|
||||
_: bool,
|
||||
_: &mut AsyncAppContext,
|
||||
) -> oneshot::Receiver<Result<(ServerBinary, SemanticVersion)>> {
|
||||
unreachable!()
|
||||
|
|
|
@ -23,7 +23,6 @@ test-support = [
|
|||
"gpui/test-support",
|
||||
"http_client/test-support",
|
||||
"project/test-support",
|
||||
"settings/test-support",
|
||||
"util/test-support",
|
||||
"workspace/test-support",
|
||||
]
|
||||
|
@ -43,7 +42,6 @@ recent_projects.workspace = true
|
|||
remote.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
|
|
|
@ -18,10 +18,8 @@ use gpui::{
|
|||
StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use project::{Project, RepositoryEntry};
|
||||
use recent_projects::{OpenRemote, RecentProjects, SshSettings};
|
||||
use remote::SshConnectionOptions;
|
||||
use recent_projects::{OpenRemote, RecentProjects};
|
||||
use rpc::proto;
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
use theme::ActiveTheme;
|
||||
|
@ -29,7 +27,7 @@ use ui::{
|
|||
h_flex, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconName,
|
||||
IconSize, IconWithIndicator, Indicator, PopoverMenu, Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt};
|
||||
use util::ResultExt;
|
||||
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
|
||||
|
@ -268,15 +266,11 @@ impl TitleBar {
|
|||
let options = self.project.read(cx).ssh_connection_options(cx)?;
|
||||
let host: SharedString = options.connection_string().into();
|
||||
|
||||
let nickname = maybe!({
|
||||
SshSettings::get_global(cx)
|
||||
.ssh_connections
|
||||
.as_ref()?
|
||||
.into_iter()
|
||||
.find(|connection| SshConnectionOptions::from((*connection).clone()) == options)
|
||||
.and_then(|connection| connection.nickname.clone())
|
||||
})
|
||||
.unwrap_or_else(|| host.clone());
|
||||
let nickname = options
|
||||
.nickname
|
||||
.clone()
|
||||
.map(|nick| nick.into())
|
||||
.unwrap_or_else(|| host.clone());
|
||||
|
||||
let (indicator_color, meta) = match self.project.read(cx).ssh_connection_state(cx)? {
|
||||
remote::ConnectionState::Connecting => (Color::Info, format!("Connecting to: {host}")),
|
||||
|
|
|
@ -25,7 +25,6 @@ use gpui::{
|
|||
use http_client::{read_proxy_from_env, Uri};
|
||||
use language::LanguageRegistry;
|
||||
use log::LevelFilter;
|
||||
use remote::SshConnectionOptions;
|
||||
use reqwest_client::ReqwestClient;
|
||||
|
||||
use assets::Assets;
|
||||
|
@ -616,26 +615,15 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
|
|||
return;
|
||||
}
|
||||
|
||||
if let Some(connection_info) = request.ssh_connection {
|
||||
if let Some(connection_options) = request.ssh_connection {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let nickname = cx
|
||||
.update(|cx| {
|
||||
SshSettings::get_global(cx).nickname_for(
|
||||
&connection_info.host,
|
||||
connection_info.port,
|
||||
&connection_info.username,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let paths_with_position =
|
||||
derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
|
||||
open_ssh_project(
|
||||
connection_info,
|
||||
connection_options,
|
||||
paths_with_position.into_iter().map(|p| p.path).collect(),
|
||||
app_state,
|
||||
workspace::OpenOptions::default(),
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
|
@ -798,25 +786,10 @@ async fn restore_or_create_workspace(
|
|||
task.await?;
|
||||
}
|
||||
SerializedWorkspaceLocation::Ssh(ssh) => {
|
||||
let args = cx
|
||||
.update(|cx| {
|
||||
SshSettings::get_global(cx).args_for(&ssh.host, ssh.port, &ssh.user)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let nickname = cx
|
||||
.update(|cx| {
|
||||
SshSettings::get_global(cx).nickname_for(&ssh.host, ssh.port, &ssh.user)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let connection_options = SshConnectionOptions {
|
||||
args,
|
||||
host: ssh.host.clone(),
|
||||
username: ssh.user.clone(),
|
||||
port: ssh.port,
|
||||
password: None,
|
||||
};
|
||||
let connection_options = cx.update(|cx| {
|
||||
SshSettings::get_global(cx)
|
||||
.connection_options_for(ssh.host, ssh.port, ssh.user)
|
||||
})?;
|
||||
let app_state = app_state.clone();
|
||||
cx.spawn(move |mut cx| async move {
|
||||
recent_projects::open_ssh_project(
|
||||
|
@ -824,7 +797,6 @@ async fn restore_or_create_workspace(
|
|||
ssh.paths.into_iter().map(PathBuf::from).collect(),
|
||||
app_state,
|
||||
workspace::OpenOptions::default(),
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -881,12 +881,6 @@ pub fn open_new_ssh_project_from_project(
|
|||
return Task::ready(Err(anyhow::anyhow!("Not an ssh project")));
|
||||
};
|
||||
let connection_options = ssh_client.read(cx).connection_options();
|
||||
let nickname = recent_projects::SshSettings::get_global(cx).nickname_for(
|
||||
&connection_options.host,
|
||||
connection_options.port,
|
||||
&connection_options.username,
|
||||
);
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
open_ssh_project(
|
||||
connection_options,
|
||||
|
@ -897,7 +891,6 @@ pub fn open_new_ssh_project_from_project(
|
|||
replace_window: None,
|
||||
env: None,
|
||||
},
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -72,25 +72,24 @@ impl OpenRequest {
|
|||
.ok_or_else(|| anyhow!("missing host in ssh url: {}", file))?
|
||||
.to_string();
|
||||
let username = Some(url.username().to_string()).filter(|s| !s.is_empty());
|
||||
let password = url.password().map(|s| s.to_string());
|
||||
let port = url.port();
|
||||
if !self.open_paths.is_empty() {
|
||||
return Err(anyhow!("cannot open both local and ssh paths"));
|
||||
}
|
||||
let args = SshSettings::get_global(cx).args_for(&host, port, &username);
|
||||
let connection = SshConnectionOptions {
|
||||
username,
|
||||
password,
|
||||
host,
|
||||
let mut connection_options = SshSettings::get_global(cx).connection_options_for(
|
||||
host.clone(),
|
||||
port,
|
||||
args,
|
||||
};
|
||||
username.clone(),
|
||||
);
|
||||
if let Some(password) = url.password() {
|
||||
connection_options.password = Some(password.to_string());
|
||||
}
|
||||
if let Some(ssh_connection) = &self.ssh_connection {
|
||||
if *ssh_connection != connection {
|
||||
if *ssh_connection != connection_options {
|
||||
return Err(anyhow!("cannot open multiple ssh connections"));
|
||||
}
|
||||
}
|
||||
self.ssh_connection = Some(connection);
|
||||
self.ssh_connection = Some(connection_options);
|
||||
self.parse_file_path(url.path());
|
||||
Ok(())
|
||||
}
|
||||
|
@ -374,40 +373,28 @@ async fn open_workspaces(
|
|||
}
|
||||
SerializedWorkspaceLocation::Ssh(ssh) => {
|
||||
let app_state = app_state.clone();
|
||||
let args = cx
|
||||
.update(|cx| {
|
||||
SshSettings::get_global(cx).args_for(&ssh.host, ssh.port, &ssh.user)
|
||||
let connection_options = cx.update(|cx| {
|
||||
SshSettings::get_global(cx)
|
||||
.connection_options_for(ssh.host, ssh.port, ssh.user)
|
||||
});
|
||||
if let Ok(connection_options) = connection_options {
|
||||
cx.spawn(|mut cx| async move {
|
||||
open_ssh_project(
|
||||
connection_options,
|
||||
ssh.paths.into_iter().map(PathBuf::from).collect(),
|
||||
app_state,
|
||||
OpenOptions::default(),
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let connection_options = SshConnectionOptions {
|
||||
args,
|
||||
host: ssh.host.clone(),
|
||||
username: ssh.user.clone(),
|
||||
port: ssh.port,
|
||||
password: None,
|
||||
};
|
||||
let nickname = cx
|
||||
.update(|cx| {
|
||||
SshSettings::get_global(cx).nickname_for(&ssh.host, ssh.port, &ssh.user)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
cx.spawn(|mut cx| async move {
|
||||
open_ssh_project(
|
||||
connection_options,
|
||||
ssh.paths.into_iter().map(PathBuf::from).collect(),
|
||||
app_state,
|
||||
OpenOptions::default(),
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
// We don't set `errored` here, because for ssh projects, the
|
||||
// error is displayed in the window.
|
||||
.detach();
|
||||
// We don't set `errored` here if `open_ssh_project` fails, because for ssh projects, the
|
||||
// error is displayed in the window.
|
||||
} else {
|
||||
errored = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ Remote Development allows you to code at the speed of thought, even when your co
|
|||
|
||||
Remote development requires two computers, your local machine that runs the Zed UI and the remote server which runs a Zed headless server. The two communicate over SSH, so you will need to be able to SSH from your local machine into the remote server to use this feature.
|
||||
|
||||
> **Note:** The original version of remote development sent traffic via Zed's servers. As of Zed v0.157 you can no-longer use this mode.
|
||||
> **Note:** The original version of remote development sent traffic via Zed's servers. As of Zed v0.157 you can no-longer use that mode.
|
||||
|
||||
## Setup
|
||||
|
||||
|
@ -29,7 +29,63 @@ The remote machine must be able to run Zed's server. The following platforms sho
|
|||
- Linux (x86_64 or arm64, we do not yet support 32-bit platforms)
|
||||
- Windows is not yet supported.
|
||||
|
||||
## Settings
|
||||
## Configuration
|
||||
|
||||
The list of remote servers is stored in your settings file {#kb zed::OpenSettings}. You can edit this list using the Remote Projects dialogue {#kb projects::OpenRemote}, which provides some robustness - for example it checks that the connection can be established before writing it to the settings file.
|
||||
|
||||
```json
|
||||
{
|
||||
"ssh_connections": [
|
||||
{
|
||||
"host": "192.168.1.10",
|
||||
"projects": ["~/code/zed/zed"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Zed shells out to the `ssh` on your path, and so it will inherit any configuration you have in `~/.ssh/config` for the given host. That said, if you need to override anything you can configure the following additional options on each connection:
|
||||
|
||||
```json
|
||||
{
|
||||
"ssh_connections": [
|
||||
{
|
||||
"host": "192.168.1.10",
|
||||
"projects": ["~/code/zed/zed"],
|
||||
// any argument to pass to the ssh master process
|
||||
"args": ["-i", "~/.ssh/work_id_file"],
|
||||
"port": 22, // defaults to 22
|
||||
// defaults to your username on your local machine
|
||||
"username": "me"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
There are two additional Zed-specific options per connection, `upload_binary_over_ssh` and `nickname`:
|
||||
|
||||
```json
|
||||
{
|
||||
"ssh_connections": [
|
||||
{
|
||||
"host": "192.168.1.10",
|
||||
"projects": ["~/code/zed/zed"],
|
||||
// by default Zed will download the server binary from the internet on the remote.
|
||||
// When this is true, it'll be downloaded to your laptop and uploaded over SSH.
|
||||
// This is useful when your remote server has restricted internet access.
|
||||
"upload_binary_over_ssh": true,
|
||||
// Shown in the Zed UI to help distinguish multiple hosts.
|
||||
"nickname": "lil-linux"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If you use the command line to open a connection to a host by doing `zed ssh://192.168.1.10/~/.vimrc`, then extra options are read from your settings file by finding the first connection that matches the host/username/port of the URL on the command line.
|
||||
|
||||
Additionally it's worth noting that while you can pass a password on the command line `zed ssh://user:password@host/~`, we do not support writing a password to your settings file. If you're connecting repeatedly to the same host, you should configure key-based authentication.
|
||||
|
||||
## Zed settings
|
||||
|
||||
When opening a remote project there are three relevant settings locations:
|
||||
|
||||
|
|
Loading…
Reference in a new issue