Workspace persistence for SSH projects (#17996)

TODOs:

- [x] Add tests to `workspace/src/persistence.rs`
- [x] Add a icon for ssh projects
- [x] Fix all `TODO` comments
- [x] Use `port` if it's passed in the ssh connection options

In next PRs:
- Make sure unsaved buffers are persisted/restored, along with other
items/layout
- Handle multiple paths/worktrees correctly


Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
This commit is contained in:
Thorsten Ball 2024-09-19 17:51:28 +02:00 committed by GitHub
parent 7d0a7541bf
commit e9f2e72ff0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 592 additions and 141 deletions

View file

@ -11,6 +11,7 @@ use db::sqlez::{
};
use gpui::{AsyncWindowContext, Model, View, WeakView};
use project::Project;
use remote::ssh_session::SshProjectId;
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
@ -20,6 +21,69 @@ use ui::SharedString;
use util::ResultExt;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct SerializedSshProject {
pub id: SshProjectId,
pub host: String,
pub port: Option<u16>,
pub path: String,
pub user: Option<String>,
}
impl SerializedSshProject {
pub fn ssh_url(&self) -> String {
let mut result = String::from("ssh://");
if let Some(user) = &self.user {
result.push_str(user);
result.push('@');
}
result.push_str(&self.host);
if let Some(port) = &self.port {
result.push(':');
result.push_str(&port.to_string());
}
result.push_str(&self.path);
result
}
}
impl StaticColumnCount for SerializedSshProject {
fn column_count() -> usize {
5
}
}
impl Bind for &SerializedSshProject {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let next_index = statement.bind(&self.id.0, start_index)?;
let next_index = statement.bind(&self.host, next_index)?;
let next_index = statement.bind(&self.port, next_index)?;
let next_index = statement.bind(&self.path, next_index)?;
statement.bind(&self.user, next_index)
}
}
impl Column for SerializedSshProject {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let id = statement.column_int64(start_index)?;
let host = statement.column_text(start_index + 1)?.to_string();
let (port, _) = Option::<u16>::column(statement, start_index + 2)?;
let path = statement.column_text(start_index + 3)?.to_string();
let (user, _) = Option::<String>::column(statement, start_index + 4)?;
Ok((
Self {
id: SshProjectId(id as u64),
host,
port,
path,
user,
},
start_index + 5,
))
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct SerializedDevServerProject {
pub id: DevServerProjectId,
@ -58,7 +122,6 @@ impl Column for LocalPaths {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let path_blob = statement.column_blob(start_index)?;
let paths: Arc<Vec<PathBuf>> = if path_blob.is_empty() {
println!("path blog is empty");
Default::default()
} else {
bincode::deserialize(path_blob).context("Bincode deserialization of paths failed")?
@ -146,6 +209,7 @@ impl Column for SerializedDevServerProject {
#[derive(Debug, PartialEq, Clone)]
pub enum SerializedWorkspaceLocation {
Local(LocalPaths, LocalPathsOrder),
Ssh(SerializedSshProject),
DevServer(SerializedDevServerProject),
}