Skip to content

Commit

Permalink
open new buffer (zed-industries#11203)
Browse files Browse the repository at this point in the history
Release Notes:

- Allow creating new untitled buffers in remote projects

TODO:
- Add a Test
- Fix version number check
  • Loading branch information
ConradIrwin authored and luckydye committed May 2, 2024
1 parent 71a3b98 commit 3374705
Show file tree
Hide file tree
Showing 19 changed files with 276 additions and 111 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/activity_indicator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ language.workspace = true
project.workspace = true
smallvec.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

[dev-dependencies]
Expand Down
43 changes: 26 additions & 17 deletions crates/activity_indicator/src/activity_indicator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use project::{LanguageServerProgress, Project};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc};
use ui::prelude::*;
use util::ResultExt;
use workspace::{item::ItemHandle, StatusItemView, Workspace};

actions!(activity_indicator, [ShowErrorMessage]);
Expand Down Expand Up @@ -82,27 +81,37 @@ impl ActivityIndicator {
}
});

cx.subscribe(&this, move |workspace, _, event, cx| match event {
cx.subscribe(&this, move |_, _, event, cx| match event {
Event::ShowError { lsp_name, error } => {
if let Some(buffer) = project
.update(cx, |project, cx| project.create_buffer(error, None, cx))
.log_err()
{
buffer.update(cx, |buffer, cx| {
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
let project = project.clone();
let error = error.clone();
let lsp_name = lsp_name.clone();
cx.spawn(|workspace, mut cx| async move {
let buffer = create_buffer.await?;
buffer.update(&mut cx, |buffer, cx| {
buffer.edit(
[(0..0, format!("Language server error: {}\n\n", lsp_name))],
[(
0..0,
format!("Language server error: {}\n\n{}", lsp_name, error),
)],
None,
cx,
);
});
workspace.add_item_to_active_pane(
Box::new(
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
),
None,
cx,
);
}
})?;
workspace.update(&mut cx, |workspace, cx| {
workspace.add_item_to_active_pane(
Box::new(cx.new_view(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), cx)
})),
None,
cx,
);
})?;

anyhow::Ok(())
})
.detach();
}
})
.detach();
Expand Down
6 changes: 3 additions & 3 deletions crates/auto_update/src/auto_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project
.update(cx, |project, cx| project.create_buffer("", markdown, cx))
.expect("creating buffers on a local workspace always succeeds");
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx)
});
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, body.release_notes)], None, cx)
});
Expand Down
47 changes: 46 additions & 1 deletion crates/collab/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ use tracing::{
};
use util::http::IsahcHttpClient;

use self::connection_pool::VersionedMessage;

pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);

