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:
parent
130f19d8f9
commit
36eb1c15ea
45 changed files with 1553 additions and 671 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue