use ssh lsp store (#17655)

Release Notes:

- ssh remoting: Added support for booting langauge servers (in limited
circumstances)

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Conrad Irwin 2024-09-10 15:51:01 -04:00 committed by GitHub
parent 130f19d8f9
commit 36eb1c15ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 1553 additions and 671 deletions

View file

@ -53,7 +53,8 @@ use language_model::{
}; };
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate}; 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 search::{buffer_search::DivRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
@ -5340,9 +5341,17 @@ fn make_lsp_adapter_delegate(
.worktrees(cx) .worktrees(cx)
.next() .next()
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?; .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| { project.lsp_store().update(cx, |lsp_store, cx| {
Ok(ProjectLspAdapterDelegate::new(lsp_store, &worktree, cx) Ok(
as Arc<dyn LspAdapterDelegate>) ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, cx)
as Arc<dyn LspAdapterDelegate>,
)
}) })
}) })
} }

View file

@ -2377,7 +2377,7 @@ impl Codegen {
// If Markdown or No Language is Known, increase the randomness for more creative output // If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs // If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() { let temperature = if let Some(language) = language_name.clone() {
if language.as_ref() == "Markdown" { if language == "Markdown".into() {
1.0 1.0
} else { } else {
0.5 0.5
@ -2386,7 +2386,7 @@ impl Codegen {
1.0 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 start = buffer.point_to_buffer_offset(edit_range.start);
let end = buffer.point_to_buffer_offset(edit_range.end); let end = buffer.point_to_buffer_offset(edit_range.end);
let (buffer, range) = if let Some((start, end)) = start.zip(end) { let (buffer, range) = if let Some((start, end)) = start.zip(end) {

View file

@ -4,7 +4,7 @@ use fs::Fs;
use futures::StreamExt; use futures::StreamExt;
use gpui::AssetSource; use gpui::AssetSource;
use handlebars::{Handlebars, RenderError}; use handlebars::{Handlebars, RenderError};
use language::BufferSnapshot; use language::{BufferSnapshot, LanguageName};
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Serialize; use serde::Serialize;
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration}; use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
@ -204,11 +204,11 @@ impl PromptBuilder {
pub fn generate_content_prompt( pub fn generate_content_prompt(
&self, &self,
user_prompt: String, user_prompt: String,
language_name: Option<&str>, language_name: Option<&LanguageName>,
buffer: BufferSnapshot, buffer: BufferSnapshot,
range: Range<usize>, range: Range<usize>,
) -> Result<String, RenderError> { ) -> 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", None | Some("Markdown" | "Plain Text") => "text",
Some(_) => "code", Some(_) => "code",
}; };

View file

@ -2328,11 +2328,11 @@ async fn test_propagate_saves_and_fs_changes(
.unwrap(); .unwrap();
buffer_b.read_with(cx_b, |buffer, _| { 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, _| { 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_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)); 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, _| { buffer_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js")); 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, _| { buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js")); 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, _| { buffer_c.read_with(cx_c, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js")); 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 let new_buffer_a = project_a

View file

@ -100,7 +100,7 @@ async fn test_sharing_an_ssh_remote_project(
let file = buffer_b.read(cx).file(); let file = buffer_b.read(cx).file();
assert_eq!( assert_eq!(
all_language_settings(file, cx) all_language_settings(file, cx)
.language(Some("Rust")) .language(Some(&("Rust".into())))
.language_servers, .language_servers,
["override-rust-analyzer".into()] ["override-rust-analyzer".into()]
) )

View file

@ -12,7 +12,7 @@ use crate::{element::register_action, Editor, SwitchSourceHeader};
static CLANGD_SERVER_NAME: &str = "clangd"; static CLANGD_SERVER_NAME: &str = "clangd";
fn is_c_language(language: &Language) -> bool { 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( pub fn switch_source_header(

View file

@ -12465,7 +12465,7 @@ fn inlay_hint_settings(
let language = snapshot.language_at(location); let language = snapshot.language_at(location);
let settings = all_language_settings(file, cx); let settings = all_language_settings(file, cx);
settings settings
.language(language.map(|l| l.name()).as_deref()) .language(language.map(|l| l.name()).as_ref())
.inlay_hints .inlay_hints
} }

View file

@ -20,8 +20,8 @@ use language::{
}, },
BracketPairConfig, BracketPairConfig,
Capability::ReadWrite, Capability::ReadWrite,
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override, FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
ParsedMarkdown, Point, LanguageName, Override, ParsedMarkdown, Point,
}; };
use language_settings::{Formatter, FormatterList, IndentGuideSettings}; use language_settings::{Formatter, FormatterList, IndentGuideSettings};
use multi_buffer::MultiBufferIndentGuide; 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 server_restarts = Arc::new(AtomicUsize::new(0));
let closure_restarts = Arc::clone(&server_restarts); let closure_restarts = Arc::clone(&server_restarts);
let language_server_name = "test language server"; 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()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: Arc::clone(&language_name), name: language_name.clone(),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["rs".to_string()],
..Default::default() ..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(); let _fake_server = fake_servers.next().await.unwrap();
update_test_language_settings(cx, |language_settings| { update_test_language_settings(cx, |language_settings| {
language_settings.languages.insert( language_settings.languages.insert(
Arc::clone(&language_name), language_name.clone(),
LanguageSettingsContent { LanguageSettingsContent {
tab_size: NonZeroU32::new(8), tab_size: NonZeroU32::new(8),
..Default::default() ..Default::default()

View file

@ -1705,8 +1705,8 @@ mod tests {
let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx); let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
assert_eq!( assert_eq!(
buffer.language().map(|lang| lang.name()).as_deref(), buffer.language().map(|lang| lang.name()),
Some("Rust") Some("Rust".into())
); // Language should be set to Rust ); // Language should be set to Rust
assert!(buffer.file().is_none()); // The buffer should not have an associated file assert!(buffer.file().is_none()); // The buffer should not have an associated file
}); });

View file

@ -13,7 +13,7 @@ use crate::{
static RUST_ANALYZER_NAME: &str = "rust-analyzer"; static RUST_ANALYZER_NAME: &str = "rust-analyzer";
fn is_rust_language(language: &Language) -> bool { 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) { pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {

View file

@ -58,7 +58,7 @@ impl EditorLspTestContext {
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let mut fake_servers = language_registry.register_fake_lsp_adapter( let mut fake_servers = language_registry.register_fake_lsp_adapter(
language.name().as_ref(), language.name(),
FakeLspAdapter { FakeLspAdapter {
capabilities, capabilities,
..Default::default() ..Default::default()

View file

@ -38,7 +38,6 @@ impl LspAdapter for ExtensionLspAdapter {
fn get_language_server_command<'a>( fn get_language_server_command<'a>(
self: Arc<Self>, self: Arc<Self>,
_: Arc<Language>,
_: Arc<Path>, _: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>, _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,

View file

@ -1,7 +1,7 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::{BTreeMap, HashMap}; use collections::{BTreeMap, HashMap};
use fs::Fs; use fs::Fs;
use language::LanguageServerName; use language::{LanguageName, LanguageServerName};
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
@ -106,10 +106,10 @@ pub struct GrammarManifestEntry {
pub struct LanguageServerManifestEntry { pub struct LanguageServerManifestEntry {
/// Deprecated in favor of `languages`. /// Deprecated in favor of `languages`.
#[serde(default)] #[serde(default)]
language: Option<Arc<str>>, language: Option<LanguageName>,
/// The list of languages this language server should work with. /// The list of languages this language server should work with.
#[serde(default)] #[serde(default)]
languages: Vec<Arc<str>>, languages: Vec<LanguageName>,
#[serde(default)] #[serde(default)]
pub language_ids: HashMap<String, String>, pub language_ids: HashMap<String, String>,
#[serde(default)] #[serde(default)]
@ -124,7 +124,7 @@ impl LanguageServerManifestEntry {
/// ///
/// We can replace this with just field access for the `languages` field once /// We can replace this with just field access for the `languages` field once
/// we have removed `language`. /// 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() { let language = if self.languages.is_empty() {
self.language.clone() self.language.clone()
} else { } else {

View file

@ -36,7 +36,8 @@ use gpui::{
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use indexed_docs::{IndexedDocsRegistry, ProviderId}; use indexed_docs::{IndexedDocsRegistry, ProviderId};
use language::{ use language::{
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES, LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
QUERY_FILENAME_PREFIXES,
}; };
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks; use project::ContextProviderWithTasks;
@ -148,7 +149,7 @@ impl Global for GlobalExtensionStore {}
pub struct ExtensionIndex { pub struct ExtensionIndex {
pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>, pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,
pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>, pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,
pub languages: BTreeMap<Arc<str>, ExtensionIndexLanguageEntry>, pub languages: BTreeMap<LanguageName, ExtensionIndexLanguageEntry>,
} }
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]

View file

@ -609,7 +609,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
.await .await
.unwrap(); .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 let buffer = project
.update(cx, |project, cx| { .update(cx, |project, cx| {

View file

@ -9,6 +9,7 @@ use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt}; use futures::{lock::Mutex, AsyncReadExt};
use indexed_docs::IndexedDocsDatabase; use indexed_docs::IndexedDocsDatabase;
use isahc::config::{Configurable, RedirectPolicy}; use isahc::config::{Configurable, RedirectPolicy};
use language::LanguageName;
use language::{ use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate, language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
}; };
@ -399,8 +400,9 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() { cx.update(|cx| match category.as_str() {
"language" => { "language" => {
let key = key.map(|k| LanguageName::new(&k));
let settings = 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 { Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size, tab_size: settings.tab_size,
})?) })?)

View file

@ -1504,3 +1504,9 @@ pub struct KeystrokeEvent {
/// The action that was resolved for the keystroke, if any /// The action that was resolved for the keystroke, if any
pub action: Option<Box<dyn Action>>, pub action: Option<Box<dyn Action>>,
} }
impl Drop for AppContext {
fn drop(&mut self) {
println!("Dropping the App Context");
}
}

View file

@ -72,7 +72,7 @@ fn test_select_language(cx: &mut AppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
registry.add(Arc::new(Language::new( registry.add(Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: "Rust".into(), name: LanguageName::new("Rust"),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["rs".to_string()],
..Default::default() ..Default::default()
@ -83,7 +83,7 @@ fn test_select_language(cx: &mut AppContext) {
))); )));
registry.add(Arc::new(Language::new( registry.add(Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: "Make".into(), name: LanguageName::new("Make"),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
..Default::default() ..Default::default()
@ -97,15 +97,13 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!( assert_eq!(
registry registry
.language_for_file(&file("src/lib.rs"), None, cx) .language_for_file(&file("src/lib.rs"), None, cx)
.now_or_never() .map(|l| l.name()),
.and_then(|l| Some(l.ok()?.name())),
Some("Rust".into()) Some("Rust".into())
); );
assert_eq!( assert_eq!(
registry registry
.language_for_file(&file("src/lib.mk"), None, cx) .language_for_file(&file("src/lib.mk"), None, cx)
.now_or_never() .map(|l| l.name()),
.and_then(|l| Some(l.ok()?.name())),
Some("Make".into()) Some("Make".into())
); );
@ -113,8 +111,7 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!( assert_eq!(
registry registry
.language_for_file(&file("src/Makefile"), None, cx) .language_for_file(&file("src/Makefile"), None, cx)
.now_or_never() .map(|l| l.name()),
.and_then(|l| Some(l.ok()?.name())),
Some("Make".into()) Some("Make".into())
); );
@ -122,22 +119,19 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!( assert_eq!(
registry registry
.language_for_file(&file("zed/cars"), None, cx) .language_for_file(&file("zed/cars"), None, cx)
.now_or_never() .map(|l| l.name()),
.and_then(|l| Some(l.ok()?.name())),
None None
); );
assert_eq!( assert_eq!(
registry registry
.language_for_file(&file("zed/a.cars"), None, cx) .language_for_file(&file("zed/a.cars"), None, cx)
.now_or_never() .map(|l| l.name()),
.and_then(|l| Some(l.ok()?.name())),
None None
); );
assert_eq!( assert_eq!(
registry registry
.language_for_file(&file("zed/sumk"), None, cx) .language_for_file(&file("zed/sumk"), None, cx)
.now_or_never() .map(|l| l.name()),
.and_then(|l| Some(l.ok()?.name())),
None None
); );
} }
@ -158,23 +152,22 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
..Default::default() ..Default::default()
}); });
cx.read(|cx| languages.language_for_file(&file("the/script"), None, cx)) assert!(cx
.await .read(|cx| languages.language_for_file(&file("the/script"), None, cx))
.unwrap_err(); .is_none());
cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx)) assert!(cx
.await .read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
.unwrap_err(); .is_none());
assert_eq!( assert_eq!(
cx.read(|cx| languages.language_for_file( cx.read(|cx| languages.language_for_file(
&file("the/script"), &file("the/script"),
Some(&"#!/bin/env node".into()), Some(&"#!/bin/env node".into()),
cx cx
)) ))
.await
.unwrap() .unwrap()
.name() .name(),
.as_ref(), "JavaScript".into()
"JavaScript"
); );
} }
@ -242,19 +235,16 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext)
let language = cx let language = cx
.read(|cx| languages.language_for_file(&file("foo.js"), None, cx)) .read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
.await
.unwrap(); .unwrap();
assert_eq!(language.name().as_ref(), "TypeScript"); assert_eq!(language.name(), "TypeScript".into());
let language = cx let language = cx
.read(|cx| languages.language_for_file(&file("foo.c"), None, cx)) .read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
.await
.unwrap(); .unwrap();
assert_eq!(language.name().as_ref(), "C++"); assert_eq!(language.name(), "C++".into());
let language = cx let language = cx
.read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx)) .read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
.await
.unwrap(); .unwrap();
assert_eq!(language.name().as_ref(), "Dockerfile"); assert_eq!(language.name(), "Dockerfile".into());
} }
fn file(path: &str) -> Arc<dyn File> { 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)] { for point in [Point::new(0, 4), Point::new(0, 16)] {
let config = snapshot.language_scope_at(point).unwrap(); 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(); let language = snapshot.language_at(point).unwrap();
assert_eq!(language.name().as_ref(), "Markdown"); assert_eq!(language.name().0.as_ref(), "Markdown");
} }
buffer buffer
@ -2757,7 +2747,7 @@ fn ruby_lang() -> Language {
fn html_lang() -> Language { fn html_lang() -> Language {
Language::new( Language::new(
LanguageConfig { LanguageConfig {
name: "HTML".into(), name: LanguageName::new("HTML"),
block_comment: Some(("<!--".into(), "-->".into())), block_comment: Some(("<!--".into(), "-->".into())),
..Default::default() ..Default::default()
}, },

View file

@ -28,6 +28,7 @@ use futures::Future;
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task}; use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
pub use highlight_map::HighlightMap; pub use highlight_map::HighlightMap;
use http_client::HttpClient; use http_client::HttpClient;
pub use language_registry::LanguageName;
use lsp::{CodeActionKind, LanguageServerBinary}; use lsp::{CodeActionKind, LanguageServerBinary};
use parking_lot::Mutex; use parking_lot::Mutex;
use regex::Regex; use regex::Regex;
@ -67,8 +68,8 @@ pub use buffer::Operation;
pub use buffer::*; pub use buffer::*;
pub use diagnostic_set::DiagnosticEntry; pub use diagnostic_set::DiagnosticEntry;
pub use language_registry::{ pub use language_registry::{
LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry,
PendingLanguageServer, QUERY_FILENAME_PREFIXES, LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES,
}; };
pub use lsp::LanguageServerId; pub use lsp::LanguageServerId;
pub use outline::*; pub use outline::*;
@ -140,6 +141,12 @@ pub trait ToLspPosition {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct LanguageServerName(pub Arc<str>); 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)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Location { pub struct Location {
pub buffer: Model<Buffer>, 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( pub async fn get_language_server_command(
self: Arc<Self>, self: Arc<Self>,
language: Arc<Language>,
container_dir: Arc<Path>, container_dir: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
@ -205,18 +215,10 @@ impl CachedLspAdapter {
let cached_binary = self.cached_binary.lock().await; let cached_binary = self.cached_binary.lock().await;
self.adapter self.adapter
.clone() .clone()
.get_language_server_command(language, container_dir, delegate, cached_binary, cx) .get_language_server_command(container_dir, delegate, cached_binary, cx)
.await .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 { pub fn can_be_reinstalled(&self) -> bool {
self.adapter.can_be_reinstalled() self.adapter.can_be_reinstalled()
} }
@ -262,11 +264,11 @@ impl CachedLspAdapter {
.await .await
} }
pub fn language_id(&self, language: &Language) -> String { pub fn language_id(&self, language_name: &LanguageName) -> String {
self.language_ids self.language_ids
.get(language.name().as_ref()) .get(language_name.0.as_ref())
.cloned() .cloned()
.unwrap_or_else(|| language.lsp_id()) .unwrap_or_else(|| language_name.lsp_id())
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -296,7 +298,6 @@ pub trait LspAdapter: 'static + Send + Sync {
fn get_language_server_command<'a>( fn get_language_server_command<'a>(
self: Arc<Self>, self: Arc<Self>,
language: Arc<Language>,
container_dir: Arc<Path>, container_dir: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn LspAdapterDelegate>,
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>, 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 { if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await {
log::info!( log::info!(
"found user-installed language server for {}. path: {:?}, arguments: {:?}", "found user-installed language server for {}. path: {:?}, arguments: {:?}",
language.name(), self.name().0,
binary.path, binary.path,
binary.arguments binary.arguments
); );
@ -387,14 +388,6 @@ pub trait LspAdapter: 'static + Send + Sync {
None None
} }
fn will_start_server(
&self,
_: &Arc<dyn LspAdapterDelegate>,
_: &mut AsyncAppContext,
) -> Option<Task<Result<()>>> {
None
}
async fn fetch_server_binary( async fn fetch_server_binary(
&self, &self,
latest_version: Box<dyn 'static + Send + Any>, latest_version: Box<dyn 'static + Send + Any>,
@ -562,7 +555,7 @@ pub struct CodeLabel {
#[derive(Clone, Deserialize, JsonSchema)] #[derive(Clone, Deserialize, JsonSchema)]
pub struct LanguageConfig { pub struct LanguageConfig {
/// Human-readable name of the language. /// Human-readable name of the language.
pub name: Arc<str>, pub name: LanguageName,
/// The name of this language for a Markdown code fence block /// The name of this language for a Markdown code fence block
pub code_fence_block_name: Option<Arc<str>>, pub code_fence_block_name: Option<Arc<str>>,
// The name of the grammar in a WASM bundle (experimental). // The name of the grammar in a WASM bundle (experimental).
@ -699,7 +692,7 @@ impl<T> Override<T> {
impl Default for LanguageConfig { impl Default for LanguageConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
name: Arc::default(), name: LanguageName::new(""),
code_fence_block_name: None, code_fence_block_name: None,
grammar: None, grammar: None,
matcher: LanguageMatcher::default(), matcher: LanguageMatcher::default(),
@ -1335,7 +1328,7 @@ impl Language {
Arc::get_mut(self.grammar.as_mut()?) Arc::get_mut(self.grammar.as_mut()?)
} }
pub fn name(&self) -> Arc<str> { pub fn name(&self) -> LanguageName {
self.config.name.clone() self.config.name.clone()
} }
@ -1343,7 +1336,7 @@ impl Language {
self.config self.config
.code_fence_block_name .code_fence_block_name
.clone() .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>> { pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {
@ -1408,10 +1401,7 @@ impl Language {
} }
pub fn lsp_id(&self) -> String { pub fn lsp_id(&self) -> String {
match self.config.name.as_ref() { self.config.name.lsp_id()
"Plain Text" => "plaintext".to_string(),
language_name => language_name.to_lowercase(),
}
} }
pub fn prettier_parser_name(&self) -> Option<&str> { pub fn prettier_parser_name(&self) -> Option<&str> {
@ -1420,7 +1410,7 @@ impl Language {
} }
impl LanguageScope { impl LanguageScope {
pub fn language_name(&self) -> Arc<str> { pub fn language_name(&self) -> LanguageName {
self.language.config.name.clone() self.language.config.name.clone()
} }
@ -1663,7 +1653,6 @@ impl LspAdapter for FakeLspAdapter {
fn get_language_server_command<'a>( fn get_language_server_command<'a>(
self: Arc<Self>, self: Arc<Self>,
_: Arc<Language>,
_: Arc<Path>, _: Arc<Path>,
_: Arc<dyn LspAdapterDelegate>, _: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>, _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,

View file

@ -6,9 +6,9 @@ use crate::{
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher, with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT, LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT,
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context, Result};
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use futures::TryFutureExt;
use futures::{ use futures::{
channel::{mpsc, oneshot}, channel::{mpsc, oneshot},
future::Shared, future::Shared,
@ -19,8 +19,10 @@ use gpui::{AppContext, BackgroundExecutor, Task};
use lsp::LanguageServerId; use lsp::LanguageServerId;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use postage::watch; use postage::watch;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{ use std::{
borrow::Cow, borrow::{Borrow, Cow},
ffi::OsStr, ffi::OsStr,
ops::Not, ops::Not,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -32,6 +34,48 @@ use theme::Theme;
use unicase::UniCase; use unicase::UniCase;
use util::{maybe, paths::PathExt, post_inc, ResultExt}; 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 { pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>, state: RwLock<LanguageRegistryState>,
language_server_download_dir: Option<Arc<Path>>, language_server_download_dir: Option<Arc<Path>>,
@ -46,7 +90,7 @@ struct LanguageRegistryState {
language_settings: AllLanguageSettingsContent, language_settings: AllLanguageSettingsContent,
available_languages: Vec<AvailableLanguage>, available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>, grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>, lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
available_lsp_adapters: available_lsp_adapters:
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>, HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>, loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
@ -56,8 +100,10 @@ struct LanguageRegistryState {
reload_count: usize, reload_count: usize,
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
fake_server_txs: fake_server_txs: HashMap<
HashMap<Arc<str>, Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>>, LanguageName,
Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>,
>,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -75,9 +121,9 @@ pub struct PendingLanguageServer {
} }
#[derive(Clone)] #[derive(Clone)]
struct AvailableLanguage { pub struct AvailableLanguage {
id: LanguageId, id: LanguageId,
name: Arc<str>, name: LanguageName,
grammar: Option<Arc<str>>, grammar: Option<Arc<str>>,
matcher: LanguageMatcher, matcher: LanguageMatcher,
load: Arc< load: Arc<
@ -93,6 +139,16 @@ struct AvailableLanguage {
loaded: bool, loaded: bool,
} }
impl AvailableLanguage {
pub fn name(&self) -> LanguageName {
self.name.clone()
}
pub fn matcher(&self) -> &LanguageMatcher {
&self.matcher
}
}
enum AvailableGrammar { enum AvailableGrammar {
Native(tree_sitter::Language), Native(tree_sitter::Language),
Loaded(#[allow(unused)] PathBuf, tree_sitter::Language), Loaded(#[allow(unused)] PathBuf, tree_sitter::Language),
@ -196,7 +252,7 @@ impl LanguageRegistry {
/// appended to the end. /// appended to the end.
pub fn reorder_language_servers( pub fn reorder_language_servers(
&self, &self,
language: &Arc<Language>, language: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>, ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) { ) {
self.state self.state
@ -207,7 +263,7 @@ impl LanguageRegistry {
/// Removes the specified languages and grammars from the registry. /// Removes the specified languages and grammars from the registry.
pub fn remove_languages( pub fn remove_languages(
&self, &self,
languages_to_remove: &[Arc<str>], languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>], grammars_to_remove: &[Arc<str>],
) { ) {
self.state self.state
@ -215,7 +271,7 @@ impl LanguageRegistry {
.remove_languages(languages_to_remove, grammars_to_remove) .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(); let mut state = self.state.write();
if let Some(adapters) = state.lsp_adapters.get_mut(language_name) { if let Some(adapters) = state.lsp_adapters.get_mut(language_name) {
adapters.retain(|adapter| &adapter.name != name) adapters.retain(|adapter| &adapter.name != name)
@ -267,7 +323,7 @@ impl LanguageRegistry {
Some(load_lsp_adapter()) 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 self.state
.write() .write()
.lsp_adapters .lsp_adapters
@ -279,13 +335,14 @@ impl LanguageRegistry {
#[cfg(any(feature = "test-support", test))] #[cfg(any(feature = "test-support", test))]
pub fn register_fake_lsp_adapter( pub fn register_fake_lsp_adapter(
&self, &self,
language_name: &str, language_name: impl Into<LanguageName>,
adapter: crate::FakeLspAdapter, adapter: crate::FakeLspAdapter,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> { ) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
let language_name = language_name.into();
self.state self.state
.write() .write()
.lsp_adapters .lsp_adapters
.entry(language_name.into()) .entry(language_name.clone())
.or_default() .or_default()
.push(CachedLspAdapter::new(Arc::new(adapter))); .push(CachedLspAdapter::new(Arc::new(adapter)));
self.fake_language_servers(language_name) self.fake_language_servers(language_name)
@ -294,13 +351,13 @@ impl LanguageRegistry {
#[cfg(any(feature = "test-support", test))] #[cfg(any(feature = "test-support", test))]
pub fn fake_language_servers( pub fn fake_language_servers(
&self, &self,
language_name: &str, language_name: LanguageName,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> { ) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded(); let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
self.state self.state
.write() .write()
.fake_server_txs .fake_server_txs
.entry(language_name.into()) .entry(language_name)
.or_default() .or_default()
.push(servers_tx); .push(servers_tx);
servers_rx servers_rx
@ -309,7 +366,7 @@ impl LanguageRegistry {
/// Adds a language to the registry, which can be loaded if needed. /// Adds a language to the registry, which can be loaded if needed.
pub fn register_language( pub fn register_language(
&self, &self,
name: Arc<str>, name: LanguageName,
grammar_name: Option<Arc<str>>, grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher, matcher: LanguageMatcher,
load: impl Fn() -> Result<( load: impl Fn() -> Result<(
@ -445,7 +502,7 @@ impl LanguageRegistry {
) -> impl Future<Output = Result<Arc<Language>>> { ) -> impl Future<Output = Result<Arc<Language>>> {
let name = UniCase::new(name); let name = UniCase::new(name);
let rx = self.get_or_load_language(|language_name, _| { let rx = self.get_or_load_language(|language_name, _| {
if UniCase::new(language_name) == name { if UniCase::new(&language_name.0) == name {
1 1
} else { } else {
0 0
@ -460,7 +517,7 @@ impl LanguageRegistry {
) -> impl Future<Output = Result<Arc<Language>>> { ) -> impl Future<Output = Result<Arc<Language>>> {
let string = UniCase::new(string); let string = UniCase::new(string);
let rx = self.get_or_load_language(|name, config| { let rx = self.get_or_load_language(|name, config| {
if UniCase::new(name) == string if UniCase::new(&name.0) == string
|| config || config
.path_suffixes .path_suffixes
.iter() .iter()
@ -474,13 +531,26 @@ impl LanguageRegistry {
async move { rx.await? } 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( pub fn language_for_file(
self: &Arc<Self>, self: &Arc<Self>,
file: &Arc<dyn File>, file: &Arc<dyn File>,
content: Option<&Rope>, content: Option<&Rope>,
cx: &AppContext, cx: &AppContext,
) -> impl Future<Output = Result<Arc<Language>>> { ) -> Option<AvailableLanguage> {
let user_file_types = all_language_settings(Some(file), cx); let user_file_types = all_language_settings(Some(file), cx);
self.language_for_file_internal( self.language_for_file_internal(
&file.full_path(cx), &file.full_path(cx),
content, content,
@ -492,8 +562,16 @@ impl LanguageRegistry {
self: &Arc<Self>, self: &Arc<Self>,
path: &'a Path, path: &'a Path,
) -> impl Future<Output = Result<Arc<Language>>> + 'a { ) -> impl Future<Output = Result<Arc<Language>>> + 'a {
self.language_for_file_internal(path, None, None) let available_language = self.language_for_file_internal(path, None, None);
.map_err(|error| error.context(format!("language for file path {}", path.display())))
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( fn language_for_file_internal(
@ -501,19 +579,19 @@ impl LanguageRegistry {
path: &Path, path: &Path,
content: Option<&Rope>, content: Option<&Rope>,
user_file_types: Option<&HashMap<Arc<str>, GlobSet>>, 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 filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name(); let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename, path.to_str()]; let path_suffixes = [extension, filename, path.to_str()];
let empty = GlobSet::empty(); 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 let path_matches_default_suffix = config
.path_suffixes .path_suffixes
.iter() .iter()
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))); .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
let custom_suffixes = user_file_types let custom_suffixes = user_file_types
.and_then(|types| types.get(language_name)) .and_then(|types| types.get(&language_name.0))
.unwrap_or(&empty); .unwrap_or(&empty);
let path_matches_custom_suffix = path_suffixes let path_matches_custom_suffix = path_suffixes
.iter() .iter()
@ -535,18 +613,15 @@ impl LanguageRegistry {
} else { } else {
0 0
} }
}); })
async move { rx.await? }
} }
fn get_or_load_language( fn find_matching_language(
self: &Arc<Self>, self: &Arc<Self>,
callback: impl Fn(&str, &LanguageMatcher) -> usize, callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
) -> oneshot::Receiver<Result<Arc<Language>>> { ) -> Option<AvailableLanguage> {
let (tx, rx) = oneshot::channel(); let state = self.state.read();
let available_language = state
let mut state = self.state.write();
let Some((language, _)) = state
.available_languages .available_languages
.iter() .iter()
.filter_map(|language| { .filter_map(|language| {
@ -559,15 +634,23 @@ impl LanguageRegistry {
}) })
.max_by_key(|e| e.1) .max_by_key(|e| e.1)
.clone() .clone()
else { .map(|(available_language, _)| available_language);
let _ = tx.send(Err(anyhow!(LanguageNotFound))); drop(state);
return rx; 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. // If the language is already loaded, resolve with it immediately.
for loaded_language in state.languages.iter() { for loaded_language in state.languages.iter() {
if loaded_language.id == language.id { if loaded_language.id == language.id {
let _ = tx.send(Ok(loaded_language.clone())); tx.send(Ok(loaded_language.clone())).unwrap();
return rx; return rx;
} }
} }
@ -580,12 +663,15 @@ impl LanguageRegistry {
// Otherwise, start loading the language. // Otherwise, start loading the language.
hash_map::Entry::Vacant(entry) => { hash_map::Entry::Vacant(entry) => {
let this = self.clone(); let this = self.clone();
let id = language.id;
let name = language.name.clone();
let language_load = language.load.clone();
self.executor self.executor
.spawn(async move { .spawn(async move {
let id = language.id;
let name = language.name.clone();
let language = async { let language = async {
let (config, queries, provider) = (language.load)()?; let (config, queries, provider) = (language_load)()?;
if let Some(grammar) = config.grammar.clone() { if let Some(grammar) = config.grammar.clone() {
let grammar = Some(this.get_or_load_grammar(grammar).await?); let grammar = Some(this.get_or_load_grammar(grammar).await?);
@ -629,13 +715,28 @@ impl LanguageRegistry {
}; };
}) })
.detach(); .detach();
entry.insert(vec![tx]); entry.insert(vec![tx]);
} }
} }
drop(state);
rx 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( fn get_or_load_grammar(
self: &Arc<Self>, self: &Arc<Self>,
name: Arc<str>, name: Arc<str>,
@ -702,11 +803,11 @@ impl LanguageRegistry {
self.state.read().languages.to_vec() 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 self.state
.read() .read()
.lsp_adapters .lsp_adapters
.get(&language.config.name) .get(language_name)
.cloned() .cloned()
.unwrap_or_default() .unwrap_or_default()
} }
@ -723,7 +824,7 @@ impl LanguageRegistry {
pub fn create_pending_language_server( pub fn create_pending_language_server(
self: &Arc<Self>, self: &Arc<Self>,
stderr_capture: Arc<Mutex<Option<String>>>, stderr_capture: Arc<Mutex<Option<String>>>,
language: Arc<Language>, _language_name_for_tests: LanguageName,
adapter: Arc<CachedLspAdapter>, adapter: Arc<CachedLspAdapter>,
root_path: Arc<Path>, root_path: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn LspAdapterDelegate>,
@ -741,7 +842,6 @@ impl LanguageRegistry {
.clone() .clone()
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server")) .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
.log_err()?; .log_err()?;
let language = language.clone();
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref())); let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
let root_path = root_path.clone(); let root_path = root_path.clone();
let login_shell_env_loaded = self.login_shell_env_loaded.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone();
@ -756,12 +856,7 @@ impl LanguageRegistry {
let binary_result = adapter let binary_result = adapter
.clone() .clone()
.get_language_server_command( .get_language_server_command(container_dir, delegate.clone(), &mut cx)
language.clone(),
container_dir,
delegate.clone(),
&mut cx,
)
.await; .await;
delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None); delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None);
@ -785,10 +880,6 @@ impl LanguageRegistry {
.initialization_options(&delegate) .initialization_options(&delegate)
.await?; .await?;
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
task.await?;
}
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
if true { if true {
let capabilities = adapter let capabilities = adapter
@ -825,7 +916,7 @@ impl LanguageRegistry {
.state .state
.write() .write()
.fake_server_txs .fake_server_txs
.get_mut(language.name().as_ref()) .get_mut(&_language_name_for_tests)
{ {
for tx in txs { for tx in txs {
tx.unbounded_send(fake_server.clone()).ok(); tx.unbounded_send(fake_server.clone()).ok();
@ -935,10 +1026,10 @@ impl LanguageRegistryState {
/// appended to the end. /// appended to the end.
fn reorder_language_servers( fn reorder_language_servers(
&mut self, &mut self,
language: &Arc<Language>, language_name: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>, 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; return;
}; };
@ -959,7 +1050,7 @@ impl LanguageRegistryState {
fn remove_languages( fn remove_languages(
&mut self, &mut self,
languages_to_remove: &[Arc<str>], languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>], grammars_to_remove: &[Arc<str>],
) { ) {
if languages_to_remove.is_empty() && grammars_to_remove.is_empty() { if languages_to_remove.is_empty() && grammars_to_remove.is_empty() {

View file

@ -1,6 +1,6 @@
//! Provides `language`-related settings. //! Provides `language`-related settings.
use crate::{File, Language, LanguageServerName}; use crate::{File, Language, LanguageName, LanguageServerName};
use anyhow::Result; use anyhow::Result;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use core::slice; use core::slice;
@ -32,7 +32,7 @@ pub fn language_settings<'a>(
cx: &'a AppContext, cx: &'a AppContext,
) -> &'a LanguageSettings { ) -> &'a LanguageSettings {
let language_name = language.map(|l| l.name()); 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. /// Returns the settings for all languages from the provided file.
@ -53,7 +53,7 @@ pub struct AllLanguageSettings {
/// The inline completion settings. /// The inline completion settings.
pub inline_completions: InlineCompletionSettings, pub inline_completions: InlineCompletionSettings,
defaults: LanguageSettings, defaults: LanguageSettings,
languages: HashMap<Arc<str>, LanguageSettings>, languages: HashMap<LanguageName, LanguageSettings>,
pub(crate) file_types: HashMap<Arc<str>, GlobSet>, pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
} }
@ -204,7 +204,7 @@ pub struct AllLanguageSettingsContent {
pub defaults: LanguageSettingsContent, pub defaults: LanguageSettingsContent,
/// The settings for individual languages. /// The settings for individual languages.
#[serde(default)] #[serde(default)]
pub languages: HashMap<Arc<str>, LanguageSettingsContent>, pub languages: HashMap<LanguageName, LanguageSettingsContent>,
/// Settings for associating file extensions and filenames /// Settings for associating file extensions and filenames
/// with languages. /// with languages.
#[serde(default)] #[serde(default)]
@ -791,7 +791,7 @@ impl InlayHintSettings {
impl AllLanguageSettings { impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name. /// 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(name) = language_name {
if let Some(overrides) = self.languages.get(name) { if let Some(overrides) = self.languages.get(name) {
return overrides; 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 .show_inline_completions
} }
} }

View file

@ -1,13 +1,13 @@
use editor::Editor; use editor::Editor;
use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView}; 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 ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, Workspace}; use workspace::{item::ItemHandle, StatusItemView, Workspace};
use crate::LanguageSelector; use crate::LanguageSelector;
pub struct ActiveBufferLanguage { pub struct ActiveBufferLanguage {
active_language: Option<Option<Arc<str>>>, active_language: Option<Option<LanguageName>>,
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
_observe_active_editor: Option<Subscription>, _observe_active_editor: Option<Subscription>,
} }

View file

@ -217,7 +217,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
let mat = &self.matches[ix]; let mat = &self.matches[ix];
let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name()); let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
let mut label = mat.string.clone(); 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)"); label.push_str(" (current)");
} }

View file

@ -683,7 +683,7 @@ impl LspLogView {
self.project self.project
.read(cx) .read(cx)
.supplementary_language_servers(cx) .supplementary_language_servers(cx)
.filter_map(|(&server_id, name)| { .filter_map(|(server_id, name)| {
let state = log_store.language_servers.get(&server_id)?; let state = log_store.language_servers.get(&server_id)?;
Some(LogMenuItem { Some(LogMenuItem {
server_id, server_id,

View file

@ -471,7 +471,7 @@ impl SyntaxTreeToolbarItemView {
fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike { fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike {
ButtonLike::new("syntax tree header") 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()))) .child(Label::new(format_node_range(active_layer.node())))
} }
} }

View file

@ -451,7 +451,7 @@ impl ContextProvider for RustContextProvider {
) -> Option<TaskTemplates> { ) -> Option<TaskTemplates> {
const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN"; const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN";
let package_to_run = all_language_settings(file.as_ref(), cx) let package_to_run = all_language_settings(file.as_ref(), cx)
.language(Some("Rust")) .language(Some(&"Rust".into()))
.tasks .tasks
.variables .variables
.get(DEFAULT_RUN_NAME_STR); .get(DEFAULT_RUN_NAME_STR);

View file

@ -141,7 +141,7 @@ impl LspAdapter for YamlLspAdapter {
let tab_size = cx.update(|cx| { let tab_size = cx.update(|cx| {
AllLanguageSettings::get(Some(location), cx) AllLanguageSettings::get(Some(location), cx)
.language(Some("YAML")) .language(Some(&"YAML".into()))
.tab_size .tab_size
})?; })?;
let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}}); let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}});

View file

@ -89,6 +89,16 @@ pub struct LanguageServer {
#[repr(transparent)] #[repr(transparent)]
pub struct LanguageServerId(pub usize); 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. /// Handle to a language server RPC activity subscription.
pub enum Subscription { pub enum Subscription {
Notification { Notification {

View file

@ -282,7 +282,7 @@ impl MarkdownPreviewView {
let buffer = editor.read(cx).buffer().read(cx); let buffer = editor.read(cx).buffer().read(cx);
if let Some(buffer) = buffer.as_singleton() { if let Some(buffer) = buffer.as_singleton() {
if let Some(language) = buffer.read(cx).language() { if let Some(language) = buffer.read(cx).language() {
return language.name().as_ref() == "Markdown"; return language.name() == "Markdown".into();
} }
} }
false false

View file

@ -86,7 +86,7 @@ impl SignatureHelp {
} else { } else {
let markdown = markdown.join(str_for_join); let markdown = markdown.join(str_for_join);
let language_name = language let language_name = language
.map(|n| n.name().to_lowercase()) .map(|n| n.name().0.to_lowercase())
.unwrap_or_default(); .unwrap_or_default();
let markdown = if function_options_count >= 2 { let markdown = if function_options_count >= 2 {

File diff suppressed because it is too large Load diff

View file

@ -107,7 +107,7 @@ pub use buffer_store::ProjectTransaction;
pub use lsp_store::{ pub use lsp_store::{
DiagnosticSummary, LanguageServerLogType, LanguageServerProgress, LanguageServerPromptRequest, DiagnosticSummary, LanguageServerLogType, LanguageServerProgress, LanguageServerPromptRequest,
LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent, LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent,
ProjectLspAdapterDelegate, SERVER_PROGRESS_THROTTLE_TIMEOUT, SERVER_PROGRESS_THROTTLE_TIMEOUT,
}; };
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500; const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
@ -643,16 +643,13 @@ impl Project {
let environment = ProjectEnvironment::new(&worktree_store, env, cx); let environment = ProjectEnvironment::new(&worktree_store, env, cx);
let lsp_store = cx.new_model(|cx| { let lsp_store = cx.new_model(|cx| {
LspStore::new( LspStore::new_local(
buffer_store.clone(), buffer_store.clone(),
worktree_store.clone(), worktree_store.clone(),
Some(environment.clone()), environment.clone(),
languages.clone(), languages.clone(),
Some(client.http_client()), Some(client.http_client()),
fs.clone(), fs.clone(),
None,
None,
None,
cx, cx,
) )
}); });
@ -712,17 +709,90 @@ impl Project {
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
cx: &mut AppContext, cx: &mut AppContext,
) -> Model<Self> { ) -> Model<Self> {
let this = Self::local(client, node, user_store, languages, fs, None, cx); cx.new_model(|cx: &mut ModelContext<Self>| {
this.update(cx, |this, cx| { let (tx, rx) = mpsc::unbounded();
let client: AnyProtoClient = ssh.clone().into(); 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);
this.worktree_store.update(cx, |store, _cx| { let worktree_store = cx.new_model(|_| {
store.set_upstream_client(client.clone()); let mut worktree_store = WorktreeStore::new(false, fs.clone());
worktree_store.set_upstream_client(ssh.clone().into());
worktree_store
}); });
this.settings_observer = cx.new_model(|cx| { cx.subscribe(&worktree_store, Self::on_worktree_store_event)
SettingsObserver::new_ssh(ssh.clone().into(), this.worktree_store.clone(), cx) .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();
let settings_observer = cx.new_model(|cx| {
SettingsObserver::new_ssh(ssh.clone().into(), 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, &cx.handle());
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store); ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store); ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store);
@ -735,9 +805,8 @@ impl Project {
LspStore::init(&client); LspStore::init(&client);
SettingsObserver::init(&client); SettingsObserver::init(&client);
this.ssh_session = Some(ssh); this
}); })
this
} }
pub async fn remote( pub async fn remote(
@ -820,16 +889,12 @@ impl Project {
cx.new_model(|cx| BufferStore::new(worktree_store.clone(), Some(remote_id), cx))?; cx.new_model(|cx| BufferStore::new(worktree_store.clone(), Some(remote_id), cx))?;
let lsp_store = cx.new_model(|cx| { let lsp_store = cx.new_model(|cx| {
let mut lsp_store = LspStore::new( let mut lsp_store = LspStore::new_remote(
buffer_store.clone(), buffer_store.clone(),
worktree_store.clone(), worktree_store.clone(),
None,
languages.clone(), languages.clone(),
Some(client.http_client()), client.clone().into(),
fs.clone(), remote_id,
None,
Some(client.clone().into()),
Some(remote_id),
cx, cx,
); );
lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers); 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 let Some(language) = buffer_language {
if settings.enable_language_server { if settings.enable_language_server {
if let Some(file) = buffer_file { if let Some(file) = buffer_file {
language_servers_to_start language_servers_to_start.push((file.worktree.clone(), language.name()));
.push((file.worktree.clone(), Arc::clone(language)));
} }
} }
language_formatters_to_check language_formatters_to_check
@ -1144,7 +1208,7 @@ impl Project {
let language = languages.iter().find_map(|l| { let language = languages.iter().find_map(|l| {
let adapter = self let adapter = self
.languages .languages
.lsp_adapters(l) .lsp_adapters(&l.name())
.iter() .iter()
.find(|adapter| adapter.name == started_lsp_name)? .find(|adapter| adapter.name == started_lsp_name)?
.clone(); .clone();
@ -1165,11 +1229,11 @@ impl Project {
) { ) {
(None, None) => {} (None, None) => {}
(Some(_), None) | (None, Some(_)) => { (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)) => { (Some(current_lsp_settings), Some(new_lsp_settings)) => {
if current_lsp_settings != 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>( pub fn supplementary_language_servers<'a>(
&'a self, &'a self,
cx: &'a AppContext, cx: &'a AppContext,
) -> impl '_ + Iterator<Item = (&'a LanguageServerId, &'a LanguageServerName)> { ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName)> {
self.lsp_store.read(cx).supplementary_language_servers() self.lsp_store.read(cx).supplementary_language_servers()
} }

View file

@ -19,7 +19,7 @@ use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId};
use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent}; use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ProjectSettings { pub struct ProjectSettings {
/// Configuration for language servers. /// Configuration for language servers.
/// ///

View file

@ -6,7 +6,7 @@ use http_client::Url;
use language::{ use language::{
language_settings::{AllLanguageSettings, LanguageSettingsContent}, language_settings::{AllLanguageSettings, LanguageSettingsContent},
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter, 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 lsp::{DiagnosticSeverity, NumberOrString};
use parking_lot::Mutex; 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| { SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| { settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert( settings.languages.insert(
Arc::from("Rust"), "Rust".into(),
LanguageSettingsContent { LanguageSettingsContent {
enable_language_server: Some(false), enable_language_server: Some(false),
..Default::default() ..Default::default()
@ -1578,14 +1578,14 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
SettingsStore::update_global(cx, |settings, cx| { SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| { settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert( settings.languages.insert(
Arc::from("Rust"), LanguageName::new("Rust"),
LanguageSettingsContent { LanguageSettingsContent {
enable_language_server: Some(true), enable_language_server: Some(true),
..Default::default() ..Default::default()
}, },
); );
settings.languages.insert( settings.languages.insert(
Arc::from("JavaScript"), LanguageName::new("JavaScript"),
LanguageSettingsContent { LanguageSettingsContent {
enable_language_server: Some(false), enable_language_server: Some(false),
..Default::default() ..Default::default()
@ -2983,7 +2983,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
buffer.edit([(0..0, "abc")], None, cx); buffer.edit([(0..0, "abc")], None, cx);
assert!(buffer.is_dirty()); assert!(buffer.is_dirty());
assert!(!buffer.has_conflict()); assert!(!buffer.has_conflict());
assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); assert_eq!(buffer.language().unwrap().name(), "Plain Text".into());
}); });
project project
.update(cx, |project, cx| { .update(cx, |project, cx| {
@ -3006,7 +3006,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
); );
assert!(!buffer.is_dirty()); assert!(!buffer.is_dirty());
assert!(!buffer.has_conflict()); 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 let opened_buffer = project
@ -5308,7 +5308,7 @@ fn json_lang() -> Arc<Language> {
fn js_lang() -> Arc<Language> { fn js_lang() -> Arc<Language> {
Arc::new(Language::new( Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: Arc::from("JavaScript"), name: "JavaScript".into(),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["js".to_string()], path_suffixes: vec!["js".to_string()],
..Default::default() ..Default::default()

View file

@ -161,7 +161,7 @@ impl Inventory {
cx: &AppContext, cx: &AppContext,
) -> Vec<(TaskSourceKind, TaskTemplate)> { ) -> Vec<(TaskSourceKind, TaskTemplate)> {
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name(), name: language.name().0,
}); });
let language_tasks = language let language_tasks = language
.and_then(|language| language.context_provider()?.associated_tasks(file, cx)) .and_then(|language| language.context_provider()?.associated_tasks(file, cx))
@ -207,7 +207,7 @@ impl Inventory {
.as_ref() .as_ref()
.and_then(|location| location.buffer.read(cx).language_at(location.range.start)); .and_then(|location| location.buffer.read(cx).language_at(location.range.start));
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name(), name: language.name().0,
}); });
let file = location let file = location
.as_ref() .as_ref()

View file

@ -281,7 +281,9 @@ message Envelope {
FindSearchCandidatesResponse find_search_candidates_response = 244; FindSearchCandidatesResponse find_search_candidates_response = 244;
CloseBuffer close_buffer = 245; 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; reserved 158 to 161;
@ -2497,3 +2499,36 @@ message UpdateUserSettings {
uint64 project_id = 1; uint64 project_id = 1;
string content = 2; 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 {
// }

View file

@ -366,7 +366,8 @@ messages!(
(FindSearchCandidates, Background), (FindSearchCandidates, Background),
(FindSearchCandidatesResponse, Background), (FindSearchCandidatesResponse, Background),
(CloseBuffer, Foreground), (CloseBuffer, Foreground),
(UpdateUserSettings, Foreground) (UpdateUserSettings, Foreground),
(CreateLanguageServer, Foreground)
); );
request_messages!( request_messages!(
@ -490,6 +491,7 @@ request_messages!(
(SynchronizeContexts, SynchronizeContextsResponse), (SynchronizeContexts, SynchronizeContextsResponse),
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse), (LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(AddWorktree, AddWorktreeResponse), (AddWorktree, AddWorktreeResponse),
(CreateLanguageServer, Ack)
); );
entity_messages!( entity_messages!(
@ -562,7 +564,8 @@ entity_messages!(
UpdateContext, UpdateContext,
SynchronizeContexts, SynchronizeContexts,
LspExtSwitchSourceHeader, LspExtSwitchSourceHeader,
UpdateUserSettings UpdateUserSettings,
CreateLanguageServer
); );
entity_messages!( entity_messages!(

View file

@ -62,7 +62,7 @@ impl QuickActionBar {
return self.render_repl_launch_menu(spec, cx); return self.render_repl_launch_menu(spec, cx);
} }
SessionSupport::RequiresSetup(language) => { SessionSupport::RequiresSetup(language) => {
return self.render_repl_setup(&language, cx); return self.render_repl_setup(&language.0, cx);
} }
SessionSupport::Unsupported => return None, SessionSupport::Unsupported => return None,
}; };

View file

@ -291,11 +291,24 @@ impl SshClientDelegate {
self.update_status(Some("building remote server binary from source"), cx); self.update_status(Some("building remote server binary from source"), cx);
log::info!("building remote server binary from source"); log::info!("building remote server binary from source");
run_cmd(Command::new("cargo").args(["build", "--package", "remote_server"])).await?; run_cmd(Command::new("cargo").args([
run_cmd(Command::new("strip").args(["target/debug/remote_server"])).await?; "build",
run_cmd(Command::new("gzip").args(["-9", "-f", "target/debug/remote_server"])).await?; "--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)); return Ok((path, version));
async fn run_cmd(command: &mut Command) -> Result<()> { async fn run_cmd(command: &mut Command) -> Result<()> {

View file

@ -41,11 +41,11 @@ pub struct SshSocket {
pub struct SshSession { pub struct SshSession {
next_message_id: AtomicU32, next_message_id: AtomicU32,
response_channels: ResponseChannels, response_channels: ResponseChannels, // Lock
outgoing_tx: mpsc::UnboundedSender<Envelope>, outgoing_tx: mpsc::UnboundedSender<Envelope>,
spawn_process_tx: mpsc::UnboundedSender<SpawnRequest>, spawn_process_tx: mpsc::UnboundedSender<SpawnRequest>,
client_socket: Option<SshSocket>, client_socket: Option<SshSocket>,
state: Mutex<ProtoMessageHandlerSet>, state: Mutex<ProtoMessageHandlerSet>, // Lock
} }
struct SshClientState { struct SshClientState {
@ -392,9 +392,9 @@ impl SshSession {
) -> impl 'static + Future<Output = Result<proto::Envelope>> { ) -> impl 'static + Future<Output = Result<proto::Envelope>> {
envelope.id = self.next_message_id.fetch_add(1, SeqCst); envelope.id = self.next_message_id.fetch_add(1, SeqCst);
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
self.response_channels let mut response_channels_lock = self.response_channels.lock();
.lock() response_channels_lock.insert(MessageId(envelope.id), tx);
.insert(MessageId(envelope.id), tx); drop(response_channels_lock);
self.outgoing_tx.unbounded_send(envelope).ok(); self.outgoing_tx.unbounded_send(envelope).ok();
async move { Ok(rx.await.context("connection lost")?.0) } async move { Ok(rx.await.context("connection lost")?.0) }
} }

View file

@ -4,14 +4,13 @@ use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task};
use language::LanguageRegistry; use language::LanguageRegistry;
use project::{ use project::{
buffer_store::BufferStore, project_settings::SettingsObserver, search::SearchQuery, 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 remote::SshSession;
use rpc::{ use rpc::{
proto::{self, AnyProtoClient, SSH_PEER_ID, SSH_PROJECT_ID}, proto::{self, AnyProtoClient, SSH_PEER_ID, SSH_PROJECT_ID},
TypedEnvelope, TypedEnvelope,
}; };
use settings::Settings as _;
use smol::stream::StreamExt; use smol::stream::StreamExt;
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -33,15 +32,17 @@ impl HeadlessProject {
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
settings::init(cx); settings::init(cx);
language::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 { 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? // 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( let mut languages =
Task::ready(()), LanguageRegistry::new(Task::ready(()), cx.background_executor().clone());
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 worktree_store = cx.new_model(|_| WorktreeStore::new(true, fs.clone()));
let buffer_store = cx.new_model(|cx| { let buffer_store = cx.new_model(|cx| {
@ -57,18 +58,17 @@ impl HeadlessProject {
}); });
let environment = project::ProjectEnvironment::new(&worktree_store, None, cx); let environment = project::ProjectEnvironment::new(&worktree_store, None, cx);
let lsp_store = cx.new_model(|cx| { let lsp_store = cx.new_model(|cx| {
LspStore::new( let mut lsp_store = LspStore::new_local(
buffer_store.clone(), buffer_store.clone(),
worktree_store.clone(), worktree_store.clone(),
Some(environment), environment,
languages, languages,
None, None,
fs.clone(), fs.clone(),
Some(session.clone().into()),
None,
Some(0),
cx, cx,
) );
lsp_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
lsp_store
}); });
let client: AnyProtoClient = session.clone().into(); let client: AnyProtoClient = session.clone().into();
@ -88,9 +88,12 @@ impl HeadlessProject {
client.add_model_request_handler(BufferStore::handle_update_buffer); client.add_model_request_handler(BufferStore::handle_update_buffer);
client.add_model_message_handler(BufferStore::handle_close_buffer); client.add_model_message_handler(BufferStore::handle_close_buffer);
client.add_model_request_handler(LspStore::handle_create_language_server);
BufferStore::init(&client); BufferStore::init(&client);
WorktreeStore::init(&client); WorktreeStore::init(&client);
SettingsObserver::init(&client); SettingsObserver::init(&client);
LspStore::init(&client);
HeadlessProject { HeadlessProject {
session: client, session: client,

View file

@ -6,7 +6,7 @@ use gpui::{Context, Model, TestAppContext};
use http_client::FakeHttpClient; use http_client::FakeHttpClient;
use language::{ use language::{
language_settings::{all_language_settings, AllLanguageSettings}, language_settings::{all_language_settings, AllLanguageSettings},
Buffer, LanguageRegistry, Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry,
}; };
use node_runtime::FakeNodeRuntime; use node_runtime::FakeNodeRuntime;
use project::{ use project::{
@ -202,15 +202,29 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
server_cx.read(|cx| { server_cx.read(|cx| {
assert_eq!( assert_eq!(
AllLanguageSettings::get_global(cx) AllLanguageSettings::get_global(cx)
.language(Some("Rust")) .language(Some(&"Rust".into()))
.language_servers, .language_servers,
["custom-rust-analyzer".into()] ["custom-rust-analyzer".into()]
) )
}); });
fs.insert_tree("/code/project1/.zed", json!({ fs.insert_tree(
"settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"# "/code/project1/.zed",
})).await; 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 let worktree_id = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
@ -247,7 +261,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
}), }),
cx cx
) )
.language(Some("Rust")) .language(Some(&"Rust".into()))
.language_servers, .language_servers,
["override-rust-analyzer".into()] ["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(); let file = buffer.read(cx).file();
assert_eq!( assert_eq!(
all_language_settings(file, cx) all_language_settings(file, cx)
.language(Some("Rust")) .language(Some(&"Rust".into()))
.language_servers, .language_servers,
["override-rust-analyzer".into()] ["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() { fn init_logger() {
if std::env::var("RUST_LOG").is_ok() { if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok(); env_logger::try_init().ok();

View file

@ -6,7 +6,7 @@ use std::sync::Arc;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use editor::Editor; use editor::Editor;
use gpui::{prelude::*, AppContext, Entity, View, WeakView, WindowContext}; 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::repl_store::ReplStore;
use crate::session::SessionEvent; use crate::session::SessionEvent;
@ -99,7 +99,7 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
pub enum SessionSupport { pub enum SessionSupport {
ActiveSession(View<Session>), ActiveSession(View<Session>),
Inactive(Box<KernelSpecification>), Inactive(Box<KernelSpecification>),
RequiresSetup(Arc<str>), RequiresSetup(LanguageName),
Unsupported, Unsupported,
} }
@ -268,7 +268,7 @@ fn runnable_ranges(
range: Range<Point>, range: Range<Point>,
) -> (Vec<Range<Point>>, Option<Point>) { ) -> (Vec<Range<Point>>, Option<Point>) {
if let Some(language) = buffer.language() { 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); 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 { fn language_supported(language: &Arc<Language>) -> bool {
match language.name().as_ref() { match language.name().0.as_ref() {
"TypeScript" | "Python" => true, "TypeScript" | "Python" => true,
_ => false, _ => false,
} }

View file

@ -564,6 +564,13 @@ impl Worktree {
!self.is_local() !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 { pub fn snapshot(&self) -> Snapshot {
match self { match self {
Worktree::Local(worktree) => worktree.snapshot.snapshot.clone(), Worktree::Local(worktree) => worktree.snapshot.snapshot.clone(),

View file

@ -2251,14 +2251,8 @@ mod tests {
assert!(!editor.is_dirty(cx)); assert!(!editor.is_dirty(cx));
assert_eq!(editor.title(cx), "the-new-name.rs"); assert_eq!(editor.title(cx), "the-new-name.rs");
assert_eq!( assert_eq!(
editor editor.buffer().read(cx).language_at(0, cx).unwrap().name(),
.buffer() "Rust".into()
.read(cx)
.language_at(0, cx)
.unwrap()
.name()
.as_ref(),
"Rust"
); );
}); });
}) })
@ -2374,14 +2368,8 @@ mod tests {
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
assert!(!editor.is_dirty(cx)); assert!(!editor.is_dirty(cx));
assert_eq!( assert_eq!(
editor editor.buffer().read(cx).language_at(0, cx).unwrap().name(),
.buffer() "Rust".into()
.read(cx)
.language_at(0, cx)
.unwrap()
.name()
.as_ref(),
"Rust"
) )
}); });
}) })