Assign buffer's completion triggers from LSP capabilities
Also, make LanguageServer::new() async. The future resolves once the server is initialized.
This commit is contained in:
parent
317a1bb07b
commit
4cb4b99c56
8 changed files with 345 additions and 368 deletions
|
@ -186,7 +186,7 @@ impl UserStore {
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Arc<User>>> {
|
) -> Task<Result<Arc<User>>> {
|
||||||
if let Some(user) = self.users.get(&user_id).cloned() {
|
if let Some(user) = self.users.get(&user_id).cloned() {
|
||||||
return cx.spawn_weak(|_, _| async move { Ok(user) });
|
return cx.foreground().spawn(async move { Ok(user) });
|
||||||
}
|
}
|
||||||
|
|
||||||
let load_users = self.load_users(vec![user_id], cx);
|
let load_users = self.load_users(vec![user_id], cx);
|
||||||
|
|
|
@ -5912,9 +5912,9 @@ pub fn styled_runs_for_code_label<'a>(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use language::LanguageConfig;
|
use language::{LanguageConfig, LanguageServerConfig};
|
||||||
use lsp::FakeLanguageServer;
|
use lsp::FakeLanguageServer;
|
||||||
use project::{FakeFs, ProjectPath};
|
use project::FakeFs;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||||
use text::Point;
|
use text::Point;
|
||||||
|
@ -8196,18 +8196,24 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_completion(cx: &mut gpui::TestAppContext) {
|
async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||||
let settings = cx.read(Settings::test);
|
let settings = cx.read(Settings::test);
|
||||||
let (language_server, mut fake) = cx.update(|cx| {
|
|
||||||
lsp::LanguageServer::fake_with_capabilities(
|
let (mut language_server_config, mut fake_servers) = LanguageServerConfig::fake();
|
||||||
lsp::ServerCapabilities {
|
language_server_config.set_fake_capabilities(lsp::ServerCapabilities {
|
||||||
completion_provider: Some(lsp::CompletionOptions {
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
|
trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "Rust".into(),
|
||||||
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
language_server: Some(language_server_config),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::language()),
|
||||||
|
));
|
||||||
|
|
||||||
let text = "
|
let text = "
|
||||||
one
|
one
|
||||||
|
@ -8217,28 +8223,26 @@ mod tests {
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background().clone());
|
let fs = FakeFs::new(cx.background().clone());
|
||||||
fs.insert_file("/file", text).await;
|
fs.insert_file("/file.rs", text).await;
|
||||||
|
|
||||||
let project = Project::test(fs, cx);
|
let project = Project::test(fs, cx);
|
||||||
|
project.update(cx, |project, _| project.languages().add(language));
|
||||||
|
|
||||||
let (worktree, relative_path) = project
|
let worktree_id = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.find_or_create_local_worktree("/file", true, cx)
|
project.find_or_create_local_worktree("/file.rs", true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap()
|
||||||
let project_path = ProjectPath {
|
.0
|
||||||
worktree_id: worktree.read_with(cx, |worktree, _| worktree.id()),
|
.read_with(cx, |tree, _| tree.id());
|
||||||
path: relative_path.into(),
|
|
||||||
};
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))
|
.update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let mut fake_server = fake_servers.next().await.unwrap();
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
buffer.next_notification(&cx).await;
|
|
||||||
|
|
||||||
let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx));
|
let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
@ -8248,8 +8252,8 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
handle_completion_request(
|
handle_completion_request(
|
||||||
&mut fake,
|
&mut fake_server,
|
||||||
"/file",
|
"/file.rs",
|
||||||
Point::new(0, 4),
|
Point::new(0, 4),
|
||||||
vec![
|
vec![
|
||||||
(Point::new(0, 4)..Point::new(0, 4), "first_completion"),
|
(Point::new(0, 4)..Point::new(0, 4), "first_completion"),
|
||||||
|
@ -8279,7 +8283,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
handle_resolve_completion_request(
|
handle_resolve_completion_request(
|
||||||
&mut fake,
|
&mut fake_server,
|
||||||
Some((Point::new(2, 5)..Point::new(2, 5), "\nadditional edit")),
|
Some((Point::new(2, 5)..Point::new(2, 5), "\nadditional edit")),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -8312,8 +8316,8 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
handle_completion_request(
|
handle_completion_request(
|
||||||
&mut fake,
|
&mut fake_server,
|
||||||
"/file",
|
"/file.rs",
|
||||||
Point::new(2, 7),
|
Point::new(2, 7),
|
||||||
vec![
|
vec![
|
||||||
(Point::new(2, 6)..Point::new(2, 7), "fourth_completion"),
|
(Point::new(2, 6)..Point::new(2, 7), "fourth_completion"),
|
||||||
|
@ -8331,8 +8335,8 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
handle_completion_request(
|
handle_completion_request(
|
||||||
&mut fake,
|
&mut fake_server,
|
||||||
"/file",
|
"/file.rs",
|
||||||
Point::new(2, 8),
|
Point::new(2, 8),
|
||||||
vec![
|
vec![
|
||||||
(Point::new(2, 6)..Point::new(2, 8), "fourth_completion"),
|
(Point::new(2, 6)..Point::new(2, 8), "fourth_completion"),
|
||||||
|
@ -8361,7 +8365,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
apply_additional_edits
|
apply_additional_edits
|
||||||
});
|
});
|
||||||
handle_resolve_completion_request(&mut fake, None).await;
|
handle_resolve_completion_request(&mut fake_server, None).await;
|
||||||
apply_additional_edits.await.unwrap();
|
apply_additional_edits.await.unwrap();
|
||||||
|
|
||||||
async fn handle_completion_request(
|
async fn handle_completion_request(
|
||||||
|
|
|
@ -203,79 +203,6 @@ pub trait LocalFile: File {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub struct FakeFile {
|
|
||||||
pub path: Arc<Path>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
impl FakeFile {
|
|
||||||
pub fn new(path: impl AsRef<Path>) -> Self {
|
|
||||||
Self {
|
|
||||||
path: path.as_ref().into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
impl File for FakeFile {
|
|
||||||
fn as_local(&self) -> Option<&dyn LocalFile> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mtime(&self) -> SystemTime {
|
|
||||||
SystemTime::UNIX_EPOCH
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(&self) -> &Arc<Path> {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_path(&self, _: &AppContext) -> PathBuf {
|
|
||||||
self.path.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self, _: &AppContext) -> OsString {
|
|
||||||
self.path.file_name().unwrap().to_os_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_deleted(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save(
|
|
||||||
&self,
|
|
||||||
_: u64,
|
|
||||||
_: Rope,
|
|
||||||
_: clock::Global,
|
|
||||||
cx: &mut MutableAppContext,
|
|
||||||
) -> Task<Result<(clock::Global, SystemTime)>> {
|
|
||||||
cx.spawn(|_| async move { Ok((Default::default(), SystemTime::UNIX_EPOCH)) })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_proto(&self) -> rpc::proto::File {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
impl LocalFile for FakeFile {
|
|
||||||
fn abs_path(&self, _: &AppContext) -> PathBuf {
|
|
||||||
self.path.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load(&self, cx: &AppContext) -> Task<Result<String>> {
|
|
||||||
cx.background().spawn(async move { Ok(Default::default()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn buffer_reloaded(&self, _: u64, _: &clock::Global, _: SystemTime, _: &mut MutableAppContext) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct QueryCursorHandle(Option<QueryCursor>);
|
pub(crate) struct QueryCursorHandle(Option<QueryCursor>);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -1435,8 +1362,21 @@ impl Buffer {
|
||||||
redone
|
redone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_completion_triggers(&mut self, triggers: Vec<String>, cx: &mut ModelContext<Self>) {
|
||||||
|
self.completion_triggers = triggers.clone();
|
||||||
|
let lamport_timestamp = self.text.lamport_clock.tick();
|
||||||
|
self.send_operation(
|
||||||
|
Operation::UpdateCompletionTriggers {
|
||||||
|
triggers,
|
||||||
|
lamport_timestamp,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn completion_triggers(&self) -> &[String] {
|
pub fn completion_triggers(&self) -> &[String] {
|
||||||
todo!()
|
&self.completion_triggers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -247,29 +247,41 @@ impl LanguageRegistry {
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
) -> Option<Task<Result<Arc<lsp::LanguageServer>>>> {
|
) -> Option<Task<Result<Arc<lsp::LanguageServer>>>> {
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
if let Some(config) = &language.config.language_server {
|
if language
|
||||||
if let Some(fake_config) = &config.fake_config {
|
.config
|
||||||
let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities(
|
.language_server
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|config| config.fake_config.as_ref())
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
let language = language.clone();
|
||||||
|
return Some(cx.spawn(|mut cx| async move {
|
||||||
|
let fake_config = language
|
||||||
|
.config
|
||||||
|
.language_server
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.fake_config
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
let (server, mut fake_server) = cx
|
||||||
|
.update(|cx| {
|
||||||
|
lsp::LanguageServer::fake_with_capabilities(
|
||||||
fake_config.capabilities.clone(),
|
fake_config.capabilities.clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
if let Some(initalizer) = &fake_config.initializer {
|
if let Some(initalizer) = &fake_config.initializer {
|
||||||
initalizer(&mut fake_server);
|
initalizer(&mut fake_server);
|
||||||
}
|
}
|
||||||
|
fake_config
|
||||||
let servers_tx = fake_config.servers_tx.clone();
|
.servers_tx
|
||||||
let initialized = server.capabilities();
|
.clone()
|
||||||
cx.background()
|
.unbounded_send(fake_server)
|
||||||
.spawn(async move {
|
.ok();
|
||||||
if initialized.await.is_some() {
|
Ok(server.clone())
|
||||||
servers_tx.unbounded_send(fake_server).ok();
|
}));
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
return Some(Task::ready(Ok(server.clone())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let download_dir = self
|
let download_dir = self
|
||||||
|
@ -310,7 +322,8 @@ impl LanguageRegistry {
|
||||||
adapter.initialization_options(),
|
adapter.initialization_options(),
|
||||||
&root_path,
|
&root_path,
|
||||||
background,
|
background,
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use collections::HashMap;
|
||||||
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite};
|
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite};
|
||||||
use gpui::{executor, Task};
|
use gpui::{executor, Task};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use postage::{barrier, prelude::Stream, watch};
|
use postage::{barrier, prelude::Stream};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, value::RawValue, Value};
|
use serde_json::{json, value::RawValue, Value};
|
||||||
use smol::{
|
use smol::{
|
||||||
|
@ -34,12 +34,11 @@ type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
next_id: AtomicUsize,
|
next_id: AtomicUsize,
|
||||||
outbound_tx: channel::Sender<Vec<u8>>,
|
outbound_tx: channel::Sender<Vec<u8>>,
|
||||||
capabilities: watch::Receiver<Option<ServerCapabilities>>,
|
capabilities: ServerCapabilities,
|
||||||
notification_handlers: Arc<RwLock<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<RwLock<HashMap<&'static str, NotificationHandler>>>,
|
||||||
response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
|
response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
|
||||||
executor: Arc<executor::Background>,
|
executor: Arc<executor::Background>,
|
||||||
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
|
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
|
||||||
initialized: barrier::Receiver,
|
|
||||||
output_done_rx: Mutex<Option<barrier::Receiver>>,
|
output_done_rx: Mutex<Option<barrier::Receiver>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +99,7 @@ struct Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn new(
|
pub async fn new(
|
||||||
binary_path: &Path,
|
binary_path: &Path,
|
||||||
args: &[&str],
|
args: &[&str],
|
||||||
options: Option<Value>,
|
options: Option<Value>,
|
||||||
|
@ -116,10 +115,10 @@ impl LanguageServer {
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
let stdin = server.stdin.take().unwrap();
|
let stdin = server.stdin.take().unwrap();
|
||||||
let stdout = server.stdout.take().unwrap();
|
let stdout = server.stdout.take().unwrap();
|
||||||
Self::new_internal(stdin, stdout, root_path, options, background)
|
Self::new_internal(stdin, stdout, root_path, options, background).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_internal<Stdin, Stdout>(
|
async fn new_internal<Stdin, Stdout>(
|
||||||
stdin: Stdin,
|
stdin: Stdin,
|
||||||
stdout: Stdout,
|
stdout: Stdout,
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
|
@ -215,42 +214,21 @@ impl LanguageServer {
|
||||||
.log_err()
|
.log_err()
|
||||||
});
|
});
|
||||||
|
|
||||||
let (initialized_tx, initialized_rx) = barrier::channel();
|
let mut this = Arc::new(Self {
|
||||||
let (mut capabilities_tx, capabilities_rx) = watch::channel();
|
|
||||||
let this = Arc::new(Self {
|
|
||||||
notification_handlers,
|
notification_handlers,
|
||||||
response_handlers,
|
response_handlers,
|
||||||
capabilities: capabilities_rx,
|
capabilities: Default::default(),
|
||||||
next_id: Default::default(),
|
next_id: Default::default(),
|
||||||
outbound_tx,
|
outbound_tx,
|
||||||
executor: executor.clone(),
|
executor: executor.clone(),
|
||||||
io_tasks: Mutex::new(Some((input_task, output_task))),
|
io_tasks: Mutex::new(Some((input_task, output_task))),
|
||||||
initialized: initialized_rx,
|
|
||||||
output_done_rx: Mutex::new(Some(output_done_rx)),
|
output_done_rx: Mutex::new(Some(output_done_rx)),
|
||||||
});
|
});
|
||||||
|
|
||||||
let root_uri = Url::from_file_path(root_path).map_err(|_| anyhow!("invalid root path"))?;
|
let root_uri = Url::from_file_path(root_path).map_err(|_| anyhow!("invalid root path"))?;
|
||||||
|
|
||||||
executor
|
executor
|
||||||
.spawn({
|
.spawn(async move {
|
||||||
let this = this.clone();
|
|
||||||
async move {
|
|
||||||
if let Some(capabilities) = this.init(root_uri, options).log_err().await {
|
|
||||||
*capabilities_tx.borrow_mut() = Some(capabilities);
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(initialized_tx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Ok(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn init(
|
|
||||||
self: Arc<Self>,
|
|
||||||
root_uri: Url,
|
|
||||||
options: Option<Value>,
|
|
||||||
) -> Result<ServerCapabilities> {
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let params = InitializeParams {
|
let params = InitializeParams {
|
||||||
process_id: Default::default(),
|
process_id: Default::default(),
|
||||||
|
@ -305,19 +283,20 @@ impl LanguageServer {
|
||||||
locale: Default::default(),
|
locale: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let this = self.clone();
|
|
||||||
let request = Self::request_internal::<request::Initialize>(
|
let request = Self::request_internal::<request::Initialize>(
|
||||||
&this.next_id,
|
&this.next_id,
|
||||||
&this.response_handlers,
|
&this.response_handlers,
|
||||||
&this.outbound_tx,
|
&this.outbound_tx,
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
let response = request.await?;
|
Arc::get_mut(&mut this).unwrap().capabilities = request.await?.capabilities;
|
||||||
Self::notify_internal::<notification::Initialized>(
|
Self::notify_internal::<notification::Initialized>(
|
||||||
&this.outbound_tx,
|
&this.outbound_tx,
|
||||||
InitializedParams {},
|
InitializedParams {},
|
||||||
)?;
|
)?;
|
||||||
Ok(response.capabilities)
|
Ok(this)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
|
pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
|
||||||
|
@ -378,16 +357,8 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn capabilities(&self) -> impl 'static + Future<Output = Option<ServerCapabilities>> {
|
pub fn capabilities(&self) -> &ServerCapabilities {
|
||||||
let mut rx = self.capabilities.clone();
|
&self.capabilities
|
||||||
async move {
|
|
||||||
loop {
|
|
||||||
let value = rx.recv().await?;
|
|
||||||
if value.is_some() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request<T: request::Request>(
|
pub fn request<T: request::Request>(
|
||||||
|
@ -399,7 +370,6 @@ impl LanguageServer {
|
||||||
{
|
{
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
async move {
|
async move {
|
||||||
this.initialized.clone().recv().await;
|
|
||||||
Self::request_internal::<T>(
|
Self::request_internal::<T>(
|
||||||
&this.next_id,
|
&this.next_id,
|
||||||
&this.response_handlers,
|
&this.response_handlers,
|
||||||
|
@ -452,16 +422,8 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify<T: notification::Notification>(
|
pub fn notify<T: notification::Notification>(&self, params: T::Params) -> Result<()> {
|
||||||
self: &Arc<Self>,
|
Self::notify_internal::<T>(&self.outbound_tx, params)
|
||||||
params: T::Params,
|
|
||||||
) -> impl Future<Output = Result<()>> {
|
|
||||||
let this = self.clone();
|
|
||||||
async move {
|
|
||||||
this.initialized.clone().recv().await;
|
|
||||||
Self::notify_internal::<T>(&this.outbound_tx, params)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notify_internal<T: notification::Notification>(
|
fn notify_internal<T: notification::Notification>(
|
||||||
|
@ -530,14 +492,16 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fake(cx: &mut gpui::MutableAppContext) -> (Arc<Self>, FakeLanguageServer) {
|
pub fn fake(
|
||||||
|
cx: &mut gpui::MutableAppContext,
|
||||||
|
) -> impl Future<Output = (Arc<Self>, FakeLanguageServer)> {
|
||||||
Self::fake_with_capabilities(Self::full_capabilities(), cx)
|
Self::fake_with_capabilities(Self::full_capabilities(), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fake_with_capabilities(
|
pub fn fake_with_capabilities(
|
||||||
capabilities: ServerCapabilities,
|
capabilities: ServerCapabilities,
|
||||||
cx: &mut gpui::MutableAppContext,
|
cx: &mut gpui::MutableAppContext,
|
||||||
) -> (Arc<Self>, FakeLanguageServer) {
|
) -> impl Future<Output = (Arc<Self>, FakeLanguageServer)> {
|
||||||
let (stdin_writer, stdin_reader) = async_pipe::pipe();
|
let (stdin_writer, stdin_reader) = async_pipe::pipe();
|
||||||
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
||||||
|
|
||||||
|
@ -550,18 +514,17 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let server = Self::new_internal(
|
let executor = cx.background().clone();
|
||||||
stdin_writer,
|
async move {
|
||||||
stdout_reader,
|
let server =
|
||||||
Path::new("/"),
|
Self::new_internal(stdin_writer, stdout_reader, Path::new("/"), None, executor)
|
||||||
None,
|
.await
|
||||||
cx.background().clone(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
(server, fake)
|
(server, fake)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
impl FakeLanguageServer {
|
impl FakeLanguageServer {
|
||||||
|
@ -758,7 +721,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_fake(cx: &mut TestAppContext) {
|
async fn test_fake(cx: &mut TestAppContext) {
|
||||||
let (server, mut fake) = cx.update(LanguageServer::fake);
|
let (server, mut fake) = cx.update(LanguageServer::fake).await;
|
||||||
|
|
||||||
let (message_tx, message_rx) = channel::unbounded();
|
let (message_tx, message_rx) = channel::unbounded();
|
||||||
let (diagnostics_tx, diagnostics_rx) = channel::unbounded();
|
let (diagnostics_tx, diagnostics_rx) = channel::unbounded();
|
||||||
|
@ -782,7 +745,6 @@ mod tests {
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
.await
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fake.receive_notification::<notification::DidOpenTextDocument>()
|
fake.receive_notification::<notification::DidOpenTextDocument>()
|
||||||
|
|
|
@ -959,6 +959,7 @@ impl Project {
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let buffer = buffer_handle.read(cx);
|
let buffer = buffer_handle.read(cx);
|
||||||
|
let buffer_language_name = buffer.language().map(|l| l.name().clone());
|
||||||
if let Some(file) = File::from_dyn(buffer.file()) {
|
if let Some(file) = File::from_dyn(buffer.file()) {
|
||||||
let worktree_id = file.worktree_id(cx);
|
let worktree_id = file.worktree_id(cx);
|
||||||
if file.is_local() {
|
if file.is_local() {
|
||||||
|
@ -977,14 +978,6 @@ impl Project {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
for lang_server in self.language_servers_for_worktree(worktree_id) {
|
|
||||||
notifications.push(
|
|
||||||
lang_server.notify::<lsp::notification::DidOpenTextDocument>(
|
|
||||||
did_open_text_document.clone(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(local_worktree) = file.worktree.read(cx).as_local() {
|
if let Some(local_worktree) = file.worktree.read(cx).as_local() {
|
||||||
if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
|
if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
|
||||||
self.update_buffer_diagnostics(&buffer_handle, diagnostics, None, cx)
|
self.update_buffer_diagnostics(&buffer_handle, diagnostics, None, cx)
|
||||||
|
@ -992,34 +985,46 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (language_name, server) in self.language_servers_for_worktree(worktree_id) {
|
||||||
|
notifications.push(server.notify::<lsp::notification::DidOpenTextDocument>(
|
||||||
|
did_open_text_document.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
if Some(language_name) == buffer_language_name.as_deref() {
|
||||||
|
buffer_handle.update(cx, |buffer, cx| {
|
||||||
|
buffer.set_completion_triggers(
|
||||||
|
server
|
||||||
|
.capabilities()
|
||||||
|
.completion_provider
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|provider| provider.trigger_characters.clone())
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.observe_release(buffer_handle, |this, buffer, cx| {
|
cx.observe_release(buffer_handle, |this, buffer, cx| {
|
||||||
if let Some(file) = File::from_dyn(buffer.file()) {
|
if let Some(file) = File::from_dyn(buffer.file()) {
|
||||||
let worktree_id = file.worktree_id(cx);
|
let worktree_id = file.worktree_id(cx);
|
||||||
if file.is_local() {
|
if file.is_local() {
|
||||||
let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
|
let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
|
||||||
let mut notifications = Vec::new();
|
for (_, server) in this.language_servers_for_worktree(worktree_id) {
|
||||||
for lang_server in this.language_servers_for_worktree(worktree_id) {
|
server
|
||||||
notifications.push(
|
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||||
lang_server.notify::<lsp::notification::DidCloseTextDocument>(
|
|
||||||
lsp::DidCloseTextDocumentParams {
|
lsp::DidCloseTextDocumentParams {
|
||||||
text_document: lsp::TextDocumentIdentifier::new(
|
text_document: lsp::TextDocumentIdentifier::new(
|
||||||
uri.clone(),
|
uri.clone(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
);
|
.log_err();
|
||||||
}
|
}
|
||||||
cx.background()
|
|
||||||
.spawn(futures::future::try_join_all(notifications))
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.background()
|
|
||||||
.spawn(futures::future::try_join_all(notifications))
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1077,17 +1082,11 @@ impl Project {
|
||||||
|
|
||||||
buffer_snapshots.push((next_version, next_snapshot));
|
buffer_snapshots.push((next_version, next_snapshot));
|
||||||
|
|
||||||
let mut notifications = Vec::new();
|
for (_, server) in self.language_servers_for_worktree(worktree_id) {
|
||||||
for lang_server in self.language_servers_for_worktree(worktree_id) {
|
server
|
||||||
notifications.push(
|
.notify::<lsp::notification::DidChangeTextDocument>(changes.clone())
|
||||||
lang_server
|
.log_err();
|
||||||
.notify::<lsp::notification::DidChangeTextDocument>(changes.clone()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.background()
|
|
||||||
.spawn(futures::future::try_join_all(notifications))
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
BufferEvent::Saved => {
|
BufferEvent::Saved => {
|
||||||
let file = File::from_dyn(buffer.read(cx).file())?;
|
let file = File::from_dyn(buffer.read(cx).file())?;
|
||||||
|
@ -1097,21 +1096,16 @@ impl Project {
|
||||||
uri: lsp::Url::from_file_path(abs_path).unwrap(),
|
uri: lsp::Url::from_file_path(abs_path).unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut notifications = Vec::new();
|
for (_, server) in self.language_servers_for_worktree(worktree_id) {
|
||||||
for lang_server in self.language_servers_for_worktree(worktree_id) {
|
server
|
||||||
notifications.push(
|
.notify::<lsp::notification::DidSaveTextDocument>(
|
||||||
lang_server.notify::<lsp::notification::DidSaveTextDocument>(
|
|
||||||
lsp::DidSaveTextDocumentParams {
|
lsp::DidSaveTextDocumentParams {
|
||||||
text_document: text_document.clone(),
|
text_document: text_document.clone(),
|
||||||
text: None,
|
text: None,
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
);
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.background()
|
|
||||||
.spawn(futures::future::try_join_all(notifications))
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -1122,11 +1116,11 @@ impl Project {
|
||||||
fn language_servers_for_worktree(
|
fn language_servers_for_worktree(
|
||||||
&self,
|
&self,
|
||||||
worktree_id: WorktreeId,
|
worktree_id: WorktreeId,
|
||||||
) -> impl Iterator<Item = &Arc<LanguageServer>> {
|
) -> impl Iterator<Item = (&str, &Arc<LanguageServer>)> {
|
||||||
self.language_servers.iter().filter_map(
|
self.language_servers.iter().filter_map(
|
||||||
move |((lang_server_worktree_id, _), lang_server)| {
|
move |((language_server_worktree_id, language_name), server)| {
|
||||||
if *lang_server_worktree_id == worktree_id {
|
if *language_server_worktree_id == worktree_id {
|
||||||
Some(lang_server)
|
Some((language_name.as_ref(), server))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1182,22 +1176,25 @@ impl Project {
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
let language_server = language_server?.await.log_err()?;
|
let language_server = language_server?.await.log_err()?;
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
let mut open_notifications = Vec::new();
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.language_servers.insert(key, language_server.clone());
|
this.language_servers.insert(key, language_server.clone());
|
||||||
|
|
||||||
for buffer in this.opened_buffers.values() {
|
for buffer in this.opened_buffers.values() {
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(buffer_handle) = buffer.upgrade(cx) {
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer_handle.read(cx);
|
||||||
if let Some(file) = File::from_dyn(buffer.file()) {
|
let file = File::from_dyn(buffer.file())?;
|
||||||
if let Some(file) = file.as_local() {
|
if file.worktree.read(cx).id() != worktree_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the language server about every open buffer in the worktree.
|
||||||
|
let file = file.as_local()?;
|
||||||
let versions = this
|
let versions = this
|
||||||
.buffer_snapshots
|
.buffer_snapshots
|
||||||
.entry(buffer.remote_id())
|
.entry(buffer.remote_id())
|
||||||
.or_insert_with(|| vec![(0, buffer.text_snapshot())]);
|
.or_insert_with(|| vec![(0, buffer.text_snapshot())]);
|
||||||
let (version, initial_snapshot) = versions.last().unwrap();
|
let (version, initial_snapshot) = versions.last().unwrap();
|
||||||
let uri =
|
let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
|
||||||
lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
|
|
||||||
open_notifications.push(
|
|
||||||
language_server
|
language_server
|
||||||
.notify::<lsp::notification::DidOpenTextDocument>(
|
.notify::<lsp::notification::DidOpenTextDocument>(
|
||||||
lsp::DidOpenTextDocumentParams {
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
@ -1208,17 +1205,33 @@ impl Project {
|
||||||
initial_snapshot.text(),
|
initial_snapshot.text(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
);
|
.log_err()?;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
futures::future::try_join_all(open_notifications)
|
// Update the language buffers
|
||||||
.await
|
if buffer
|
||||||
.log_err();
|
.language()
|
||||||
|
.map_or(false, |l| l.name() == language.name())
|
||||||
|
{
|
||||||
|
buffer_handle.update(cx, |buffer, cx| {
|
||||||
|
buffer.set_completion_triggers(
|
||||||
|
language_server
|
||||||
|
.capabilities()
|
||||||
|
.completion_provider
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|provider| {
|
||||||
|
provider.trigger_characters.clone()
|
||||||
|
})
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
|
||||||
let disk_based_sources = language
|
let disk_based_sources = language
|
||||||
.disk_based_diagnostic_sources()
|
.disk_based_diagnostic_sources()
|
||||||
|
@ -1623,21 +1636,17 @@ impl Project {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (buffer, buffer_abs_path, lang_server) in local_buffers {
|
for (buffer, buffer_abs_path, language_server) in local_buffers {
|
||||||
let capabilities = if let Some(capabilities) = lang_server.capabilities().await {
|
|
||||||
capabilities
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let text_document = lsp::TextDocumentIdentifier::new(
|
let text_document = lsp::TextDocumentIdentifier::new(
|
||||||
lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
|
lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
|
||||||
);
|
);
|
||||||
|
let capabilities = &language_server.capabilities();
|
||||||
let lsp_edits = if capabilities
|
let lsp_edits = if capabilities
|
||||||
.document_formatting_provider
|
.document_formatting_provider
|
||||||
.map_or(false, |provider| provider != lsp::OneOf::Left(false))
|
.as_ref()
|
||||||
|
.map_or(false, |provider| *provider != lsp::OneOf::Left(false))
|
||||||
{
|
{
|
||||||
lang_server
|
language_server
|
||||||
.request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
|
.request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
|
||||||
text_document,
|
text_document,
|
||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
|
@ -1646,13 +1655,14 @@ impl Project {
|
||||||
.await?
|
.await?
|
||||||
} else if capabilities
|
} else if capabilities
|
||||||
.document_range_formatting_provider
|
.document_range_formatting_provider
|
||||||
.map_or(false, |provider| provider != lsp::OneOf::Left(false))
|
.as_ref()
|
||||||
|
.map_or(false, |provider| *provider != lsp::OneOf::Left(false))
|
||||||
{
|
{
|
||||||
let buffer_start = lsp::Position::new(0, 0);
|
let buffer_start = lsp::Position::new(0, 0);
|
||||||
let buffer_end = buffer
|
let buffer_end = buffer
|
||||||
.read_with(&cx, |buffer, _| buffer.max_point_utf16())
|
.read_with(&cx, |buffer, _| buffer.max_point_utf16())
|
||||||
.to_lsp_position();
|
.to_lsp_position();
|
||||||
lang_server
|
language_server
|
||||||
.request::<lsp::request::RangeFormatting>(
|
.request::<lsp::request::RangeFormatting>(
|
||||||
lsp::DocumentRangeFormattingParams {
|
lsp::DocumentRangeFormattingParams {
|
||||||
text_document,
|
text_document,
|
||||||
|
@ -2132,13 +2142,7 @@ impl Project {
|
||||||
range.end.to_point_utf16(buffer).to_lsp_position(),
|
range.end.to_point_utf16(buffer).to_lsp_position(),
|
||||||
);
|
);
|
||||||
cx.foreground().spawn(async move {
|
cx.foreground().spawn(async move {
|
||||||
if !lang_server
|
if !lang_server.capabilities().code_action_provider.is_some() {
|
||||||
.capabilities()
|
|
||||||
.await
|
|
||||||
.map_or(false, |capabilities| {
|
|
||||||
capabilities.code_action_provider.is_some()
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return Ok(Default::default());
|
return Ok(Default::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2674,13 +2678,7 @@ impl Project {
|
||||||
{
|
{
|
||||||
let lsp_params = request.to_lsp(&file.abs_path(cx), cx);
|
let lsp_params = request.to_lsp(&file.abs_path(cx), cx);
|
||||||
return cx.spawn(|this, cx| async move {
|
return cx.spawn(|this, cx| async move {
|
||||||
if !language_server
|
if !request.check_capabilities(language_server.capabilities()) {
|
||||||
.capabilities()
|
|
||||||
.await
|
|
||||||
.map_or(false, |capabilities| {
|
|
||||||
request.check_capabilities(&capabilities)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return Ok(Default::default());
|
return Ok(Default::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4262,18 +4260,32 @@ mod tests {
|
||||||
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
let (lsp_config, mut fake_rust_servers) = LanguageServerConfig::fake();
|
let (mut rust_lsp_config, mut fake_rust_servers) = LanguageServerConfig::fake();
|
||||||
|
let (mut json_lsp_config, mut fake_json_servers) = LanguageServerConfig::fake();
|
||||||
|
rust_lsp_config.set_fake_capabilities(lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
json_lsp_config.set_fake_capabilities(lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![":".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
let rust_language = Arc::new(Language::new(
|
let rust_language = Arc::new(Language::new(
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".into(),
|
name: "Rust".into(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
language_server: Some(lsp_config),
|
language_server: Some(rust_lsp_config),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let (json_lsp_config, mut fake_json_servers) = LanguageServerConfig::fake();
|
|
||||||
let json_language = Arc::new(Language::new(
|
let json_language = Arc::new(Language::new(
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "JSON".into(),
|
name: "JSON".into(),
|
||||||
|
@ -4289,6 +4301,7 @@ mod tests {
|
||||||
"/the-root",
|
"/the-root",
|
||||||
json!({
|
json!({
|
||||||
"test.rs": "const A: i32 = 1;",
|
"test.rs": "const A: i32 = 1;",
|
||||||
|
"test2.rs": "",
|
||||||
"Cargo.toml": "a = 1",
|
"Cargo.toml": "a = 1",
|
||||||
"package.json": "{\"a\": 1}",
|
"package.json": "{\"a\": 1}",
|
||||||
}),
|
}),
|
||||||
|
@ -4353,6 +4366,17 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The buffer is configured based on the language server's capabilities.
|
||||||
|
rust_buffer.read_with(cx, |buffer, _| {
|
||||||
|
assert_eq!(
|
||||||
|
buffer.completion_triggers(),
|
||||||
|
&[".".to_string(), "::".to_string()]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
toml_buffer.read_with(cx, |buffer, _| {
|
||||||
|
assert!(buffer.completion_triggers().is_empty());
|
||||||
|
});
|
||||||
|
|
||||||
// Edit a buffer. The changes are reported to the language server.
|
// Edit a buffer. The changes are reported to the language server.
|
||||||
rust_buffer.update(cx, |buffer, cx| buffer.edit([16..16], "2", cx));
|
rust_buffer.update(cx, |buffer, cx| buffer.edit([16..16], "2", cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -4414,6 +4438,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// This buffer is configured based on the second language server's
|
||||||
|
// capabilities.
|
||||||
|
json_buffer.read_with(cx, |buffer, _| {
|
||||||
|
assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
|
||||||
|
});
|
||||||
|
|
||||||
// The first language server is also notified about the new open buffer.
|
// The first language server is also notified about the new open buffer.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fake_rust_server
|
fake_rust_server
|
||||||
|
@ -4428,6 +4458,21 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// When opening another buffer whose language server is already running,
|
||||||
|
// it is also configured based on the existing language server's capabilities.
|
||||||
|
let rust_buffer2 = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_buffer((worktree_id, "test2.rs"), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
rust_buffer2.read_with(cx, |buffer, _| {
|
||||||
|
assert_eq!(
|
||||||
|
buffer.completion_triggers(),
|
||||||
|
&[".".to_string(), "::".to_string()]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Edit a buffer. The changes are reported to both the language servers.
|
// Edit a buffer. The changes are reported to both the language servers.
|
||||||
toml_buffer.update(cx, |buffer, cx| buffer.edit([5..5], "23", cx));
|
toml_buffer.update(cx, |buffer, cx| buffer.edit([5..5], "23", cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -6000,6 +6045,8 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
"/the-dir",
|
"/the-dir",
|
||||||
|
@ -6259,6 +6306,8 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_rename(cx: &mut gpui::TestAppContext) {
|
async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
let (language_server_config, mut fake_servers) = LanguageServerConfig::fake();
|
let (language_server_config, mut fake_servers) = LanguageServerConfig::fake();
|
||||||
let language = Arc::new(Language::new(
|
let language = Arc::new(Language::new(
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
|
|
|
@ -556,7 +556,6 @@ impl LocalWorktree {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostics_for_path(&self, path: &Path) -> Option<Vec<DiagnosticEntry<PointUtf16>>> {
|
pub fn diagnostics_for_path(&self, path: &Path) -> Option<Vec<DiagnosticEntry<PointUtf16>>> {
|
||||||
dbg!(&self.diagnostics);
|
|
||||||
self.diagnostics.get(path).cloned()
|
self.diagnostics.get(path).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1833,13 +1833,13 @@ mod tests {
|
||||||
|
|
||||||
// Client A sees that a guest has joined.
|
// Client A sees that a guest has joined.
|
||||||
project_a
|
project_a
|
||||||
.condition(&cx_a, |p, _| p.collaborators().len() == 1)
|
.condition(cx_a, |p, _| p.collaborators().len() == 1)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Drop client B's connection and ensure client A observes client B leaving the project.
|
// Drop client B's connection and ensure client A observes client B leaving the project.
|
||||||
client_b.disconnect(&cx_b.to_async()).unwrap();
|
client_b.disconnect(&cx_b.to_async()).unwrap();
|
||||||
project_a
|
project_a
|
||||||
.condition(&cx_a, |p, _| p.collaborators().len() == 0)
|
.condition(cx_a, |p, _| p.collaborators().len() == 0)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Rejoin the project as client B
|
// Rejoin the project as client B
|
||||||
|
@ -1856,14 +1856,15 @@ mod tests {
|
||||||
|
|
||||||
// Client A sees that a guest has re-joined.
|
// Client A sees that a guest has re-joined.
|
||||||
project_a
|
project_a
|
||||||
.condition(&cx_a, |p, _| p.collaborators().len() == 1)
|
.condition(cx_a, |p, _| p.collaborators().len() == 1)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Simulate connection loss for client B and ensure client A observes client B leaving the project.
|
// Simulate connection loss for client B and ensure client A observes client B leaving the project.
|
||||||
|
client_b.wait_for_current_user(cx_b).await;
|
||||||
server.disconnect_client(client_b.current_user_id(cx_b));
|
server.disconnect_client(client_b.current_user_id(cx_b));
|
||||||
cx_a.foreground().advance_clock(Duration::from_secs(3));
|
cx_a.foreground().advance_clock(Duration::from_secs(3));
|
||||||
project_a
|
project_a
|
||||||
.condition(&cx_a, |p, _| p.collaborators().len() == 0)
|
.condition(cx_a, |p, _| p.collaborators().len() == 0)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1944,6 +1945,9 @@ mod tests {
|
||||||
|
|
||||||
// Simulate a language server reporting errors for a file.
|
// Simulate a language server reporting errors for a file.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
|
fake_language_server
|
||||||
|
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await;
|
||||||
fake_language_server
|
fake_language_server
|
||||||
.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
|
.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||||
|
@ -4467,17 +4471,16 @@ mod tests {
|
||||||
|
|
||||||
let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
|
let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
||||||
let mut authed_user =
|
|
||||||
user_store.read_with(cx, |user_store, _| user_store.watch_current_user());
|
|
||||||
while authed_user.next().await.unwrap().is_none() {}
|
|
||||||
|
|
||||||
TestClient {
|
let client = TestClient {
|
||||||
client,
|
client,
|
||||||
peer_id,
|
peer_id,
|
||||||
user_store,
|
user_store,
|
||||||
project: Default::default(),
|
project: Default::default(),
|
||||||
buffers: Default::default(),
|
buffers: Default::default(),
|
||||||
}
|
};
|
||||||
|
client.wait_for_current_user(cx).await;
|
||||||
|
client
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disconnect_client(&self, user_id: UserId) {
|
fn disconnect_client(&self, user_id: UserId) {
|
||||||
|
@ -4557,6 +4560,13 @@ mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_for_current_user(&self, cx: &TestAppContext) {
|
||||||
|
let mut authed_user = self
|
||||||
|
.user_store
|
||||||
|
.read_with(cx, |user_store, _| user_store.watch_current_user());
|
||||||
|
while authed_user.next().await.unwrap().is_none() {}
|
||||||
|
}
|
||||||
|
|
||||||
fn simulate_host(
|
fn simulate_host(
|
||||||
mut self,
|
mut self,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue