Detailed changes
@@ -2764,6 +2764,15 @@ impl MultiBufferSnapshot {
.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 {
self.is_dirty
}
@@ -156,6 +156,7 @@ pub struct Completion {
#[derive(Clone, Debug)]
pub struct CodeAction {
+ pub server_id: usize,
pub range: Range<Anchor>,
pub lsp_action: lsp::CodeAction,
}
@@ -414,7 +414,7 @@ pub struct BracketPair {
pub struct Language {
pub(crate) config: LanguageConfig,
pub(crate) grammar: Option<Arc<Grammar>>,
- pub(crate) adapter: Option<Arc<CachedLspAdapter>>,
+ pub(crate) adapters: Vec<Arc<CachedLspAdapter>>,
#[cfg(any(test, feature = "test-support"))]
fake_adapter: Option<(
@@ -492,7 +492,7 @@ struct AvailableLanguage {
path: &'static str,
config: LanguageConfig,
grammar: tree_sitter::Language,
- lsp_adapter: Option<Arc<dyn LspAdapter>>,
+ lsp_adapters: Vec<Arc<dyn LspAdapter>>,
get_queries: fn(&str) -> LanguageQueries,
}
@@ -513,6 +513,7 @@ pub struct LanguageRegistry {
}
struct LanguageRegistryState {
+ next_language_server_id: usize,
languages: Vec<Arc<Language>>,
available_languages: Vec<AvailableLanguage>,
next_available_language_id: AvailableLanguageId,
@@ -522,11 +523,17 @@ struct LanguageRegistryState {
version: usize,
}
+pub struct PendingLanguageServer {
+ pub server_id: usize,
+ pub task: Task<Result<lsp::LanguageServer>>,
+}
+
impl LanguageRegistry {
pub fn new(login_shell_env_loaded: Task<()>) -> Self {
let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
Self {
state: RwLock::new(LanguageRegistryState {
+ next_language_server_id: 0,
languages: vec![PLAIN_TEXT.clone()],
available_languages: Default::default(),
next_available_language_id: 0,
@@ -558,7 +565,7 @@ impl LanguageRegistry {
path: &'static str,
config: LanguageConfig,
grammar: tree_sitter::Language,
- lsp_adapter: Option<Arc<dyn LspAdapter>>,
+ lsp_adapters: Vec<Arc<dyn LspAdapter>>,
get_queries: fn(&str) -> LanguageQueries,
) {
let state = &mut *self.state.write();
@@ -567,7 +574,7 @@ impl LanguageRegistry {
path,
config,
grammar,
- lsp_adapter,
+ lsp_adapters,
get_queries,
});
}
@@ -590,12 +597,13 @@ impl LanguageRegistry {
state
.available_languages
.iter()
- .filter_map(|l| l.lsp_adapter.clone())
+ .flat_map(|l| l.lsp_adapters.clone())
.chain(
state
.languages
.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<_>>()
};
@@ -721,7 +729,7 @@ impl LanguageRegistry {
let queries = (language.get_queries)(&language.path);
let language =
Language::new(language.config, Some(language.grammar))
- .with_lsp_adapter(language.lsp_adapter)
+ .with_lsp_adapters(language.lsp_adapters)
.await;
let name = language.name();
match language.with_queries(queries) {
@@ -774,18 +782,16 @@ impl LanguageRegistry {
self.state.read().languages.iter().cloned().collect()
}
- pub fn start_language_server(
+ pub fn start_language_servers(
self: &Arc<Self>,
- server_id: usize,
language: Arc<Language>,
root_path: Arc<Path>,
http_client: Arc<dyn HttpClient>,
cx: &mut AppContext,
- ) -> Option<Task<Result<lsp::LanguageServer>>> {
+ ) -> Vec<PendingLanguageServer> {
#[cfg(any(test, feature = "test-support"))]
if language.fake_adapter.is_some() {
- let language = language;
- return Some(cx.spawn(|cx| async move {
+ let task = cx.spawn(|cx| async move {
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
let (server, mut fake_server) = lsp::LanguageServer::fake(
fake_adapter.name.to_string(),
@@ -810,53 +816,71 @@ impl LanguageRegistry {
})
.detach();
Ok(server)
- }));
+ });
+ return vec![PendingLanguageServer { server_id: 0, task }];
}
let download_dir = self
.language_server_download_dir
.clone()
.ok_or_else(|| anyhow!("language server download directory has not been assigned"))
- .log_err()?;
-
- let this = self.clone();
- let adapter = language.adapter.clone()?;
- let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
- let login_shell_env_loaded = self.login_shell_env_loaded.clone();
-
- Some(cx.spawn(|cx| async move {
- login_shell_env_loaded.await;
-
- let mut lock = this.lsp_binary_paths.lock();
- let entry = lock
- .entry(adapter.name.clone())
- .or_insert_with(|| {
- get_binary(
- adapter.clone(),
- language.clone(),
- http_client,
- download_dir,
- lsp_binary_statuses,
- )
- .map_err(Arc::new)
- .boxed()
- .shared()
- })
- .clone();
- drop(lock);
- let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
+ .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 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 login_shell_env_loaded = self.login_shell_env_loaded.clone();
+ let server_id = post_inc(&mut self.state.write().next_language_server_id);
+
+ let task = cx.spawn(|cx| async move {
+ login_shell_env_loaded.await;
+
+ let mut lock = this.lsp_binary_paths.lock();
+ let entry = lock
+ .entry(adapter.name.clone())
+ .or_insert_with(|| {
+ get_binary(
+ adapter.clone(),
+ language.clone(),
+ http_client,
+ download_dir,
+ lsp_binary_statuses,
+ )
+ .map_err(Arc::new)
+ .boxed()
+ .shared()
+ })
+ .clone();
+ drop(lock);
+ let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
+
+ let server = lsp::LanguageServer::new(
+ server_id,
+ &binary.path,
+ &binary.arguments,
+ &root_path,
+ adapter.code_action_kinds(),
+ cx,
+ )?;
+
+ Ok(server)
+ });
- let server = lsp::LanguageServer::new(
- server_id,
- &binary.path,
- &binary.arguments,
- &root_path,
- adapter.code_action_kinds(),
- cx,
- )?;
+ results.push(PendingLanguageServer { server_id, task });
+ }
- Ok(server)
- }))
+ results
}
pub fn language_server_binary_statuses(
@@ -974,15 +998,15 @@ impl Language {
highlight_map: Default::default(),
})
}),
- adapter: None,
+ adapters: Vec::new(),
#[cfg(any(test, feature = "test-support"))]
fake_adapter: None,
}
}
- pub fn lsp_adapter(&self) -> Option<Arc<CachedLspAdapter>> {
- self.adapter.clone()
+ pub fn lsp_adapters(&self) -> &[Arc<CachedLspAdapter>] {
+ &self.adapters
}
pub fn id(&self) -> Option<usize> {
@@ -1209,9 +1233,9 @@ impl Language {
Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
}
- pub async fn with_lsp_adapter(mut self, lsp_adapter: Option<Arc<dyn LspAdapter>>) -> Self {
- if let Some(adapter) = lsp_adapter {
- self.adapter = Some(CachedLspAdapter::new(adapter).await);
+ pub async fn with_lsp_adapters(mut self, lsp_adapters: Vec<Arc<dyn LspAdapter>>) -> Self {
+ for adapter in lsp_adapters {
+ self.adapters.push(CachedLspAdapter::new(adapter).await);
}
self
}
@@ -1224,7 +1248,7 @@ impl Language {
let (servers_tx, servers_rx) = mpsc::unbounded();
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
- self.adapter = Some(adapter);
+ self.adapters = vec![adapter];
servers_rx
}
@@ -1233,28 +1257,31 @@ impl Language {
}
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,
None => &[],
}
}
pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> {
- if let Some(adapter) = self.adapter.as_ref() {
- adapter.disk_based_diagnostics_progress_token.as_deref()
- } else {
- None
+ for adapter in &self.adapters {
+ let token = adapter.disk_based_diagnostics_progress_token.as_deref();
+ if token.is_some() {
+ return token;
+ }
}
+
+ None
}
pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
- if let Some(processor) = self.adapter.as_ref() {
- processor.process_diagnostics(diagnostics).await;
+ for adapter in &self.adapters {
+ adapter.process_diagnostics(diagnostics).await;
}
}
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;
}
}
@@ -1263,7 +1290,8 @@ impl Language {
self: &Arc<Self>,
completion: &lsp::CompletionItem,
) -> Option<CodeLabel> {
- self.adapter
+ self.adapters
+ .first()
.as_ref()?
.label_for_completion(completion, self)
.await
@@ -1274,7 +1302,8 @@ impl Language {
name: &str,
kind: lsp::SymbolKind,
) -> Option<CodeLabel> {
- self.adapter
+ self.adapters
+ .first()
.as_ref()?
.label_for_symbol(name, kind, self)
.await
@@ -1595,7 +1624,7 @@ mod tests {
..Default::default()
},
tree_sitter_json::language(),
- None,
+ vec![],
|_| Default::default(),
);
languages.register(
@@ -1606,7 +1635,7 @@ mod tests {
..Default::default()
},
tree_sitter_rust::language(),
- None,
+ vec![],
|_| Default::default(),
);
assert_eq!(
@@ -462,6 +462,7 @@ pub async fn deserialize_completion(
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
proto::CodeAction {
+ server_id: action.server_id as u64,
start: Some(serialize_anchor(&action.range.start)),
end: Some(serialize_anchor(&action.range.end)),
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"))?;
let lsp_action = serde_json::from_slice(&action.lsp_action)?;
Ok(CodeAction {
+ server_id: action.server_id as usize,
range: start..end,
lsp_action,
})
@@ -33,21 +33,25 @@ pub(crate) trait LspCommand: 'static + Sized {
language_server: &Arc<LanguageServer>,
cx: &AppContext,
) -> <Self::LspRequest as lsp::request::Request>::Params;
+
async fn response_from_lsp(
self,
message: <Self::LspRequest as lsp::request::Request>::Result,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ server_id: usize,
cx: AsyncAppContext,
) -> Result<Self::Response>;
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
+
async fn from_proto(
message: Self::ProtoRequest,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
cx: AsyncAppContext,
) -> Result<Self>;
+
fn response_to_proto(
response: Self::Response,
project: &mut Project,
@@ -55,6 +59,7 @@ pub(crate) trait LspCommand: 'static + Sized {
buffer_version: &clock::Global,
cx: &mut AppContext,
) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
+
async fn response_from_proto(
self,
message: <Self::ProtoRequest as proto::RequestMessage>::Response,
@@ -62,6 +67,7 @@ pub(crate) trait LspCommand: 'static + Sized {
buffer: ModelHandle<Buffer>,
cx: AsyncAppContext,
) -> Result<Self::Response>;
+
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
}
@@ -137,6 +143,7 @@ impl LspCommand for PrepareRename {
message: Option<lsp::PrepareRenameResponse>,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ _: usize,
cx: AsyncAppContext,
) -> Result<Option<Range<Anchor>>> {
buffer.read_with(&cx, |buffer, _| {
@@ -263,10 +270,12 @@ impl LspCommand for PerformRename {
message: Option<lsp::WorkspaceEdit>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ server_id: usize,
mut cx: AsyncAppContext,
) -> Result<ProjectTransaction> {
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,
edit,
@@ -380,9 +389,10 @@ impl LspCommand for GetDefinition {
message: Option<lsp::GotoDefinitionResponse>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ server_id: usize,
cx: AsyncAppContext,
) -> 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 {
@@ -472,9 +482,10 @@ impl LspCommand for GetTypeDefinition {
message: Option<lsp::GotoTypeDefinitionResponse>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ server_id: usize,
cx: AsyncAppContext,
) -> 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 {
@@ -537,12 +548,13 @@ impl LspCommand for GetTypeDefinition {
fn language_server_for_buffer(
project: &ModelHandle<Project>,
buffer: &ModelHandle<Buffer>,
+ server_id: usize,
cx: &mut AsyncAppContext,
) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
project
.read_with(cx, |project, cx| {
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()))
})
.ok_or_else(|| anyhow!("no language server found for buffer"))
@@ -614,6 +626,7 @@ async fn location_links_from_lsp(
message: Option<lsp::GotoDefinitionResponse>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ server_id: usize,
mut cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
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();
for (origin_range, target_uri, target_range) in unresolved_links {
let target_buffer_handle = project
@@ -756,11 +770,12 @@ impl LspCommand for GetReferences {
locations: Option<Vec<lsp::Location>>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ server_id: usize,
mut cx: AsyncAppContext,
) -> Result<Vec<Location>> {
let mut references = Vec::new();
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 {
for lsp_location in locations {
@@ -917,6 +932,7 @@ impl LspCommand for GetDocumentHighlights {
lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ _: usize,
cx: AsyncAppContext,
) -> Result<Vec<DocumentHighlight>> {
buffer.read_with(&cx, |buffer, _| {
@@ -1062,6 +1078,7 @@ impl LspCommand for GetHover {
message: Option<lsp::Hover>,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ _: usize,
cx: AsyncAppContext,
) -> Result<Self::Response> {
Ok(message.and_then(|hover| {
@@ -1283,6 +1300,7 @@ impl LspCommand for GetCompletions {
completions: Option<lsp::CompletionResponse>,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
+ _: usize,
cx: AsyncAppContext,
) -> Result<Vec<Completion>> {
let completions = if let Some(completions) = completions {
@@ -1502,6 +1520,7 @@ impl LspCommand for GetCodeActions {
actions: Option<lsp::CodeActionResponse>,
_: ModelHandle<Project>,
_: ModelHandle<Buffer>,
+ server_id: usize,
_: AsyncAppContext,
) -> Result<Vec<CodeAction>> {
Ok(actions
@@ -1510,6 +1529,7 @@ impl LspCommand for GetCodeActions {
.filter_map(|entry| {
if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
Some(CodeAction {
+ server_id,
range: self.range.clone(),
lsp_action,
})
@@ -31,8 +31,8 @@ use language::{
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel,
Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _,
Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, Operation, Patch,
- PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
- Unclipped,
+ PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
+ Transaction, Unclipped,
};
use lsp::{
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
@@ -99,7 +99,6 @@ pub struct Project {
language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>,
language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>,
- next_language_server_id: usize,
client: Arc<client::Client>,
next_entry_id: Arc<AtomicUsize>,
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.
/// Used for re-issuing buffer requests when peers temporarily disconnect
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>,
nonce: u128,
_maintain_buffer_languages: Task<()>,
@@ -133,6 +132,11 @@ pub struct Project {
copilot_enabled: bool,
}
+struct LspBufferSnapshot {
+ version: i32,
+ snapshot: TextBufferSnapshot,
+}
+
enum BufferMessage {
Operation {
buffer_id: u64,
@@ -469,7 +473,6 @@ impl Project {
language_server_statuses: Default::default(),
last_workspace_edits_by_language_server: Default::default(),
buffers_being_formatted: Default::default(),
- next_language_server_id: 0,
nonce: StdRng::from_entropy().gen(),
terminals: Terminals {
local_handles: Vec::new(),
@@ -554,7 +557,6 @@ impl Project {
})
.collect(),
last_workspace_edits_by_language_server: Default::default(),
- next_language_server_id: 0,
opened_buffers: Default::default(),
buffers_being_formatted: Default::default(),
buffer_snapshots: Default::default(),
@@ -645,7 +647,7 @@ impl Project {
let mut language_servers_to_stop = Vec::new();
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())) {
let lsp_name = &lsp_adapter.name;
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.
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() {
@@ -1550,7 +1552,7 @@ impl Project {
cx.spawn(|this, mut cx| async move {
if let Some(old_path) = old_path {
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?;
@@ -1564,7 +1566,7 @@ impl Project {
.await?;
this.update(&mut cx, |this, 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(())
})
@@ -1628,14 +1630,15 @@ impl Project {
.detach();
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);
cx.observe_release(buffer, |this, buffer, cx| {
if let Some(file) = File::from_dyn(buffer.file()) {
if file.is_local() {
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
+ .1
.notify::<lsp::notification::DidCloseTextDocument>(
lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(uri),
@@ -1652,46 +1655,50 @@ impl Project {
Ok(())
}
- fn register_buffer_with_language_server(
+ fn register_buffer_with_language_servers(
&mut self,
buffer_handle: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
) {
let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id();
+
if let Some(file) = File::from_dyn(buffer.file()) {
- if file.is_local() {
- let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
- let initial_snapshot = buffer.text_snapshot();
-
- let mut language_server = None;
- let mut language_id = None;
- if let Some(language) = buffer.language() {
- 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();
- language_server = self
- .language_server_ids
- .get(&(worktree_id, adapter.name.clone()))
- .and_then(|id| self.language_servers.get(id))
- .and_then(|server_state| {
- if let LanguageServerState::Running { server, .. } = server_state {
- Some(server.clone())
- } else {
- None
- }
- });
- }
- }
+ if !file.is_local() {
+ return;
+ }
- 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();
- }
+ let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
+ let initial_snapshot = buffer.text_snapshot();
+
+ 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(language) = buffer.language() {
+ let worktree_id = file.worktree_id(cx);
+
+ for adapter in language.lsp_adapters() {
+ let language_id = adapter.language_ids.get(language.name().as_ref()).cloned();
+ let server = self
+ .language_server_ids
+ .get(&(worktree_id, adapter.name.clone()))
+ .and_then(|id| self.language_servers.get(id))
+ .and_then(|server_state| {
+ if let LanguageServerState::Running { server, .. } = server_state {
+ Some(server.clone())
+ } else {
+ None
+ }
+ });
+ let server = match server {
+ Some(server) => server,
+ None => continue,
+ };
- if let Some(server) = language_server {
server
.notify::<lsp::notification::DidOpenTextDocument>(
lsp::DidOpenTextDocumentParams {
@@ -1704,6 +1711,7 @@ impl Project {
},
)
.log_err();
+
buffer_handle.update(cx, |buffer, cx| {
buffer.set_completion_triggers(
server
@@ -1713,16 +1721,23 @@ impl Project {
.and_then(|provider| provider.trigger_characters.clone())
.unwrap_or_default(),
cx,
- )
+ );
});
+
+ let snapshot = LspBufferSnapshot {
+ version: 0,
+ snapshot: initial_snapshot,
+ };
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,
buffer: &ModelHandle<Buffer>,
old_path: PathBuf,
@@ -1731,7 +1746,7 @@ impl Project {
buffer.update(cx, |buffer, cx| {
buffer.update_diagnostics(Default::default(), cx);
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
.notify::<lsp::notification::DidCloseTextDocument>(
lsp::DidCloseTextDocumentParams {
@@ -1833,52 +1848,62 @@ impl Project {
})
.ok();
}
+
BufferEvent::Edited { .. } => {
- let language_server = self
- .language_server_for_buffer(buffer.read(cx), cx)
- .map(|(_, server)| server.clone())?;
let buffer = buffer.read(cx);
let file = File::from_dyn(buffer.file())?;
let abs_path = file.as_local()?.abs_path(cx);
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_version = version + 1;
-
- let content_changes = buffer
- .edits_since::<(PointUtf16, usize)>(prev_snapshot.version())
- .map(|edit| {
- let edit_start = edit.new.start.0;
- let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
- let new_text = next_snapshot
- .text_for_range(edit.new.start.1..edit.new.end.1)
- .collect();
- lsp::TextDocumentContentChangeEvent {
- range: Some(lsp::Range::new(
- point_to_lsp(edit_start),
- point_to_lsp(edit_end),
- )),
- range_length: None,
- text: new_text,
- }
- })
- .collect();
- buffer_snapshots.push((next_version, next_snapshot));
+ 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
+ .edits_since::<(PointUtf16, usize)>(previous_snapshot.snapshot.version())
+ .map(|edit| {
+ let edit_start = edit.new.start.0;
+ let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
+ let new_text = next_snapshot
+ .text_for_range(edit.new.start.1..edit.new.end.1)
+ .collect();
+ lsp::TextDocumentContentChangeEvent {
+ range: Some(lsp::Range::new(
+ point_to_lsp(edit_start),
+ point_to_lsp(edit_end),
+ )),
+ range_length: None,
+ text: new_text,
+ }
+ })
+ .collect();
- language_server
- .notify::<lsp::notification::DidChangeTextDocument>(
- lsp::DidChangeTextDocumentParams {
- text_document: lsp::VersionedTextDocumentIdentifier::new(
- uri,
- next_version,
- ),
- content_changes,
- },
- )
- .log_err();
+ buffer_snapshots.push(LspBufferSnapshot {
+ version: next_version,
+ snapshot: next_snapshot,
+ });
+
+ language_server
+ .notify::<lsp::notification::DidChangeTextDocument>(
+ lsp::DidChangeTextDocumentParams {
+ text_document: lsp::VersionedTextDocumentIdentifier::new(
+ uri,
+ next_version,
+ ),
+ content_changes,
+ },
+ )
+ .log_err();
+ }
}
+
BufferEvent::Saved => {
let file = File::from_dyn(buffer.read(cx).file())?;
let worktree_id = file.worktree_id(cx);
@@ -1898,13 +1923,17 @@ impl Project {
.log_err();
}
- let language_server_id = self.language_server_id_for_buffer(buffer.read(cx), cx)?;
- if let Some(LanguageServerState::Running {
- adapter,
- simulate_disk_based_diagnostics_completion,
- ..
- }) = self.language_servers.get_mut(&language_server_id)
- {
+ let language_server_ids = self.language_server_ids_for_buffer(buffer.read(cx), cx);
+ for language_server_id in language_server_ids {
+ let LanguageServerState::Running {
+ adapter,
+ simulate_disk_based_diagnostics_completion,
+ ..
+ } = 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
// a disk-based progress token, kick off a timer that will reset every
// 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 {
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 {
@@ -2071,12 +2101,12 @@ impl Project {
if let Some(worktree) = file.worktree.read(cx).as_local() {
let worktree_id = worktree.id();
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,
worktree_id: WorktreeId,
worktree_path: Arc<Path>,
@@ -2090,313 +2120,333 @@ impl Project {
return;
}
- let adapter = if let Some(adapter) = language.lsp_adapter() {
- adapter
- } else {
- return;
- };
- let key = (worktree_id, adapter.name.clone());
+ let adapters = language.lsp_adapters();
+ let language_servers = self.languages.start_language_servers(
+ language.clone(),
+ worktree_path,
+ self.client.http_client(),
+ cx,
+ );
+ debug_assert_eq!(adapters.len(), language_servers.len());
- let mut initialization_options = adapter.initialization_options.clone();
+ for (adapter, pending_server) in adapters.into_iter().zip(language_servers.into_iter()) {
+ let key = (worktree_id, adapter.name.clone());
+ let lsp = &cx.global::<Settings>().lsp.get(&adapter.name.0);
+ let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
- let lsp = &cx.global::<Settings>().lsp.get(&adapter.name.0);
- let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
- match (&mut initialization_options, override_options) {
- (Some(initialization_options), Some(override_options)) => {
- merge_json_value_into(override_options, initialization_options);
+ let mut initialization_options = adapter.initialization_options.clone();
+ match (&mut initialization_options, override_options) {
+ (Some(initialization_options), Some(override_options)) => {
+ merge_json_value_into(override_options, initialization_options);
+ }
+ (None, override_options) => initialization_options = override_options,
+ _ => {}
}
- (None, override_options) => initialization_options = override_options,
- _ => {}
- }
- self.language_server_ids
- .entry(key.clone())
- .or_insert_with(|| {
- let languages = self.languages.clone();
- 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,
- self.client.http_client(),
- cx,
- );
- self.language_servers.insert(
- server_id,
- LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move {
- let workspace_config =
- cx.update(|cx| languages.workspace_configuration(cx)).await;
- let language_server = language_server?.await.log_err()?;
- let language_server = language_server
- .initialize(initialization_options)
- .await
- .log_err()?;
- let this = this.upgrade(&cx)?;
-
- language_server
- .on_notification::<lsp::notification::PublishDiagnostics, _>({
- let this = this.downgrade();
- let adapter = adapter.clone();
- move |mut params, cx| {
- let this = this;
- let adapter = adapter.clone();
- cx.spawn(|mut cx| async move {
- adapter.process_diagnostics(&mut params).await;
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| {
- this.update_diagnostics(
- server_id,
- params,
- &adapter.disk_based_diagnostic_sources,
- cx,
- )
- .log_err();
- });
- }
- })
- .detach();
- }
- })
- .detach();
+ self.language_server_ids
+ .entry(key.clone())
+ .or_insert_with(|| {
+ self.setup_language_adapter(
+ worktree_path,
+ initialization_options,
+ pending_server,
+ adapter,
+ &language,
+ key,
+ cx,
+ )
+ });
+ }
+ }
- language_server
- .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
- let languages = languages.clone();
- move |params, mut cx| {
- let languages = languages.clone();
- async move {
- let workspace_config = cx
- .update(|cx| languages.workspace_configuration(cx))
- .await;
- Ok(params
- .items
- .into_iter()
- .map(|item| {
- if let Some(section) = &item.section {
- workspace_config
- .get(section)
- .cloned()
- .unwrap_or(serde_json::Value::Null)
- } else {
- workspace_config.clone()
- }
- })
- .collect())
- }
- }
- })
- .detach();
+ 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();
- // Even though we don't have handling for these requests, respond to them to
- // avoid stalling any language server like `gopls` which waits for a response
- // to these requests when initializing.
- language_server
- .on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
- let this = this.downgrade();
- move |params, mut cx| async move {
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, _| {
- if let Some(status) =
- this.language_server_statuses.get_mut(&server_id)
- {
- if let lsp::NumberOrString::String(token) =
- params.token
- {
- status.progress_tokens.insert(token);
- }
- }
- });
- }
- Ok(())
- }
- })
- .detach();
- language_server
- .on_request::<lsp::request::RegisterCapability, _, _>({
- let this = this.downgrade();
- move |params, mut cx| async move {
- let this = this
- .upgrade(&cx)
- .ok_or_else(|| anyhow!("project dropped"))?;
- for reg in params.registrations {
- if reg.method == "workspace/didChangeWatchedFiles" {
- if let Some(options) = reg.register_options {
- let options = serde_json::from_value(options)?;
- this.update(&mut cx, |this, cx| {
- this.on_lsp_did_change_watched_files(
- server_id, options, cx,
- );
- });
- }
- }
- }
- Ok(())
- }
- })
- .detach();
+ self.language_servers.insert(
+ server_id,
+ LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move {
+ let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await;
+ let language_server = pending_server.task.await.log_err()?;
+ let language_server = language_server
+ .initialize(initialization_options)
+ .await
+ .log_err()?;
+ let this = this.upgrade(&cx)?;
- language_server
- .on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
- let this = this.downgrade();
- let adapter = adapter.clone();
- let language_server = language_server.clone();
- move |params, cx| {
- Self::on_lsp_workspace_edit(
- this,
- params,
- server_id,
- adapter.clone(),
- language_server.clone(),
- cx,
- )
+ language_server
+ .on_notification::<lsp::notification::PublishDiagnostics, _>({
+ let this = this.downgrade();
+ let adapter = adapter.clone();
+ move |mut params, cx| {
+ let this = this;
+ let adapter = adapter.clone();
+ cx.spawn(|mut cx| async move {
+ adapter.process_diagnostics(&mut params).await;
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| {
+ this.update_diagnostics(
+ server_id,
+ params,
+ &adapter.disk_based_diagnostic_sources,
+ cx,
+ )
+ .log_err();
+ });
}
})
.detach();
+ }
+ })
+ .detach();
- let disk_based_diagnostics_progress_token =
- adapter.disk_based_diagnostics_progress_token.clone();
+ language_server
+ .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
+ let languages = languages.clone();
+ move |params, mut cx| {
+ let languages = languages.clone();
+ async move {
+ let workspace_config =
+ cx.update(|cx| languages.workspace_configuration(cx)).await;
+ Ok(params
+ .items
+ .into_iter()
+ .map(|item| {
+ if let Some(section) = &item.section {
+ workspace_config
+ .get(section)
+ .cloned()
+ .unwrap_or(serde_json::Value::Null)
+ } else {
+ workspace_config.clone()
+ }
+ })
+ .collect())
+ }
+ }
+ })
+ .detach();
- language_server
- .on_notification::<lsp::notification::Progress, _>({
- let this = this.downgrade();
- move |params, mut cx| {
- if let Some(this) = this.upgrade(&cx) {
+ // Even though we don't have handling for these requests, respond to them to
+ // avoid stalling any language server like `gopls` which waits for a response
+ // to these requests when initializing.
+ language_server
+ .on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
+ let this = this.downgrade();
+ move |params, mut cx| async move {
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, _| {
+ if let Some(status) =
+ this.language_server_statuses.get_mut(&server_id)
+ {
+ if let lsp::NumberOrString::String(token) = params.token {
+ status.progress_tokens.insert(token);
+ }
+ }
+ });
+ }
+ Ok(())
+ }
+ })
+ .detach();
+ language_server
+ .on_request::<lsp::request::RegisterCapability, _, _>({
+ let this = this.downgrade();
+ move |params, mut cx| async move {
+ let this = this
+ .upgrade(&cx)
+ .ok_or_else(|| anyhow!("project dropped"))?;
+ for reg in params.registrations {
+ if reg.method == "workspace/didChangeWatchedFiles" {
+ if let Some(options) = reg.register_options {
+ let options = serde_json::from_value(options)?;
this.update(&mut cx, |this, cx| {
- this.on_lsp_progress(
- params,
- server_id,
- disk_based_diagnostics_progress_token.clone(),
- cx,
+ this.on_lsp_did_change_watched_files(
+ server_id, options, cx,
);
});
}
}
- })
- .detach();
-
- language_server
- .notify::<lsp::notification::DidChangeConfiguration>(
- lsp::DidChangeConfigurationParams {
- settings: workspace_config,
- },
- )
- .ok();
-
- this.update(&mut cx, |this, cx| {
- // If the language server for this key doesn't match the server id, don't store the
- // server. Which will cause it to be dropped, killing the process
- if this
- .language_server_ids
- .get(&key)
- .map(|id| id != &server_id)
- .unwrap_or(false)
- {
- return None;
}
+ Ok(())
+ }
+ })
+ .detach();
- // Update language_servers collection with Running variant of LanguageServerState
- // indicating that the server is up and running and ready
- this.language_servers.insert(
- server_id,
- LanguageServerState::Running {
- adapter: adapter.clone(),
- language,
- watched_paths: Default::default(),
- server: language_server.clone(),
- simulate_disk_based_diagnostics_completion: None,
- },
- );
- this.language_server_statuses.insert(
+ language_server
+ .on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
+ let this = this.downgrade();
+ let adapter = adapter.clone();
+ let language_server = language_server.clone();
+ move |params, cx| {
+ Self::on_lsp_workspace_edit(
+ this,
+ params,
server_id,
- LanguageServerStatus {
- name: language_server.name().to_string(),
- pending_work: Default::default(),
- has_pending_diagnostic_updates: false,
- progress_tokens: Default::default(),
- },
- );
+ adapter.clone(),
+ language_server.clone(),
+ cx,
+ )
+ }
+ })
+ .detach();
- if let Some(project_id) = this.remote_id() {
- this.client
- .send(proto::StartLanguageServer {
- project_id,
- server: Some(proto::LanguageServer {
- id: server_id as u64,
- name: language_server.name().to_string(),
- }),
- })
- .log_err();
+ let disk_based_diagnostics_progress_token =
+ adapter.disk_based_diagnostics_progress_token.clone();
+
+ language_server
+ .on_notification::<lsp::notification::Progress, _>({
+ let this = this.downgrade();
+ move |params, mut cx| {
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| {
+ this.on_lsp_progress(
+ params,
+ server_id,
+ disk_based_diagnostics_progress_token.clone(),
+ cx,
+ );
+ });
}
+ }
+ })
+ .detach();
- // Tell the language server about every open buffer in the worktree that matches the language.
- for buffer in this.opened_buffers.values() {
- if let Some(buffer_handle) = buffer.upgrade(cx) {
- let buffer = buffer_handle.read(cx);
- let file = if let Some(file) = File::from_dyn(buffer.file()) {
- file
- } else {
- continue;
- };
- let language = if let Some(language) = buffer.language() {
- language
- } else {
- continue;
- };
- if file.worktree.read(cx).id() != key.0
- || language.lsp_adapter().map(|a| a.name.clone())
- != Some(key.1.clone())
- {
- continue;
- }
+ language_server
+ .notify::<lsp::notification::DidChangeConfiguration>(
+ lsp::DidChangeConfigurationParams {
+ settings: workspace_config,
+ },
+ )
+ .ok();
- let file = file.as_local()?;
- let versions = this
- .buffer_snapshots
- .entry(buffer.remote_id())
- .or_insert_with(|| vec![(0, buffer.text_snapshot())]);
+ this.update(&mut cx, |this, cx| {
+ // If the language server for this key doesn't match the server id, don't store the
+ // server. Which will cause it to be dropped, killing the process
+ if this
+ .language_server_ids
+ .get(&key)
+ .map(|id| id != &server_id)
+ .unwrap_or(false)
+ {
+ return None;
+ }
- let (version, initial_snapshot) = versions.last().unwrap();
- let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
- language_server
- .notify::<lsp::notification::DidOpenTextDocument>(
- lsp::DidOpenTextDocumentParams {
- text_document: lsp::TextDocumentItem::new(
- uri,
- adapter
- .language_ids
- .get(language.name().as_ref())
- .cloned()
- .unwrap_or_default(),
- *version,
- initial_snapshot.text(),
- ),
- },
- )
- .log_err()?;
- 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_default(),
- cx,
- )
- });
- }
+ // Update language_servers collection with Running variant of LanguageServerState
+ // indicating that the server is up and running and ready
+ this.language_servers.insert(
+ server_id,
+ LanguageServerState::Running {
+ adapter: adapter.clone(),
+ language: language.clone(),
+ watched_paths: Default::default(),
+ server: language_server.clone(),
+ simulate_disk_based_diagnostics_completion: None,
+ },
+ );
+ this.language_server_statuses.insert(
+ server_id,
+ LanguageServerStatus {
+ name: language_server.name().to_string(),
+ pending_work: Default::default(),
+ has_pending_diagnostic_updates: false,
+ progress_tokens: Default::default(),
+ },
+ );
+
+ if let Some(project_id) = this.remote_id() {
+ this.client
+ .send(proto::StartLanguageServer {
+ project_id,
+ server: Some(proto::LanguageServer {
+ id: server_id as u64,
+ name: language_server.name().to_string(),
+ }),
+ })
+ .log_err();
+ }
+
+ // Tell the language server about every open buffer in the worktree that matches the language.
+ for buffer in this.opened_buffers.values() {
+ if let Some(buffer_handle) = buffer.upgrade(cx) {
+ let buffer = buffer_handle.read(cx);
+ let file = match File::from_dyn(buffer.file()) {
+ Some(file) => file,
+ None => continue,
+ };
+ let language = match buffer.language() {
+ Some(language) => language,
+ None => continue,
+ };
+
+ if file.worktree.read(cx).id() != key.0
+ || !language.lsp_adapters().iter().any(|a| a.name == key.1)
+ {
+ continue;
}
- cx.notify();
- Some(language_server)
- })
- })),
- );
+ let file = file.as_local()?;
+ let versions = this
+ .buffer_snapshots
+ .entry(buffer.remote_id())
+ .or_default()
+ .entry(server_id)
+ .or_insert_with(|| {
+ vec![LspBufferSnapshot {
+ version: 0,
+ snapshot: buffer.text_snapshot(),
+ }]
+ });
- server_id
- });
+ 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();
+ language_server
+ .notify::<lsp::notification::DidOpenTextDocument>(
+ lsp::DidOpenTextDocumentParams {
+ text_document: lsp::TextDocumentItem::new(
+ uri,
+ adapter
+ .language_ids
+ .get(language.name().as_ref())
+ .cloned()
+ .unwrap_or_default(),
+ version,
+ initial_snapshot.text(),
+ ),
+ },
+ )
+ .log_err()?;
+ 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_default(),
+ cx,
+ )
+ });
+ }
+ }
+
+ cx.notify();
+ Some(language_server)
+ })
+ })),
+ );
+ server_id
}
// Returns a list of all of the worktrees which no longer have a language server and the root path
@@ -1573,6 +1573,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
new_text: "".into(),
},
],
+ 0,
Some(lsp_document_version),
cx,
)
@@ -1667,6 +1668,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
new_text: "".into(),
},
],
+ 0,
None,
cx,
)
@@ -1770,6 +1772,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
.unindent(),
},
],
+ 0,
None,
cx,
)
@@ -2258,7 +2261,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
..Default::default()
},
tree_sitter_rust::language(),
- None,
+ vec![],
|_| Default::default(),
);
@@ -684,9 +684,10 @@ message SearchProjectResponse {
}
message CodeAction {
- Anchor start = 1;
- Anchor end = 2;
- bytes lsp_action = 3;
+ uint64 server_id = 1;
+ Anchor start = 2;
+ Anchor end = 3;
+ bytes lsp_action = 4;
}
message ProjectTransaction {
@@ -37,121 +37,107 @@ pub fn init(
themes: Arc<ThemeRegistry>,
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",
tree_sitter_c::language(),
- Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
+ vec![adapter_arc(c::CLspAdapter)],
),
(
"cpp",
tree_sitter_cpp::language(),
- Some(Arc::new(c::CLspAdapter)),
- ),
- (
- "css",
- tree_sitter_css::language(),
- None, //
+ vec![adapter_arc(c::CLspAdapter)],
),
+ ("css", tree_sitter_css::language(), vec![]),
(
"elixir",
tree_sitter_elixir::language(),
- Some(Arc::new(elixir::ElixirLspAdapter)),
+ vec![adapter_arc(elixir::ElixirLspAdapter)],
),
(
"go",
tree_sitter_go::language(),
- Some(Arc::new(go::GoLspAdapter)),
+ vec![adapter_arc(go::GoLspAdapter)],
),
(
"json",
tree_sitter_json::language(),
- Some(Arc::new(json::JsonLspAdapter::new(
+ vec![adapter_arc(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
themes.clone(),
- ))),
- ),
- (
- "markdown",
- tree_sitter_markdown::language(),
- None, //
+ ))],
),
+ ("markdown", tree_sitter_markdown::language(), vec![]),
(
"python",
tree_sitter_python::language(),
- Some(Arc::new(python::PythonLspAdapter::new(
+ vec![adapter_arc(python::PythonLspAdapter::new(
node_runtime.clone(),
- ))),
+ ))],
),
(
"rust",
tree_sitter_rust::language(),
- Some(Arc::new(rust::RustLspAdapter)),
- ),
- (
- "toml",
- tree_sitter_toml::language(),
- None, //
+ vec![adapter_arc(rust::RustLspAdapter)],
),
+ ("toml", tree_sitter_toml::language(), vec![]),
(
"tsx",
tree_sitter_typescript::language_tsx(),
- Some(Arc::new(typescript::TypeScriptLspAdapter::new(
+ vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
node_runtime.clone(),
- ))),
+ ))],
),
(
"typescript",
tree_sitter_typescript::language_typescript(),
- Some(Arc::new(typescript::TypeScriptLspAdapter::new(
+ vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
node_runtime.clone(),
- ))),
+ ))],
),
(
"javascript",
tree_sitter_typescript::language_tsx(),
- Some(Arc::new(typescript::TypeScriptLspAdapter::new(
+ vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
node_runtime.clone(),
- ))),
+ ))],
),
(
"html",
tree_sitter_html::language(),
- Some(Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))),
+ vec![adapter_arc(html::HtmlLspAdapter::new(node_runtime.clone()))],
),
(
"ruby",
tree_sitter_ruby::language(),
- Some(Arc::new(ruby::RubyLanguageServer)),
+ vec![adapter_arc(ruby::RubyLanguageServer)],
),
(
"erb",
tree_sitter_embedded_template::language(),
- Some(Arc::new(ruby::RubyLanguageServer)),
- ),
- (
- "scheme",
- tree_sitter_scheme::language(),
- None, //
- ),
- (
- "racket",
- tree_sitter_racket::language(),
- None, //
+ vec![adapter_arc(ruby::RubyLanguageServer)],
),
+ ("scheme", tree_sitter_scheme::language(), vec![]),
+ ("racket", tree_sitter_racket::language(), vec![]),
(
"lua",
tree_sitter_lua::language(),
- Some(Arc::new(lua::LuaLspAdapter)),
+ vec![adapter_arc(lua::LuaLspAdapter)],
),
(
"yaml",
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::new(
Language::new(load_config(name), Some(grammar))
- .with_lsp_adapter(lsp_adapter)
+ .with_lsp_adapters(lsp_adapter)
.await
.with_queries(load_queries(name))
.unwrap(),
@@ -37,7 +37,7 @@ impl TypeScriptLspAdapter {
}
}
-struct Versions {
+struct TypeScriptVersions {
typescript_version: String,
server_version: String,
}
@@ -52,7 +52,8 @@ impl LspAdapter for TypeScriptLspAdapter {
&self,
_: Arc<dyn HttpClient>,
) -> 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?,
server_version: self
.node
@@ -67,7 +68,8 @@ impl LspAdapter for TypeScriptLspAdapter {
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
) -> 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);
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> {
+ dbg!();
(|| async move {
- let mut last_version_dir = None;
- let mut entries = fs::read_dir(&container_dir).await?;
- 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);
+ let old_server_path = container_dir.join(Self::OLD_SERVER_PATH);
+ let new_server_path = container_dir.join(Self::NEW_SERVER_PATH);
if new_server_path.exists() {
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
@@ -117,7 +111,7 @@ impl LspAdapter for TypeScriptLspAdapter {
} else {
Err(anyhow!(
"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)]
mod tests {
use gpui::TestAppContext;