Cleanup logic for registering languages and grammars (#7593)
This is a refactor, follow-up to the work we've been doing on loading WASM language extensions. Release Notes: - N/A --------- Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
1da5241ef7
commit
ed54665711
6 changed files with 873 additions and 901 deletions
|
@ -141,21 +141,27 @@ impl ExtensionStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
|
fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
|
||||||
for (grammar_name, grammar) in &manifest.grammars {
|
self.language_registry
|
||||||
|
.register_wasm_grammars(manifest.grammars.iter().map(|(grammar_name, grammar)| {
|
||||||
let mut grammar_path = self.extensions_dir.clone();
|
let mut grammar_path = self.extensions_dir.clone();
|
||||||
grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
|
grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
|
||||||
self.language_registry
|
(grammar_name.clone(), grammar_path)
|
||||||
.register_grammar(grammar_name.clone(), grammar_path);
|
}));
|
||||||
}
|
|
||||||
for (language_name, language) in &manifest.languages {
|
for (language_name, language) in &manifest.languages {
|
||||||
let mut language_path = self.extensions_dir.clone();
|
let mut language_path = self.extensions_dir.clone();
|
||||||
language_path.extend([language.extension.as_ref(), language.path.as_path()]);
|
language_path.extend([language.extension.as_ref(), language.path.as_path()]);
|
||||||
self.language_registry.register_extension(
|
self.language_registry.register_language(
|
||||||
language_path.into(),
|
|
||||||
language_name.clone(),
|
language_name.clone(),
|
||||||
language.grammar.clone(),
|
language.grammar.clone(),
|
||||||
language.matcher.clone(),
|
language.matcher.clone(),
|
||||||
load_plugin_queries,
|
vec![],
|
||||||
|
move || {
|
||||||
|
let config = std::fs::read(language_path.join("config.toml"))?;
|
||||||
|
let config: LanguageConfig = ::toml::from_slice(&config)?;
|
||||||
|
let queries = load_plugin_queries(&language_path);
|
||||||
|
Ok((config, queries))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::language_settings::{
|
||||||
use crate::Buffer;
|
use crate::Buffer;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
|
use futures::FutureExt as _;
|
||||||
use gpui::{AppContext, Model};
|
use gpui::{AppContext, Model};
|
||||||
use gpui::{Context, TestAppContext};
|
use gpui::{Context, TestAppContext};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod diagnostic_set;
|
mod diagnostic_set;
|
||||||
mod highlight_map;
|
mod highlight_map;
|
||||||
|
mod language_registry;
|
||||||
pub mod language_settings;
|
pub mod language_settings;
|
||||||
mod outline;
|
mod outline;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
|
@ -20,30 +21,22 @@ pub mod markdown;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use futures::{
|
use gpui::{AppContext, AsyncAppContext, Task};
|
||||||
channel::{mpsc, oneshot},
|
|
||||||
future::Shared,
|
|
||||||
FutureExt, TryFutureExt as _,
|
|
||||||
};
|
|
||||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
|
||||||
pub use highlight_map::HighlightMap;
|
pub use highlight_map::HighlightMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::Mutex;
|
||||||
use postage::watch;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
borrow::Cow,
|
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
ffi::OsStr,
|
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
mem,
|
mem,
|
||||||
ops::{Not, Range},
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str,
|
str,
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -52,15 +45,17 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use syntax_map::SyntaxSnapshot;
|
use syntax_map::SyntaxSnapshot;
|
||||||
use theme::{SyntaxTheme, Theme};
|
use theme::SyntaxTheme;
|
||||||
use tree_sitter::{self, wasmtime, Query, WasmStore};
|
use tree_sitter::{self, wasmtime, Query, WasmStore};
|
||||||
use unicase::UniCase;
|
use util::http::HttpClient;
|
||||||
use util::{http::HttpClient, paths::PathExt};
|
|
||||||
use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
|
|
||||||
|
|
||||||
pub use buffer::Operation;
|
pub use buffer::Operation;
|
||||||
pub use buffer::*;
|
pub use buffer::*;
|
||||||
pub use diagnostic_set::DiagnosticEntry;
|
pub use diagnostic_set::DiagnosticEntry;
|
||||||
|
pub use language_registry::{
|
||||||
|
LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, PendingLanguageServer,
|
||||||
|
QUERY_FILENAME_PREFIXES,
|
||||||
|
};
|
||||||
pub use lsp::LanguageServerId;
|
pub use lsp::LanguageServerId;
|
||||||
pub use outline::{Outline, OutlineItem};
|
pub use outline::{Outline, OutlineItem};
|
||||||
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
|
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
|
||||||
|
@ -74,27 +69,6 @@ pub fn init(cx: &mut AppContext) {
|
||||||
language_settings::init(cx);
|
language_settings::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
struct LspBinaryStatusSender {
|
|
||||||
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(Arc<Language>, LanguageServerBinaryStatus)>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LspBinaryStatusSender {
|
|
||||||
fn subscribe(&self) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
|
||||||
let (tx, rx) = mpsc::unbounded();
|
|
||||||
self.txs.lock().push(tx);
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send(&self, language: Arc<Language>, status: LanguageServerBinaryStatus) {
|
|
||||||
let mut txs = self.txs.lock();
|
|
||||||
txs.retain(|tx| {
|
|
||||||
tx.unbounded_send((language.clone(), status.clone()))
|
|
||||||
.is_ok()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static PARSER: RefCell<Parser> = {
|
static PARSER: RefCell<Parser> = {
|
||||||
let mut parser = Parser::new();
|
let mut parser = Parser::new();
|
||||||
|
@ -104,10 +78,11 @@ thread_local! {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub(crate) static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
|
static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
|
||||||
|
|
||||||
|
static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
|
||||||
|
|
||||||
/// A shared grammar for plain text, exposed for reuse by downstream crates.
|
/// A shared grammar for plain text, exposed for reuse by downstream crates.
|
||||||
#[doc(hidden)]
|
|
||||||
pub static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
|
|
||||||
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
|
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Plain Text".into(),
|
name: "Plain Text".into(),
|
||||||
|
@ -457,33 +432,6 @@ pub struct LanguageMatcher {
|
||||||
pub first_line_pattern: Option<Regex>,
|
pub first_line_pattern: Option<Regex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const QUERY_FILENAME_PREFIXES: &[(
|
|
||||||
&str,
|
|
||||||
fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
|
|
||||||
)] = &[
|
|
||||||
("highlights", |q| &mut q.highlights),
|
|
||||||
("brackets", |q| &mut q.brackets),
|
|
||||||
("outline", |q| &mut q.outline),
|
|
||||||
("indents", |q| &mut q.indents),
|
|
||||||
("embedding", |q| &mut q.embedding),
|
|
||||||
("injections", |q| &mut q.injections),
|
|
||||||
("overrides", |q| &mut q.overrides),
|
|
||||||
("redactions", |q| &mut q.redactions),
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Tree-sitter language queries for a given language.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct LanguageQueries {
|
|
||||||
pub highlights: Option<Cow<'static, str>>,
|
|
||||||
pub brackets: Option<Cow<'static, str>>,
|
|
||||||
pub indents: Option<Cow<'static, str>>,
|
|
||||||
pub outline: Option<Cow<'static, str>>,
|
|
||||||
pub embedding: Option<Cow<'static, str>>,
|
|
||||||
pub injections: Option<Cow<'static, str>>,
|
|
||||||
pub overrides: Option<Cow<'static, str>>,
|
|
||||||
pub redactions: Option<Cow<'static, str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a language for the given range. Some languages (e.g. HTML)
|
/// Represents a language for the given range. Some languages (e.g. HTML)
|
||||||
/// interleave several languages together, thus a single buffer might actually contain
|
/// interleave several languages together, thus a single buffer might actually contain
|
||||||
/// several nested scopes.
|
/// several nested scopes.
|
||||||
|
@ -650,7 +598,7 @@ pub struct Language {
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fake_adapter: Option<(
|
fake_adapter: Option<(
|
||||||
mpsc::UnboundedSender<lsp::FakeLanguageServer>,
|
futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>,
|
||||||
Arc<FakeLspAdapter>,
|
Arc<FakeLspAdapter>,
|
||||||
)>,
|
)>,
|
||||||
}
|
}
|
||||||
|
@ -725,775 +673,6 @@ struct BracketConfig {
|
||||||
close_capture_ix: u32,
|
close_capture_ix: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum LanguageServerBinaryStatus {
|
|
||||||
CheckingForUpdate,
|
|
||||||
Downloading,
|
|
||||||
Downloaded,
|
|
||||||
Cached,
|
|
||||||
Failed { error: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
type AvailableLanguageId = usize;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct AvailableLanguage {
|
|
||||||
id: AvailableLanguageId,
|
|
||||||
name: Arc<str>,
|
|
||||||
grammar: Option<Arc<str>>,
|
|
||||||
source: AvailableLanguageSource,
|
|
||||||
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
|
||||||
loaded: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AvailableGrammar {
|
|
||||||
Native(tree_sitter::Language),
|
|
||||||
Loaded(PathBuf, tree_sitter::Language),
|
|
||||||
Loading(PathBuf, Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
|
|
||||||
Unloaded(PathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
enum AvailableLanguageSource {
|
|
||||||
BuiltIn {
|
|
||||||
asset_dir: &'static str,
|
|
||||||
get_queries: fn(&str) -> LanguageQueries,
|
|
||||||
config: LanguageConfig,
|
|
||||||
},
|
|
||||||
Extension {
|
|
||||||
path: Arc<Path>,
|
|
||||||
get_queries: fn(&Path) -> LanguageQueries,
|
|
||||||
matcher: LanguageMatcher,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LanguageRegistry {
|
|
||||||
state: RwLock<LanguageRegistryState>,
|
|
||||||
language_server_download_dir: Option<Arc<Path>>,
|
|
||||||
login_shell_env_loaded: Shared<Task<()>>,
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
lsp_binary_paths: Mutex<
|
|
||||||
HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
|
|
||||||
>,
|
|
||||||
executor: Option<BackgroundExecutor>,
|
|
||||||
lsp_binary_status_tx: LspBinaryStatusSender,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LanguageRegistryState {
|
|
||||||
next_language_server_id: usize,
|
|
||||||
languages: Vec<Arc<Language>>,
|
|
||||||
available_languages: Vec<AvailableLanguage>,
|
|
||||||
grammars: HashMap<Arc<str>, AvailableGrammar>,
|
|
||||||
next_available_language_id: AvailableLanguageId,
|
|
||||||
loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
|
|
||||||
subscription: (watch::Sender<()>, watch::Receiver<()>),
|
|
||||||
theme: Option<Arc<Theme>>,
|
|
||||||
version: usize,
|
|
||||||
reload_count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PendingLanguageServer {
|
|
||||||
pub server_id: LanguageServerId,
|
|
||||||
pub task: Task<Result<lsp::LanguageServer>>,
|
|
||||||
pub container_dir: Option<Arc<Path>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LanguageRegistry {
|
|
||||||
pub fn new(login_shell_env_loaded: Task<()>) -> Self {
|
|
||||||
Self {
|
|
||||||
state: RwLock::new(LanguageRegistryState {
|
|
||||||
next_language_server_id: 0,
|
|
||||||
languages: vec![PLAIN_TEXT.clone()],
|
|
||||||
available_languages: Default::default(),
|
|
||||||
grammars: Default::default(),
|
|
||||||
next_available_language_id: 0,
|
|
||||||
loading_languages: Default::default(),
|
|
||||||
subscription: watch::channel(),
|
|
||||||
theme: Default::default(),
|
|
||||||
version: 0,
|
|
||||||
reload_count: 0,
|
|
||||||
}),
|
|
||||||
language_server_download_dir: None,
|
|
||||||
login_shell_env_loaded: login_shell_env_loaded.shared(),
|
|
||||||
lsp_binary_paths: Default::default(),
|
|
||||||
executor: None,
|
|
||||||
lsp_binary_status_tx: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn test() -> Self {
|
|
||||||
Self::new(Task::ready(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_executor(&mut self, executor: BackgroundExecutor) {
|
|
||||||
self.executor = Some(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear out all of the loaded languages and reload them from scratch.
|
|
||||||
pub fn reload(&self) {
|
|
||||||
self.state.write().reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear out the given languages and reload them from scratch.
|
|
||||||
pub fn reload_languages(&self, languages: &[Arc<str>], grammars: &[Arc<str>]) {
|
|
||||||
self.state.write().reload_languages(languages, grammars);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(
|
|
||||||
&self,
|
|
||||||
asset_dir: &'static str,
|
|
||||||
config: LanguageConfig,
|
|
||||||
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
|
||||||
get_queries: fn(&str) -> LanguageQueries,
|
|
||||||
) {
|
|
||||||
let state = &mut *self.state.write();
|
|
||||||
state.available_languages.push(AvailableLanguage {
|
|
||||||
id: post_inc(&mut state.next_available_language_id),
|
|
||||||
name: config.name.clone(),
|
|
||||||
grammar: config.grammar.clone(),
|
|
||||||
source: AvailableLanguageSource::BuiltIn {
|
|
||||||
config,
|
|
||||||
get_queries,
|
|
||||||
asset_dir,
|
|
||||||
},
|
|
||||||
lsp_adapters,
|
|
||||||
loaded: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_extension(
|
|
||||||
&self,
|
|
||||||
path: Arc<Path>,
|
|
||||||
name: Arc<str>,
|
|
||||||
grammar_name: Option<Arc<str>>,
|
|
||||||
matcher: LanguageMatcher,
|
|
||||||
get_queries: fn(&Path) -> LanguageQueries,
|
|
||||||
) {
|
|
||||||
let state = &mut *self.state.write();
|
|
||||||
let source = AvailableLanguageSource::Extension {
|
|
||||||
path,
|
|
||||||
get_queries,
|
|
||||||
matcher,
|
|
||||||
};
|
|
||||||
for existing_language in &mut state.available_languages {
|
|
||||||
if existing_language.name == name
|
|
||||||
&& matches!(
|
|
||||||
existing_language.source,
|
|
||||||
AvailableLanguageSource::Extension { .. }
|
|
||||||
)
|
|
||||||
{
|
|
||||||
existing_language.source = source;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.available_languages.push(AvailableLanguage {
|
|
||||||
id: post_inc(&mut state.next_available_language_id),
|
|
||||||
grammar: grammar_name,
|
|
||||||
name,
|
|
||||||
source,
|
|
||||||
lsp_adapters: Vec::new(),
|
|
||||||
loaded: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_grammars(
|
|
||||||
&self,
|
|
||||||
grammars: impl IntoIterator<Item = (impl Into<Arc<str>>, tree_sitter::Language)>,
|
|
||||||
) {
|
|
||||||
self.state.write().grammars.extend(
|
|
||||||
grammars
|
|
||||||
.into_iter()
|
|
||||||
.map(|(name, grammar)| (name.into(), AvailableGrammar::Native(grammar))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_grammar(&self, name: Arc<str>, path: PathBuf) {
|
|
||||||
self.state
|
|
||||||
.write()
|
|
||||||
.grammars
|
|
||||||
.insert(name, AvailableGrammar::Unloaded(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn language_names(&self) -> Vec<String> {
|
|
||||||
let state = self.state.read();
|
|
||||||
let mut result = state
|
|
||||||
.available_languages
|
|
||||||
.iter()
|
|
||||||
.filter_map(|l| l.loaded.not().then_some(l.name.to_string()))
|
|
||||||
.chain(state.languages.iter().map(|l| l.config.name.to_string()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&self, language: Arc<Language>) {
|
|
||||||
self.state.write().add(language);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subscribe(&self) -> watch::Receiver<()> {
|
|
||||||
self.state.read().subscription.1.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The number of times that the registry has been changed,
|
|
||||||
/// by adding languages or reloading.
|
|
||||||
pub fn version(&self) -> usize {
|
|
||||||
self.state.read().version
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The number of times that the registry has been reloaded.
|
|
||||||
pub fn reload_count(&self) -> usize {
|
|
||||||
self.state.read().reload_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_theme(&self, theme: Arc<Theme>) {
|
|
||||||
let mut state = self.state.write();
|
|
||||||
state.theme = Some(theme.clone());
|
|
||||||
for language in &state.languages {
|
|
||||||
language.set_theme(theme.syntax());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_language_server_download_dir(&mut self, path: impl Into<Arc<Path>>) {
|
|
||||||
self.language_server_download_dir = Some(path.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn language_for_name(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
name: &str,
|
|
||||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
|
||||||
let name = UniCase::new(name);
|
|
||||||
self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn language_for_name_or_extension(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
string: &str,
|
|
||||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
|
||||||
let string = UniCase::new(string);
|
|
||||||
self.get_or_load_language(|name, config| {
|
|
||||||
UniCase::new(name) == string
|
|
||||||
|| config
|
|
||||||
.path_suffixes
|
|
||||||
.iter()
|
|
||||||
.any(|suffix| UniCase::new(suffix) == string)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn language_for_file(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
path: impl AsRef<Path>,
|
|
||||||
content: Option<&Rope>,
|
|
||||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let filename = path.file_name().and_then(|name| name.to_str());
|
|
||||||
let extension = path.extension_or_hidden_file_name();
|
|
||||||
let path_suffixes = [extension, filename];
|
|
||||||
self.get_or_load_language(|_, config| {
|
|
||||||
let path_matches = config
|
|
||||||
.path_suffixes
|
|
||||||
.iter()
|
|
||||||
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
|
|
||||||
let content_matches = content.zip(config.first_line_pattern.as_ref()).map_or(
|
|
||||||
false,
|
|
||||||
|(content, pattern)| {
|
|
||||||
let end = content.clip_point(Point::new(0, 256), Bias::Left);
|
|
||||||
let end = content.point_to_offset(end);
|
|
||||||
let text = content.chunks_in_range(0..end).collect::<String>();
|
|
||||||
pattern.is_match(&text)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
path_matches || content_matches
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_or_load_language(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
callback: impl Fn(&str, &LanguageMatcher) -> bool,
|
|
||||||
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
|
|
||||||
let mut state = self.state.write();
|
|
||||||
if let Some(language) = state
|
|
||||||
.languages
|
|
||||||
.iter()
|
|
||||||
.find(|language| callback(language.config.name.as_ref(), &language.config.matcher))
|
|
||||||
{
|
|
||||||
let _ = tx.send(Ok(language.clone()));
|
|
||||||
} else if let Some(executor) = self.executor.clone() {
|
|
||||||
if let Some(language) = state
|
|
||||||
.available_languages
|
|
||||||
.iter()
|
|
||||||
.rfind(|l| {
|
|
||||||
!l.loaded
|
|
||||||
&& match &l.source {
|
|
||||||
AvailableLanguageSource::BuiltIn { config, .. } => {
|
|
||||||
callback(l.name.as_ref(), &config.matcher)
|
|
||||||
}
|
|
||||||
AvailableLanguageSource::Extension { matcher, .. } => {
|
|
||||||
callback(l.name.as_ref(), &matcher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
{
|
|
||||||
match state.loading_languages.entry(language.id) {
|
|
||||||
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
|
|
||||||
hash_map::Entry::Vacant(entry) => {
|
|
||||||
let this = self.clone();
|
|
||||||
executor
|
|
||||||
.spawn(async move {
|
|
||||||
let id = language.id;
|
|
||||||
let name = language.name.clone();
|
|
||||||
let language = async {
|
|
||||||
let (config, queries) = match language.source {
|
|
||||||
AvailableLanguageSource::BuiltIn {
|
|
||||||
asset_dir,
|
|
||||||
get_queries,
|
|
||||||
config,
|
|
||||||
} => (config, (get_queries)(asset_dir)),
|
|
||||||
AvailableLanguageSource::Extension {
|
|
||||||
path,
|
|
||||||
get_queries,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let config = std::fs::read(path.join("config.toml"));
|
|
||||||
let config: LanguageConfig =
|
|
||||||
::toml::from_slice(&config?)?;
|
|
||||||
(config, get_queries(path.as_ref()))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let grammar = if let Some(grammar) = config.grammar.clone() {
|
|
||||||
Some(this.get_or_load_grammar(grammar).await?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Language::new(config, grammar)
|
|
||||||
.with_lsp_adapters(language.lsp_adapters)
|
|
||||||
.await
|
|
||||||
.with_queries(queries)
|
|
||||||
}
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match language {
|
|
||||||
Ok(language) => {
|
|
||||||
let language = Arc::new(language);
|
|
||||||
let mut state = this.state.write();
|
|
||||||
|
|
||||||
state.add(language.clone());
|
|
||||||
state.mark_language_loaded(id);
|
|
||||||
if let Some(mut txs) = state.loading_languages.remove(&id) {
|
|
||||||
for tx in txs.drain(..) {
|
|
||||||
let _ = tx.send(Ok(language.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("failed to load language {name}:\n{:?}", e);
|
|
||||||
let mut state = this.state.write();
|
|
||||||
state.mark_language_loaded(id);
|
|
||||||
if let Some(mut txs) = state.loading_languages.remove(&id) {
|
|
||||||
for tx in txs.drain(..) {
|
|
||||||
let _ = tx.send(Err(anyhow!(
|
|
||||||
"failed to load language {}: {}",
|
|
||||||
name,
|
|
||||||
e
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
entry.insert(vec![tx]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = tx.send(Err(anyhow!("language not found")));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = tx.send(Err(anyhow!("executor does not exist")));
|
|
||||||
}
|
|
||||||
|
|
||||||
rx.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_or_load_grammar(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
name: Arc<str>,
|
|
||||||
) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
let mut state = self.state.write();
|
|
||||||
|
|
||||||
if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
|
|
||||||
match grammar {
|
|
||||||
AvailableGrammar::Native(grammar) | AvailableGrammar::Loaded(_, grammar) => {
|
|
||||||
tx.send(Ok(grammar.clone())).ok();
|
|
||||||
}
|
|
||||||
AvailableGrammar::Loading(_, txs) => {
|
|
||||||
txs.push(tx);
|
|
||||||
}
|
|
||||||
AvailableGrammar::Unloaded(wasm_path) => {
|
|
||||||
if let Some(executor) = &self.executor {
|
|
||||||
let this = self.clone();
|
|
||||||
executor
|
|
||||||
.spawn({
|
|
||||||
let wasm_path = wasm_path.clone();
|
|
||||||
async move {
|
|
||||||
let wasm_bytes = std::fs::read(&wasm_path)?;
|
|
||||||
let grammar_name = wasm_path
|
|
||||||
.file_stem()
|
|
||||||
.and_then(OsStr::to_str)
|
|
||||||
.ok_or_else(|| anyhow!("invalid grammar filename"))?;
|
|
||||||
let grammar = PARSER.with(|parser| {
|
|
||||||
let mut parser = parser.borrow_mut();
|
|
||||||
let mut store = parser.take_wasm_store().unwrap();
|
|
||||||
let grammar =
|
|
||||||
store.load_language(&grammar_name, &wasm_bytes);
|
|
||||||
parser.set_wasm_store(store).unwrap();
|
|
||||||
grammar
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(AvailableGrammar::Loading(_, txs)) =
|
|
||||||
this.state.write().grammars.insert(
|
|
||||||
name,
|
|
||||||
AvailableGrammar::Loaded(wasm_path, grammar.clone()),
|
|
||||||
)
|
|
||||||
{
|
|
||||||
for tx in txs {
|
|
||||||
tx.send(Ok(grammar.clone())).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
*grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tx.send(Err(anyhow!("no such grammar {}", name))).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
rx.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Vec<Arc<Language>> {
|
|
||||||
self.state.read().languages.iter().cloned().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_pending_language_server(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
stderr_capture: Arc<Mutex<Option<String>>>,
|
|
||||||
language: Arc<Language>,
|
|
||||||
adapter: Arc<CachedLspAdapter>,
|
|
||||||
root_path: Arc<Path>,
|
|
||||||
delegate: Arc<dyn LspAdapterDelegate>,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Option<PendingLanguageServer> {
|
|
||||||
let server_id = self.state.write().next_language_server_id();
|
|
||||||
log::info!(
|
|
||||||
"starting language server {:?}, path: {root_path:?}, id: {server_id}",
|
|
||||||
adapter.name.0
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
if language.fake_adapter.is_some() {
|
|
||||||
let task = cx.spawn(|cx| async move {
|
|
||||||
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
|
||||||
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
|
|
||||||
fake_adapter.name.to_string(),
|
|
||||||
fake_adapter.capabilities.clone(),
|
|
||||||
cx.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(initializer) = &fake_adapter.initializer {
|
|
||||||
initializer(&mut fake_server);
|
|
||||||
}
|
|
||||||
|
|
||||||
let servers_tx = servers_tx.clone();
|
|
||||||
cx.background_executor()
|
|
||||||
.spawn(async move {
|
|
||||||
if fake_server
|
|
||||||
.try_receive_notification::<lsp::notification::Initialized>()
|
|
||||||
.await
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
servers_tx.unbounded_send(fake_server).ok();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Ok(server)
|
|
||||||
});
|
|
||||||
|
|
||||||
return Some(PendingLanguageServer {
|
|
||||||
server_id,
|
|
||||||
task,
|
|
||||||
container_dir: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let download_dir = self
|
|
||||||
.language_server_download_dir
|
|
||||||
.clone()
|
|
||||||
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
|
|
||||||
.log_err()?;
|
|
||||||
let this = self.clone();
|
|
||||||
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 adapter = adapter.clone();
|
|
||||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
|
||||||
let lsp_binary_statuses = self.lsp_binary_status_tx.clone();
|
|
||||||
|
|
||||||
let task = {
|
|
||||||
let container_dir = container_dir.clone();
|
|
||||||
cx.spawn(move |mut cx| async move {
|
|
||||||
login_shell_env_loaded.await;
|
|
||||||
|
|
||||||
let entry = this
|
|
||||||
.lsp_binary_paths
|
|
||||||
.lock()
|
|
||||||
.entry(adapter.name.clone())
|
|
||||||
.or_insert_with(|| {
|
|
||||||
let adapter = adapter.clone();
|
|
||||||
let language = language.clone();
|
|
||||||
let delegate = delegate.clone();
|
|
||||||
cx.spawn(|cx| {
|
|
||||||
get_binary(
|
|
||||||
adapter,
|
|
||||||
language,
|
|
||||||
delegate,
|
|
||||||
container_dir,
|
|
||||||
lsp_binary_statuses,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.map_err(Arc::new)
|
|
||||||
})
|
|
||||||
.shared()
|
|
||||||
})
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let binary = match entry.await {
|
|
||||||
Ok(binary) => binary,
|
|
||||||
Err(err) => anyhow::bail!("{err}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
|
||||||
task.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
lsp::LanguageServer::new(
|
|
||||||
stderr_capture,
|
|
||||||
server_id,
|
|
||||||
binary,
|
|
||||||
&root_path,
|
|
||||||
adapter.code_action_kinds(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(PendingLanguageServer {
|
|
||||||
server_id,
|
|
||||||
task,
|
|
||||||
container_dir: Some(container_dir),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn language_server_binary_statuses(
|
|
||||||
&self,
|
|
||||||
) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
|
||||||
self.lsp_binary_status_tx.subscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_server_container(
|
|
||||||
&self,
|
|
||||||
adapter: Arc<CachedLspAdapter>,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Task<()> {
|
|
||||||
log::info!("deleting server container");
|
|
||||||
|
|
||||||
let mut lock = self.lsp_binary_paths.lock();
|
|
||||||
lock.remove(&adapter.name);
|
|
||||||
|
|
||||||
let download_dir = self
|
|
||||||
.language_server_download_dir
|
|
||||||
.clone()
|
|
||||||
.expect("language server download directory has not been assigned before deleting server container");
|
|
||||||
|
|
||||||
cx.spawn(|_| async move {
|
|
||||||
let container_dir = download_dir.join(adapter.name.0.as_ref());
|
|
||||||
smol::fs::remove_dir_all(container_dir)
|
|
||||||
.await
|
|
||||||
.context("server container removal")
|
|
||||||
.log_err();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_language_server_id(&self) -> LanguageServerId {
|
|
||||||
self.state.write().next_language_server_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LanguageRegistryState {
|
|
||||||
fn next_language_server_id(&mut self) -> LanguageServerId {
|
|
||||||
LanguageServerId(post_inc(&mut self.next_language_server_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(&mut self, language: Arc<Language>) {
|
|
||||||
if let Some(theme) = self.theme.as_ref() {
|
|
||||||
language.set_theme(theme.syntax());
|
|
||||||
}
|
|
||||||
self.languages.push(language);
|
|
||||||
self.version += 1;
|
|
||||||
*self.subscription.0.borrow_mut() = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload(&mut self) {
|
|
||||||
self.languages.clear();
|
|
||||||
self.version += 1;
|
|
||||||
self.reload_count += 1;
|
|
||||||
for language in &mut self.available_languages {
|
|
||||||
language.loaded = false;
|
|
||||||
}
|
|
||||||
*self.subscription.0.borrow_mut() = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload_languages(
|
|
||||||
&mut self,
|
|
||||||
languages_to_reload: &[Arc<str>],
|
|
||||||
grammars_to_reload: &[Arc<str>],
|
|
||||||
) {
|
|
||||||
for (name, grammar) in self.grammars.iter_mut() {
|
|
||||||
if grammars_to_reload.contains(name) {
|
|
||||||
if let AvailableGrammar::Loaded(path, _) = grammar {
|
|
||||||
*grammar = AvailableGrammar::Unloaded(path.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.languages.retain(|language| {
|
|
||||||
let should_reload = languages_to_reload.contains(&language.config.name)
|
|
||||||
|| language
|
|
||||||
.config
|
|
||||||
.grammar
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |grammar| grammars_to_reload.contains(&grammar));
|
|
||||||
!should_reload
|
|
||||||
});
|
|
||||||
|
|
||||||
for language in &mut self.available_languages {
|
|
||||||
if languages_to_reload.contains(&language.name)
|
|
||||||
|| language
|
|
||||||
.grammar
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |grammar| grammars_to_reload.contains(grammar))
|
|
||||||
{
|
|
||||||
language.loaded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.version += 1;
|
|
||||||
self.reload_count += 1;
|
|
||||||
*self.subscription.0.borrow_mut() = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark the given language as having been loaded, so that the
|
|
||||||
/// language registry won't try to load it again.
|
|
||||||
fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
|
|
||||||
for language in &mut self.available_languages {
|
|
||||||
if language.id == id {
|
|
||||||
language.loaded = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
impl Default for LanguageRegistry {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::test()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_binary(
|
|
||||||
adapter: Arc<CachedLspAdapter>,
|
|
||||||
language: Arc<Language>,
|
|
||||||
delegate: Arc<dyn LspAdapterDelegate>,
|
|
||||||
container_dir: Arc<Path>,
|
|
||||||
statuses: LspBinaryStatusSender,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<LanguageServerBinary> {
|
|
||||||
if !container_dir.exists() {
|
|
||||||
smol::fs::create_dir_all(&container_dir)
|
|
||||||
.await
|
|
||||||
.context("failed to create container directory")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(task) = adapter.will_fetch_server(&delegate, &mut cx) {
|
|
||||||
task.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let binary = fetch_latest_binary(
|
|
||||||
adapter.clone(),
|
|
||||||
language.clone(),
|
|
||||||
delegate.as_ref(),
|
|
||||||
&container_dir,
|
|
||||||
statuses.clone(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(error) = binary.as_ref() {
|
|
||||||
if let Some(binary) = adapter
|
|
||||||
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
|
|
||||||
return Ok(binary);
|
|
||||||
} else {
|
|
||||||
statuses.send(
|
|
||||||
language.clone(),
|
|
||||||
LanguageServerBinaryStatus::Failed {
|
|
||||||
error: format!("{:?}", error),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binary
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_latest_binary(
|
|
||||||
adapter: Arc<CachedLspAdapter>,
|
|
||||||
language: Arc<Language>,
|
|
||||||
delegate: &dyn LspAdapterDelegate,
|
|
||||||
container_dir: &Path,
|
|
||||||
lsp_binary_statuses_tx: LspBinaryStatusSender,
|
|
||||||
) -> Result<LanguageServerBinary> {
|
|
||||||
let container_dir: Arc<Path> = container_dir.into();
|
|
||||||
lsp_binary_statuses_tx.send(
|
|
||||||
language.clone(),
|
|
||||||
LanguageServerBinaryStatus::CheckingForUpdate,
|
|
||||||
);
|
|
||||||
|
|
||||||
let version_info = adapter.fetch_latest_server_version(delegate).await?;
|
|
||||||
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
|
|
||||||
|
|
||||||
let binary = adapter
|
|
||||||
.fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
|
|
||||||
.await?;
|
|
||||||
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloaded);
|
|
||||||
|
|
||||||
Ok(binary)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Language {
|
impl Language {
|
||||||
pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
|
pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -1831,8 +1010,8 @@ impl Language {
|
||||||
pub async fn set_fake_lsp_adapter(
|
pub async fn set_fake_lsp_adapter(
|
||||||
&mut self,
|
&mut self,
|
||||||
fake_lsp_adapter: Arc<FakeLspAdapter>,
|
fake_lsp_adapter: Arc<FakeLspAdapter>,
|
||||||
) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||||
let (servers_tx, servers_rx) = mpsc::unbounded();
|
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
|
||||||
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
|
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
|
||||||
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
|
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
|
||||||
self.adapters = vec![adapter];
|
self.adapters = vec![adapter];
|
||||||
|
@ -2255,19 +1434,14 @@ mod tests {
|
||||||
|
|
||||||
languages.set_executor(cx.executor());
|
languages.set_executor(cx.executor());
|
||||||
let languages = Arc::new(languages);
|
let languages = Arc::new(languages);
|
||||||
languages.register(
|
languages.register_test_language(LanguageConfig {
|
||||||
"/javascript",
|
|
||||||
LanguageConfig {
|
|
||||||
name: "JavaScript".into(),
|
name: "JavaScript".into(),
|
||||||
matcher: LanguageMatcher {
|
matcher: LanguageMatcher {
|
||||||
path_suffixes: vec!["js".into()],
|
path_suffixes: vec!["js".into()],
|
||||||
first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
|
first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
});
|
||||||
vec![],
|
|
||||||
|_| Default::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
languages
|
languages
|
||||||
.language_for_file("the/script", None)
|
.language_for_file("the/script", None)
|
||||||
|
@ -2293,13 +1467,11 @@ mod tests {
|
||||||
let mut languages = LanguageRegistry::test();
|
let mut languages = LanguageRegistry::test();
|
||||||
languages.set_executor(cx.executor());
|
languages.set_executor(cx.executor());
|
||||||
let languages = Arc::new(languages);
|
let languages = Arc::new(languages);
|
||||||
languages.add_grammars([
|
languages.register_native_grammars([
|
||||||
("json", tree_sitter_json::language()),
|
("json", tree_sitter_json::language()),
|
||||||
("rust", tree_sitter_rust::language()),
|
("rust", tree_sitter_rust::language()),
|
||||||
]);
|
]);
|
||||||
languages.register(
|
languages.register_test_language(LanguageConfig {
|
||||||
"/JSON",
|
|
||||||
LanguageConfig {
|
|
||||||
name: "JSON".into(),
|
name: "JSON".into(),
|
||||||
grammar: Some("json".into()),
|
grammar: Some("json".into()),
|
||||||
matcher: LanguageMatcher {
|
matcher: LanguageMatcher {
|
||||||
|
@ -2307,13 +1479,8 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
});
|
||||||
vec![],
|
languages.register_test_language(LanguageConfig {
|
||||||
|_| Default::default(),
|
|
||||||
);
|
|
||||||
languages.register(
|
|
||||||
"/rust",
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
name: "Rust".into(),
|
||||||
grammar: Some("rust".into()),
|
grammar: Some("rust".into()),
|
||||||
matcher: LanguageMatcher {
|
matcher: LanguageMatcher {
|
||||||
|
@ -2321,10 +1488,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
});
|
||||||
vec![],
|
|
||||||
|_| Default::default(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
languages.language_names(),
|
languages.language_names(),
|
||||||
&[
|
&[
|
||||||
|
|
799
crates/language/src/language_registry.rs
Normal file
799
crates/language/src/language_registry.rs
Normal file
|
@ -0,0 +1,799 @@
|
||||||
|
use crate::{
|
||||||
|
CachedLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName, LspAdapter,
|
||||||
|
LspAdapterDelegate, PARSER, PLAIN_TEXT,
|
||||||
|
};
|
||||||
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
use collections::{hash_map, HashMap};
|
||||||
|
use futures::{
|
||||||
|
channel::{mpsc, oneshot},
|
||||||
|
future::Shared,
|
||||||
|
FutureExt as _, TryFutureExt as _,
|
||||||
|
};
|
||||||
|
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||||
|
use lsp::{LanguageServerBinary, LanguageServerId};
|
||||||
|
use parking_lot::{Mutex, RwLock};
|
||||||
|
use postage::watch;
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
ffi::OsStr,
|
||||||
|
ops::Not,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use sum_tree::Bias;
|
||||||
|
use text::{Point, Rope};
|
||||||
|
use theme::Theme;
|
||||||
|
use unicase::UniCase;
|
||||||
|
use util::{paths::PathExt, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
|
||||||
|
|
||||||
|
pub struct LanguageRegistry {
|
||||||
|
state: RwLock<LanguageRegistryState>,
|
||||||
|
language_server_download_dir: Option<Arc<Path>>,
|
||||||
|
login_shell_env_loaded: Shared<Task<()>>,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
lsp_binary_paths: Mutex<
|
||||||
|
HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
|
||||||
|
>,
|
||||||
|
executor: Option<BackgroundExecutor>,
|
||||||
|
lsp_binary_status_tx: LspBinaryStatusSender,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LanguageRegistryState {
|
||||||
|
next_language_server_id: usize,
|
||||||
|
languages: Vec<Arc<Language>>,
|
||||||
|
available_languages: Vec<AvailableLanguage>,
|
||||||
|
grammars: HashMap<Arc<str>, AvailableGrammar>,
|
||||||
|
next_available_language_id: AvailableLanguageId,
|
||||||
|
loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
|
||||||
|
subscription: (watch::Sender<()>, watch::Receiver<()>),
|
||||||
|
theme: Option<Arc<Theme>>,
|
||||||
|
version: usize,
|
||||||
|
reload_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum LanguageServerBinaryStatus {
|
||||||
|
CheckingForUpdate,
|
||||||
|
Downloading,
|
||||||
|
Downloaded,
|
||||||
|
Cached,
|
||||||
|
Failed { error: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PendingLanguageServer {
|
||||||
|
pub server_id: LanguageServerId,
|
||||||
|
pub task: Task<Result<lsp::LanguageServer>>,
|
||||||
|
pub container_dir: Option<Arc<Path>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AvailableLanguage {
|
||||||
|
id: AvailableLanguageId,
|
||||||
|
name: Arc<str>,
|
||||||
|
grammar: Option<Arc<str>>,
|
||||||
|
matcher: LanguageMatcher,
|
||||||
|
load: Arc<dyn Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync>,
|
||||||
|
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||||
|
loaded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvailableLanguageId = usize;
|
||||||
|
|
||||||
|
enum AvailableGrammar {
|
||||||
|
Native(tree_sitter::Language),
|
||||||
|
Loaded(PathBuf, tree_sitter::Language),
|
||||||
|
Loading(PathBuf, Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
|
||||||
|
Unloaded(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const QUERY_FILENAME_PREFIXES: &[(
|
||||||
|
&str,
|
||||||
|
fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
|
||||||
|
)] = &[
|
||||||
|
("highlights", |q| &mut q.highlights),
|
||||||
|
("brackets", |q| &mut q.brackets),
|
||||||
|
("outline", |q| &mut q.outline),
|
||||||
|
("indents", |q| &mut q.indents),
|
||||||
|
("embedding", |q| &mut q.embedding),
|
||||||
|
("injections", |q| &mut q.injections),
|
||||||
|
("overrides", |q| &mut q.overrides),
|
||||||
|
("redactions", |q| &mut q.redactions),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Tree-sitter language queries for a given language.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct LanguageQueries {
|
||||||
|
pub highlights: Option<Cow<'static, str>>,
|
||||||
|
pub brackets: Option<Cow<'static, str>>,
|
||||||
|
pub indents: Option<Cow<'static, str>>,
|
||||||
|
pub outline: Option<Cow<'static, str>>,
|
||||||
|
pub embedding: Option<Cow<'static, str>>,
|
||||||
|
pub injections: Option<Cow<'static, str>>,
|
||||||
|
pub overrides: Option<Cow<'static, str>>,
|
||||||
|
pub redactions: Option<Cow<'static, str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
struct LspBinaryStatusSender {
|
||||||
|
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(Arc<Language>, LanguageServerBinaryStatus)>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageRegistry {
|
||||||
|
pub fn new(login_shell_env_loaded: Task<()>) -> Self {
|
||||||
|
Self {
|
||||||
|
state: RwLock::new(LanguageRegistryState {
|
||||||
|
next_language_server_id: 0,
|
||||||
|
languages: vec![PLAIN_TEXT.clone()],
|
||||||
|
available_languages: Default::default(),
|
||||||
|
grammars: Default::default(),
|
||||||
|
next_available_language_id: 0,
|
||||||
|
loading_languages: Default::default(),
|
||||||
|
subscription: watch::channel(),
|
||||||
|
theme: Default::default(),
|
||||||
|
version: 0,
|
||||||
|
reload_count: 0,
|
||||||
|
}),
|
||||||
|
language_server_download_dir: None,
|
||||||
|
login_shell_env_loaded: login_shell_env_loaded.shared(),
|
||||||
|
lsp_binary_paths: Default::default(),
|
||||||
|
executor: None,
|
||||||
|
lsp_binary_status_tx: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn test() -> Self {
|
||||||
|
Self::new(Task::ready(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_executor(&mut self, executor: BackgroundExecutor) {
|
||||||
|
self.executor = Some(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears out all of the loaded languages and reload them from scratch.
|
||||||
|
pub fn reload(&self) {
|
||||||
|
self.state.write().reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears out the given languages and reload them from scratch.
|
||||||
|
pub fn reload_languages(&self, languages: &[Arc<str>], grammars: &[Arc<str>]) {
|
||||||
|
self.state.write().reload_languages(languages, grammars);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "test-support", test))]
|
||||||
|
pub fn register_test_language(&self, config: LanguageConfig) {
|
||||||
|
self.register_language(
|
||||||
|
config.name.clone(),
|
||||||
|
config.grammar.clone(),
|
||||||
|
config.matcher.clone(),
|
||||||
|
vec![],
|
||||||
|
move || Ok((config.clone(), Default::default())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a language to the registry, which can be loaded if needed.
|
||||||
|
pub fn register_language(
|
||||||
|
&self,
|
||||||
|
name: Arc<str>,
|
||||||
|
grammar_name: Option<Arc<str>>,
|
||||||
|
matcher: LanguageMatcher,
|
||||||
|
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||||
|
load: impl Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync,
|
||||||
|
) {
|
||||||
|
let load = Arc::new(load);
|
||||||
|
let state = &mut *self.state.write();
|
||||||
|
|
||||||
|
for existing_language in &mut state.available_languages {
|
||||||
|
if existing_language.name == name {
|
||||||
|
existing_language.grammar = grammar_name;
|
||||||
|
existing_language.matcher = matcher;
|
||||||
|
existing_language.lsp_adapters = lsp_adapters;
|
||||||
|
existing_language.load = load;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.available_languages.push(AvailableLanguage {
|
||||||
|
id: post_inc(&mut state.next_available_language_id),
|
||||||
|
name,
|
||||||
|
grammar: grammar_name,
|
||||||
|
matcher,
|
||||||
|
load,
|
||||||
|
lsp_adapters,
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds grammars to the registry. Language configurations reference a grammar by name. The
|
||||||
|
/// grammar controls how the source code is parsed.
|
||||||
|
pub fn register_native_grammars(
|
||||||
|
&self,
|
||||||
|
grammars: impl IntoIterator<Item = (impl Into<Arc<str>>, tree_sitter::Language)>,
|
||||||
|
) {
|
||||||
|
self.state.write().grammars.extend(
|
||||||
|
grammars
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, grammar)| (name.into(), AvailableGrammar::Native(grammar))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds paths to WASM grammar files, which can be loaded if needed.
|
||||||
|
pub fn register_wasm_grammars(
|
||||||
|
&self,
|
||||||
|
grammars: impl IntoIterator<Item = (impl Into<Arc<str>>, PathBuf)>,
|
||||||
|
) {
|
||||||
|
self.state.write().grammars.extend(
|
||||||
|
grammars
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, path)| (name.into(), AvailableGrammar::Unloaded(path))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_names(&self) -> Vec<String> {
|
||||||
|
let state = self.state.read();
|
||||||
|
let mut result = state
|
||||||
|
.available_languages
|
||||||
|
.iter()
|
||||||
|
.filter_map(|l| l.loaded.not().then_some(l.name.to_string()))
|
||||||
|
.chain(state.languages.iter().map(|l| l.config.name.to_string()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&self, language: Arc<Language>) {
|
||||||
|
self.state.write().add(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&self) -> watch::Receiver<()> {
|
||||||
|
self.state.read().subscription.1.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of times that the registry has been changed,
|
||||||
|
/// by adding languages or reloading.
|
||||||
|
pub fn version(&self) -> usize {
|
||||||
|
self.state.read().version
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of times that the registry has been reloaded.
|
||||||
|
pub fn reload_count(&self) -> usize {
|
||||||
|
self.state.read().reload_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_theme(&self, theme: Arc<Theme>) {
|
||||||
|
let mut state = self.state.write();
|
||||||
|
state.theme = Some(theme.clone());
|
||||||
|
for language in &state.languages {
|
||||||
|
language.set_theme(theme.syntax());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_language_server_download_dir(&mut self, path: impl Into<Arc<Path>>) {
|
||||||
|
self.language_server_download_dir = Some(path.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_for_name(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
name: &str,
|
||||||
|
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||||
|
let name = UniCase::new(name);
|
||||||
|
self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_for_name_or_extension(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
string: &str,
|
||||||
|
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||||
|
let string = UniCase::new(string);
|
||||||
|
self.get_or_load_language(|name, config| {
|
||||||
|
UniCase::new(name) == string
|
||||||
|
|| config
|
||||||
|
.path_suffixes
|
||||||
|
.iter()
|
||||||
|
.any(|suffix| UniCase::new(suffix) == string)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_for_file(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
content: Option<&Rope>,
|
||||||
|
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let filename = path.file_name().and_then(|name| name.to_str());
|
||||||
|
let extension = path.extension_or_hidden_file_name();
|
||||||
|
let path_suffixes = [extension, filename];
|
||||||
|
self.get_or_load_language(|_, config| {
|
||||||
|
let path_matches = config
|
||||||
|
.path_suffixes
|
||||||
|
.iter()
|
||||||
|
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
|
||||||
|
let content_matches = content.zip(config.first_line_pattern.as_ref()).map_or(
|
||||||
|
false,
|
||||||
|
|(content, pattern)| {
|
||||||
|
let end = content.clip_point(Point::new(0, 256), Bias::Left);
|
||||||
|
let end = content.point_to_offset(end);
|
||||||
|
let text = content.chunks_in_range(0..end).collect::<String>();
|
||||||
|
pattern.is_match(&text)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
path_matches || content_matches
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_or_load_language(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
callback: impl Fn(&str, &LanguageMatcher) -> bool,
|
||||||
|
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let mut state = self.state.write();
|
||||||
|
if let Some(language) = state
|
||||||
|
.languages
|
||||||
|
.iter()
|
||||||
|
.find(|language| callback(language.config.name.as_ref(), &language.config.matcher))
|
||||||
|
{
|
||||||
|
let _ = tx.send(Ok(language.clone()));
|
||||||
|
} else if let Some(executor) = self.executor.clone() {
|
||||||
|
if let Some(language) = state
|
||||||
|
.available_languages
|
||||||
|
.iter()
|
||||||
|
.rfind(|l| !l.loaded && callback(&l.name, &l.matcher))
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
match state.loading_languages.entry(language.id) {
|
||||||
|
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
|
||||||
|
hash_map::Entry::Vacant(entry) => {
|
||||||
|
let this = self.clone();
|
||||||
|
executor
|
||||||
|
.spawn(async move {
|
||||||
|
let id = language.id;
|
||||||
|
let name = language.name.clone();
|
||||||
|
let language = async {
|
||||||
|
let (config, queries) = (language.load)()?;
|
||||||
|
|
||||||
|
let grammar = if let Some(grammar) = config.grammar.clone() {
|
||||||
|
Some(this.get_or_load_grammar(grammar).await?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Language::new(config, grammar)
|
||||||
|
.with_lsp_adapters(language.lsp_adapters)
|
||||||
|
.await
|
||||||
|
.with_queries(queries)
|
||||||
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match language {
|
||||||
|
Ok(language) => {
|
||||||
|
let language = Arc::new(language);
|
||||||
|
let mut state = this.state.write();
|
||||||
|
|
||||||
|
state.add(language.clone());
|
||||||
|
state.mark_language_loaded(id);
|
||||||
|
if let Some(mut txs) = state.loading_languages.remove(&id) {
|
||||||
|
for tx in txs.drain(..) {
|
||||||
|
let _ = tx.send(Ok(language.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("failed to load language {name}:\n{:?}", e);
|
||||||
|
let mut state = this.state.write();
|
||||||
|
state.mark_language_loaded(id);
|
||||||
|
if let Some(mut txs) = state.loading_languages.remove(&id) {
|
||||||
|
for tx in txs.drain(..) {
|
||||||
|
let _ = tx.send(Err(anyhow!(
|
||||||
|
"failed to load language {}: {}",
|
||||||
|
name,
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
entry.insert(vec![tx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = tx.send(Err(anyhow!("language not found")));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = tx.send(Err(anyhow!("executor does not exist")));
|
||||||
|
}
|
||||||
|
|
||||||
|
rx.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_or_load_grammar(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
name: Arc<str>,
|
||||||
|
) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
let mut state = self.state.write();
|
||||||
|
|
||||||
|
if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
|
||||||
|
match grammar {
|
||||||
|
AvailableGrammar::Native(grammar) | AvailableGrammar::Loaded(_, grammar) => {
|
||||||
|
tx.send(Ok(grammar.clone())).ok();
|
||||||
|
}
|
||||||
|
AvailableGrammar::Loading(_, txs) => {
|
||||||
|
txs.push(tx);
|
||||||
|
}
|
||||||
|
AvailableGrammar::Unloaded(wasm_path) => {
|
||||||
|
if let Some(executor) = &self.executor {
|
||||||
|
let this = self.clone();
|
||||||
|
executor
|
||||||
|
.spawn({
|
||||||
|
let wasm_path = wasm_path.clone();
|
||||||
|
async move {
|
||||||
|
let wasm_bytes = std::fs::read(&wasm_path)?;
|
||||||
|
let grammar_name = wasm_path
|
||||||
|
.file_stem()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.ok_or_else(|| anyhow!("invalid grammar filename"))?;
|
||||||
|
let grammar = PARSER.with(|parser| {
|
||||||
|
let mut parser = parser.borrow_mut();
|
||||||
|
let mut store = parser.take_wasm_store().unwrap();
|
||||||
|
let grammar =
|
||||||
|
store.load_language(&grammar_name, &wasm_bytes);
|
||||||
|
parser.set_wasm_store(store).unwrap();
|
||||||
|
grammar
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(AvailableGrammar::Loading(_, txs)) =
|
||||||
|
this.state.write().grammars.insert(
|
||||||
|
name,
|
||||||
|
AvailableGrammar::Loaded(wasm_path, grammar.clone()),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
for tx in txs {
|
||||||
|
tx.send(Ok(grammar.clone())).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
*grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tx.send(Err(anyhow!("no such grammar {}", name))).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
rx.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Vec<Arc<Language>> {
|
||||||
|
self.state.read().languages.iter().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_pending_language_server(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||||
|
language: Arc<Language>,
|
||||||
|
adapter: Arc<CachedLspAdapter>,
|
||||||
|
root_path: Arc<Path>,
|
||||||
|
delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<PendingLanguageServer> {
|
||||||
|
let server_id = self.state.write().next_language_server_id();
|
||||||
|
log::info!(
|
||||||
|
"starting language server {:?}, path: {root_path:?}, id: {server_id}",
|
||||||
|
adapter.name.0
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
if language.fake_adapter.is_some() {
|
||||||
|
let task = cx.spawn(|cx| async move {
|
||||||
|
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
||||||
|
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
|
||||||
|
fake_adapter.name.to_string(),
|
||||||
|
fake_adapter.capabilities.clone(),
|
||||||
|
cx.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(initializer) = &fake_adapter.initializer {
|
||||||
|
initializer(&mut fake_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
let servers_tx = servers_tx.clone();
|
||||||
|
cx.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
if fake_server
|
||||||
|
.try_receive_notification::<lsp::notification::Initialized>()
|
||||||
|
.await
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
servers_tx.unbounded_send(fake_server).ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Ok(server)
|
||||||
|
});
|
||||||
|
|
||||||
|
return Some(PendingLanguageServer {
|
||||||
|
server_id,
|
||||||
|
task,
|
||||||
|
container_dir: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let download_dir = self
|
||||||
|
.language_server_download_dir
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
|
||||||
|
.log_err()?;
|
||||||
|
let this = self.clone();
|
||||||
|
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 adapter = adapter.clone();
|
||||||
|
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||||
|
let lsp_binary_statuses = self.lsp_binary_status_tx.clone();
|
||||||
|
|
||||||
|
let task = {
|
||||||
|
let container_dir = container_dir.clone();
|
||||||
|
cx.spawn(move |mut cx| async move {
|
||||||
|
login_shell_env_loaded.await;
|
||||||
|
|
||||||
|
let entry = this
|
||||||
|
.lsp_binary_paths
|
||||||
|
.lock()
|
||||||
|
.entry(adapter.name.clone())
|
||||||
|
.or_insert_with(|| {
|
||||||
|
let adapter = adapter.clone();
|
||||||
|
let language = language.clone();
|
||||||
|
let delegate = delegate.clone();
|
||||||
|
cx.spawn(|cx| {
|
||||||
|
get_binary(
|
||||||
|
adapter,
|
||||||
|
language,
|
||||||
|
delegate,
|
||||||
|
container_dir,
|
||||||
|
lsp_binary_statuses,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map_err(Arc::new)
|
||||||
|
})
|
||||||
|
.shared()
|
||||||
|
})
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let binary = match entry.await {
|
||||||
|
Ok(binary) => binary,
|
||||||
|
Err(err) => anyhow::bail!("{err}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
||||||
|
task.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
lsp::LanguageServer::new(
|
||||||
|
stderr_capture,
|
||||||
|
server_id,
|
||||||
|
binary,
|
||||||
|
&root_path,
|
||||||
|
adapter.code_action_kinds(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(PendingLanguageServer {
|
||||||
|
server_id,
|
||||||
|
task,
|
||||||
|
container_dir: Some(container_dir),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_server_binary_statuses(
|
||||||
|
&self,
|
||||||
|
) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
||||||
|
self.lsp_binary_status_tx.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_server_container(
|
||||||
|
&self,
|
||||||
|
adapter: Arc<CachedLspAdapter>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Task<()> {
|
||||||
|
log::info!("deleting server container");
|
||||||
|
|
||||||
|
let mut lock = self.lsp_binary_paths.lock();
|
||||||
|
lock.remove(&adapter.name);
|
||||||
|
|
||||||
|
let download_dir = self
|
||||||
|
.language_server_download_dir
|
||||||
|
.clone()
|
||||||
|
.expect("language server download directory has not been assigned before deleting server container");
|
||||||
|
|
||||||
|
cx.spawn(|_| async move {
|
||||||
|
let container_dir = download_dir.join(adapter.name.0.as_ref());
|
||||||
|
smol::fs::remove_dir_all(container_dir)
|
||||||
|
.await
|
||||||
|
.context("server container removal")
|
||||||
|
.log_err();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_language_server_id(&self) -> LanguageServerId {
|
||||||
|
self.state.write().next_language_server_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
impl Default for LanguageRegistry {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::test()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageRegistryState {
|
||||||
|
fn next_language_server_id(&mut self) -> LanguageServerId {
|
||||||
|
LanguageServerId(post_inc(&mut self.next_language_server_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, language: Arc<Language>) {
|
||||||
|
if let Some(theme) = self.theme.as_ref() {
|
||||||
|
language.set_theme(theme.syntax());
|
||||||
|
}
|
||||||
|
self.languages.push(language);
|
||||||
|
self.version += 1;
|
||||||
|
*self.subscription.0.borrow_mut() = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload(&mut self) {
|
||||||
|
self.languages.clear();
|
||||||
|
self.version += 1;
|
||||||
|
self.reload_count += 1;
|
||||||
|
for language in &mut self.available_languages {
|
||||||
|
language.loaded = false;
|
||||||
|
}
|
||||||
|
*self.subscription.0.borrow_mut() = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_languages(
|
||||||
|
&mut self,
|
||||||
|
languages_to_reload: &[Arc<str>],
|
||||||
|
grammars_to_reload: &[Arc<str>],
|
||||||
|
) {
|
||||||
|
for (name, grammar) in self.grammars.iter_mut() {
|
||||||
|
if grammars_to_reload.contains(name) {
|
||||||
|
if let AvailableGrammar::Loaded(path, _) = grammar {
|
||||||
|
*grammar = AvailableGrammar::Unloaded(path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.languages.retain(|language| {
|
||||||
|
let should_reload = languages_to_reload.contains(&language.config.name)
|
||||||
|
|| language
|
||||||
|
.config
|
||||||
|
.grammar
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |grammar| grammars_to_reload.contains(&grammar));
|
||||||
|
!should_reload
|
||||||
|
});
|
||||||
|
|
||||||
|
for language in &mut self.available_languages {
|
||||||
|
if languages_to_reload.contains(&language.name)
|
||||||
|
|| language
|
||||||
|
.grammar
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |grammar| grammars_to_reload.contains(grammar))
|
||||||
|
{
|
||||||
|
language.loaded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.version += 1;
|
||||||
|
self.reload_count += 1;
|
||||||
|
*self.subscription.0.borrow_mut() = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the given language as having been loaded, so that the
|
||||||
|
/// language registry won't try to load it again.
|
||||||
|
fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
|
||||||
|
for language in &mut self.available_languages {
|
||||||
|
if language.id == id {
|
||||||
|
language.loaded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LspBinaryStatusSender {
|
||||||
|
fn subscribe(&self) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
||||||
|
let (tx, rx) = mpsc::unbounded();
|
||||||
|
self.txs.lock().push(tx);
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(&self, language: Arc<Language>, status: LanguageServerBinaryStatus) {
|
||||||
|
let mut txs = self.txs.lock();
|
||||||
|
txs.retain(|tx| {
|
||||||
|
tx.unbounded_send((language.clone(), status.clone()))
|
||||||
|
.is_ok()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_binary(
|
||||||
|
adapter: Arc<CachedLspAdapter>,
|
||||||
|
language: Arc<Language>,
|
||||||
|
delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
|
container_dir: Arc<Path>,
|
||||||
|
statuses: LspBinaryStatusSender,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
if !container_dir.exists() {
|
||||||
|
smol::fs::create_dir_all(&container_dir)
|
||||||
|
.await
|
||||||
|
.context("failed to create container directory")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(task) = adapter.will_fetch_server(&delegate, &mut cx) {
|
||||||
|
task.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let binary = fetch_latest_binary(
|
||||||
|
adapter.clone(),
|
||||||
|
language.clone(),
|
||||||
|
delegate.as_ref(),
|
||||||
|
&container_dir,
|
||||||
|
statuses.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = binary.as_ref() {
|
||||||
|
if let Some(binary) = adapter
|
||||||
|
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
|
||||||
|
return Ok(binary);
|
||||||
|
} else {
|
||||||
|
statuses.send(
|
||||||
|
language.clone(),
|
||||||
|
LanguageServerBinaryStatus::Failed {
|
||||||
|
error: format!("{:?}", error),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binary
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_binary(
|
||||||
|
adapter: Arc<CachedLspAdapter>,
|
||||||
|
language: Arc<Language>,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
container_dir: &Path,
|
||||||
|
lsp_binary_statuses_tx: LspBinaryStatusSender,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let container_dir: Arc<Path> = container_dir.into();
|
||||||
|
lsp_binary_statuses_tx.send(
|
||||||
|
language.clone(),
|
||||||
|
LanguageServerBinaryStatus::CheckingForUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let version_info = adapter.fetch_latest_server_version(delegate).await?;
|
||||||
|
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
|
||||||
|
|
||||||
|
let binary = adapter
|
||||||
|
.fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
|
||||||
|
.await?;
|
||||||
|
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloaded);
|
||||||
|
|
||||||
|
Ok(binary)
|
||||||
|
}
|
|
@ -2861,10 +2861,8 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
|
||||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
|
|
||||||
let languages = project.update(cx, |project, _| project.languages().clone());
|
let languages = project.update(cx, |project, _| project.languages().clone());
|
||||||
languages.add_grammars([("rust", tree_sitter_rust::language())]);
|
languages.register_native_grammars([("rust", tree_sitter_rust::language())]);
|
||||||
languages.register(
|
languages.register_test_language(LanguageConfig {
|
||||||
"/some/path",
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
name: "Rust".into(),
|
||||||
grammar: Some("rust".into()),
|
grammar: Some("rust".into()),
|
||||||
matcher: LanguageMatcher {
|
matcher: LanguageMatcher {
|
||||||
|
@ -2872,10 +2870,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
});
|
||||||
vec![],
|
|
||||||
|_| Default::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let buffer = project.update(cx, |project, cx| {
|
let buffer = project.update(cx, |project, cx| {
|
||||||
project.create_buffer("", None, cx).unwrap()
|
project.create_buffer("", None, cx).unwrap()
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub fn init(
|
||||||
ElixirSettings::register(cx);
|
ElixirSettings::register(cx);
|
||||||
DenoSettings::register(cx);
|
DenoSettings::register(cx);
|
||||||
|
|
||||||
languages.add_grammars([
|
languages.register_native_grammars([
|
||||||
("bash", tree_sitter_bash::language()),
|
("bash", tree_sitter_bash::language()),
|
||||||
("beancount", tree_sitter_beancount::language()),
|
("beancount", tree_sitter_beancount::language()),
|
||||||
("c", tree_sitter_c::language()),
|
("c", tree_sitter_c::language()),
|
||||||
|
@ -115,8 +115,15 @@ pub fn init(
|
||||||
("zig", tree_sitter_zig::language()),
|
("zig", tree_sitter_zig::language()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let language = |name: &'static str, adapters| {
|
let language = |asset_dir_name: &'static str, adapters| {
|
||||||
languages.register(name, load_config(name), adapters, load_queries)
|
let config = load_config(asset_dir_name);
|
||||||
|
languages.register_language(
|
||||||
|
config.name.clone(),
|
||||||
|
config.grammar.clone(),
|
||||||
|
config.matcher.clone(),
|
||||||
|
adapters,
|
||||||
|
move || Ok((config.clone(), load_queries(asset_dir_name))),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
language("bash", vec![]);
|
language("bash", vec![]);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue