Fix renames over language server for SSH remoting (#17897)

Release Notes:

- ssh remoting: Fix rename over language server

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Conrad Irwin 2024-09-16 16:20:17 -06:00 committed by GitHub
parent 01bb10f518
commit e66ea9e5d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 505 additions and 329 deletions

View file

@ -46,7 +46,9 @@ gpui = { workspace = true, features = ["test-support"] }
http_client = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
node_runtime = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] }
remote = { workspace = true, features = ["test-support"] }
lsp = { workspace = true, features=["test-support"] }
serde_json.workspace = true

View file

@ -1,10 +1,13 @@
use anyhow::{anyhow, Result};
use fs::Fs;
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
use language::LanguageRegistry;
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
use project::{
buffer_store::BufferStore, project_settings::SettingsObserver, search::SearchQuery,
worktree_store::WorktreeStore, LspStore, ProjectPath, WorktreeId,
buffer_store::{BufferStore, BufferStoreEvent},
project_settings::SettingsObserver,
search::SearchQuery,
worktree_store::WorktreeStore,
LspStore, ProjectPath, WorktreeId,
};
use remote::SshSession;
use rpc::{
@ -26,6 +29,7 @@ pub struct HeadlessProject {
pub lsp_store: Model<LspStore>,
pub settings_observer: Model<SettingsObserver>,
pub next_entry_id: Arc<AtomicUsize>,
pub languages: Arc<LanguageRegistry>,
}
impl HeadlessProject {
@ -60,7 +64,7 @@ impl HeadlessProject {
buffer_store.clone(),
worktree_store.clone(),
environment,
languages,
languages.clone(),
None,
fs.clone(),
cx,
@ -69,6 +73,17 @@ impl HeadlessProject {
lsp_store
});
cx.subscribe(
&buffer_store,
|_this, _buffer_store, event, cx| match event {
BufferStoreEvent::BufferAdded(buffer) => {
cx.subscribe(buffer, Self::on_buffer_event).detach();
}
_ => {}
},
)
.detach();
let client: AnyProtoClient = session.clone().into();
session.subscribe_to_entity(SSH_PROJECT_ID, &worktree_store);
@ -103,6 +118,26 @@ impl HeadlessProject {
buffer_store,
lsp_store,
next_entry_id: Default::default(),
languages,
}
}
fn on_buffer_event(
&mut self,
buffer: Model<Buffer>,
event: &BufferEvent,
cx: &mut ModelContext<Self>,
) {
match event {
BufferEvent::Operation(op) => cx
.background_executor()
.spawn(self.session.request(proto::UpdateBuffer {
project_id: SSH_PROJECT_ID,
buffer_id: buffer.read(cx).remote_id().to_proto(),
operations: vec![serialize_operation(op)],
}))
.detach(),
_ => {}
}
}

View file

@ -6,8 +6,9 @@ use gpui::{Context, Model, TestAppContext};
use http_client::FakeHttpClient;
use language::{
language_settings::{all_language_settings, AllLanguageSettings},
Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry,
Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerName,
};
use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind};
use node_runtime::FakeNodeRuntime;
use project::{
search::{SearchQuery, SearchResult},
@ -317,6 +318,15 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
},
)
});
let mut fake_lsp = server_cx.update(|cx| {
headless.read(cx).languages.register_fake_language_server(
LanguageServerName("rust-analyzer".into()),
Default::default(),
None,
)
});
cx.run_until_parked();
let worktree_id = project
@ -339,6 +349,8 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
.unwrap();
cx.run_until_parked();
let fake_lsp = fake_lsp.next().await.unwrap();
cx.read(|cx| {
let file = buffer.read(cx).file();
assert_eq!(
@ -370,6 +382,62 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
let lsp_store = headless.read(cx).lsp_store.read(cx);
assert_eq!(lsp_store.as_local().unwrap().language_servers.len(), 1);
});
fake_lsp.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
Ok(Some(CompletionResponse::Array(vec![lsp::CompletionItem {
label: "boop".to_string(),
..Default::default()
}])))
});
let result = project
.update(cx, |project, cx| {
project.completions(
&buffer,
0,
CompletionContext {
trigger_kind: CompletionTriggerKind::INVOKED,
trigger_character: None,
},
cx,
)
})
.await
.unwrap();
assert_eq!(
result.into_iter().map(|c| c.label.text).collect::<Vec<_>>(),
vec!["boop".to_string()]
);
fake_lsp.handle_request::<lsp::request::Rename, _, _>(|_, _| async move {
Ok(Some(lsp::WorkspaceEdit {
changes: Some(
[(
lsp::Url::from_file_path("/code/project1/src/lib.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 6)),
"two".to_string(),
)],
)]
.into_iter()
.collect(),
),
..Default::default()
}))
});
project
.update(cx, |project, cx| {
project.perform_rename(buffer.clone(), 3, "two".to_string(), true, cx)
})
.await
.unwrap();
cx.run_until_parked();
buffer.update(cx, |buffer, _| {
assert_eq!(buffer.text(), "fn two() -> usize { 1 }")
})
}
fn init_logger() {