// kubernetes gives terminated pods 10s to shutdown gracefully. After they're gone, we can clean up old resources.
Expand Down Expand Up @@ -465,6 +467,9 @@ impl Server {
.add_request_handler(user_handler(
forward_mutating_project_request::<proto::ApplyCompletionAdditionalEdits>,
))
.add_request_handler(user_handler(
forward_versioned_mutating_project_request::<proto::OpenNewBuffer>,
))
.add_request_handler(user_handler(
forward_mutating_project_request::<proto::ResolveCompletionDocumentation>,
))
Expand Down Expand Up @@ -505,7 +510,7 @@ impl Server {
forward_mutating_project_request::<proto::OnTypeFormatting>,
))
.add_request_handler(user_handler(
forward_mutating_project_request::<proto::SaveBuffer>,
forward_versioned_mutating_project_request::<proto::SaveBuffer>,
))
.add_request_handler(user_handler(
forward_mutating_project_request::<proto::BlameBuffer>,
Expand Down Expand Up @@ -2677,11 +2682,51 @@ where
T: EntityMessage + RequestMessage,
{
let project_id = ProjectId::from_proto(request.remote_entity_id());

let host_connection_id = session
.db()
.await
.host_for_mutating_project_request(project_id, session.connection_id, session.user_id())
.await?;
let payload = session
.peer
.forward_request(session.connection_id, host_connection_id, request)
.await?;
response.send(payload)?;
Ok(())
}

/// forward a project request to the host. These requests are disallowed
/// for guests.
async fn forward_versioned_mutating_project_request<T>(
request: T,
response: Response<T>,
session: UserSession,
) -> Result<()>
where
T: EntityMessage + RequestMessage + VersionedMessage,
{
let project_id = ProjectId::from_proto(request.remote_entity_id());

let host_connection_id = session
.db()
.await
.host_for_mutating_project_request(project_id, session.connection_id, session.user_id())
.await?;
if let Some(host_version) = session
.connection_pool()
.await
.connection(host_connection_id)
.map(|c| c.zed_version)
{
if let Some(min_required_version) = request.required_host_version() {
if min_required_version > host_version {
return Err(anyhow!(ErrorCode::RemoteUpgradeRequired
.with_tag("required", &min_required_version.to_string())))?;
}
}
}

let payload = session
.peer
.forward_request(session.connection_id, host_connection_id, request)
Expand Down
32 changes: 31 additions & 1 deletion crates/collab/src/rpc/connection_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct ConnectedPrincipal {
connection_ids: HashSet<ConnectionId>,
}

#[derive(Debug, Serialize)]
#[derive(Copy, Clone, Debug, Serialize, PartialOrd, PartialEq, Eq, Ord)]
pub struct ZedVersion(pub SemanticVersion);

impl fmt::Display for ZedVersion {
Expand All @@ -34,6 +34,32 @@ impl ZedVersion {
pub fn can_collaborate(&self) -> bool {
self.0 >= SemanticVersion::new(0, 129, 2)
}

pub fn with_save_as() -> ZedVersion {
ZedVersion(SemanticVersion::new(0, 134, 0))
}
}

pub trait VersionedMessage {
fn required_host_version(&self) -> Option<ZedVersion> {
None
}
}

impl VersionedMessage for proto::SaveBuffer {
fn required_host_version(&self) -> Option<ZedVersion> {
if self.new_path.is_some() {
Some(ZedVersion::with_save_as())
} else {
None
}
}
}

impl VersionedMessage for proto::OpenNewBuffer {
fn required_host_version(&self) -> Option<ZedVersion> {
Some(ZedVersion::with_save_as())
}
}

#[derive(Serialize)]
Expand All @@ -50,6 +76,10 @@ impl ConnectionPool {
self.channels.clear();
}

pub fn connection(&mut self, connection_id: ConnectionId) -> Option<&Connection> {
self.connections.get(&connection_id)
}

#[instrument(skip(self))]
pub fn add_connection(
&mut self,
Expand Down
30 changes: 30 additions & 0 deletions crates/collab/src/tests/dev_server_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,33 @@ async fn test_save_as_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::Tes
"remote\nremote\nremote"
);
}

#[gpui::test]
async fn test_new_file_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
let (server, client1) = TestServer::start1(cx1).await;

// Creating a project with a path that does exist should not fail
let (dev_server, remote_workspace) =
create_remote_project(&server, client1.app_state.clone(), cx1, cx2).await;

let mut cx = VisualTestContext::from_window(remote_workspace.into(), cx1);

cx.simulate_keystrokes("cmd-n");
cx.simulate_input("new!");
cx.simulate_keystrokes("cmd-shift-s");
cx.simulate_input("2.txt");
cx.simulate_keystrokes("enter");

cx.executor().run_until_parked();

let title = remote_workspace
.update(&mut cx, |ws, cx| {
ws.active_item(cx).unwrap().tab_description(0, &cx).unwrap()
})
.unwrap();

assert_eq!(title, "2.txt");

let path = Path::new("/remote/2.txt");
assert_eq!(dev_server.fs().load(&path).await.unwrap(), "new!");
}
3 changes: 2 additions & 1 deletion crates/collab/src/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2450,7 +2450,8 @@ async fn test_propagate_saves_and_fs_changes(
});

let new_buffer_a = project_a
.update(cx_a, |p, cx| p.create_buffer("", None, cx))
.update(cx_a, |p, cx| p.create_buffer(cx))
.await
.unwrap();

let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
Expand Down
72 changes: 46 additions & 26 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ use project::{
CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath, ProjectTransaction,
};
use rand::prelude::*;
use rpc::proto::*;
use rpc::{proto::*, ErrorExt};
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -131,7 +131,7 @@ use ui::{
};
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::item::{ItemHandle, PreviewTabsSettings};
use workspace::notifications::NotificationId;
use workspace::notifications::{DetachAndPromptErr, NotificationId};
use workspace::{
searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
};
Expand Down Expand Up @@ -1610,18 +1610,27 @@ impl Editor {
cx: &mut ViewContext<Workspace>,
) {
let project = workspace.project().clone();
if project.read(cx).is_remote() {
cx.propagate();
} else if let Some(buffer) = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.log_err()
{
workspace.add_item_to_active_pane(
Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
None,
cx,
);
}
let create = project.update(cx, |project, cx| project.create_buffer(cx));

cx.spawn(|workspace, mut cx| async move {
let buffer = create.await?;
workspace.update(&mut cx, |workspace, cx| {
workspace.add_item_to_active_pane(
Box::new(
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
),
None,
cx,
)
})
})
.detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
ErrorCode::RemoteUpgradeRequired => Some(format!(
"The remote instance of Zed does not support this yet. It must be upgraded to {}",
e.error_tag("required").unwrap_or("the latest version")
)),
_ => None,
});
}

pub fn new_file_in_direction(
Expand All @@ -1630,18 +1639,29 @@ impl Editor {
cx: &mut ViewContext<Workspace>,
) {
let project = workspace.project().clone();
if project.read(cx).is_remote() {
cx.propagate();
} else if let Some(buffer) = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.log_err()
{
workspace.split_item(
action.0,
Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
cx,
);
}
let create = project.update(cx, |project, cx| project.create_buffer(cx));
let direction = action.0;

cx.spawn(|workspace, mut cx| async move {
let buffer = create.await?;
workspace.update(&mut cx, move |workspace, cx| {
workspace.split_item(
direction,
Box::new(
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
),
cx,
)
})?;
anyhow::Ok(())
})
.detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
ErrorCode::RemoteUpgradeRequired => Some(format!(
"The remote instance of Zed does not support this yet. It must be upgraded to {}",
e.error_tag("required").unwrap_or("the latest version")
)),
_ => None,
});
}

pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
Expand Down
12 changes: 3 additions & 9 deletions crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7356,9 +7356,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;

let buffer = project.update(cx, |project, cx| {
let buffer = project
.create_buffer(&sample_text(16, 8, 'a'), None, cx)
.unwrap();
let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
});
let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
Expand Down Expand Up @@ -7565,12 +7563,8 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {

let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
(
project
.create_buffer("abc\ndef\nghi\njkl\n", None, cx)
.unwrap(),
project
.create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
.unwrap(),
project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
)
});

Expand Down

0 comments on commit 3374705

Please sign in to comment.