Incomplete refactor to allow for multiple adapters per language

This commit is contained in:
Julia 2023-04-07 11:46:05 -04:00 committed by Max Brunsfeld
parent 21e39e7523
commit ba7233f265
10 changed files with 818 additions and 577 deletions

View file

@ -2764,6 +2764,15 @@ impl MultiBufferSnapshot {
.and_then(|(buffer, offset)| buffer.language_scope_at(offset)) .and_then(|(buffer, offset)| buffer.language_scope_at(offset))
} }
pub fn language_indent_size_at<T: ToOffset>(
&self,
position: T,
cx: &AppContext,
) -> Option<IndentSize> {
let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?;
Some(buffer_snapshot.language_indent_size_at(offset, cx))
}
pub fn is_dirty(&self) -> bool { pub fn is_dirty(&self) -> bool {
self.is_dirty self.is_dirty
} }

View file

@ -156,6 +156,7 @@ pub struct Completion {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CodeAction { pub struct CodeAction {
pub server_id: usize,
pub range: Range<Anchor>, pub range: Range<Anchor>,
pub lsp_action: lsp::CodeAction, pub lsp_action: lsp::CodeAction,
} }

View file

@ -414,7 +414,7 @@ pub struct BracketPair {
pub struct Language { pub struct Language {
pub(crate) config: LanguageConfig, pub(crate) config: LanguageConfig,
pub(crate) grammar: Option<Arc<Grammar>>, pub(crate) grammar: Option<Arc<Grammar>>,
pub(crate) adapter: Option<Arc<CachedLspAdapter>>, pub(crate) adapters: Vec<Arc<CachedLspAdapter>>,
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
fake_adapter: Option<( fake_adapter: Option<(
@ -492,7 +492,7 @@ struct AvailableLanguage {
path: &'static str, path: &'static str,
config: LanguageConfig, config: LanguageConfig,
grammar: tree_sitter::Language, grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<dyn LspAdapter>>, lsp_adapters: Vec<Arc<dyn LspAdapter>>,
get_queries: fn(&str) -> LanguageQueries, get_queries: fn(&str) -> LanguageQueries,
} }
@ -513,6 +513,7 @@ pub struct LanguageRegistry {
} }
struct LanguageRegistryState { struct LanguageRegistryState {
next_language_server_id: usize,
languages: Vec<Arc<Language>>, languages: Vec<Arc<Language>>,
available_languages: Vec<AvailableLanguage>, available_languages: Vec<AvailableLanguage>,
next_available_language_id: AvailableLanguageId, next_available_language_id: AvailableLanguageId,
@ -522,11 +523,17 @@ struct LanguageRegistryState {
version: usize, version: usize,
} }
pub struct PendingLanguageServer {
pub server_id: usize,
pub task: Task<Result<lsp::LanguageServer>>,
}
impl LanguageRegistry { impl LanguageRegistry {
pub fn new(login_shell_env_loaded: Task<()>) -> Self { pub fn new(login_shell_env_loaded: Task<()>) -> Self {
let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16); let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
Self { Self {
state: RwLock::new(LanguageRegistryState { state: RwLock::new(LanguageRegistryState {
next_language_server_id: 0,
languages: vec![PLAIN_TEXT.clone()], languages: vec![PLAIN_TEXT.clone()],
available_languages: Default::default(), available_languages: Default::default(),
next_available_language_id: 0, next_available_language_id: 0,
@ -558,7 +565,7 @@ impl LanguageRegistry {
path: &'static str, path: &'static str,
config: LanguageConfig, config: LanguageConfig,
grammar: tree_sitter::Language, grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<dyn LspAdapter>>, lsp_adapters: Vec<Arc<dyn LspAdapter>>,
get_queries: fn(&str) -> LanguageQueries, get_queries: fn(&str) -> LanguageQueries,
) { ) {
let state = &mut *self.state.write(); let state = &mut *self.state.write();
@ -567,7 +574,7 @@ impl LanguageRegistry {
path, path,
config, config,
grammar, grammar,
lsp_adapter, lsp_adapters,
get_queries, get_queries,
}); });
} }
@ -590,12 +597,13 @@ impl LanguageRegistry {
state state
.available_languages .available_languages
.iter() .iter()
.filter_map(|l| l.lsp_adapter.clone()) .flat_map(|l| l.lsp_adapters.clone())
.chain( .chain(
state state
.languages .languages
.iter() .iter()
.filter_map(|l| l.adapter.as_ref().map(|a| a.adapter.clone())), .flat_map(|language| &language.adapters)
.map(|adapter| adapter.adapter.clone()),
) )
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
@ -721,7 +729,7 @@ impl LanguageRegistry {
let queries = (language.get_queries)(&language.path); let queries = (language.get_queries)(&language.path);
let language = let language =
Language::new(language.config, Some(language.grammar)) Language::new(language.config, Some(language.grammar))
.with_lsp_adapter(language.lsp_adapter) .with_lsp_adapters(language.lsp_adapters)
.await; .await;
let name = language.name(); let name = language.name();
match language.with_queries(queries) { match language.with_queries(queries) {
@ -774,18 +782,16 @@ impl LanguageRegistry {
self.state.read().languages.iter().cloned().collect() self.state.read().languages.iter().cloned().collect()
} }
pub fn start_language_server( pub fn start_language_servers(
self: &Arc<Self>, self: &Arc<Self>,
server_id: usize,
language: Arc<Language>, language: Arc<Language>,
root_path: Arc<Path>, root_path: Arc<Path>,
http_client: Arc<dyn HttpClient>, http_client: Arc<dyn HttpClient>,
cx: &mut AppContext, cx: &mut AppContext,
) -> Option<Task<Result<lsp::LanguageServer>>> { ) -> Vec<PendingLanguageServer> {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
if language.fake_adapter.is_some() { if language.fake_adapter.is_some() {
let language = language; let task = cx.spawn(|cx| async move {
return Some(cx.spawn(|cx| async move {
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap(); let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
let (server, mut fake_server) = lsp::LanguageServer::fake( let (server, mut fake_server) = lsp::LanguageServer::fake(
fake_adapter.name.to_string(), fake_adapter.name.to_string(),
@ -810,21 +816,34 @@ impl LanguageRegistry {
}) })
.detach(); .detach();
Ok(server) Ok(server)
})); });
return vec![PendingLanguageServer { server_id: 0, task }];
} }
let download_dir = self let download_dir = self
.language_server_download_dir .language_server_download_dir
.clone() .clone()
.ok_or_else(|| anyhow!("language server download directory has not been assigned")) .ok_or_else(|| anyhow!("language server download directory has not been assigned"))
.log_err()?; .log_err();
let download_dir = match download_dir {
Some(download_dir) => download_dir,
None => return Vec::new(),
};
let mut results = Vec::new();
for adapter in &language.adapters {
let this = self.clone(); let this = self.clone();
let adapter = language.adapter.clone()?; let language = language.clone();
let http_client = http_client.clone();
let download_dir = download_dir.clone();
let root_path = root_path.clone();
let adapter = adapter.clone();
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
let login_shell_env_loaded = self.login_shell_env_loaded.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone();
let server_id = post_inc(&mut self.state.write().next_language_server_id);
Some(cx.spawn(|cx| async move { let task = cx.spawn(|cx| async move {
login_shell_env_loaded.await; login_shell_env_loaded.await;
let mut lock = this.lsp_binary_paths.lock(); let mut lock = this.lsp_binary_paths.lock();
@ -856,7 +875,12 @@ impl LanguageRegistry {
)?; )?;
Ok(server) Ok(server)
})) });
results.push(PendingLanguageServer { server_id, task });
}
results
} }
pub fn language_server_binary_statuses( pub fn language_server_binary_statuses(
@ -974,15 +998,15 @@ impl Language {
highlight_map: Default::default(), highlight_map: Default::default(),
}) })
}), }),
adapter: None, adapters: Vec::new(),
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
fake_adapter: None, fake_adapter: None,
} }
} }
pub fn lsp_adapter(&self) -> Option<Arc<CachedLspAdapter>> { pub fn lsp_adapters(&self) -> &[Arc<CachedLspAdapter>] {
self.adapter.clone() &self.adapters
} }
pub fn id(&self) -> Option<usize> { pub fn id(&self) -> Option<usize> {
@ -1209,9 +1233,9 @@ impl Language {
Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
} }
pub async fn with_lsp_adapter(mut self, lsp_adapter: Option<Arc<dyn LspAdapter>>) -> Self { pub async fn with_lsp_adapters(mut self, lsp_adapters: Vec<Arc<dyn LspAdapter>>) -> Self {
if let Some(adapter) = lsp_adapter { for adapter in lsp_adapters {
self.adapter = Some(CachedLspAdapter::new(adapter).await); self.adapters.push(CachedLspAdapter::new(adapter).await);
} }
self self
} }
@ -1224,7 +1248,7 @@ impl Language {
let (servers_tx, servers_rx) = mpsc::unbounded(); let (servers_tx, servers_rx) = mpsc::unbounded();
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await; let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
self.adapter = Some(adapter); self.adapters = vec![adapter];
servers_rx servers_rx
} }
@ -1233,28 +1257,31 @@ impl Language {
} }
pub async fn disk_based_diagnostic_sources(&self) -> &[String] { pub async fn disk_based_diagnostic_sources(&self) -> &[String] {
match self.adapter.as_ref() { match self.adapters.first().as_ref() {
Some(adapter) => &adapter.disk_based_diagnostic_sources, Some(adapter) => &adapter.disk_based_diagnostic_sources,
None => &[], None => &[],
} }
} }
pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> { pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> {
if let Some(adapter) = self.adapter.as_ref() { for adapter in &self.adapters {
adapter.disk_based_diagnostics_progress_token.as_deref() let token = adapter.disk_based_diagnostics_progress_token.as_deref();
} else { if token.is_some() {
None return token;
} }
} }
None
}
pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) { pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
if let Some(processor) = self.adapter.as_ref() { for adapter in &self.adapters {
processor.process_diagnostics(diagnostics).await; adapter.process_diagnostics(diagnostics).await;
} }
} }
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) { pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
if let Some(adapter) = self.adapter.as_ref() { for adapter in &self.adapters {
adapter.process_completion(completion).await; adapter.process_completion(completion).await;
} }
} }
@ -1263,7 +1290,8 @@ impl Language {
self: &Arc<Self>, self: &Arc<Self>,
completion: &lsp::CompletionItem, completion: &lsp::CompletionItem,
) -> Option<CodeLabel> { ) -> Option<CodeLabel> {
self.adapter self.adapters
.first()
.as_ref()? .as_ref()?
.label_for_completion(completion, self) .label_for_completion(completion, self)
.await .await
@ -1274,7 +1302,8 @@ impl Language {
name: &str, name: &str,
kind: lsp::SymbolKind, kind: lsp::SymbolKind,
) -> Option<CodeLabel> { ) -> Option<CodeLabel> {
self.adapter self.adapters
.first()
.as_ref()? .as_ref()?
.label_for_symbol(name, kind, self) .label_for_symbol(name, kind, self)
.await .await
@ -1595,7 +1624,7 @@ mod tests {
..Default::default() ..Default::default()
}, },
tree_sitter_json::language(), tree_sitter_json::language(),
None, vec![],
|_| Default::default(), |_| Default::default(),
); );
languages.register( languages.register(
@ -1606,7 +1635,7 @@ mod tests {
..Default::default() ..Default::default()
}, },
tree_sitter_rust::language(), tree_sitter_rust::language(),
None, vec![],
|_| Default::default(), |_| Default::default(),
); );
assert_eq!( assert_eq!(

View file

@ -462,6 +462,7 @@ pub async fn deserialize_completion(
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction { pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
proto::CodeAction { proto::CodeAction {
server_id: action.server_id as u64,
start: Some(serialize_anchor(&action.range.start)), start: Some(serialize_anchor(&action.range.start)),
end: Some(serialize_anchor(&action.range.end)), end: Some(serialize_anchor(&action.range.end)),
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(), lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
@ -479,6 +480,7 @@ pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction>
.ok_or_else(|| anyhow!("invalid end"))?; .ok_or_else(|| anyhow!("invalid end"))?;
let lsp_action = serde_json::from_slice(&action.lsp_action)?; let lsp_action = serde_json::from_slice(&action.lsp_action)?;
Ok(CodeAction { Ok(CodeAction {
server_id: action.server_id as usize,
range: start..end, range: start..end,
lsp_action, lsp_action,
}) })

View file

@ -33,21 +33,25 @@ pub(crate) trait LspCommand: 'static + Sized {
language_server: &Arc<LanguageServer>, language_server: &Arc<LanguageServer>,
cx: &AppContext, cx: &AppContext,
) -> <Self::LspRequest as lsp::request::Request>::Params; ) -> <Self::LspRequest as lsp::request::Request>::Params;
async fn response_from_lsp( async fn response_from_lsp(
self, self,
message: <Self::LspRequest as lsp::request::Request>::Result, message: <Self::LspRequest as lsp::request::Request>::Result,
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
server_id: usize,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Self::Response>; ) -> Result<Self::Response>;
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest; fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
async fn from_proto( async fn from_proto(
message: Self::ProtoRequest, message: Self::ProtoRequest,
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Self>; ) -> Result<Self>;
fn response_to_proto( fn response_to_proto(
response: Self::Response, response: Self::Response,
project: &mut Project, project: &mut Project,
@ -55,6 +59,7 @@ pub(crate) trait LspCommand: 'static + Sized {
buffer_version: &clock::Global, buffer_version: &clock::Global,
cx: &mut AppContext, cx: &mut AppContext,
) -> <Self::ProtoRequest as proto::RequestMessage>::Response; ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
async fn response_from_proto( async fn response_from_proto(
self, self,
message: <Self::ProtoRequest as proto::RequestMessage>::Response, message: <Self::ProtoRequest as proto::RequestMessage>::Response,
@ -62,6 +67,7 @@ pub(crate) trait LspCommand: 'static + Sized {
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Self::Response>; ) -> Result<Self::Response>;
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64; fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
} }
@ -137,6 +143,7 @@ impl LspCommand for PrepareRename {
message: Option<lsp::PrepareRenameResponse>, message: Option<lsp::PrepareRenameResponse>,
_: ModelHandle<Project>, _: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
_: usize,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Option<Range<Anchor>>> { ) -> Result<Option<Range<Anchor>>> {
buffer.read_with(&cx, |buffer, _| { buffer.read_with(&cx, |buffer, _| {
@ -263,10 +270,12 @@ impl LspCommand for PerformRename {
message: Option<lsp::WorkspaceEdit>, message: Option<lsp::WorkspaceEdit>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
server_id: usize,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<ProjectTransaction> { ) -> Result<ProjectTransaction> {
if let Some(edit) = message { if let Some(edit) = message {
let (lsp_adapter, lsp_server) = language_server_for_buffer(&project, &buffer, &mut cx)?; let (lsp_adapter, lsp_server) =
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
Project::deserialize_workspace_edit( Project::deserialize_workspace_edit(
project, project,
edit, edit,
@ -380,9 +389,10 @@ impl LspCommand for GetDefinition {
message: Option<lsp::GotoDefinitionResponse>, message: Option<lsp::GotoDefinitionResponse>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
server_id: usize,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> { ) -> Result<Vec<LocationLink>> {
location_links_from_lsp(message, project, buffer, cx).await location_links_from_lsp(message, project, buffer, server_id, cx).await
} }
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition { fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
@ -472,9 +482,10 @@ impl LspCommand for GetTypeDefinition {
message: Option<lsp::GotoTypeDefinitionResponse>, message: Option<lsp::GotoTypeDefinitionResponse>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
server_id: usize,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> { ) -> Result<Vec<LocationLink>> {
location_links_from_lsp(message, project, buffer, cx).await location_links_from_lsp(message, project, buffer, server_id, cx).await
} }
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition { fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
@ -537,12 +548,13 @@ impl LspCommand for GetTypeDefinition {
fn language_server_for_buffer( fn language_server_for_buffer(
project: &ModelHandle<Project>, project: &ModelHandle<Project>,
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,
server_id: usize,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> { ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
project project
.read_with(cx, |project, cx| { .read_with(cx, |project, cx| {
project project
.language_server_for_buffer(buffer.read(cx), cx) .language_server_for_buffer(buffer.read(cx), server_id, cx)
.map(|(adapter, server)| (adapter.clone(), server.clone())) .map(|(adapter, server)| (adapter.clone(), server.clone()))
}) })
.ok_or_else(|| anyhow!("no language server found for buffer")) .ok_or_else(|| anyhow!("no language server found for buffer"))
@ -614,6 +626,7 @@ async fn location_links_from_lsp(
message: Option<lsp::GotoDefinitionResponse>, message: Option<lsp::GotoDefinitionResponse>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
server_id: usize,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> { ) -> Result<Vec<LocationLink>> {
let message = match message { let message = match message {
@ -642,7 +655,8 @@ async fn location_links_from_lsp(
} }
} }
let (lsp_adapter, language_server) = language_server_for_buffer(&project, &buffer, &mut cx)?; let (lsp_adapter, language_server) =
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
let mut definitions = Vec::new(); let mut definitions = Vec::new();
for (origin_range, target_uri, target_range) in unresolved_links { for (origin_range, target_uri, target_range) in unresolved_links {
let target_buffer_handle = project let target_buffer_handle = project
@ -756,11 +770,12 @@ impl LspCommand for GetReferences {
locations: Option<Vec<lsp::Location>>, locations: Option<Vec<lsp::Location>>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
server_id: usize,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Vec<Location>> { ) -> Result<Vec<Location>> {
let mut references = Vec::new(); let mut references = Vec::new();
let (lsp_adapter, language_server) = let (lsp_adapter, language_server) =
language_server_for_buffer(&project, &buffer, &mut cx)?; language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
if let Some(locations) = locations { if let Some(locations) = locations {
for lsp_location in locations { for lsp_location in locations {
@ -917,6 +932,7 @@ impl LspCommand for GetDocumentHighlights {
lsp_highlights: Option<Vec<lsp::DocumentHighlight>>, lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
_: ModelHandle<Project>, _: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
_: usize,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Vec<DocumentHighlight>> { ) -> Result<Vec<DocumentHighlight>> {
buffer.read_with(&cx, |buffer, _| { buffer.read_with(&cx, |buffer, _| {
@ -1062,6 +1078,7 @@ impl LspCommand for GetHover {
message: Option<lsp::Hover>, message: Option<lsp::Hover>,
_: ModelHandle<Project>, _: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
_: usize,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Self::Response> { ) -> Result<Self::Response> {
Ok(message.and_then(|hover| { Ok(message.and_then(|hover| {
@ -1283,6 +1300,7 @@ impl LspCommand for GetCompletions {
completions: Option<lsp::CompletionResponse>, completions: Option<lsp::CompletionResponse>,
_: ModelHandle<Project>, _: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
_: usize,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Vec<Completion>> { ) -> Result<Vec<Completion>> {
let completions = if let Some(completions) = completions { let completions = if let Some(completions) = completions {
@ -1502,6 +1520,7 @@ impl LspCommand for GetCodeActions {
actions: Option<lsp::CodeActionResponse>, actions: Option<lsp::CodeActionResponse>,
_: ModelHandle<Project>, _: ModelHandle<Project>,
_: ModelHandle<Buffer>, _: ModelHandle<Buffer>,
server_id: usize,
_: AsyncAppContext, _: AsyncAppContext,
) -> Result<Vec<CodeAction>> { ) -> Result<Vec<CodeAction>> {
Ok(actions Ok(actions
@ -1510,6 +1529,7 @@ impl LspCommand for GetCodeActions {
.filter_map(|entry| { .filter_map(|entry| {
if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry { if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
Some(CodeAction { Some(CodeAction {
server_id,
range: self.range.clone(), range: self.range.clone(),
lsp_action, lsp_action,
}) })

View file

@ -31,8 +31,8 @@ use language::{
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel,
Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _,
Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, Operation, Patch, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, Operation, Patch,
PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
Unclipped, Transaction, Unclipped,
}; };
use lsp::{ use lsp::{
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
@ -99,7 +99,6 @@ pub struct Project {
language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>, language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>,
language_server_statuses: BTreeMap<usize, LanguageServerStatus>, language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>, last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>,
next_language_server_id: usize,
client: Arc<client::Client>, client: Arc<client::Client>,
next_entry_id: Arc<AtomicUsize>, next_entry_id: Arc<AtomicUsize>,
join_project_response_message_id: u32, join_project_response_message_id: u32,
@ -124,7 +123,7 @@ pub struct Project {
/// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it. /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
/// Used for re-issuing buffer requests when peers temporarily disconnect /// Used for re-issuing buffer requests when peers temporarily disconnect
incomplete_remote_buffers: HashMap<u64, Option<ModelHandle<Buffer>>>, incomplete_remote_buffers: HashMap<u64, Option<ModelHandle<Buffer>>>,
buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>, buffer_snapshots: HashMap<u64, HashMap<usize, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
buffers_being_formatted: HashSet<usize>, buffers_being_formatted: HashSet<usize>,
nonce: u128, nonce: u128,
_maintain_buffer_languages: Task<()>, _maintain_buffer_languages: Task<()>,
@ -133,6 +132,11 @@ pub struct Project {
copilot_enabled: bool, copilot_enabled: bool,
} }
struct LspBufferSnapshot {
version: i32,
snapshot: TextBufferSnapshot,
}
enum BufferMessage { enum BufferMessage {
Operation { Operation {
buffer_id: u64, buffer_id: u64,
@ -469,7 +473,6 @@ impl Project {
language_server_statuses: Default::default(), language_server_statuses: Default::default(),
last_workspace_edits_by_language_server: Default::default(), last_workspace_edits_by_language_server: Default::default(),
buffers_being_formatted: Default::default(), buffers_being_formatted: Default::default(),
next_language_server_id: 0,
nonce: StdRng::from_entropy().gen(), nonce: StdRng::from_entropy().gen(),
terminals: Terminals { terminals: Terminals {
local_handles: Vec::new(), local_handles: Vec::new(),
@ -554,7 +557,6 @@ impl Project {
}) })
.collect(), .collect(),
last_workspace_edits_by_language_server: Default::default(), last_workspace_edits_by_language_server: Default::default(),
next_language_server_id: 0,
opened_buffers: Default::default(), opened_buffers: Default::default(),
buffers_being_formatted: Default::default(), buffers_being_formatted: Default::default(),
buffer_snapshots: Default::default(), buffer_snapshots: Default::default(),
@ -645,7 +647,7 @@ impl Project {
let mut language_servers_to_stop = Vec::new(); let mut language_servers_to_stop = Vec::new();
for language in self.languages.to_vec() { for language in self.languages.to_vec() {
if let Some(lsp_adapter) = language.lsp_adapter() { for lsp_adapter in language.lsp_adapters() {
if !settings.enable_language_server(Some(&language.name())) { if !settings.enable_language_server(Some(&language.name())) {
let lsp_name = &lsp_adapter.name; let lsp_name = &lsp_adapter.name;
for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
@ -665,7 +667,7 @@ impl Project {
// Start all the newly-enabled language servers. // Start all the newly-enabled language servers.
for (worktree_id, worktree_path, language) in language_servers_to_start { for (worktree_id, worktree_path, language) in language_servers_to_start {
self.start_language_server(worktree_id, worktree_path, language, cx); self.start_language_servers(worktree_id, worktree_path, language, cx);
} }
if !self.copilot_enabled && Copilot::global(cx).is_some() { if !self.copilot_enabled && Copilot::global(cx).is_some() {
@ -1550,7 +1552,7 @@ impl Project {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
if let Some(old_path) = old_path { if let Some(old_path) = old_path {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.unregister_buffer_from_language_server(&buffer, old_path, cx); this.unregister_buffer_from_language_servers(&buffer, old_path, cx);
}); });
} }
let (worktree, path) = worktree_task.await?; let (worktree, path) = worktree_task.await?;
@ -1564,7 +1566,7 @@ impl Project {
.await?; .await?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.detect_language_for_buffer(&buffer, cx); this.detect_language_for_buffer(&buffer, cx);
this.register_buffer_with_language_server(&buffer, cx); this.register_buffer_with_language_servers(&buffer, cx);
}); });
Ok(()) Ok(())
}) })
@ -1628,14 +1630,15 @@ impl Project {
.detach(); .detach();
self.detect_language_for_buffer(buffer, cx); self.detect_language_for_buffer(buffer, cx);
self.register_buffer_with_language_server(buffer, cx); self.register_buffer_with_language_servers(buffer, cx);
self.register_buffer_with_copilot(buffer, cx); self.register_buffer_with_copilot(buffer, cx);
cx.observe_release(buffer, |this, buffer, cx| { cx.observe_release(buffer, |this, buffer, cx| {
if let Some(file) = File::from_dyn(buffer.file()) { if let Some(file) = File::from_dyn(buffer.file()) {
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();
if let Some((_, server)) = this.language_server_for_buffer(buffer, cx) { for server in this.language_servers_for_buffer(buffer, cx) {
server server
.1
.notify::<lsp::notification::DidCloseTextDocument>( .notify::<lsp::notification::DidCloseTextDocument>(
lsp::DidCloseTextDocumentParams { lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(uri), text_document: lsp::TextDocumentIdentifier::new(uri),
@ -1652,25 +1655,35 @@ impl Project {
Ok(()) Ok(())
} }
fn register_buffer_with_language_server( fn register_buffer_with_language_servers(
&mut self, &mut self,
buffer_handle: &ModelHandle<Buffer>, buffer_handle: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id(); let buffer_id = buffer.remote_id();
if let Some(file) = File::from_dyn(buffer.file()) { if let Some(file) = File::from_dyn(buffer.file()) {
if file.is_local() { if !file.is_local() {
return;
}
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 initial_snapshot = buffer.text_snapshot(); let initial_snapshot = buffer.text_snapshot();
let mut language_server = None; if let Some(local_worktree) = file.worktree.read(cx).as_local() {
let mut language_id = None; if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
self.update_buffer_diagnostics(buffer_handle, diagnostics, None, cx)
.log_err();
}
}
if let Some(language) = buffer.language() { if let Some(language) = buffer.language() {
let worktree_id = file.worktree_id(cx); let worktree_id = file.worktree_id(cx);
if let Some(adapter) = language.lsp_adapter() {
language_id = adapter.language_ids.get(language.name().as_ref()).cloned(); for adapter in language.lsp_adapters() {
language_server = self let language_id = adapter.language_ids.get(language.name().as_ref()).cloned();
let server = self
.language_server_ids .language_server_ids
.get(&(worktree_id, adapter.name.clone())) .get(&(worktree_id, adapter.name.clone()))
.and_then(|id| self.language_servers.get(id)) .and_then(|id| self.language_servers.get(id))
@ -1681,17 +1694,11 @@ impl Project {
None None
} }
}); });
} let server = match server {
} Some(server) => server,
None => continue,
};
if let Some(local_worktree) = file.worktree.read(cx).as_local() {
if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
self.update_buffer_diagnostics(buffer_handle, diagnostics, None, cx)
.log_err();
}
}
if let Some(server) = language_server {
server server
.notify::<lsp::notification::DidOpenTextDocument>( .notify::<lsp::notification::DidOpenTextDocument>(
lsp::DidOpenTextDocumentParams { lsp::DidOpenTextDocumentParams {
@ -1704,6 +1711,7 @@ impl Project {
}, },
) )
.log_err(); .log_err();
buffer_handle.update(cx, |buffer, cx| { buffer_handle.update(cx, |buffer, cx| {
buffer.set_completion_triggers( buffer.set_completion_triggers(
server server
@ -1713,16 +1721,23 @@ impl Project {
.and_then(|provider| provider.trigger_characters.clone()) .and_then(|provider| provider.trigger_characters.clone())
.unwrap_or_default(), .unwrap_or_default(),
cx, cx,
) );
}); });
let snapshot = LspBufferSnapshot {
version: 0,
snapshot: initial_snapshot,
};
self.buffer_snapshots self.buffer_snapshots
.insert(buffer_id, vec![(0, initial_snapshot)]); .entry(buffer_id)
.or_default()
.insert(server.server_id(), vec![snapshot]);
} }
} }
} }
} }
fn unregister_buffer_from_language_server( fn unregister_buffer_from_language_servers(
&mut self, &mut self,
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,
old_path: PathBuf, old_path: PathBuf,
@ -1731,7 +1746,7 @@ impl Project {
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
buffer.update_diagnostics(Default::default(), cx); buffer.update_diagnostics(Default::default(), cx);
self.buffer_snapshots.remove(&buffer.remote_id()); self.buffer_snapshots.remove(&buffer.remote_id());
if let Some((_, language_server)) = self.language_server_for_buffer(buffer, cx) { for (_, language_server) in self.language_servers_for_buffer(buffer, cx) {
language_server language_server
.notify::<lsp::notification::DidCloseTextDocument>( .notify::<lsp::notification::DidCloseTextDocument>(
lsp::DidCloseTextDocumentParams { lsp::DidCloseTextDocumentParams {
@ -1833,21 +1848,26 @@ impl Project {
}) })
.ok(); .ok();
} }
BufferEvent::Edited { .. } => { BufferEvent::Edited { .. } => {
let language_server = self
.language_server_for_buffer(buffer.read(cx), cx)
.map(|(_, server)| server.clone())?;
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let file = File::from_dyn(buffer.file())?; let file = File::from_dyn(buffer.file())?;
let abs_path = file.as_local()?.abs_path(cx); let abs_path = file.as_local()?.abs_path(cx);
let uri = lsp::Url::from_file_path(abs_path).unwrap(); let uri = lsp::Url::from_file_path(abs_path).unwrap();
let buffer_snapshots = self.buffer_snapshots.get_mut(&buffer.remote_id())?;
let (version, prev_snapshot) = buffer_snapshots.last()?;
let next_snapshot = buffer.text_snapshot(); let next_snapshot = buffer.text_snapshot();
let next_version = version + 1;
for (_, language_server) in self.language_servers_for_buffer(buffer, cx) {
let language_server = language_server.clone();
let buffer_snapshots = self
.buffer_snapshots
.get_mut(&buffer.remote_id())
.and_then(|m| m.get_mut(&language_server.server_id()))?;
let previous_snapshot = buffer_snapshots.last()?;
let next_version = previous_snapshot.version + 1;
let content_changes = buffer let content_changes = buffer
.edits_since::<(PointUtf16, usize)>(prev_snapshot.version()) .edits_since::<(PointUtf16, usize)>(previous_snapshot.snapshot.version())
.map(|edit| { .map(|edit| {
let edit_start = edit.new.start.0; let edit_start = edit.new.start.0;
let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0); let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
@ -1865,7 +1885,10 @@ impl Project {
}) })
.collect(); .collect();
buffer_snapshots.push((next_version, next_snapshot)); buffer_snapshots.push(LspBufferSnapshot {
version: next_version,
snapshot: next_snapshot,
});
language_server language_server
.notify::<lsp::notification::DidChangeTextDocument>( .notify::<lsp::notification::DidChangeTextDocument>(
@ -1879,6 +1902,8 @@ impl Project {
) )
.log_err(); .log_err();
} }
}
BufferEvent::Saved => { BufferEvent::Saved => {
let file = File::from_dyn(buffer.read(cx).file())?; let file = File::from_dyn(buffer.read(cx).file())?;
let worktree_id = file.worktree_id(cx); let worktree_id = file.worktree_id(cx);
@ -1898,13 +1923,17 @@ impl Project {
.log_err(); .log_err();
} }
let language_server_id = self.language_server_id_for_buffer(buffer.read(cx), cx)?; let language_server_ids = self.language_server_ids_for_buffer(buffer.read(cx), cx);
if let Some(LanguageServerState::Running { for language_server_id in language_server_ids {
let LanguageServerState::Running {
adapter, adapter,
simulate_disk_based_diagnostics_completion, simulate_disk_based_diagnostics_completion,
.. ..
}) = self.language_servers.get_mut(&language_server_id) } = match self.language_servers.get_mut(&language_server_id) {
{ Some(state) => state,
None => continue,
};
// After saving a buffer using a language server that doesn't provide // After saving a buffer using a language server that doesn't provide
// a disk-based progress token, kick off a timer that will reset every // a disk-based progress token, kick off a timer that will reset every
// time the buffer is saved. If the timer eventually fires, simulate // time the buffer is saved. If the timer eventually fires, simulate
@ -1933,6 +1962,7 @@ impl Project {
} }
} }
} }
_ => {} _ => {}
} }
@ -1987,7 +2017,7 @@ impl Project {
for buffer in plain_text_buffers { for buffer in plain_text_buffers {
project.detect_language_for_buffer(&buffer, cx); project.detect_language_for_buffer(&buffer, cx);
project.register_buffer_with_language_server(&buffer, cx); project.register_buffer_with_language_servers(&buffer, cx);
} }
for buffer in buffers_with_unknown_injections { for buffer in buffers_with_unknown_injections {
@ -2071,12 +2101,12 @@ impl Project {
if let Some(worktree) = file.worktree.read(cx).as_local() { if let Some(worktree) = file.worktree.read(cx).as_local() {
let worktree_id = worktree.id(); let worktree_id = worktree.id();
let worktree_abs_path = worktree.abs_path().clone(); let worktree_abs_path = worktree.abs_path().clone();
self.start_language_server(worktree_id, worktree_abs_path, new_language, cx); self.start_language_servers(worktree_id, worktree_abs_path, new_language, cx);
} }
} }
} }
fn start_language_server( fn start_language_servers(
&mut self, &mut self,
worktree_id: WorktreeId, worktree_id: WorktreeId,
worktree_path: Arc<Path>, worktree_path: Arc<Path>,
@ -2090,17 +2120,21 @@ impl Project {
return; return;
} }
let adapter = if let Some(adapter) = language.lsp_adapter() { let adapters = language.lsp_adapters();
adapter let language_servers = self.languages.start_language_servers(
} else { language.clone(),
return; worktree_path,
}; self.client.http_client(),
cx,
);
debug_assert_eq!(adapters.len(), language_servers.len());
for (adapter, pending_server) in adapters.into_iter().zip(language_servers.into_iter()) {
let key = (worktree_id, adapter.name.clone()); let key = (worktree_id, adapter.name.clone());
let mut initialization_options = adapter.initialization_options.clone();
let lsp = &cx.global::<Settings>().lsp.get(&adapter.name.0); let lsp = &cx.global::<Settings>().lsp.get(&adapter.name.0);
let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
let mut initialization_options = adapter.initialization_options.clone();
match (&mut initialization_options, override_options) { match (&mut initialization_options, override_options) {
(Some(initialization_options), Some(override_options)) => { (Some(initialization_options), Some(override_options)) => {
merge_json_value_into(override_options, initialization_options); merge_json_value_into(override_options, initialization_options);
@ -2112,21 +2146,37 @@ impl Project {
self.language_server_ids self.language_server_ids
.entry(key.clone()) .entry(key.clone())
.or_insert_with(|| { .or_insert_with(|| {
let languages = self.languages.clone(); self.setup_language_adapter(
let server_id = post_inc(&mut self.next_language_server_id);
let language_server = self.languages.start_language_server(
server_id,
language.clone(),
worktree_path, worktree_path,
self.client.http_client(), initialization_options,
pending_server,
adapter,
&language,
key,
cx, cx,
); )
});
}
}
fn setup_language_adapter(
&mut self,
worktree_path: Arc<Path>,
initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
adapter: &Arc<CachedLspAdapter>,
language: &Arc<Language>,
key: (WorktreeId, LanguageServerName),
cx: &mut ModelContext<Project>,
) -> usize {
let server_id = pending_server.server_id;
let languages = self.languages.clone();
self.language_servers.insert( self.language_servers.insert(
server_id, server_id,
LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move {
let workspace_config = let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await;
cx.update(|cx| languages.workspace_configuration(cx)).await; let language_server = pending_server.task.await.log_err()?;
let language_server = language_server?.await.log_err()?;
let language_server = language_server let language_server = language_server
.initialize(initialization_options) .initialize(initialization_options)
.await .await
@ -2165,9 +2215,8 @@ impl Project {
move |params, mut cx| { move |params, mut cx| {
let languages = languages.clone(); let languages = languages.clone();
async move { async move {
let workspace_config = cx let workspace_config =
.update(|cx| languages.workspace_configuration(cx)) cx.update(|cx| languages.workspace_configuration(cx)).await;
.await;
Ok(params Ok(params
.items .items
.into_iter() .into_iter()
@ -2199,9 +2248,7 @@ impl Project {
if let Some(status) = if let Some(status) =
this.language_server_statuses.get_mut(&server_id) this.language_server_statuses.get_mut(&server_id)
{ {
if let lsp::NumberOrString::String(token) = if let lsp::NumberOrString::String(token) = params.token {
params.token
{
status.progress_tokens.insert(token); status.progress_tokens.insert(token);
} }
} }
@ -2300,7 +2347,7 @@ impl Project {
server_id, server_id,
LanguageServerState::Running { LanguageServerState::Running {
adapter: adapter.clone(), adapter: adapter.clone(),
language, language: language.clone(),
watched_paths: Default::default(), watched_paths: Default::default(),
server: language_server.clone(), server: language_server.clone(),
simulate_disk_based_diagnostics_completion: None, simulate_disk_based_diagnostics_completion: None,
@ -2332,19 +2379,17 @@ impl Project {
for buffer in this.opened_buffers.values() { for buffer in this.opened_buffers.values() {
if let Some(buffer_handle) = buffer.upgrade(cx) { if let Some(buffer_handle) = buffer.upgrade(cx) {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let file = if let Some(file) = File::from_dyn(buffer.file()) { let file = match File::from_dyn(buffer.file()) {
file Some(file) => file,
} else { None => continue,
continue;
}; };
let language = if let Some(language) = buffer.language() { let language = match buffer.language() {
language Some(language) => language,
} else { None => continue,
continue;
}; };
if file.worktree.read(cx).id() != key.0 if file.worktree.read(cx).id() != key.0
|| language.lsp_adapter().map(|a| a.name.clone()) || !language.lsp_adapters().iter().any(|a| a.name == key.1)
!= Some(key.1.clone())
{ {
continue; continue;
} }
@ -2353,9 +2398,18 @@ impl Project {
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_default()
.entry(server_id)
.or_insert_with(|| {
vec![LspBufferSnapshot {
version: 0,
snapshot: buffer.text_snapshot(),
}]
});
let (version, initial_snapshot) = versions.last().unwrap(); let snapshot = versions.last().unwrap();
let version = snapshot.version;
let initial_snapshot = snapshot.snapshot;
let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
language_server language_server
.notify::<lsp::notification::DidOpenTextDocument>( .notify::<lsp::notification::DidOpenTextDocument>(
@ -2367,7 +2421,7 @@ impl Project {
.get(language.name().as_ref()) .get(language.name().as_ref())
.cloned() .cloned()
.unwrap_or_default(), .unwrap_or_default(),
*version, version,
initial_snapshot.text(), initial_snapshot.text(),
), ),
}, },
@ -2379,9 +2433,7 @@ impl Project {
.capabilities() .capabilities()
.completion_provider .completion_provider
.as_ref() .as_ref()
.and_then(|provider| { .and_then(|provider| provider.trigger_characters.clone())
provider.trigger_characters.clone()
})
.unwrap_or_default(), .unwrap_or_default(),
cx, cx,
) )
@ -2394,9 +2446,7 @@ impl Project {
}) })
})), })),
); );
server_id server_id
});
} }
// Returns a list of all of the worktrees which no longer have a language server and the root path // Returns a list of all of the worktrees which no longer have a language server and the root path
@ -2476,30 +2526,41 @@ impl Project {
}) })
.collect(); .collect();
for (worktree_id, worktree_abs_path, language) in language_server_lookup_info { for (worktree_id, worktree_abs_path, language) in language_server_lookup_info {
self.restart_language_server(worktree_id, worktree_abs_path, language, cx); self.restart_language_servers(worktree_id, worktree_abs_path, language, cx);
} }
None None
} }
fn restart_language_server( fn restart_language_servers(
&mut self, &mut self,
worktree_id: WorktreeId, worktree_id: WorktreeId,
fallback_path: Arc<Path>, fallback_path: Arc<Path>,
language: Arc<Language>, language: Arc<Language>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
let adapter = if let Some(adapter) = language.lsp_adapter() { let mut stops = Vec::new();
adapter for adapter in language.lsp_adapters() {
} else { stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx));
}
if stops.is_empty() {
return; return;
}
let mut stops = stops.into_iter();
cx.spawn_weak(|this, mut cx| async move {
let (original_root_path, mut orphaned_worktrees) = stops.next().unwrap().await;
for stop in stops {
let (_, worktrees) = stop.await;
orphaned_worktrees.extend_from_slice(&worktrees);
}
let this = match this.upgrade(&cx) {
Some(this) => this,
None => return,
}; };
let server_name = adapter.name.clone();
let stop = self.stop_language_server(worktree_id, server_name.clone(), cx);
cx.spawn_weak(|this, mut cx| async move {
let (original_root_path, orphaned_worktrees) = stop.await;
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
// Attempt to restart using original server path. Fallback to passed in // Attempt to restart using original server path. Fallback to passed in
// path if we could not retrieve the root path // path if we could not retrieve the root path
@ -2507,21 +2568,22 @@ impl Project {
.map(|path_buf| Arc::from(path_buf.as_path())) .map(|path_buf| Arc::from(path_buf.as_path()))
.unwrap_or(fallback_path); .unwrap_or(fallback_path);
this.start_language_server(worktree_id, root_path, language, cx); this.start_language_servers(worktree_id, root_path, language, cx);
// Lookup new server id and set it for each of the orphaned worktrees // Lookup new server ids and set them for each of the orphaned worktrees
for adapter in language.lsp_adapters() {
if let Some(new_server_id) = this if let Some(new_server_id) = this
.language_server_ids .language_server_ids
.get(&(worktree_id, server_name.clone())) .get(&(worktree_id, adapter.name.clone()))
.cloned() .cloned()
{ {
for orphaned_worktree in orphaned_worktrees { for orphaned_worktree in orphaned_worktrees {
this.language_server_ids this.language_server_ids
.insert((orphaned_worktree, server_name.clone()), new_server_id); .insert((orphaned_worktree, adapter.name.clone()), new_server_id);
}
} }
} }
}); });
}
}) })
.detach(); .detach();
} }
@ -3074,7 +3136,7 @@ impl Project {
let file = File::from_dyn(buffer.file())?; let file = File::from_dyn(buffer.file())?;
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx)); let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
let server = self let server = self
.language_server_for_buffer(buffer, cx) .primary_language_servers_for_buffer(buffer, cx)
.map(|s| s.1.clone()); .map(|s| s.1.clone());
Some((buffer_handle, buffer_abs_path, server)) Some((buffer_handle, buffer_abs_path, server))
}) })
@ -3323,7 +3385,7 @@ impl Project {
if let Some(lsp_edits) = lsp_edits { if let Some(lsp_edits) = lsp_edits {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.edits_from_lsp(buffer, lsp_edits, None, cx) this.edits_from_lsp(buffer, lsp_edits, language_server.server_id(), None, cx)
}) })
.await .await
} else { } else {
@ -3654,7 +3716,7 @@ impl Project {
let buffer_id = buffer.remote_id(); let buffer_id = buffer.remote_id();
if self.is_local() { if self.is_local() {
let lang_server = match self.language_server_for_buffer(buffer, cx) { let lang_server = match self.primary_language_servers_for_buffer(buffer, cx) {
Some((_, server)) => server.clone(), Some((_, server)) => server.clone(),
_ => return Task::ready(Ok(Default::default())), _ => return Task::ready(Ok(Default::default())),
}; };
@ -3667,7 +3729,13 @@ impl Project {
if let Some(edits) = resolved_completion.additional_text_edits { if let Some(edits) = resolved_completion.additional_text_edits {
let edits = this let edits = this
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.edits_from_lsp(&buffer_handle, edits, None, cx) this.edits_from_lsp(
&buffer_handle,
edits,
lang_server.server_id(),
None,
cx,
)
}) })
.await?; .await?;
@ -3757,8 +3825,9 @@ impl Project {
) -> Task<Result<ProjectTransaction>> { ) -> Task<Result<ProjectTransaction>> {
if self.is_local() { if self.is_local() {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let (lsp_adapter, lang_server) = let (lsp_adapter, lang_server) = if let Some((adapter, server)) =
if let Some((adapter, server)) = self.language_server_for_buffer(buffer, cx) { self.language_server_for_buffer(buffer, action.server_id, cx)
{
(adapter.clone(), server.clone()) (adapter.clone(), server.clone())
} else { } else {
return Task::ready(Ok(Default::default())); return Task::ready(Ok(Default::default()));
@ -3896,6 +3965,7 @@ impl Project {
.await?; .await?;
} }
} }
lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => { lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => {
let source_abs_path = op let source_abs_path = op
.old_uri .old_uri
@ -3912,6 +3982,7 @@ impl Project {
) )
.await?; .await?;
} }
lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => { lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => {
let abs_path = op let abs_path = op
.uri .uri
@ -3924,6 +3995,7 @@ impl Project {
fs.remove_file(&abs_path, options).await?; fs.remove_file(&abs_path, options).await?;
} }
} }
lsp::DocumentChangeOperation::Edit(op) => { lsp::DocumentChangeOperation::Edit(op) => {
let buffer_to_edit = this let buffer_to_edit = this
.update(cx, |this, cx| { .update(cx, |this, cx| {
@ -3945,6 +4017,7 @@ impl Project {
this.edits_from_lsp( this.edits_from_lsp(
&buffer_to_edit, &buffer_to_edit,
edits, edits,
language_server.server_id(),
op.text_document.version, op.text_document.version,
cx, cx,
) )
@ -4214,6 +4287,7 @@ impl Project {
} }
} }
// TODO: Wire this up to allow selecting a server?
fn request_lsp<R: LspCommand>( fn request_lsp<R: LspCommand>(
&self, &self,
buffer_handle: ModelHandle<Buffer>, buffer_handle: ModelHandle<Buffer>,
@ -4227,7 +4301,7 @@ impl Project {
if self.is_local() { if self.is_local() {
let file = File::from_dyn(buffer.file()).and_then(File::as_local); let file = File::from_dyn(buffer.file()).and_then(File::as_local);
if let Some((file, language_server)) = file.zip( if let Some((file, language_server)) = file.zip(
self.language_server_for_buffer(buffer, cx) self.primary_language_servers_for_buffer(buffer, cx)
.map(|(_, server)| server.clone()), .map(|(_, server)| server.clone()),
) { ) {
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
@ -4241,7 +4315,13 @@ impl Project {
.await .await
.context("lsp request failed")?; .context("lsp request failed")?;
request request
.response_from_lsp(response, this, buffer_handle, cx) .response_from_lsp(
response,
this,
buffer_handle,
language_server.server_id(),
cx,
)
.await .await
}); });
} }
@ -4491,9 +4571,9 @@ impl Project {
} }
for (buffer, old_path) in renamed_buffers { for (buffer, old_path) in renamed_buffers {
self.unregister_buffer_from_language_server(&buffer, old_path, cx); self.unregister_buffer_from_language_servers(&buffer, old_path, cx);
self.detect_language_for_buffer(&buffer, cx); self.detect_language_for_buffer(&buffer, cx);
self.register_buffer_with_language_server(&buffer, cx); self.register_buffer_with_language_servers(&buffer, cx);
} }
} }
@ -6048,10 +6128,11 @@ impl Project {
&mut self, &mut self,
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,
lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>, lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,
server_id: usize,
version: Option<i32>, version: Option<i32>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<(Range<Anchor>, String)>>> { ) -> Task<Result<Vec<(Range<Anchor>, String)>>> {
let snapshot = self.buffer_snapshot_for_lsp_version(buffer, version, cx); let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx);
cx.background().spawn(async move { cx.background().spawn(async move {
let snapshot = snapshot?; let snapshot = snapshot?;
let mut lsp_edits = lsp_edits let mut lsp_edits = lsp_edits
@ -6150,6 +6231,7 @@ impl Project {
fn buffer_snapshot_for_lsp_version( fn buffer_snapshot_for_lsp_version(
&mut self, &mut self,
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,
server_id: usize,
version: Option<i32>, version: Option<i32>,
cx: &AppContext, cx: &AppContext,
) -> Result<TextBufferSnapshot> { ) -> Result<TextBufferSnapshot> {
@ -6160,32 +6242,33 @@ impl Project {
let snapshots = self let snapshots = self
.buffer_snapshots .buffer_snapshots
.get_mut(&buffer_id) .get_mut(&buffer_id)
.ok_or_else(|| anyhow!("no snapshot found for buffer {}", buffer_id))?; .and_then(|m| m.get_mut(&server_id))
let found_snapshot = snapshots .ok_or_else(|| {
.binary_search_by_key(&version, |e| e.0) anyhow!("no snapshots found for buffer {buffer_id} and server {server_id}")
.map(|ix| snapshots[ix].1.clone())
.map_err(|_| {
anyhow!(
"snapshot not found for buffer {} at version {}",
buffer_id,
version
)
})?; })?;
snapshots.retain(|(snapshot_version, _)| {
snapshot_version + OLD_VERSIONS_TO_RETAIN >= version let found_snapshot = snapshots
}); .binary_search_by_key(&version, |e| e.version)
.map(|ix| snapshots[ix].snapshot.clone())
.map_err(|_| {
anyhow!("snapshot not found for buffer {buffer_id} server {server_id} at version {version}")
})?;
snapshots.retain(|snapshot| snapshot.version + OLD_VERSIONS_TO_RETAIN >= version);
Ok(found_snapshot) Ok(found_snapshot)
} else { } else {
Ok((buffer.read(cx)).text_snapshot()) Ok((buffer.read(cx)).text_snapshot())
} }
} }
fn language_server_for_buffer( fn running_language_servers_for_buffer(
&self, &self,
buffer: &Buffer, buffer: &Buffer,
cx: &AppContext, cx: &AppContext,
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> { ) -> impl Iterator<Item = (&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
let server_id = self.language_server_id_for_buffer(buffer, cx)?; self.language_server_ids_for_buffer(buffer, cx)
.into_iter()
.filter_map(|server_id| {
let server = self.language_servers.get(&server_id)?; let server = self.language_servers.get(&server_id)?;
if let LanguageServerState::Running { if let LanguageServerState::Running {
adapter, server, .. adapter, server, ..
@ -6195,16 +6278,49 @@ impl Project {
} else { } else {
None None
} }
})
} }
fn language_server_id_for_buffer(&self, buffer: &Buffer, cx: &AppContext) -> Option<usize> { fn language_servers_for_buffer(
&self,
buffer: &Buffer,
cx: &AppContext,
) -> Vec<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
self.running_language_servers_for_buffer(buffer, cx)
.collect()
}
fn primary_language_servers_for_buffer(
&self,
buffer: &Buffer,
cx: &AppContext,
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
self.running_language_servers_for_buffer(buffer, cx).next()
}
fn language_server_for_buffer(
&self,
buffer: &Buffer,
server_id: usize,
cx: &AppContext,
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
self.running_language_servers_for_buffer(buffer, cx)
.find(|(_, s)| s.server_id() == server_id)
}
fn language_server_ids_for_buffer(&self, buffer: &Buffer, cx: &AppContext) -> Vec<usize> {
if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
let name = language.lsp_adapter()?.name.clone();
let worktree_id = file.worktree_id(cx); let worktree_id = file.worktree_id(cx);
let key = (worktree_id, name); language
.lsp_adapters()
.iter()
.flat_map(|adapter| {
let key = (worktree_id, adapter.name.clone());
self.language_server_ids.get(&key).copied() self.language_server_ids.get(&key).copied()
})
.collect()
} else { } else {
None Vec::new()
} }
} }
} }

View file

@ -1573,6 +1573,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
new_text: "".into(), new_text: "".into(),
}, },
], ],
0,
Some(lsp_document_version), Some(lsp_document_version),
cx, cx,
) )
@ -1667,6 +1668,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
new_text: "".into(), new_text: "".into(),
}, },
], ],
0,
None, None,
cx, cx,
) )
@ -1770,6 +1772,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
.unindent(), .unindent(),
}, },
], ],
0,
None, None,
cx, cx,
) )
@ -2258,7 +2261,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
..Default::default() ..Default::default()
}, },
tree_sitter_rust::language(), tree_sitter_rust::language(),
None, vec![],
|_| Default::default(), |_| Default::default(),
); );

View file

@ -684,9 +684,10 @@ message SearchProjectResponse {
} }
message CodeAction { message CodeAction {
Anchor start = 1; uint64 server_id = 1;
Anchor end = 2; Anchor start = 2;
bytes lsp_action = 3; Anchor end = 3;
bytes lsp_action = 4;
} }
message ProjectTransaction { message ProjectTransaction {

View file

@ -37,121 +37,107 @@ pub fn init(
themes: Arc<ThemeRegistry>, themes: Arc<ThemeRegistry>,
node_runtime: Arc<NodeRuntime>, node_runtime: Arc<NodeRuntime>,
) { ) {
for (name, grammar, lsp_adapter) in [ fn adapter_arc(adapter: impl LspAdapter) -> Arc<dyn LspAdapter> {
Arc::new(adapter)
}
let languages_list = [
( (
"c", "c",
tree_sitter_c::language(), tree_sitter_c::language(),
Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>), vec![adapter_arc(c::CLspAdapter)],
), ),
( (
"cpp", "cpp",
tree_sitter_cpp::language(), tree_sitter_cpp::language(),
Some(Arc::new(c::CLspAdapter)), vec![adapter_arc(c::CLspAdapter)],
),
(
"css",
tree_sitter_css::language(),
None, //
), ),
("css", tree_sitter_css::language(), vec![]),
( (
"elixir", "elixir",
tree_sitter_elixir::language(), tree_sitter_elixir::language(),
Some(Arc::new(elixir::ElixirLspAdapter)), vec![adapter_arc(elixir::ElixirLspAdapter)],
), ),
( (
"go", "go",
tree_sitter_go::language(), tree_sitter_go::language(),
Some(Arc::new(go::GoLspAdapter)), vec![adapter_arc(go::GoLspAdapter)],
), ),
( (
"json", "json",
tree_sitter_json::language(), tree_sitter_json::language(),
Some(Arc::new(json::JsonLspAdapter::new( vec![adapter_arc(json::JsonLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
languages.clone(), languages.clone(),
themes.clone(), themes.clone(),
))), ))],
),
(
"markdown",
tree_sitter_markdown::language(),
None, //
), ),
("markdown", tree_sitter_markdown::language(), vec![]),
( (
"python", "python",
tree_sitter_python::language(), tree_sitter_python::language(),
Some(Arc::new(python::PythonLspAdapter::new( vec![adapter_arc(python::PythonLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
))), ))],
), ),
( (
"rust", "rust",
tree_sitter_rust::language(), tree_sitter_rust::language(),
Some(Arc::new(rust::RustLspAdapter)), vec![adapter_arc(rust::RustLspAdapter)],
),
(
"toml",
tree_sitter_toml::language(),
None, //
), ),
("toml", tree_sitter_toml::language(), vec![]),
( (
"tsx", "tsx",
tree_sitter_typescript::language_tsx(), tree_sitter_typescript::language_tsx(),
Some(Arc::new(typescript::TypeScriptLspAdapter::new( vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
))), ))],
), ),
( (
"typescript", "typescript",
tree_sitter_typescript::language_typescript(), tree_sitter_typescript::language_typescript(),
Some(Arc::new(typescript::TypeScriptLspAdapter::new( vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
))), ))],
), ),
( (
"javascript", "javascript",
tree_sitter_typescript::language_tsx(), tree_sitter_typescript::language_tsx(),
Some(Arc::new(typescript::TypeScriptLspAdapter::new( vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
node_runtime.clone(), node_runtime.clone(),
))), ))],
), ),
( (
"html", "html",
tree_sitter_html::language(), tree_sitter_html::language(),
Some(Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))), vec![adapter_arc(html::HtmlLspAdapter::new(node_runtime.clone()))],
), ),
( (
"ruby", "ruby",
tree_sitter_ruby::language(), tree_sitter_ruby::language(),
Some(Arc::new(ruby::RubyLanguageServer)), vec![adapter_arc(ruby::RubyLanguageServer)],
), ),
( (
"erb", "erb",
tree_sitter_embedded_template::language(), tree_sitter_embedded_template::language(),
Some(Arc::new(ruby::RubyLanguageServer)), vec![adapter_arc(ruby::RubyLanguageServer)],
),
(
"scheme",
tree_sitter_scheme::language(),
None, //
),
(
"racket",
tree_sitter_racket::language(),
None, //
), ),
("scheme", tree_sitter_scheme::language(), vec![]),
("racket", tree_sitter_racket::language(), vec![]),
( (
"lua", "lua",
tree_sitter_lua::language(), tree_sitter_lua::language(),
Some(Arc::new(lua::LuaLspAdapter)), vec![adapter_arc(lua::LuaLspAdapter)],
), ),
( (
"yaml", "yaml",
tree_sitter_yaml::language(), tree_sitter_yaml::language(),
Some(Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))), vec![adapter_arc(yaml::YamlLspAdapter::new(node_runtime.clone()))],
), ),
] { ];
languages.register(name, load_config(name), grammar, lsp_adapter, load_queries);
for (name, grammar, lsp_adapters) in languages_list {
languages.register(name, load_config(name), grammar, lsp_adapters, load_queries);
} }
} }
@ -163,7 +149,7 @@ pub async fn language(
) -> Arc<Language> { ) -> Arc<Language> {
Arc::new( Arc::new(
Language::new(load_config(name), Some(grammar)) Language::new(load_config(name), Some(grammar))
.with_lsp_adapter(lsp_adapter) .with_lsp_adapters(lsp_adapter)
.await .await
.with_queries(load_queries(name)) .with_queries(load_queries(name))
.unwrap(), .unwrap(),

View file

@ -37,7 +37,7 @@ impl TypeScriptLspAdapter {
} }
} }
struct Versions { struct TypeScriptVersions {
typescript_version: String, typescript_version: String,
server_version: String, server_version: String,
} }
@ -52,7 +52,8 @@ impl LspAdapter for TypeScriptLspAdapter {
&self, &self,
_: Arc<dyn HttpClient>, _: Arc<dyn HttpClient>,
) -> Result<Box<dyn 'static + Send + Any>> { ) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(Versions { dbg!();
Ok(Box::new(TypeScriptVersions {
typescript_version: self.node.npm_package_latest_version("typescript").await?, typescript_version: self.node.npm_package_latest_version("typescript").await?,
server_version: self server_version: self
.node .node
@ -67,7 +68,8 @@ impl LspAdapter for TypeScriptLspAdapter {
_: Arc<dyn HttpClient>, _: Arc<dyn HttpClient>,
container_dir: PathBuf, container_dir: PathBuf,
) -> Result<LanguageServerBinary> { ) -> Result<LanguageServerBinary> {
let versions = versions.downcast::<Versions>().unwrap(); dbg!();
let versions = versions.downcast::<TypeScriptVersions>().unwrap();
let server_path = container_dir.join(Self::NEW_SERVER_PATH); let server_path = container_dir.join(Self::NEW_SERVER_PATH);
if fs::metadata(&server_path).await.is_err() { if fs::metadata(&server_path).await.is_err() {
@ -92,18 +94,10 @@ impl LspAdapter for TypeScriptLspAdapter {
} }
async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> { async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
dbg!();
(|| async move { (|| async move {
let mut last_version_dir = None; let old_server_path = container_dir.join(Self::OLD_SERVER_PATH);
let mut entries = fs::read_dir(&container_dir).await?; let new_server_path = container_dir.join(Self::NEW_SERVER_PATH);
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let old_server_path = last_version_dir.join(Self::OLD_SERVER_PATH);
let new_server_path = last_version_dir.join(Self::NEW_SERVER_PATH);
if new_server_path.exists() { if new_server_path.exists() {
Ok(LanguageServerBinary { Ok(LanguageServerBinary {
path: self.node.binary_path().await?, path: self.node.binary_path().await?,
@ -117,7 +111,7 @@ impl LspAdapter for TypeScriptLspAdapter {
} else { } else {
Err(anyhow!( Err(anyhow!(
"missing executable in directory {:?}", "missing executable in directory {:?}",
last_version_dir container_dir
)) ))
} }
})() })()
@ -170,6 +164,86 @@ impl LspAdapter for TypeScriptLspAdapter {
} }
} }
pub struct EsLintLspAdapter {
node: Arc<NodeRuntime>,
}
impl EsLintLspAdapter {
const SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs";
pub fn new(node: Arc<NodeRuntime>) -> Self {
EsLintLspAdapter { node }
}
}
#[async_trait]
impl LspAdapter for EsLintLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("eslint".into())
}
async fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(
self.node.npm_package_latest_version("eslint").await?,
))
}
async fn fetch_server_binary(
&self,
versions: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
) -> Result<LanguageServerBinary> {
let version = versions.downcast::<String>().unwrap();
let server_path = container_dir.join(Self::SERVER_PATH);
if fs::metadata(&server_path).await.is_err() {
self.node
.npm_install_packages([("eslint", version.as_str())], &container_dir)
.await?;
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let server_path = container_dir.join(Self::SERVER_PATH);
if server_path.exists() {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
} else {
Err(anyhow!(
"missing executable in directory {:?}",
container_dir
))
}
})()
.await
.log_err()
}
async fn label_for_completion(
&self,
item: &lsp::CompletionItem,
language: &Arc<language::Language>,
) -> Option<language::CodeLabel> {
None
}
async fn initialization_options(&self) -> Option<serde_json::Value> {
None
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use gpui::TestAppContext; use gpui::TestAppContext;