Detailed changes
@@ -53,7 +53,8 @@ use language_model::{
};
use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate};
-use project::{Project, ProjectLspAdapterDelegate, Worktree};
+use project::lsp_store::ProjectLspAdapterDelegate;
+use project::{Project, Worktree};
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings};
@@ -5340,9 +5341,17 @@ fn make_lsp_adapter_delegate(
.worktrees(cx)
.next()
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
+ let fs = if project.is_local() {
+ Some(project.fs().clone())
+ } else {
+ None
+ };
+ let http_client = project.client().http_client().clone();
project.lsp_store().update(cx, |lsp_store, cx| {
- Ok(ProjectLspAdapterDelegate::new(lsp_store, &worktree, cx)
- as Arc<dyn LspAdapterDelegate>)
+ Ok(
+ ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, cx)
+ as Arc<dyn LspAdapterDelegate>,
+ )
})
})
}
@@ -2377,7 +2377,7 @@ impl Codegen {
// If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() {
- if language.as_ref() == "Markdown" {
+ if language == "Markdown".into() {
1.0
} else {
0.5
@@ -2386,7 +2386,7 @@ impl Codegen {
1.0
};
- let language_name = language_name.as_deref();
+ let language_name = language_name.as_ref();
let start = buffer.point_to_buffer_offset(edit_range.start);
let end = buffer.point_to_buffer_offset(edit_range.end);
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
@@ -4,7 +4,7 @@ use fs::Fs;
use futures::StreamExt;
use gpui::AssetSource;
use handlebars::{Handlebars, RenderError};
-use language::BufferSnapshot;
+use language::{BufferSnapshot, LanguageName};
use parking_lot::Mutex;
use serde::Serialize;
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
@@ -204,11 +204,11 @@ impl PromptBuilder {
pub fn generate_content_prompt(
&self,
user_prompt: String,
- language_name: Option<&str>,
+ language_name: Option<&LanguageName>,
buffer: BufferSnapshot,
range: Range<usize>,
) -> Result<String, RenderError> {
- let content_type = match language_name {
+ let content_type = match language_name.as_ref().map(|l| l.0.as_ref()) {
None | Some("Markdown" | "Plain Text") => "text",
Some(_) => "code",
};
@@ -2328,11 +2328,11 @@ async fn test_propagate_saves_and_fs_changes(
.unwrap();
buffer_b.read_with(cx_b, |buffer, _| {
- assert_eq!(&*buffer.language().unwrap().name(), "Rust");
+ assert_eq!(buffer.language().unwrap().name(), "Rust".into());
});
buffer_c.read_with(cx_c, |buffer, _| {
- assert_eq!(&*buffer.language().unwrap().name(), "Rust");
+ assert_eq!(buffer.language().unwrap().name(), "Rust".into());
});
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
@@ -2432,17 +2432,17 @@ async fn test_propagate_saves_and_fs_changes(
buffer_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
- assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
+ assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
});
buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
- assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
+ assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
});
buffer_c.read_with(cx_c, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
- assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
+ assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
});
let new_buffer_a = project_a
@@ -100,7 +100,7 @@ async fn test_sharing_an_ssh_remote_project(
let file = buffer_b.read(cx).file();
assert_eq!(
all_language_settings(file, cx)
- .language(Some("Rust"))
+ .language(Some(&("Rust".into())))
.language_servers,
["override-rust-analyzer".into()]
)
@@ -12,7 +12,7 @@ use crate::{element::register_action, Editor, SwitchSourceHeader};
static CLANGD_SERVER_NAME: &str = "clangd";
fn is_c_language(language: &Language) -> bool {
- return language.name().as_ref() == "C++" || language.name().as_ref() == "C";
+ return language.name() == "C++".into() || language.name() == "C".into();
}
pub fn switch_source_header(
@@ -12465,7 +12465,7 @@ fn inlay_hint_settings(
let language = snapshot.language_at(location);
let settings = all_language_settings(file, cx);
settings
- .language(language.map(|l| l.name()).as_deref())
+ .language(language.map(|l| l.name()).as_ref())
.inlay_hints
}
@@ -20,8 +20,8 @@ use language::{
},
BracketPairConfig,
Capability::ReadWrite,
- FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
- ParsedMarkdown, Point,
+ FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
+ LanguageName, Override, ParsedMarkdown, Point,
};
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
use multi_buffer::MultiBufferIndentGuide;
@@ -9587,12 +9587,12 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
let server_restarts = Arc::new(AtomicUsize::new(0));
let closure_restarts = Arc::clone(&server_restarts);
let language_server_name = "test language server";
- let language_name: Arc<str> = "Rust".into();
+ let language_name: LanguageName = "Rust".into();
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new(
LanguageConfig {
- name: Arc::clone(&language_name),
+ name: language_name.clone(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
@@ -9629,7 +9629,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
let _fake_server = fake_servers.next().await.unwrap();
update_test_language_settings(cx, |language_settings| {
language_settings.languages.insert(
- Arc::clone(&language_name),
+ language_name.clone(),
LanguageSettingsContent {
tab_size: NonZeroU32::new(8),
..Default::default()
@@ -1705,8 +1705,8 @@ mod tests {
let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
assert_eq!(
- buffer.language().map(|lang| lang.name()).as_deref(),
- Some("Rust")
+ buffer.language().map(|lang| lang.name()),
+ Some("Rust".into())
); // Language should be set to Rust
assert!(buffer.file().is_none()); // The buffer should not have an associated file
});
@@ -13,7 +13,7 @@ use crate::{
static RUST_ANALYZER_NAME: &str = "rust-analyzer";
fn is_rust_language(language: &Language) -> bool {
- language.name().as_ref() == "Rust"
+ language.name() == "Rust".into()
}
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
@@ -58,7 +58,7 @@ impl EditorLspTestContext {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
- language.name().as_ref(),
+ language.name(),
FakeLspAdapter {
capabilities,
..Default::default()
@@ -38,7 +38,6 @@ impl LspAdapter for ExtensionLspAdapter {
fn get_language_server_command<'a>(
self: Arc<Self>,
- _: Arc<Language>,
_: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
@@ -1,7 +1,7 @@
use anyhow::{anyhow, Context, Result};
use collections::{BTreeMap, HashMap};
use fs::Fs;
-use language::LanguageServerName;
+use language::{LanguageName, LanguageServerName};
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use std::{
@@ -106,10 +106,10 @@ pub struct GrammarManifestEntry {
pub struct LanguageServerManifestEntry {
/// Deprecated in favor of `languages`.
#[serde(default)]
- language: Option<Arc<str>>,
+ language: Option<LanguageName>,
/// The list of languages this language server should work with.
#[serde(default)]
- languages: Vec<Arc<str>>,
+ languages: Vec<LanguageName>,
#[serde(default)]
pub language_ids: HashMap<String, String>,
#[serde(default)]
@@ -124,7 +124,7 @@ impl LanguageServerManifestEntry {
///
/// We can replace this with just field access for the `languages` field once
/// we have removed `language`.
- pub fn languages(&self) -> impl IntoIterator<Item = Arc<str>> + '_ {
+ pub fn languages(&self) -> impl IntoIterator<Item = LanguageName> + '_ {
let language = if self.languages.is_empty() {
self.language.clone()
} else {
@@ -36,7 +36,8 @@ use gpui::{
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use indexed_docs::{IndexedDocsRegistry, ProviderId};
use language::{
- LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
+ LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
+ QUERY_FILENAME_PREFIXES,
};
use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks;
@@ -148,7 +149,7 @@ impl Global for GlobalExtensionStore {}
pub struct ExtensionIndex {
pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,
pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,
- pub languages: BTreeMap<Arc<str>, ExtensionIndexLanguageEntry>,
+ pub languages: BTreeMap<LanguageName, ExtensionIndexLanguageEntry>,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -609,7 +609,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
.await
.unwrap();
- let mut fake_servers = language_registry.fake_language_servers("Gleam");
+ let mut fake_servers = language_registry.fake_language_servers("Gleam".into());
let buffer = project
.update(cx, |project, cx| {
@@ -9,6 +9,7 @@ use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use indexed_docs::IndexedDocsDatabase;
use isahc::config::{Configurable, RedirectPolicy};
+use language::LanguageName;
use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
};
@@ -399,8 +400,9 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() {
"language" => {
+ let key = key.map(|k| LanguageName::new(&k));
let settings =
- AllLanguageSettings::get(location, cx).language(key.as_deref());
+ AllLanguageSettings::get(location, cx).language(key.as_ref());
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)
@@ -1504,3 +1504,9 @@ pub struct KeystrokeEvent {
/// The action that was resolved for the keystroke, if any
pub action: Option<Box<dyn Action>>,
}
+
+impl Drop for AppContext {
+ fn drop(&mut self) {
+ println!("Dropping the App Context");
+ }
+}
@@ -72,7 +72,7 @@ fn test_select_language(cx: &mut AppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
registry.add(Arc::new(Language::new(
LanguageConfig {
- name: "Rust".into(),
+ name: LanguageName::new("Rust"),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
@@ -83,7 +83,7 @@ fn test_select_language(cx: &mut AppContext) {
)));
registry.add(Arc::new(Language::new(
LanguageConfig {
- name: "Make".into(),
+ name: LanguageName::new("Make"),
matcher: LanguageMatcher {
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
..Default::default()
@@ -97,15 +97,13 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!(
registry
.language_for_file(&file("src/lib.rs"), None, cx)
- .now_or_never()
- .and_then(|l| Some(l.ok()?.name())),
+ .map(|l| l.name()),
Some("Rust".into())
);
assert_eq!(
registry
.language_for_file(&file("src/lib.mk"), None, cx)
- .now_or_never()
- .and_then(|l| Some(l.ok()?.name())),
+ .map(|l| l.name()),
Some("Make".into())
);
@@ -113,8 +111,7 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!(
registry
.language_for_file(&file("src/Makefile"), None, cx)
- .now_or_never()
- .and_then(|l| Some(l.ok()?.name())),
+ .map(|l| l.name()),
Some("Make".into())
);
@@ -122,22 +119,19 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!(
registry
.language_for_file(&file("zed/cars"), None, cx)
- .now_or_never()
- .and_then(|l| Some(l.ok()?.name())),
+ .map(|l| l.name()),
None
);
assert_eq!(
registry
.language_for_file(&file("zed/a.cars"), None, cx)
- .now_or_never()
- .and_then(|l| Some(l.ok()?.name())),
+ .map(|l| l.name()),
None
);
assert_eq!(
registry
.language_for_file(&file("zed/sumk"), None, cx)
- .now_or_never()
- .and_then(|l| Some(l.ok()?.name())),
+ .map(|l| l.name()),
None
);
}
@@ -158,23 +152,22 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
..Default::default()
});
- cx.read(|cx| languages.language_for_file(&file("the/script"), None, cx))
- .await
- .unwrap_err();
- cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
- .await
- .unwrap_err();
+ assert!(cx
+ .read(|cx| languages.language_for_file(&file("the/script"), None, cx))
+ .is_none());
+ assert!(cx
+ .read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
+ .is_none());
+
assert_eq!(
cx.read(|cx| languages.language_for_file(
&file("the/script"),
Some(&"#!/bin/env node".into()),
cx
))
- .await
.unwrap()
- .name()
- .as_ref(),
- "JavaScript"
+ .name(),
+ "JavaScript".into()
);
}
@@ -242,19 +235,16 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext)
let language = cx
.read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
- .await
.unwrap();
- assert_eq!(language.name().as_ref(), "TypeScript");
+ assert_eq!(language.name(), "TypeScript".into());
let language = cx
.read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
- .await
.unwrap();
- assert_eq!(language.name().as_ref(), "C++");
+ assert_eq!(language.name(), "C++".into());
let language = cx
.read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
- .await
.unwrap();
- assert_eq!(language.name().as_ref(), "Dockerfile");
+ assert_eq!(language.name(), "Dockerfile".into());
}
fn file(path: &str) -> Arc<dyn File> {
@@ -2245,10 +2235,10 @@ fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
for point in [Point::new(0, 4), Point::new(0, 16)] {
let config = snapshot.language_scope_at(point).unwrap();
- assert_eq!(config.language_name().as_ref(), "Markdown");
+ assert_eq!(config.language_name(), "Markdown".into());
let language = snapshot.language_at(point).unwrap();
- assert_eq!(language.name().as_ref(), "Markdown");
+ assert_eq!(language.name().0.as_ref(), "Markdown");
}
buffer
@@ -2757,7 +2747,7 @@ fn ruby_lang() -> Language {
fn html_lang() -> Language {
Language::new(
LanguageConfig {
- name: "HTML".into(),
+ name: LanguageName::new("HTML"),
block_comment: Some(("<!--".into(), "-->".into())),
..Default::default()
},
@@ -28,6 +28,7 @@ use futures::Future;
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
pub use highlight_map::HighlightMap;
use http_client::HttpClient;
+pub use language_registry::LanguageName;
use lsp::{CodeActionKind, LanguageServerBinary};
use parking_lot::Mutex;
use regex::Regex;
@@ -67,8 +68,8 @@ pub use buffer::Operation;
pub use buffer::*;
pub use diagnostic_set::DiagnosticEntry;
pub use language_registry::{
- LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus,
- PendingLanguageServer, QUERY_FILENAME_PREFIXES,
+ AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry,
+ LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES,
};
pub use lsp::LanguageServerId;
pub use outline::*;
@@ -140,6 +141,12 @@ pub trait ToLspPosition {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct LanguageServerName(pub Arc<str>);
+impl LanguageServerName {
+ pub fn from_proto(s: String) -> Self {
+ Self(Arc::from(s))
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Location {
pub buffer: Model<Buffer>,
@@ -195,9 +202,12 @@ impl CachedLspAdapter {
})
}
+ pub fn name(&self) -> Arc<str> {
+ self.adapter.name().0.clone()
+ }
+
pub async fn get_language_server_command(
self: Arc<Self>,
- language: Arc<Language>,
container_dir: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
@@ -205,18 +215,10 @@ impl CachedLspAdapter {
let cached_binary = self.cached_binary.lock().await;
self.adapter
.clone()
- .get_language_server_command(language, container_dir, delegate, cached_binary, cx)
+ .get_language_server_command(container_dir, delegate, cached_binary, cx)
.await
}
- pub fn will_start_server(
- &self,
- delegate: &Arc<dyn LspAdapterDelegate>,
- cx: &mut AsyncAppContext,
- ) -> Option<Task<Result<()>>> {
- self.adapter.will_start_server(delegate, cx)
- }
-
pub fn can_be_reinstalled(&self) -> bool {
self.adapter.can_be_reinstalled()
}
@@ -262,11 +264,11 @@ impl CachedLspAdapter {
.await
}
- pub fn language_id(&self, language: &Language) -> String {
+ pub fn language_id(&self, language_name: &LanguageName) -> String {
self.language_ids
- .get(language.name().as_ref())
+ .get(language_name.0.as_ref())
.cloned()
- .unwrap_or_else(|| language.lsp_id())
+ .unwrap_or_else(|| language_name.lsp_id())
}
#[cfg(any(test, feature = "test-support"))]
@@ -296,7 +298,6 @@ pub trait LspAdapter: 'static + Send + Sync {
fn get_language_server_command<'a>(
self: Arc<Self>,
- language: Arc<Language>,
container_dir: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
@@ -317,7 +318,7 @@ pub trait LspAdapter: 'static + Send + Sync {
if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await {
log::info!(
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
- language.name(),
+ self.name().0,
binary.path,
binary.arguments
);
@@ -387,14 +388,6 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
- fn will_start_server(
- &self,
- _: &Arc<dyn LspAdapterDelegate>,
- _: &mut AsyncAppContext,
- ) -> Option<Task<Result<()>>> {
- None
- }
-
async fn fetch_server_binary(
&self,
latest_version: Box<dyn 'static + Send + Any>,
@@ -562,7 +555,7 @@ pub struct CodeLabel {
#[derive(Clone, Deserialize, JsonSchema)]
pub struct LanguageConfig {
/// Human-readable name of the language.
- pub name: Arc<str>,
+ pub name: LanguageName,
/// The name of this language for a Markdown code fence block
pub code_fence_block_name: Option<Arc<str>>,
// The name of the grammar in a WASM bundle (experimental).
@@ -699,7 +692,7 @@ impl<T> Override<T> {
impl Default for LanguageConfig {
fn default() -> Self {
Self {
- name: Arc::default(),
+ name: LanguageName::new(""),
code_fence_block_name: None,
grammar: None,
matcher: LanguageMatcher::default(),
@@ -1335,7 +1328,7 @@ impl Language {
Arc::get_mut(self.grammar.as_mut()?)
}
- pub fn name(&self) -> Arc<str> {
+ pub fn name(&self) -> LanguageName {
self.config.name.clone()
}
@@ -1343,7 +1336,7 @@ impl Language {
self.config
.code_fence_block_name
.clone()
- .unwrap_or_else(|| self.config.name.to_lowercase().into())
+ .unwrap_or_else(|| self.config.name.0.to_lowercase().into())
}
pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {
@@ -1408,10 +1401,7 @@ impl Language {
}
pub fn lsp_id(&self) -> String {
- match self.config.name.as_ref() {
- "Plain Text" => "plaintext".to_string(),
- language_name => language_name.to_lowercase(),
- }
+ self.config.name.lsp_id()
}
pub fn prettier_parser_name(&self) -> Option<&str> {
@@ -1420,7 +1410,7 @@ impl Language {
}
impl LanguageScope {
- pub fn language_name(&self) -> Arc<str> {
+ pub fn language_name(&self) -> LanguageName {
self.language.config.name.clone()
}
@@ -1663,7 +1653,6 @@ impl LspAdapter for FakeLspAdapter {
fn get_language_server_command<'a>(
self: Arc<Self>,
- _: Arc<Language>,
_: Arc<Path>,
_: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
@@ -6,9 +6,9 @@ use crate::{
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT,
};
-use anyhow::{anyhow, Context as _, Result};
+use anyhow::{anyhow, Context, Result};
use collections::{hash_map, HashMap, HashSet};
-use futures::TryFutureExt;
+
use futures::{
channel::{mpsc, oneshot},
future::Shared,
@@ -19,8 +19,10 @@ use gpui::{AppContext, BackgroundExecutor, Task};
use lsp::LanguageServerId;
use parking_lot::{Mutex, RwLock};
use postage::watch;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
use std::{
- borrow::Cow,
+ borrow::{Borrow, Cow},
ffi::OsStr,
ops::Not,
path::{Path, PathBuf},
@@ -32,6 +34,48 @@ use theme::Theme;
use unicase::UniCase;
use util::{maybe, paths::PathExt, post_inc, ResultExt};
+#[derive(
+ Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
+)]
+pub struct LanguageName(pub Arc<str>);
+
+impl LanguageName {
+ pub fn new(s: &str) -> Self {
+ Self(Arc::from(s))
+ }
+
+ pub fn from_proto(s: String) -> Self {
+ Self(Arc::from(s))
+ }
+ pub fn to_proto(self) -> String {
+ self.0.to_string()
+ }
+ pub fn lsp_id(&self) -> String {
+ match self.0.as_ref() {
+ "Plain Text" => "plaintext".to_string(),
+ language_name => language_name.to_lowercase(),
+ }
+ }
+}
+
+impl Borrow<str> for LanguageName {
+ fn borrow(&self) -> &str {
+ self.0.as_ref()
+ }
+}
+
+impl std::fmt::Display for LanguageName {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl<'a> From<&'a str> for LanguageName {
+ fn from(str: &'a str) -> LanguageName {
+ LanguageName(str.into())
+ }
+}
+
pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>,
language_server_download_dir: Option<Arc<Path>>,
@@ -46,7 +90,7 @@ struct LanguageRegistryState {
language_settings: AllLanguageSettingsContent,
available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>,
- lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
+ lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
available_lsp_adapters:
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
@@ -56,8 +100,10 @@ struct LanguageRegistryState {
reload_count: usize,
#[cfg(any(test, feature = "test-support"))]
- fake_server_txs:
- HashMap<Arc<str>, Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>>,
+ fake_server_txs: HashMap<
+ LanguageName,
+ Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>,
+ >,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -75,9 +121,9 @@ pub struct PendingLanguageServer {
}
#[derive(Clone)]
-struct AvailableLanguage {
+pub struct AvailableLanguage {
id: LanguageId,
- name: Arc<str>,
+ name: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<
@@ -93,6 +139,16 @@ struct AvailableLanguage {
loaded: bool,
}
+impl AvailableLanguage {
+ pub fn name(&self) -> LanguageName {
+ self.name.clone()
+ }
+
+ pub fn matcher(&self) -> &LanguageMatcher {
+ &self.matcher
+ }
+}
+
enum AvailableGrammar {
Native(tree_sitter::Language),
Loaded(#[allow(unused)] PathBuf, tree_sitter::Language),
@@ -196,7 +252,7 @@ impl LanguageRegistry {
/// appended to the end.
pub fn reorder_language_servers(
&self,
- language: &Arc<Language>,
+ language: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
self.state
@@ -207,7 +263,7 @@ impl LanguageRegistry {
/// Removes the specified languages and grammars from the registry.
pub fn remove_languages(
&self,
- languages_to_remove: &[Arc<str>],
+ languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
self.state
@@ -215,7 +271,7 @@ impl LanguageRegistry {
.remove_languages(languages_to_remove, grammars_to_remove)
}
- pub fn remove_lsp_adapter(&self, language_name: &str, name: &LanguageServerName) {
+ pub fn remove_lsp_adapter(&self, language_name: &LanguageName, name: &LanguageServerName) {
let mut state = self.state.write();
if let Some(adapters) = state.lsp_adapters.get_mut(language_name) {
adapters.retain(|adapter| &adapter.name != name)
@@ -267,7 +323,7 @@ impl LanguageRegistry {
Some(load_lsp_adapter())
}
- pub fn register_lsp_adapter(&self, language_name: Arc<str>, adapter: Arc<dyn LspAdapter>) {
+ pub fn register_lsp_adapter(&self, language_name: LanguageName, adapter: Arc<dyn LspAdapter>) {
self.state
.write()
.lsp_adapters
@@ -279,13 +335,14 @@ impl LanguageRegistry {
#[cfg(any(feature = "test-support", test))]
pub fn register_fake_lsp_adapter(
&self,
- language_name: &str,
+ language_name: impl Into<LanguageName>,
adapter: crate::FakeLspAdapter,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
+ let language_name = language_name.into();
self.state
.write()
.lsp_adapters
- .entry(language_name.into())
+ .entry(language_name.clone())
.or_default()
.push(CachedLspAdapter::new(Arc::new(adapter)));
self.fake_language_servers(language_name)
@@ -294,13 +351,13 @@ impl LanguageRegistry {
#[cfg(any(feature = "test-support", test))]
pub fn fake_language_servers(
&self,
- language_name: &str,
+ language_name: LanguageName,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
self.state
.write()
.fake_server_txs
- .entry(language_name.into())
+ .entry(language_name)
.or_default()
.push(servers_tx);
servers_rx
@@ -309,7 +366,7 @@ impl LanguageRegistry {
/// Adds a language to the registry, which can be loaded if needed.
pub fn register_language(
&self,
- name: Arc<str>,
+ name: LanguageName,
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher,
load: impl Fn() -> Result<(
@@ -445,7 +502,7 @@ impl LanguageRegistry {
) -> impl Future<Output = Result<Arc<Language>>> {
let name = UniCase::new(name);
let rx = self.get_or_load_language(|language_name, _| {
- if UniCase::new(language_name) == name {
+ if UniCase::new(&language_name.0) == name {
1
} else {
0
@@ -460,7 +517,7 @@ impl LanguageRegistry {
) -> impl Future<Output = Result<Arc<Language>>> {
let string = UniCase::new(string);
let rx = self.get_or_load_language(|name, config| {
- if UniCase::new(name) == string
+ if UniCase::new(&name.0) == string
|| config
.path_suffixes
.iter()
@@ -474,13 +531,26 @@ impl LanguageRegistry {
async move { rx.await? }
}
+ pub fn available_language_for_name(
+ self: &Arc<Self>,
+ name: &LanguageName,
+ ) -> Option<AvailableLanguage> {
+ let state = self.state.read();
+ state
+ .available_languages
+ .iter()
+ .find(|l| &l.name == name)
+ .cloned()
+ }
+
pub fn language_for_file(
self: &Arc<Self>,
file: &Arc<dyn File>,
content: Option<&Rope>,
cx: &AppContext,
- ) -> impl Future<Output = Result<Arc<Language>>> {
+ ) -> Option<AvailableLanguage> {
let user_file_types = all_language_settings(Some(file), cx);
+
self.language_for_file_internal(
&file.full_path(cx),
content,
@@ -492,8 +562,16 @@ impl LanguageRegistry {
self: &Arc<Self>,
path: &'a Path,
) -> impl Future<Output = Result<Arc<Language>>> + 'a {
- self.language_for_file_internal(path, None, None)
- .map_err(|error| error.context(format!("language for file path {}", path.display())))
+ let available_language = self.language_for_file_internal(path, None, None);
+
+ let this = self.clone();
+ async move {
+ if let Some(language) = available_language {
+ this.load_language(&language).await?
+ } else {
+ Err(anyhow!(LanguageNotFound))
+ }
+ }
}
fn language_for_file_internal(
@@ -501,19 +579,19 @@ impl LanguageRegistry {
path: &Path,
content: Option<&Rope>,
user_file_types: Option<&HashMap<Arc<str>, GlobSet>>,
- ) -> impl Future<Output = Result<Arc<Language>>> {
+ ) -> Option<AvailableLanguage> {
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename, path.to_str()];
let empty = GlobSet::empty();
- let rx = self.get_or_load_language(move |language_name, config| {
+ self.find_matching_language(move |language_name, config| {
let path_matches_default_suffix = config
.path_suffixes
.iter()
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
let custom_suffixes = user_file_types
- .and_then(|types| types.get(language_name))
+ .and_then(|types| types.get(&language_name.0))
.unwrap_or(&empty);
let path_matches_custom_suffix = path_suffixes
.iter()
@@ -535,18 +613,15 @@ impl LanguageRegistry {
} else {
0
}
- });
- async move { rx.await? }
+ })
}
- fn get_or_load_language(
+ fn find_matching_language(
self: &Arc<Self>,
- callback: impl Fn(&str, &LanguageMatcher) -> usize,
- ) -> oneshot::Receiver<Result<Arc<Language>>> {
- let (tx, rx) = oneshot::channel();
-
- let mut state = self.state.write();
- let Some((language, _)) = state
+ callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
+ ) -> Option<AvailableLanguage> {
+ let state = self.state.read();
+ let available_language = state
.available_languages
.iter()
.filter_map(|language| {
@@ -559,15 +634,23 @@ impl LanguageRegistry {
})
.max_by_key(|e| e.1)
.clone()
- else {
- let _ = tx.send(Err(anyhow!(LanguageNotFound)));
- return rx;
- };
+ .map(|(available_language, _)| available_language);
+ drop(state);
+ available_language
+ }
+
+ pub fn load_language(
+ self: &Arc<Self>,
+ language: &AvailableLanguage,
+ ) -> oneshot::Receiver<Result<Arc<Language>>> {
+ let (tx, rx) = oneshot::channel();
+
+ let mut state = self.state.write();
// If the language is already loaded, resolve with it immediately.
for loaded_language in state.languages.iter() {
if loaded_language.id == language.id {
- let _ = tx.send(Ok(loaded_language.clone()));
+ tx.send(Ok(loaded_language.clone())).unwrap();
return rx;
}
}
@@ -580,12 +663,15 @@ impl LanguageRegistry {
// Otherwise, start loading the language.
hash_map::Entry::Vacant(entry) => {
let this = self.clone();
+
+ let id = language.id;
+ let name = language.name.clone();
+ let language_load = language.load.clone();
+
self.executor
.spawn(async move {
- let id = language.id;
- let name = language.name.clone();
let language = async {
- let (config, queries, provider) = (language.load)()?;
+ let (config, queries, provider) = (language_load)()?;
if let Some(grammar) = config.grammar.clone() {
let grammar = Some(this.get_or_load_grammar(grammar).await?);
@@ -629,13 +715,28 @@ impl LanguageRegistry {
};
})
.detach();
+
entry.insert(vec![tx]);
}
}
+ drop(state);
rx
}
+ fn get_or_load_language(
+ self: &Arc<Self>,
+ callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
+ ) -> oneshot::Receiver<Result<Arc<Language>>> {
+ let Some(language) = self.find_matching_language(callback) else {
+ let (tx, rx) = oneshot::channel();
+ let _ = tx.send(Err(anyhow!(LanguageNotFound)));
+ return rx;
+ };
+
+ self.load_language(&language)
+ }
+
fn get_or_load_grammar(
self: &Arc<Self>,
name: Arc<str>,
@@ -702,11 +803,11 @@ impl LanguageRegistry {
self.state.read().languages.to_vec()
}
- pub fn lsp_adapters(&self, language: &Arc<Language>) -> Vec<Arc<CachedLspAdapter>> {
+ pub fn lsp_adapters(&self, language_name: &LanguageName) -> Vec<Arc<CachedLspAdapter>> {
self.state
.read()
.lsp_adapters
- .get(&language.config.name)
+ .get(language_name)
.cloned()
.unwrap_or_default()
}
@@ -723,7 +824,7 @@ impl LanguageRegistry {
pub fn create_pending_language_server(
self: &Arc<Self>,
stderr_capture: Arc<Mutex<Option<String>>>,
- language: Arc<Language>,
+ _language_name_for_tests: LanguageName,
adapter: Arc<CachedLspAdapter>,
root_path: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
@@ -741,7 +842,6 @@ impl LanguageRegistry {
.clone()
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
.log_err()?;
- let language = language.clone();
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
let root_path = root_path.clone();
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
@@ -756,12 +856,7 @@ impl LanguageRegistry {
let binary_result = adapter
.clone()
- .get_language_server_command(
- language.clone(),
- container_dir,
- delegate.clone(),
- &mut cx,
- )
+ .get_language_server_command(container_dir, delegate.clone(), &mut cx)
.await;
delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None);
@@ -785,10 +880,6 @@ impl LanguageRegistry {
.initialization_options(&delegate)
.await?;
- if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
- task.await?;
- }
-
#[cfg(any(test, feature = "test-support"))]
if true {
let capabilities = adapter
@@ -825,7 +916,7 @@ impl LanguageRegistry {
.state
.write()
.fake_server_txs
- .get_mut(language.name().as_ref())
+ .get_mut(&_language_name_for_tests)
{
for tx in txs {
tx.unbounded_send(fake_server.clone()).ok();
@@ -935,10 +1026,10 @@ impl LanguageRegistryState {
/// appended to the end.
fn reorder_language_servers(
&mut self,
- language: &Arc<Language>,
+ language_name: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
- let Some(lsp_adapters) = self.lsp_adapters.get_mut(&language.config.name) else {
+ let Some(lsp_adapters) = self.lsp_adapters.get_mut(language_name) else {
return;
};
@@ -959,7 +1050,7 @@ impl LanguageRegistryState {
fn remove_languages(
&mut self,
- languages_to_remove: &[Arc<str>],
+ languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
if languages_to_remove.is_empty() && grammars_to_remove.is_empty() {
@@ -1,6 +1,6 @@
//! Provides `language`-related settings.
-use crate::{File, Language, LanguageServerName};
+use crate::{File, Language, LanguageName, LanguageServerName};
use anyhow::Result;
use collections::{HashMap, HashSet};
use core::slice;
@@ -32,7 +32,7 @@ pub fn language_settings<'a>(
cx: &'a AppContext,
) -> &'a LanguageSettings {
let language_name = language.map(|l| l.name());
- all_language_settings(file, cx).language(language_name.as_deref())
+ all_language_settings(file, cx).language(language_name.as_ref())
}
/// Returns the settings for all languages from the provided file.
@@ -53,7 +53,7 @@ pub struct AllLanguageSettings {
/// The inline completion settings.
pub inline_completions: InlineCompletionSettings,
defaults: LanguageSettings,
- languages: HashMap<Arc<str>, LanguageSettings>,
+ languages: HashMap<LanguageName, LanguageSettings>,
pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
}
@@ -204,7 +204,7 @@ pub struct AllLanguageSettingsContent {
pub defaults: LanguageSettingsContent,
/// The settings for individual languages.
#[serde(default)]
- pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
+ pub languages: HashMap<LanguageName, LanguageSettingsContent>,
/// Settings for associating file extensions and filenames
/// with languages.
#[serde(default)]
@@ -791,7 +791,7 @@ impl InlayHintSettings {
impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name.
- pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
+ pub fn language<'a>(&'a self, language_name: Option<&LanguageName>) -> &'a LanguageSettings {
if let Some(name) = language_name {
if let Some(overrides) = self.languages.get(name) {
return overrides;
@@ -821,7 +821,7 @@ impl AllLanguageSettings {
}
}
- self.language(language.map(|l| l.name()).as_deref())
+ self.language(language.map(|l| l.name()).as_ref())
.show_inline_completions
}
}
@@ -1,13 +1,13 @@
use editor::Editor;
use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView};
-use std::sync::Arc;
+use language::LanguageName;
use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, Workspace};
use crate::LanguageSelector;
pub struct ActiveBufferLanguage {
- active_language: Option<Option<Arc<str>>>,
+ active_language: Option<Option<LanguageName>>,
workspace: WeakView<Workspace>,
_observe_active_editor: Option<Subscription>,
}
@@ -217,7 +217,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
let mat = &self.matches[ix];
let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
let mut label = mat.string.clone();
- if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
+ if buffer_language_name.map(|n| n.0).as_deref() == Some(mat.string.as_str()) {
label.push_str(" (current)");
}
@@ -683,7 +683,7 @@ impl LspLogView {
self.project
.read(cx)
.supplementary_language_servers(cx)
- .filter_map(|(&server_id, name)| {
+ .filter_map(|(server_id, name)| {
let state = log_store.language_servers.get(&server_id)?;
Some(LogMenuItem {
server_id,
@@ -471,7 +471,7 @@ impl SyntaxTreeToolbarItemView {
fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike {
ButtonLike::new("syntax tree header")
- .child(Label::new(active_layer.language.name()))
+ .child(Label::new(active_layer.language.name().0))
.child(Label::new(format_node_range(active_layer.node())))
}
}
@@ -451,7 +451,7 @@ impl ContextProvider for RustContextProvider {
) -> Option<TaskTemplates> {
const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN";
let package_to_run = all_language_settings(file.as_ref(), cx)
- .language(Some("Rust"))
+ .language(Some(&"Rust".into()))
.tasks
.variables
.get(DEFAULT_RUN_NAME_STR);
@@ -141,7 +141,7 @@ impl LspAdapter for YamlLspAdapter {
let tab_size = cx.update(|cx| {
AllLanguageSettings::get(Some(location), cx)
- .language(Some("YAML"))
+ .language(Some(&"YAML".into()))
.tab_size
})?;
let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}});
@@ -89,6 +89,16 @@ pub struct LanguageServer {
#[repr(transparent)]
pub struct LanguageServerId(pub usize);
+impl LanguageServerId {
+ pub fn from_proto(id: u64) -> Self {
+ Self(id as usize)
+ }
+
+ pub fn to_proto(self) -> u64 {
+ self.0 as u64
+ }
+}
+
/// Handle to a language server RPC activity subscription.
pub enum Subscription {
Notification {
@@ -282,7 +282,7 @@ impl MarkdownPreviewView {
let buffer = editor.read(cx).buffer().read(cx);
if let Some(buffer) = buffer.as_singleton() {
if let Some(language) = buffer.read(cx).language() {
- return language.name().as_ref() == "Markdown";
+ return language.name() == "Markdown".into();
}
}
false
@@ -86,7 +86,7 @@ impl SignatureHelp {
} else {
let markdown = markdown.join(str_for_join);
let language_name = language
- .map(|n| n.name().to_lowercase())
+ .map(|n| n.name().0.to_lowercase())
.unwrap_or_default();
let markdown = if function_options_count >= 2 {
@@ -15,7 +15,7 @@ use async_trait::async_trait;
use client::{proto, TypedEnvelope};
use collections::{btree_map, BTreeMap, HashMap, HashSet};
use futures::{
- future::{join_all, Shared},
+ future::{join_all, BoxFuture, Shared},
select,
stream::FuturesUnordered,
Future, FutureExt, StreamExt,
@@ -25,22 +25,26 @@ use gpui::{
AppContext, AsyncAppContext, Context, Entity, EventEmitter, Model, ModelContext, PromptLevel,
Task, WeakModel,
};
-use http_client::HttpClient;
+use http_client::{AsyncBody, Error, HttpClient, Request, Response, Uri};
use itertools::Itertools;
use language::{
- language_settings::{language_settings, AllLanguageSettings, LanguageSettings},
+ language_settings::{
+ all_language_settings, language_settings, AllLanguageSettings, LanguageSettings,
+ },
markdown, point_to_lsp, prepare_completion_documentation,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
- DiagnosticEntry, DiagnosticSet, Documentation, File as _, Language, LanguageRegistry,
- LanguageServerName, LocalFile, LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16,
- TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
+ DiagnosticEntry, DiagnosticSet, Documentation, File as _, Language, LanguageConfig,
+ LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerName, LocalFile, LspAdapter,
+ LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset,
+ ToPointUtf16, Transaction, Unclipped,
};
use lsp::{
- CompletionContext, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
- Edit, FileSystemWatcher, InsertTextFormat, LanguageServer, LanguageServerBinary,
- LanguageServerId, LspRequestFuture, MessageActionItem, MessageType, OneOf, ServerHealthStatus,
- ServerStatus, SymbolKind, TextEdit, Url, WorkDoneProgressCancelParams, WorkspaceFolder,
+ CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
+ DidChangeWatchedFilesRegistrationOptions, Edit, FileSystemWatcher, InsertTextFormat,
+ LanguageServer, LanguageServerBinary, LanguageServerId, LspRequestFuture, MessageActionItem,
+ MessageType, OneOf, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url,
+ WorkDoneProgressCancelParams, WorkspaceFolder,
};
use parking_lot::{Mutex, RwLock};
use postage::watch;
@@ -54,6 +58,7 @@ use similar::{ChangeTag, TextDiff};
use smol::channel::Sender;
use snippet::Snippet;
use std::{
+ any::Any,
cmp::Ordering,
convert::TryInto,
ffi::OsStr,
@@ -85,27 +90,86 @@ const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100);
+pub struct LocalLspStore {
+ http_client: Option<Arc<dyn HttpClient>>,
+ environment: Model<ProjectEnvironment>,
+ fs: Arc<dyn Fs>,
+ yarn: Model<YarnPathStore>,
+ pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
+ last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
+ language_server_watched_paths: HashMap<LanguageServerId, Model<LanguageServerWatchedPaths>>,
+ language_server_watcher_registrations:
+ HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
+ supplementary_language_servers:
+ HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
+ _subscription: gpui::Subscription,
+}
+
+impl LocalLspStore {
+ fn shutdown_language_servers(
+ &mut self,
+ _cx: &mut ModelContext<LspStore>,
+ ) -> impl Future<Output = ()> {
+ let shutdown_futures = self
+ .language_servers
+ .drain()
+ .map(|(_, server_state)| async {
+ use LanguageServerState::*;
+ match server_state {
+ Running { server, .. } => server.shutdown()?.await,
+ Starting(task) => task.await?.shutdown()?.await,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ async move {
+ futures::future::join_all(shutdown_futures).await;
+ }
+ }
+}
+
+pub struct RemoteLspStore {
+ upstream_client: AnyProtoClient,
+}
+
+impl RemoteLspStore {}
+
+pub struct SshLspStore {
+ upstream_client: AnyProtoClient,
+}
+
+#[allow(clippy::large_enum_variant)]
+pub enum LspStoreMode {
+ Local(LocalLspStore), // ssh host and collab host
+ Remote(RemoteLspStore), // collab guest
+ Ssh(SshLspStore), // ssh client
+}
+
+impl LspStoreMode {
+ fn is_local(&self) -> bool {
+ matches!(self, LspStoreMode::Local(_))
+ }
+
+ fn is_ssh(&self) -> bool {
+ matches!(self, LspStoreMode::Ssh(_))
+ }
+
+ fn is_remote(&self) -> bool {
+ matches!(self, LspStoreMode::Remote(_))
+ }
+}
+
pub struct LspStore {
+ mode: LspStoreMode,
downstream_client: Option<AnyProtoClient>,
- upstream_client: Option<AnyProtoClient>,
project_id: u64,
- http_client: Option<Arc<dyn HttpClient>>,
- fs: Arc<dyn Fs>,
nonce: u128,
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
- environment: Option<Model<ProjectEnvironment>>,
- supplementary_language_servers:
- HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
- languages: Arc<LanguageRegistry>,
- language_servers: HashMap<LanguageServerId, LanguageServerState>,
+ pub languages: Arc<LanguageRegistry>,
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
- language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
- last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
- language_server_watched_paths: HashMap<LanguageServerId, Model<LanguageServerWatchedPaths>>,
- language_server_watcher_registrations:
- HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
+ pub language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
active_entry: Option<ProjectEntryId>,
_maintain_workspace_config: Task<Result<()>>,
_maintain_buffer_languages: Task<()>,
@@ -122,8 +186,6 @@ pub struct LspStore {
)>,
>,
>,
- yarn: Model<YarnPathStore>,
- _subscription: gpui::Subscription,
}
pub enum LspStoreEvent {
@@ -209,17 +271,53 @@ impl LspStore {
client.add_model_request_handler(Self::handle_lsp_command::<LinkedEditingRange>);
}
- #[allow(clippy::too_many_arguments)]
- pub fn new(
+ pub fn as_remote(&self) -> Option<&RemoteLspStore> {
+ match &self.mode {
+ LspStoreMode::Remote(remote_lsp_store) => Some(remote_lsp_store),
+ _ => None,
+ }
+ }
+
+ pub fn as_ssh(&self) -> Option<&SshLspStore> {
+ match &self.mode {
+ LspStoreMode::Ssh(ssh_lsp_store) => Some(ssh_lsp_store),
+ _ => None,
+ }
+ }
+
+ pub fn as_local(&self) -> Option<&LocalLspStore> {
+ match &self.mode {
+ LspStoreMode::Local(local_lsp_store) => Some(local_lsp_store),
+ _ => None,
+ }
+ }
+
+ pub fn as_local_mut(&mut self) -> Option<&mut LocalLspStore> {
+ match &mut self.mode {
+ LspStoreMode::Local(local_lsp_store) => Some(local_lsp_store),
+ _ => None,
+ }
+ }
+
+ pub fn upstream_client(&self) -> Option<AnyProtoClient> {
+ match &self.mode {
+ LspStoreMode::Ssh(SshLspStore {
+ upstream_client, ..
+ })
+ | LspStoreMode::Remote(RemoteLspStore {
+ upstream_client, ..
+ }) => Some(upstream_client.clone()),
+ LspStoreMode::Local(_) => None,
+ }
+ }
+
+ pub fn new_local(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
- environment: Option<Model<ProjectEnvironment>>,
+ environment: Model<ProjectEnvironment>,
languages: Arc<LanguageRegistry>,
http_client: Option<Arc<dyn HttpClient>>,
fs: Arc<dyn Fs>,
- downstream_client: Option<AnyProtoClient>,
- upstream_client: Option<AnyProtoClient>,
- remote_id: Option<u64>,
cx: &mut ModelContext<Self>,
) -> Self {
let yarn = YarnPathStore::new(fs.clone(), cx);
@@ -229,32 +327,118 @@ impl LspStore {
.detach();
Self {
- downstream_client,
- upstream_client,
- http_client,
- fs,
- project_id: remote_id.unwrap_or(0),
+ mode: LspStoreMode::Local(LocalLspStore {
+ supplementary_language_servers: Default::default(),
+ language_servers: Default::default(),
+ last_workspace_edits_by_language_server: Default::default(),
+ language_server_watched_paths: Default::default(),
+ language_server_watcher_registrations: Default::default(),
+ environment,
+ http_client,
+ fs,
+ yarn,
+ _subscription: cx.on_app_quit(|this, cx| {
+ this.as_local_mut().unwrap().shutdown_language_servers(cx)
+ }),
+ }),
+ downstream_client: None,
+ project_id: 0,
+ buffer_store,
+ worktree_store,
+ languages: languages.clone(),
+ language_server_ids: Default::default(),
+ language_server_statuses: Default::default(),
+ nonce: StdRng::from_entropy().gen(),
+ buffer_snapshots: Default::default(),
+ next_diagnostic_group_id: Default::default(),
+ diagnostic_summaries: Default::default(),
+ diagnostics: Default::default(),
+ active_entry: None,
+ _maintain_workspace_config: Self::maintain_workspace_config(cx),
+ _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
+ }
+ }
+
+ fn send_lsp_proto_request<R: LspCommand>(
+ &self,
+ buffer: Model<Buffer>,
+ client: AnyProtoClient,
+ request: R,
+ cx: &mut ModelContext<'_, LspStore>,
+ ) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
+ let message = request.to_proto(self.project_id, buffer.read(cx));
+ cx.spawn(move |this, cx| async move {
+ let response = client.request(message).await?;
+ let this = this.upgrade().context("project dropped")?;
+ request
+ .response_from_proto(response, this, buffer, cx)
+ .await
+ })
+ }
+
+ pub fn new_ssh(
+ buffer_store: Model<BufferStore>,
+ worktree_store: Model<WorktreeStore>,
+ languages: Arc<LanguageRegistry>,
+ upstream_client: AnyProtoClient,
+ project_id: u64,
+ cx: &mut ModelContext<Self>,
+ ) -> Self {
+ cx.subscribe(&buffer_store, Self::on_buffer_store_event)
+ .detach();
+ cx.subscribe(&worktree_store, Self::on_worktree_store_event)
+ .detach();
+
+ Self {
+ mode: LspStoreMode::Ssh(SshLspStore { upstream_client }),
+ downstream_client: None,
+ project_id,
buffer_store,
worktree_store,
languages: languages.clone(),
- environment,
+ language_server_ids: Default::default(),
+ language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().gen(),
buffer_snapshots: Default::default(),
- supplementary_language_servers: Default::default(),
- language_servers: Default::default(),
+ next_diagnostic_group_id: Default::default(),
+ diagnostic_summaries: Default::default(),
+ diagnostics: Default::default(),
+ active_entry: None,
+ _maintain_workspace_config: Self::maintain_workspace_config(cx),
+ _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
+ }
+ }
+
+ pub fn new_remote(
+ buffer_store: Model<BufferStore>,
+ worktree_store: Model<WorktreeStore>,
+ languages: Arc<LanguageRegistry>,
+ upstream_client: AnyProtoClient,
+ project_id: u64,
+ cx: &mut ModelContext<Self>,
+ ) -> Self {
+ cx.subscribe(&buffer_store, Self::on_buffer_store_event)
+ .detach();
+ cx.subscribe(&worktree_store, Self::on_worktree_store_event)
+ .detach();
+
+ Self {
+ mode: LspStoreMode::Remote(RemoteLspStore { upstream_client }),
+ downstream_client: None,
+ project_id,
+ buffer_store,
+ worktree_store,
+ languages: languages.clone(),
language_server_ids: Default::default(),
language_server_statuses: Default::default(),
- last_workspace_edits_by_language_server: Default::default(),
- language_server_watched_paths: Default::default(),
- language_server_watcher_registrations: Default::default(),
+ nonce: StdRng::from_entropy().gen(),
+ buffer_snapshots: Default::default(),
next_diagnostic_group_id: Default::default(),
diagnostic_summaries: Default::default(),
diagnostics: Default::default(),
active_entry: None,
- yarn,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
- _subscription: cx.on_app_quit(Self::shutdown_language_servers),
}
}
@@ -273,7 +457,6 @@ impl LspStore {
self.unregister_buffer_from_language_servers(buffer, old_file, cx);
}
- self.detect_language_for_buffer(buffer, cx);
self.register_buffer_with_language_servers(buffer, cx);
}
BufferStoreEvent::BufferDropped(_) => {}
@@ -338,7 +521,6 @@ impl LspStore {
})
.detach();
- self.detect_language_for_buffer(buffer, cx);
self.register_buffer_with_language_servers(buffer, cx);
cx.observe_release(buffer, |this, buffer, cx| {
if let Some(file) = File::from_dyn(buffer.file()) {
@@ -406,9 +588,7 @@ impl LspStore {
buffers_with_unknown_injections.push(handle);
}
}
-
for buffer in plain_text_buffers {
- this.detect_language_for_buffer(&buffer, cx);
this.register_buffer_with_language_servers(&buffer, cx);
}
@@ -426,34 +606,29 @@ impl LspStore {
&mut self,
buffer_handle: &Model<Buffer>,
cx: &mut ModelContext<Self>,
- ) {
+ ) -> Option<language::AvailableLanguage> {
// If the buffer has a language, set it and start the language server if we haven't already.
let buffer = buffer_handle.read(cx);
- let Some(file) = buffer.file() else {
- return;
- };
- let content = buffer.as_rope();
- let Some(new_language_result) = self
- .languages
- .language_for_file(file, Some(content), cx)
- .now_or_never()
- else {
- return;
- };
+ let file = buffer.file()?;
- match new_language_result {
- Err(e) => {
- if e.is::<language::LanguageNotFound>() {
- cx.emit(LspStoreEvent::LanguageDetected {
- buffer: buffer_handle.clone(),
- new_language: None,
- });
- }
- }
- Ok(new_language) => {
+ let content = buffer.as_rope();
+ let available_language = self.languages.language_for_file(file, Some(content), cx);
+ if let Some(available_language) = &available_language {
+ if let Some(Ok(Ok(new_language))) = self
+ .languages
+ .load_language(available_language)
+ .now_or_never()
+ {
self.set_language_for_buffer(buffer_handle, new_language, cx);
}
- };
+ } else {
+ cx.emit(LspStoreEvent::LanguageDetected {
+ buffer: buffer_handle.clone(),
+ new_language: None,
+ });
+ }
+
+ available_language
}
pub fn set_language_for_buffer(
@@ -475,9 +650,7 @@ impl LspStore {
if let Some(file) = buffer_file {
let worktree = file.worktree.clone();
- if worktree.read(cx).is_local() {
- self.start_language_servers(&worktree, new_language.clone(), cx)
- }
+ self.start_language_servers(&worktree, new_language.name(), cx)
}
cx.emit(LspStoreEvent::LanguageDetected {
@@ -494,27 +667,6 @@ impl LspStore {
self.active_entry = active_entry;
}
- fn shutdown_language_servers(
- &mut self,
- _cx: &mut ModelContext<Self>,
- ) -> impl Future<Output = ()> {
- let shutdown_futures = self
- .language_servers
- .drain()
- .map(|(_, server_state)| async {
- use LanguageServerState::*;
- match server_state {
- Running { server, .. } => server.shutdown()?.await,
- Starting(task) => task.await?.shutdown()?.await,
- }
- })
- .collect::<Vec<_>>();
-
- async move {
- futures::future::join_all(shutdown_futures).await;
- }
- }
-
pub(crate) fn send_diagnostic_summaries(
&self,
worktree: &mut Worktree,
@@ -547,9 +699,11 @@ impl LspStore {
<R::LspRequest as lsp::request::Request>::Params: Send,
{
let buffer = buffer_handle.read(cx);
- if self.upstream_client.is_some() {
- return self.send_lsp_proto_request(buffer_handle, self.project_id, request, cx);
+
+ if let Some(upstream_client) = self.upstream_client() {
+ return self.send_lsp_proto_request(buffer_handle, upstream_client, request, cx);
}
+
let language_server = match server {
LanguageServerToQuery::Primary => {
match self.primary_language_server_for_buffer(buffer, cx) {
@@ -635,26 +789,6 @@ impl LspStore {
Task::ready(Ok(Default::default()))
}
- fn send_lsp_proto_request<R: LspCommand>(
- &self,
- buffer: Model<Buffer>,
- project_id: u64,
- request: R,
- cx: &mut ModelContext<'_, Self>,
- ) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
- let Some(upstream_client) = self.upstream_client.clone() else {
- return Task::ready(Err(anyhow!("disconnected before completing request")));
- };
- let message = request.to_proto(project_id, buffer.read(cx));
- cx.spawn(move |this, cx| async move {
- let response = upstream_client.request(message).await?;
- let this = this.upgrade().context("project dropped")?;
- request
- .response_from_proto(response, this, buffer, cx)
- .await
- })
- }
-
pub async fn execute_code_actions_on_servers(
this: &WeakModel<LspStore>,
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
@@ -702,8 +836,10 @@ impl LspStore {
if let Some(command) = action.lsp_action.command {
this.update(cx, |this, _| {
- this.last_workspace_edits_by_language_server
- .remove(&language_server.server_id());
+ if let LspStoreMode::Local(mode) = &mut this.mode {
+ mode.last_workspace_edits_by_language_server
+ .remove(&language_server.server_id());
+ }
})?;
language_server
@@ -715,12 +851,14 @@ impl LspStore {
.await?;
this.update(cx, |this, _| {
- project_transaction.0.extend(
- this.last_workspace_edits_by_language_server
- .remove(&language_server.server_id())
- .unwrap_or_default()
- .0,
- )
+ if let LspStoreMode::Local(mode) = &mut this.mode {
+ project_transaction.0.extend(
+ mode.last_workspace_edits_by_language_server
+ .remove(&language_server.server_id())
+ .unwrap_or_default()
+ .0,
+ )
+ }
})?;
}
}
@@ -752,7 +890,7 @@ impl LspStore {
push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<ProjectTransaction>> {
- if let Some(upstream_client) = self.upstream_client.clone() {
+ if let Some(upstream_client) = self.upstream_client() {
let request = proto::ApplyCodeAction {
project_id: self.project_id,
buffer_id: buffer_handle.read(cx).remote_id().into(),
@@ -801,7 +939,9 @@ impl LspStore {
if let Some(command) = action.lsp_action.command {
this.update(&mut cx, |this, _| {
- this.last_workspace_edits_by_language_server
+ this.as_local_mut()
+ .unwrap()
+ .last_workspace_edits_by_language_server
.remove(&lang_server.server_id());
})?;
@@ -816,7 +956,9 @@ impl LspStore {
result?;
return this.update(&mut cx, |this, _| {
- this.last_workspace_edits_by_language_server
+ this.as_local_mut()
+ .unwrap()
+ .last_workspace_edits_by_language_server
.remove(&lang_server.server_id())
.unwrap_or_default()
});
@@ -834,7 +976,7 @@ impl LspStore {
server_id: LanguageServerId,
cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<InlayHint>> {
- if let Some(upstream_client) = self.upstream_client.clone() {
+ if let Some(upstream_client) = self.upstream_client() {
let request = proto::ResolveInlayHint {
project_id: self.project_id,
buffer_id: buffer_handle.read(cx).remote_id().into(),
@@ -912,7 +1054,7 @@ impl LspStore {
.map(|(_, server)| LanguageServerToQuery::Other(server.server_id()))
.next()
.or_else(|| {
- self.upstream_client
+ self.upstream_client()
.is_some()
.then_some(LanguageServerToQuery::Primary)
})
@@ -945,7 +1087,7 @@ impl LspStore {
trigger: String,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> {
- if let Some(client) = self.upstream_client.clone() {
+ if let Some(client) = self.upstream_client() {
let request = proto::OnTypeFormatting {
project_id: self.project_id,
buffer_id: buffer.read(cx).remote_id().into(),
@@ -1095,7 +1237,7 @@ impl LspStore {
range: Range<Anchor>,
cx: &mut ModelContext<Self>,
) -> Task<Vec<CodeAction>> {
- if let Some(upstream_client) = self.upstream_client.as_ref() {
+ if let Some(upstream_client) = self.upstream_client() {
let request_task = upstream_client.request(proto::MultiLspQuery {
buffer_id: buffer_handle.read(cx).remote_id().into(),
version: serialize_version(&buffer_handle.read(cx).version()),
@@ -1175,10 +1317,10 @@ impl LspStore {
) -> Task<Result<Vec<Completion>>> {
let language_registry = self.languages.clone();
- if let Some(_) = self.upstream_client.clone() {
+ if let Some(upstream_client) = self.upstream_client() {
let task = self.send_lsp_proto_request(
buffer.clone(),
- self.project_id,
+ upstream_client,
GetCompletions { position, context },
cx,
);
@@ -1187,9 +1329,12 @@ impl LspStore {
// In the future, we should provide project guests with the names of LSP adapters,
// so that they can use the correct LSP adapter when computing labels. For now,
// guests just use the first LSP adapter associated with the buffer's language.
- let lsp_adapter = language
- .as_ref()
- .and_then(|language| language_registry.lsp_adapters(language).first().cloned());
+ let lsp_adapter = language.as_ref().and_then(|language| {
+ language_registry
+ .lsp_adapters(&language.name())
+ .first()
+ .cloned()
+ });
cx.foreground_executor().spawn(async move {
let completions = task.await?;
@@ -1269,7 +1414,7 @@ impl LspStore {
completions: Arc<RwLock<Box<[Completion]>>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<bool>> {
- let client = self.upstream_client.clone();
+ let client = self.upstream_client();
let language_registry = self.languages.clone();
let project_id = self.project_id;
@@ -1478,7 +1623,7 @@ impl LspStore {
let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id();
- if let Some(client) = self.upstream_client.clone() {
+ if let Some(client) = self.upstream_client() {
let project_id = self.project_id;
cx.spawn(move |_, mut cx| async move {
let response = client
@@ -1594,7 +1739,7 @@ impl LspStore {
let buffer_id = buffer.remote_id().into();
let lsp_request = InlayHints { range };
- if let Some(client) = self.upstream_client.clone() {
+ if let Some(client) = self.upstream_client() {
let request = proto::InlayHints {
project_id: self.project_id,
buffer_id,
@@ -1644,7 +1789,7 @@ impl LspStore {
) -> Task<Vec<SignatureHelp>> {
let position = position.to_point_utf16(buffer.read(cx));
- if let Some(client) = self.upstream_client.clone() {
+ if let Some(client) = self.upstream_client() {
let request_task = client.request(proto::MultiLspQuery {
buffer_id: buffer.read(cx).remote_id().into(),
version: serialize_version(&buffer.read(cx).version()),
@@ -1716,7 +1861,7 @@ impl LspStore {
position: PointUtf16,
cx: &mut ModelContext<Self>,
) -> Task<Vec<Hover>> {
- if let Some(client) = self.upstream_client.clone() {
+ if let Some(client) = self.upstream_client() {
let request_task = client.request(proto::MultiLspQuery {
buffer_id: buffer.read(cx).remote_id().into(),
version: serialize_version(&buffer.read(cx).version()),
@@ -1790,7 +1935,7 @@ impl LspStore {
pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
let language_registry = self.languages.clone();
- if let Some(upstream_client) = self.upstream_client.as_ref() {
+ if let Some(upstream_client) = self.upstream_client().as_ref() {
let request = upstream_client.request(proto::GetProjectSymbols {
project_id: self.project_id,
query: query.to_string(),
@@ -1816,7 +1961,7 @@ impl LspStore {
} else {
struct WorkspaceSymbolsResult {
lsp_adapter: Arc<CachedLspAdapter>,
- language: Arc<Language>,
+ language: LanguageName,
worktree: WeakModel<Worktree>,
worktree_abs_path: Arc<Path>,
lsp_symbols: Vec<(String, SymbolKind, lsp::Location)>,
@@ -1837,16 +1982,17 @@ impl LspStore {
}
let worktree_abs_path = worktree.abs_path().clone();
- let (lsp_adapter, language, server) = match self.language_servers.get(server_id) {
- Some(LanguageServerState::Running {
- adapter,
- language,
- server,
- ..
- }) => (adapter.clone(), language.clone(), server),
+ let (lsp_adapter, language, server) =
+ match self.as_local().unwrap().language_servers.get(server_id) {
+ Some(LanguageServerState::Running {
+ adapter,
+ language,
+ server,
+ ..
+ }) => (adapter.clone(), language.clone(), server),
- _ => continue,
- };
+ _ => continue,
+ };
requests.push(
server
@@ -2105,7 +2251,7 @@ impl LspStore {
uri: lsp::Url::from_file_path(abs_path).log_err()?,
};
- for (_, _, server) in self.language_servers_for_worktree(worktree_id) {
+ for server in self.language_servers_for_worktree(worktree_id) {
if let Some(include_text) = include_text(server.as_ref()) {
let text = if include_text {
Some(buffer.read(cx).text())
@@ -2148,8 +2294,9 @@ impl LspStore {
.worktree_store
.read(cx)
.worktree_for_id(*worktree_id, cx)?;
- let state = this.language_servers.get(server_id)?;
- let delegate = ProjectLspAdapterDelegate::new(this, &worktree, cx);
+ let state = this.as_local()?.language_servers.get(server_id)?;
+ let delegate =
+ ProjectLspAdapterDelegate::for_local(this, &worktree, cx);
match state {
LanguageServerState::Starting(_) => None,
LanguageServerState::Running {
@@ -2204,19 +2351,15 @@ impl LspStore {
fn language_servers_for_worktree(
&self,
worktree_id: WorktreeId,
- ) -> impl Iterator<Item = (&Arc<CachedLspAdapter>, &Arc<Language>, &Arc<LanguageServer>)> {
+ ) -> impl Iterator<Item = &Arc<LanguageServer>> {
self.language_server_ids
.iter()
.filter_map(move |((language_server_worktree_id, _), id)| {
if *language_server_worktree_id == worktree_id {
- if let Some(LanguageServerState::Running {
- adapter,
- language,
- server,
- ..
- }) = self.language_servers.get(id)
+ if let Some(LanguageServerState::Running { server, .. }) =
+ self.as_local()?.language_servers.get(id)
{
- return Some((adapter, language, server));
+ return Some(server);
}
}
None
@@ -2241,11 +2384,17 @@ impl LspStore {
self.language_server_ids
.remove(&(id_to_remove, server_name));
self.language_server_statuses.remove(&server_id_to_remove);
- self.language_server_watched_paths
- .remove(&server_id_to_remove);
- self.last_workspace_edits_by_language_server
- .remove(&server_id_to_remove);
- self.language_servers.remove(&server_id_to_remove);
+ if let Some(local_lsp_store) = self.as_local_mut() {
+ local_lsp_store
+ .language_server_watched_paths
+ .remove(&server_id_to_remove);
+ local_lsp_store
+ .last_workspace_edits_by_language_server
+ .remove(&server_id_to_remove);
+ local_lsp_store
+ .language_servers
+ .remove(&server_id_to_remove);
+ }
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove));
}
}
@@ -2306,11 +2455,14 @@ impl LspStore {
.insert((worktree_id, language_server_name), language_server_id);
}
+ #[track_caller]
pub(crate) fn register_buffer_with_language_servers(
&mut self,
buffer_handle: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) {
+ let available_language = self.detect_language_for_buffer(buffer_handle, cx);
+
let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id();
@@ -2324,7 +2476,6 @@ impl LspStore {
return;
};
let initial_snapshot = buffer.text_snapshot();
- let language = buffer.language().cloned();
let worktree_id = file.worktree_id(cx);
if let Some(diagnostics) = self.diagnostics.get(&worktree_id) {
@@ -2336,12 +2487,12 @@ impl LspStore {
}
}
- if let Some(language) = language {
- for adapter in self.languages.lsp_adapters(&language) {
+ if let Some(language) = available_language {
+ for adapter in self.languages.lsp_adapters(&language.name()) {
let server = self
.language_server_ids
.get(&(worktree_id, adapter.name.clone()))
- .and_then(|id| self.language_servers.get(id))
+ .and_then(|id| self.as_local()?.language_servers.get(id))
.and_then(|server_state| {
if let LanguageServerState::Running { server, .. } = server_state {
Some(server.clone())
@@ -2359,7 +2510,7 @@ impl LspStore {
lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new(
uri.clone(),
- adapter.language_id(&language),
+ adapter.language_id(&language.name()),
0,
initial_snapshot.text(),
),
@@ -2409,7 +2560,7 @@ impl LspStore {
let ids = &self.language_server_ids;
if let Some(language) = buffer.language().cloned() {
- for adapter in self.languages.lsp_adapters(&language) {
+ for adapter in self.languages.lsp_adapters(&language.name()) {
if let Some(server_id) = ids.get(&(worktree_id, adapter.name.clone())) {
buffer.update_diagnostics(*server_id, Default::default(), cx);
}
@@ -2537,7 +2688,7 @@ impl LspStore {
symbol: &Symbol,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> {
- if let Some(client) = self.upstream_client.clone() {
+ if let Some(client) = self.upstream_client() {
let request = client.request(proto::OpenBufferForSymbol {
project_id: self.project_id,
symbol: Some(Self::serialize_symbol(symbol)),
@@ -2605,7 +2756,7 @@ impl LspStore {
let p = abs_path.clone();
let yarn_worktree = this
.update(&mut cx, move |this, cx| {
- this.yarn.update(cx, |_, cx| {
+ this.as_local().unwrap().yarn.update(cx, |_, cx| {
cx.spawn(|this, mut cx| async move {
let t = this
.update(&mut cx, |this, cx| {
@@ -2755,7 +2906,7 @@ impl LspStore {
<R::LspRequest as lsp::request::Request>::Result: Send,
<R::LspRequest as lsp::request::Request>::Params: Send,
{
- debug_assert!(self.upstream_client.is_none());
+ debug_assert!(self.upstream_client().is_none());
let snapshot = buffer.read(cx).snapshot();
let scope = position.and_then(|position| snapshot.language_scope_at(position));
@@ -2801,7 +2952,7 @@ impl LspStore {
<T::LspRequest as lsp::request::Request>::Params: Send,
<T::LspRequest as lsp::request::Request>::Result: Send,
{
- let sender_id = envelope.original_sender_id()?;
+ let sender_id = envelope.original_sender_id().unwrap_or_default();
let buffer_id = T::buffer_id_from_proto(&envelope.payload)?;
let buffer_handle = this.update(&mut cx, |this, cx| {
this.buffer_store.read(cx).get_existing(buffer_id)
@@ -2839,7 +2990,7 @@ impl LspStore {
envelope: TypedEnvelope<proto::MultiLspQuery>,
mut cx: AsyncAppContext,
) -> Result<proto::MultiLspQueryResponse> {
- let sender_id = envelope.original_sender_id()?;
+ let sender_id = envelope.original_sender_id().unwrap_or_default();
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let version = deserialize_version(&envelope.payload.version);
let buffer = this.update(&mut cx, |this, cx| {
@@ -2979,7 +3130,7 @@ impl LspStore {
envelope: TypedEnvelope<proto::ApplyCodeAction>,
mut cx: AsyncAppContext,
) -> Result<proto::ApplyCodeActionResponse> {
- let sender_id = envelope.original_sender_id()?;
+ let sender_id = envelope.original_sender_id().unwrap_or_default();
let action = Self::deserialize_code_action(
envelope
.payload
@@ -3184,7 +3335,9 @@ impl LspStore {
simulate_disk_based_diagnostics_completion,
adapter,
..
- }) = self.language_servers.get_mut(&language_server_id)
+ }) = self
+ .as_local_mut()
+ .and_then(|local_store| local_store.language_servers.get_mut(&language_server_id))
else {
return;
};
@@ -3205,8 +3358,9 @@ impl LspStore {
if let Some(LanguageServerState::Running {
simulate_disk_based_diagnostics_completion,
..
- }) = this.language_servers.get_mut(&language_server_id)
- {
+ }) = this.as_local_mut().and_then(|local_store| {
+ local_store.language_servers.get_mut(&language_server_id)
+ }) {
*simulate_disk_based_diagnostics_completion = None;
}
})
@@ -3264,7 +3418,20 @@ impl LspStore {
language_server_id: LanguageServerId,
cx: &mut ModelContext<Self>,
) {
- let Some(watchers) = self
+ let worktrees = self
+ .worktree_store
+ .read(cx)
+ .worktrees()
+ .filter_map(|worktree| {
+ self.language_servers_for_worktree(worktree.read(cx).id())
+ .find(|server| server.server_id() == language_server_id)
+ .map(|_| worktree)
+ })
+ .collect::<Vec<_>>();
+
+ let local_lsp_store = self.as_local_mut().unwrap();
+
+ let Some(watchers) = local_lsp_store
.language_server_watcher_registrations
.get(&language_server_id)
else {
@@ -3278,17 +3445,6 @@ impl LspStore {
language_server_id
);
- let worktrees = self
- .worktree_store
- .read(cx)
- .worktrees()
- .filter_map(|worktree| {
- self.language_servers_for_worktree(worktree.read(cx).id())
- .find(|(_, _, server)| server.server_id() == language_server_id)
- .map(|_| worktree)
- })
- .collect::<Vec<_>>();
-
enum PathToWatch {
Worktree {
literal_prefix: Arc<Path>,
@@ -3438,20 +3594,29 @@ impl LspStore {
watch_builder.watch_abs_path(abs_path, globset);
}
}
- let watcher = watch_builder.build(self.fs.clone(), language_server_id, cx);
- self.language_server_watched_paths
+ let watcher = watch_builder.build(local_lsp_store.fs.clone(), language_server_id, cx);
+ local_lsp_store
+ .language_server_watched_paths
.insert(language_server_id, watcher);
cx.notify();
}
pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
- if let Some(LanguageServerState::Running { server, .. }) = self.language_servers.get(&id) {
- Some(server.clone())
- } else if let Some((_, server)) = self.supplementary_language_servers.get(&id) {
- Some(Arc::clone(server))
- } else {
- None
+ if let Some(local_lsp_store) = self.as_local() {
+ if let Some(LanguageServerState::Running { server, .. }) =
+ local_lsp_store.language_servers.get(&id)
+ {
+ Some(server.clone())
+ } else if let Some((_, server)) =
+ local_lsp_store.supplementary_language_servers.get(&id)
+ {
+ Some(Arc::clone(server))
+ } else {
+ None
+ }
+ } else {
+ None
}
}
@@ -107,7 +107,7 @@ pub use buffer_store::ProjectTransaction;
pub use lsp_store::{
DiagnosticSummary, LanguageServerLogType, LanguageServerProgress, LanguageServerPromptRequest,
LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent,
- ProjectLspAdapterDelegate, SERVER_PROGRESS_THROTTLE_TIMEOUT,
+ SERVER_PROGRESS_THROTTLE_TIMEOUT,
};
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
@@ -643,16 +643,13 @@ impl Project {
let environment = ProjectEnvironment::new(&worktree_store, env, cx);
let lsp_store = cx.new_model(|cx| {
- LspStore::new(
+ LspStore::new_local(
buffer_store.clone(),
worktree_store.clone(),
- Some(environment.clone()),
+ environment.clone(),
languages.clone(),
Some(client.http_client()),
fs.clone(),
- None,
- None,
- None,
cx,
)
});
@@ -712,16 +709,89 @@ impl Project {
fs: Arc<dyn Fs>,
cx: &mut AppContext,
) -> Model<Self> {
- let this = Self::local(client, node, user_store, languages, fs, None, cx);
- this.update(cx, |this, cx| {
- let client: AnyProtoClient = ssh.clone().into();
+ cx.new_model(|cx: &mut ModelContext<Self>| {
+ let (tx, rx) = mpsc::unbounded();
+ cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
+ .detach();
+ let tasks = Inventory::new(cx);
+ let global_snippets_dir = paths::config_dir().join("snippets");
+ let snippets =
+ SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
+
+ let worktree_store = cx.new_model(|_| {
+ let mut worktree_store = WorktreeStore::new(false, fs.clone());
+ worktree_store.set_upstream_client(ssh.clone().into());
+ worktree_store
+ });
+ cx.subscribe(&worktree_store, Self::on_worktree_store_event)
+ .detach();
+
+ let buffer_store =
+ cx.new_model(|cx| BufferStore::new(worktree_store.clone(), None, cx));
+ cx.subscribe(&buffer_store, Self::on_buffer_store_event)
+ .detach();
- this.worktree_store.update(cx, |store, _cx| {
- store.set_upstream_client(client.clone());
+ let settings_observer = cx.new_model(|cx| {
+ SettingsObserver::new_ssh(ssh.clone().into(), worktree_store.clone(), cx)
});
- this.settings_observer = cx.new_model(|cx| {
- SettingsObserver::new_ssh(ssh.clone().into(), this.worktree_store.clone(), cx)
+
+ let environment = ProjectEnvironment::new(&worktree_store, None, cx);
+ let lsp_store = cx.new_model(|cx| {
+ LspStore::new_ssh(
+ buffer_store.clone(),
+ worktree_store.clone(),
+ languages.clone(),
+ ssh.clone().into(),
+ 0,
+ cx,
+ )
});
+ cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
+
+ let this = Self {
+ buffer_ordered_messages_tx: tx,
+ collaborators: Default::default(),
+ worktree_store,
+ buffer_store,
+ lsp_store,
+ current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
+ join_project_response_message_id: 0,
+ client_state: ProjectClientState::Local,
+ client_subscriptions: Vec::new(),
+ _subscriptions: vec![
+ cx.observe_global::<SettingsStore>(Self::on_settings_changed),
+ cx.on_release(Self::release),
+ ],
+ active_entry: None,
+ snippets,
+ languages,
+ client,
+ user_store,
+ settings_observer,
+ fs,
+ ssh_session: Some(ssh.clone()),
+ buffers_needing_diff: Default::default(),
+ git_diff_debouncer: DebouncedDelay::new(),
+ terminals: Terminals {
+ local_handles: Vec::new(),
+ },
+ node: Some(node),
+ default_prettier: DefaultPrettier::default(),
+ prettiers_per_worktree: HashMap::default(),
+ prettier_instances: HashMap::default(),
+ tasks,
+ hosted_project_id: None,
+ dev_server_project_id: None,
+ search_history: Self::new_search_history(),
+ environment,
+ remotely_created_buffers: Default::default(),
+ last_formatting_failure: None,
+ buffers_being_formatted: Default::default(),
+ search_included_history: Self::new_search_history(),
+ search_excluded_history: Self::new_search_history(),
+ };
+
+ let client: AnyProtoClient = ssh.clone().into();
ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
@@ -735,9 +805,8 @@ impl Project {
LspStore::init(&client);
SettingsObserver::init(&client);
- this.ssh_session = Some(ssh);
- });
- this
+ this
+ })
}
pub async fn remote(
@@ -820,16 +889,12 @@ impl Project {
cx.new_model(|cx| BufferStore::new(worktree_store.clone(), Some(remote_id), cx))?;
let lsp_store = cx.new_model(|cx| {
- let mut lsp_store = LspStore::new(
+ let mut lsp_store = LspStore::new_remote(
buffer_store.clone(),
worktree_store.clone(),
- None,
languages.clone(),
- Some(client.http_client()),
- fs.clone(),
- None,
- Some(client.clone().into()),
- Some(remote_id),
+ client.clone().into(),
+ remote_id,
cx,
);
lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers);
@@ -1125,8 +1190,7 @@ impl Project {
if let Some(language) = buffer_language {
if settings.enable_language_server {
if let Some(file) = buffer_file {
- language_servers_to_start
- .push((file.worktree.clone(), Arc::clone(language)));
+ language_servers_to_start.push((file.worktree.clone(), language.name()));
}
}
language_formatters_to_check
@@ -1144,7 +1208,7 @@ impl Project {
let language = languages.iter().find_map(|l| {
let adapter = self
.languages
- .lsp_adapters(l)
+ .lsp_adapters(&l.name())
.iter()
.find(|adapter| adapter.name == started_lsp_name)?
.clone();
@@ -1165,11 +1229,11 @@ impl Project {
) {
(None, None) => {}
(Some(_), None) | (None, Some(_)) => {
- language_servers_to_restart.push((worktree, Arc::clone(language)));
+ language_servers_to_restart.push((worktree, language.name()));
}
(Some(current_lsp_settings), Some(new_lsp_settings)) => {
if current_lsp_settings != new_lsp_settings {
- language_servers_to_restart.push((worktree, Arc::clone(language)));
+ language_servers_to_restart.push((worktree, language.name()));
}
}
}
@@ -4777,7 +4841,7 @@ impl Project {
pub fn supplementary_language_servers<'a>(
&'a self,
cx: &'a AppContext,
- ) -> impl '_ + Iterator<Item = (&'a LanguageServerId, &'a LanguageServerName)> {
+ ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName)> {
self.lsp_store.read(cx).supplementary_language_servers()
}
@@ -19,7 +19,7 @@ use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId};
use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ProjectSettings {
/// Configuration for language servers.
///
@@ -6,7 +6,7 @@ use http_client::Url;
use language::{
language_settings::{AllLanguageSettings, LanguageSettingsContent},
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter,
- LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, ToPoint,
+ LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
};
use lsp::{DiagnosticSeverity, NumberOrString};
use parking_lot::Mutex;
@@ -1559,7 +1559,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert(
- Arc::from("Rust"),
+ "Rust".into(),
LanguageSettingsContent {
enable_language_server: Some(false),
..Default::default()
@@ -1578,14 +1578,14 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert(
- Arc::from("Rust"),
+ LanguageName::new("Rust"),
LanguageSettingsContent {
enable_language_server: Some(true),
..Default::default()
},
);
settings.languages.insert(
- Arc::from("JavaScript"),
+ LanguageName::new("JavaScript"),
LanguageSettingsContent {
enable_language_server: Some(false),
..Default::default()
@@ -2983,7 +2983,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
buffer.edit([(0..0, "abc")], None, cx);
assert!(buffer.is_dirty());
assert!(!buffer.has_conflict());
- assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
+ assert_eq!(buffer.language().unwrap().name(), "Plain Text".into());
});
project
.update(cx, |project, cx| {
@@ -3006,7 +3006,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
);
assert!(!buffer.is_dirty());
assert!(!buffer.has_conflict());
- assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
+ assert_eq!(buffer.language().unwrap().name(), "Rust".into());
});
let opened_buffer = project
@@ -5308,7 +5308,7 @@ fn json_lang() -> Arc<Language> {
fn js_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
- name: Arc::from("JavaScript"),
+ name: "JavaScript".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["js".to_string()],
..Default::default()
@@ -161,7 +161,7 @@ impl Inventory {
cx: &AppContext,
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
- name: language.name(),
+ name: language.name().0,
});
let language_tasks = language
.and_then(|language| language.context_provider()?.associated_tasks(file, cx))
@@ -207,7 +207,7 @@ impl Inventory {
.as_ref()
.and_then(|location| location.buffer.read(cx).language_at(location.range.start));
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
- name: language.name(),
+ name: language.name().0,
});
let file = location
.as_ref()
@@ -281,7 +281,9 @@ message Envelope {
FindSearchCandidatesResponse find_search_candidates_response = 244;
CloseBuffer close_buffer = 245;
- UpdateUserSettings update_user_settings = 246; // current max
+ UpdateUserSettings update_user_settings = 246;
+
+ CreateLanguageServer create_language_server = 247; // current max
}
reserved 158 to 161;
@@ -2497,3 +2499,36 @@ message UpdateUserSettings {
uint64 project_id = 1;
string content = 2;
}
+
+message LanguageServerCommand {
+ string path = 1;
+ repeated string arguments = 2;
+}
+
+message AvailableLanguage {
+ string name = 7;
+ string matcher = 8;
+}
+
+message CreateLanguageServer {
+ uint64 project_id = 1;
+ uint64 worktree_id = 2;
+ string name = 3;
+
+ LanguageServerCommand binary = 4;
+ optional string initialization_options = 5;
+ optional string code_action_kinds = 6;
+
+ AvailableLanguage language = 7;
+}
+
+// message RestartLanguageServer {
+
+// }
+// message DestroyLanguageServer {
+
+// }
+
+// message LspWorkspaceConfiguration {
+
+// }
@@ -366,7 +366,8 @@ messages!(
(FindSearchCandidates, Background),
(FindSearchCandidatesResponse, Background),
(CloseBuffer, Foreground),
- (UpdateUserSettings, Foreground)
+ (UpdateUserSettings, Foreground),
+ (CreateLanguageServer, Foreground)
);
request_messages!(
@@ -490,6 +491,7 @@ request_messages!(
(SynchronizeContexts, SynchronizeContextsResponse),
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(AddWorktree, AddWorktreeResponse),
+ (CreateLanguageServer, Ack)
);
entity_messages!(
@@ -562,7 +564,8 @@ entity_messages!(
UpdateContext,
SynchronizeContexts,
LspExtSwitchSourceHeader,
- UpdateUserSettings
+ UpdateUserSettings,
+ CreateLanguageServer
);
entity_messages!(
@@ -62,7 +62,7 @@ impl QuickActionBar {
return self.render_repl_launch_menu(spec, cx);
}
SessionSupport::RequiresSetup(language) => {
- return self.render_repl_setup(&language, cx);
+ return self.render_repl_setup(&language.0, cx);
}
SessionSupport::Unsupported => return None,
};
@@ -291,11 +291,24 @@ impl SshClientDelegate {
self.update_status(Some("building remote server binary from source"), cx);
log::info!("building remote server binary from source");
- run_cmd(Command::new("cargo").args(["build", "--package", "remote_server"])).await?;
- run_cmd(Command::new("strip").args(["target/debug/remote_server"])).await?;
- run_cmd(Command::new("gzip").args(["-9", "-f", "target/debug/remote_server"])).await?;
+ run_cmd(Command::new("cargo").args([
+ "build",
+ "--package",
+ "remote_server",
+ "--target-dir",
+ "target/remote_server",
+ ]))
+ .await?;
+ // run_cmd(Command::new("strip").args(["target/remote_server/debug/remote_server"]))
+ // .await?;
+ run_cmd(Command::new("gzip").args([
+ "-9",
+ "-f",
+ "target/remote_server/debug/remote_server",
+ ]))
+ .await?;
- let path = std::env::current_dir()?.join("target/debug/remote_server.gz");
+ let path = std::env::current_dir()?.join("target/remote_server/debug/remote_server.gz");
return Ok((path, version));
async fn run_cmd(command: &mut Command) -> Result<()> {
@@ -41,11 +41,11 @@ pub struct SshSocket {
pub struct SshSession {
next_message_id: AtomicU32,
- response_channels: ResponseChannels,
+ response_channels: ResponseChannels, // Lock
outgoing_tx: mpsc::UnboundedSender<Envelope>,
spawn_process_tx: mpsc::UnboundedSender<SpawnRequest>,
client_socket: Option<SshSocket>,
- state: Mutex<ProtoMessageHandlerSet>,
+ state: Mutex<ProtoMessageHandlerSet>, // Lock
}
struct SshClientState {
@@ -392,9 +392,9 @@ impl SshSession {
) -> impl 'static + Future<Output = Result<proto::Envelope>> {
envelope.id = self.next_message_id.fetch_add(1, SeqCst);
let (tx, rx) = oneshot::channel();
- self.response_channels
- .lock()
- .insert(MessageId(envelope.id), tx);
+ let mut response_channels_lock = self.response_channels.lock();
+ response_channels_lock.insert(MessageId(envelope.id), tx);
+ drop(response_channels_lock);
self.outgoing_tx.unbounded_send(envelope).ok();
async move { Ok(rx.await.context("connection lost")?.0) }
}
@@ -4,14 +4,13 @@ use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task};
use language::LanguageRegistry;
use project::{
buffer_store::BufferStore, project_settings::SettingsObserver, search::SearchQuery,
- worktree_store::WorktreeStore, LspStore, ProjectPath, WorktreeId, WorktreeSettings,
+ worktree_store::WorktreeStore, LspStore, ProjectPath, WorktreeId,
};
use remote::SshSession;
use rpc::{
proto::{self, AnyProtoClient, SSH_PEER_ID, SSH_PROJECT_ID},
TypedEnvelope,
};
-use settings::Settings as _;
use smol::stream::StreamExt;
use std::{
path::{Path, PathBuf},
@@ -33,15 +32,17 @@ impl HeadlessProject {
pub fn init(cx: &mut AppContext) {
settings::init(cx);
language::init(cx);
- WorktreeSettings::register(cx);
+ project::Project::init_settings(cx);
}
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
// TODO: we should load the env correctly (as we do in login_shell_env_loaded when stdout is not a pty). Can we re-use the ProjectEnvironment for that?
- let languages = Arc::new(LanguageRegistry::new(
- Task::ready(()),
- cx.background_executor().clone(),
- ));
+ let mut languages =
+ LanguageRegistry::new(Task::ready(()), cx.background_executor().clone());
+ languages
+ .set_language_server_download_dir(PathBuf::from("/Users/conrad/what-could-go-wrong"));
+
+ let languages = Arc::new(languages);
let worktree_store = cx.new_model(|_| WorktreeStore::new(true, fs.clone()));
let buffer_store = cx.new_model(|cx| {
@@ -57,18 +58,17 @@ impl HeadlessProject {
});
let environment = project::ProjectEnvironment::new(&worktree_store, None, cx);
let lsp_store = cx.new_model(|cx| {
- LspStore::new(
+ let mut lsp_store = LspStore::new_local(
buffer_store.clone(),
worktree_store.clone(),
- Some(environment),
+ environment,
languages,
None,
fs.clone(),
- Some(session.clone().into()),
- None,
- Some(0),
cx,
- )
+ );
+ lsp_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
+ lsp_store
});
let client: AnyProtoClient = session.clone().into();
@@ -88,9 +88,12 @@ impl HeadlessProject {
client.add_model_request_handler(BufferStore::handle_update_buffer);
client.add_model_message_handler(BufferStore::handle_close_buffer);
+ client.add_model_request_handler(LspStore::handle_create_language_server);
+
BufferStore::init(&client);
WorktreeStore::init(&client);
SettingsObserver::init(&client);
+ LspStore::init(&client);
HeadlessProject {
session: client,
@@ -6,7 +6,7 @@ use gpui::{Context, Model, TestAppContext};
use http_client::FakeHttpClient;
use language::{
language_settings::{all_language_settings, AllLanguageSettings},
- Buffer, LanguageRegistry,
+ Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry,
};
use node_runtime::FakeNodeRuntime;
use project::{
@@ -202,15 +202,29 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
server_cx.read(|cx| {
assert_eq!(
AllLanguageSettings::get_global(cx)
- .language(Some("Rust"))
+ .language(Some(&"Rust".into()))
.language_servers,
["custom-rust-analyzer".into()]
)
});
- fs.insert_tree("/code/project1/.zed", json!({
- "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
- })).await;
+ fs.insert_tree(
+ "/code/project1/.zed",
+ json!({
+ "settings.json": r#"
+ {
+ "languages": {"Rust":{"language_servers":["override-rust-analyzer"]}},
+ "lsp": {
+ "override-rust-analyzer": {
+ "binary": {
+ "path": "~/.cargo/bin/rust-analyzer"
+ }
+ }
+ }
+ }"#
+ }),
+ )
+ .await;
let worktree_id = project
.update(cx, |project, cx| {
@@ -247,7 +261,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
}),
cx
)
- .language(Some("Rust"))
+ .language(Some(&"Rust".into()))
.language_servers,
["override-rust-analyzer".into()]
)
@@ -257,13 +271,107 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
let file = buffer.read(cx).file();
assert_eq!(
all_language_settings(file, cx)
- .language(Some("Rust"))
+ .language(Some(&"Rust".into()))
.language_servers,
["override-rust-analyzer".into()]
)
});
}
+#[gpui::test]
+async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
+ let (project, headless, fs) = init_test(cx, server_cx).await;
+
+ fs.insert_tree(
+ "/code/project1/.zed",
+ json!({
+ "settings.json": r#"
+ {
+ "languages": {"Rust":{"language_servers":["rust-analyzer"]}},
+ "lsp": {
+ "rust-analyzer": {
+ "binary": {
+ "path": "~/.cargo/bin/rust-analyzer"
+ }
+ }
+ }
+ }"#
+ }),
+ )
+ .await;
+
+ cx.update_model(&project, |project, _| {
+ project.languages().register_test_language(LanguageConfig {
+ name: "Rust".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".into()],
+ ..Default::default()
+ },
+ ..Default::default()
+ });
+ project.languages().register_fake_lsp_adapter(
+ "Rust",
+ FakeLspAdapter {
+ name: "rust-analyzer",
+ ..Default::default()
+ },
+ )
+ });
+ cx.run_until_parked();
+
+ let worktree_id = project
+ .update(cx, |project, cx| {
+ project.find_or_create_worktree("/code/project1", true, cx)
+ })
+ .await
+ .unwrap()
+ .0
+ .read_with(cx, |worktree, _| worktree.id());
+
+ // Wait for the settings to synchronize
+ cx.run_until_parked();
+
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
+ })
+ .await
+ .unwrap();
+ cx.run_until_parked();
+
+ cx.read(|cx| {
+ let file = buffer.read(cx).file();
+ assert_eq!(
+ all_language_settings(file, cx)
+ .language(Some(&"Rust".into()))
+ .language_servers,
+ ["rust-analyzer".into()]
+ )
+ });
+
+ let buffer_id = cx.read(|cx| {
+ let buffer = buffer.read(cx);
+ assert_eq!(buffer.language().unwrap().name(), "Rust".into());
+ buffer.remote_id()
+ });
+
+ server_cx.read(|cx| {
+ let buffer = headless
+ .read(cx)
+ .buffer_store
+ .read(cx)
+ .get(buffer_id)
+ .unwrap();
+
+ assert_eq!(buffer.read(cx).language().unwrap().name(), "Rust".into());
+ });
+
+ server_cx.read(|cx| {
+ let lsp_store = headless.read(cx).lsp_store.read(cx);
+ assert_eq!(lsp_store.as_local().unwrap().language_servers.len(), 1);
+ });
+}
+
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok();
@@ -6,7 +6,7 @@ use std::sync::Arc;
use anyhow::{Context, Result};
use editor::Editor;
use gpui::{prelude::*, AppContext, Entity, View, WeakView, WindowContext};
-use language::{BufferSnapshot, Language, Point};
+use language::{BufferSnapshot, Language, LanguageName, Point};
use crate::repl_store::ReplStore;
use crate::session::SessionEvent;
@@ -99,7 +99,7 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
pub enum SessionSupport {
ActiveSession(View<Session>),
Inactive(Box<KernelSpecification>),
- RequiresSetup(Arc<str>),
+ RequiresSetup(LanguageName),
Unsupported,
}
@@ -268,7 +268,7 @@ fn runnable_ranges(
range: Range<Point>,
) -> (Vec<Range<Point>>, Option<Point>) {
if let Some(language) = buffer.language() {
- if language.name().as_ref() == "Markdown" {
+ if language.name() == "Markdown".into() {
return (markdown_code_blocks(buffer, range.clone()), None);
}
}
@@ -305,7 +305,7 @@ fn markdown_code_blocks(buffer: &BufferSnapshot, range: Range<Point>) -> Vec<Ran
}
fn language_supported(language: &Arc<Language>) -> bool {
- match language.name().as_ref() {
+ match language.name().0.as_ref() {
"TypeScript" | "Python" => true,
_ => false,
}
@@ -564,6 +564,13 @@ impl Worktree {
!self.is_local()
}
+ pub fn settings_location(&self, _: &ModelContext<Self>) -> SettingsLocation<'static> {
+ SettingsLocation {
+ worktree_id: self.id(),
+ path: Path::new(EMPTY_PATH),
+ }
+ }
+
pub fn snapshot(&self) -> Snapshot {
match self {
Worktree::Local(worktree) => worktree.snapshot.snapshot.clone(),
@@ -2251,14 +2251,8 @@ mod tests {
assert!(!editor.is_dirty(cx));
assert_eq!(editor.title(cx), "the-new-name.rs");
assert_eq!(
- editor
- .buffer()
- .read(cx)
- .language_at(0, cx)
- .unwrap()
- .name()
- .as_ref(),
- "Rust"
+ editor.buffer().read(cx).language_at(0, cx).unwrap().name(),
+ "Rust".into()
);
});
})
@@ -2374,14 +2368,8 @@ mod tests {
editor.update(cx, |editor, cx| {
assert!(!editor.is_dirty(cx));
assert_eq!(
- editor
- .buffer()
- .read(cx)
- .language_at(0, cx)
- .unwrap()
- .name()
- .as_ref(),
- "Rust"
+ editor.buffer().read(cx).language_at(0, cx).unwrap().name(),
+ "Rust".into()
)
});
})