@@ -9,6 +9,7 @@
mod buffer;
mod diagnostic_set;
mod highlight_map;
+mod language_registry;
pub mod language_settings;
mod outline;
pub mod proto;
@@ -20,30 +21,22 @@ pub mod markdown;
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
-use collections::{hash_map, HashMap, HashSet};
-use futures::{
- channel::{mpsc, oneshot},
- future::Shared,
- FutureExt, TryFutureExt as _,
-};
-use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
+use collections::{HashMap, HashSet};
+use gpui::{AppContext, AsyncAppContext, Task};
pub use highlight_map::HighlightMap;
use lazy_static::lazy_static;
use lsp::{CodeActionKind, LanguageServerBinary};
-use parking_lot::{Mutex, RwLock};
-use postage::watch;
+use parking_lot::Mutex;
use regex::Regex;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::{
any::Any,
- borrow::Cow,
cell::RefCell,
- ffi::OsStr,
fmt::Debug,
hash::Hash,
mem,
- ops::{Not, Range},
+ ops::Range,
path::{Path, PathBuf},
str,
sync::{
@@ -52,15 +45,17 @@ use std::{
},
};
use syntax_map::SyntaxSnapshot;
-use theme::{SyntaxTheme, Theme};
+use theme::SyntaxTheme;
use tree_sitter::{self, wasmtime, Query, WasmStore};
-use unicase::UniCase;
-use util::{http::HttpClient, paths::PathExt};
-use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
+use util::http::HttpClient;
pub use buffer::Operation;
pub use buffer::*;
pub use diagnostic_set::DiagnosticEntry;
+pub use language_registry::{
+ LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, PendingLanguageServer,
+ QUERY_FILENAME_PREFIXES,
+};
pub use lsp::LanguageServerId;
pub use outline::{Outline, OutlineItem};
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
@@ -74,27 +69,6 @@ pub fn init(cx: &mut AppContext) {
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! {
static PARSER: RefCell<Parser> = {
let mut parser = Parser::new();
@@ -104,10 +78,11 @@ thread_local! {
}
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.
- #[doc(hidden)]
- pub static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
LanguageConfig {
name: "Plain Text".into(),
@@ -457,33 +432,6 @@ pub struct LanguageMatcher {
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)
/// interleave several languages together, thus a single buffer might actually contain
/// several nested scopes.
@@ -650,7 +598,7 @@ pub struct Language {
#[cfg(any(test, feature = "test-support"))]
fake_adapter: Option<(
- mpsc::UnboundedSender<lsp::FakeLanguageServer>,
+ futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>,
Arc<FakeLspAdapter>,
)>,
}
@@ -725,775 +673,6 @@ struct BracketConfig {
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 {
pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
Self {
@@ -1831,8 +1010,8 @@ impl Language {
pub async fn set_fake_lsp_adapter(
&mut self,
fake_lsp_adapter: Arc<FakeLspAdapter>,
- ) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
- let (servers_tx, servers_rx) = mpsc::unbounded();
+ ) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
+ let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
self.adapters = vec![adapter];
@@ -2255,19 +1434,14 @@ mod tests {
languages.set_executor(cx.executor());
let languages = Arc::new(languages);
- languages.register(
- "/javascript",
- LanguageConfig {
- name: "JavaScript".into(),
- matcher: LanguageMatcher {
- path_suffixes: vec!["js".into()],
- first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
- },
- ..Default::default()
+ languages.register_test_language(LanguageConfig {
+ name: "JavaScript".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["js".into()],
+ first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
},
- vec![],
- |_| Default::default(),
- );
+ ..Default::default()
+ });
languages
.language_for_file("the/script", None)
@@ -2293,38 +1467,28 @@ mod tests {
let mut languages = LanguageRegistry::test();
languages.set_executor(cx.executor());
let languages = Arc::new(languages);
- languages.add_grammars([
+ languages.register_native_grammars([
("json", tree_sitter_json::language()),
("rust", tree_sitter_rust::language()),
]);
- languages.register(
- "/JSON",
- LanguageConfig {
- name: "JSON".into(),
- grammar: Some("json".into()),
- matcher: LanguageMatcher {
- path_suffixes: vec!["json".into()],
- ..Default::default()
- },
+ languages.register_test_language(LanguageConfig {
+ name: "JSON".into(),
+ grammar: Some("json".into()),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["json".into()],
..Default::default()
},
- vec![],
- |_| Default::default(),
- );
- languages.register(
- "/rust",
- LanguageConfig {
- name: "Rust".into(),
- grammar: Some("rust".into()),
- matcher: LanguageMatcher {
- path_suffixes: vec!["rs".into()],
- ..Default::default()
- },
+ ..Default::default()
+ });
+ languages.register_test_language(LanguageConfig {
+ name: "Rust".into(),
+ grammar: Some("rust".into()),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".into()],
..Default::default()
},
- vec![],
- |_| Default::default(),
- );
+ ..Default::default()
+ });
assert_eq!(
languages.language_names(),
&[
@@ -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)
+}