ZIm/crates/language/src/language_registry.rs
Piotr Osiewicz c6e2d20a02
chore: Bump Rust version to 1.86 (#28021)
Closes #ISSUE

Release Notes:

- N/A
2025-04-03 23:32:50 +02:00

1120 lines
38 KiB
Rust

use crate::{
CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
LanguageServerName, LspAdapter, PLAIN_TEXT, ToolchainLister,
language_settings::{
AllLanguageSettingsContent, LanguageSettingsContent, all_language_settings,
},
task_context::ContextProvider,
with_parser,
};
use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet, hash_map};
use futures::{
Future,
channel::{mpsc, oneshot},
};
use globset::GlobSet;
use gpui::{App, BackgroundExecutor, SharedString};
use lsp::LanguageServerId;
use parking_lot::{Mutex, RwLock};
use postage::watch;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
borrow::{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::{ResultExt, maybe, post_inc};
#[derive(
Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
pub struct LanguageName(SharedString);
impl LanguageName {
pub fn new(s: &str) -> Self {
Self(SharedString::new(s))
}
pub fn from_proto(s: String) -> Self {
Self(SharedString::from(s))
}
pub fn to_proto(self) -> String {
self.0.to_string()
}
pub fn lsp_id(&self) -> String {
match self.0.as_ref() {
"Plain Text" => "plaintext".to_string(),
language_name => language_name.to_lowercase(),
}
}
}
impl From<LanguageName> for SharedString {
fn from(value: LanguageName) -> Self {
value.0
}
}
impl AsRef<str> for LanguageName {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl Borrow<str> for LanguageName {
fn borrow(&self) -> &str {
self.0.as_ref()
}
}
impl std::fmt::Display for LanguageName {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> From<&'a str> for LanguageName {
fn from(str: &'a str) -> LanguageName {
LanguageName(SharedString::new(str))
}
}
impl From<LanguageName> for String {
fn from(value: LanguageName) -> Self {
let value: &str = &value.0;
Self::from(value)
}
}
pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>,
language_server_download_dir: Option<Arc<Path>>,
executor: BackgroundExecutor,
lsp_binary_status_tx: BinaryStatusSender,
dap_binary_status_tx: BinaryStatusSender,
}
struct LanguageRegistryState {
next_language_server_id: usize,
languages: Vec<Arc<Language>>,
language_settings: AllLanguageSettingsContent,
available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
all_lsp_adapters: HashMap<LanguageServerName, Arc<CachedLspAdapter>>,
available_lsp_adapters:
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
subscription: (watch::Sender<()>, watch::Receiver<()>),
theme: Option<Arc<Theme>>,
version: usize,
reload_count: usize,
#[cfg(any(test, feature = "test-support"))]
fake_server_entries: HashMap<LanguageServerName, FakeLanguageServerEntry>,
}
#[cfg(any(test, feature = "test-support"))]
pub struct FakeLanguageServerEntry {
pub capabilities: lsp::ServerCapabilities,
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
pub tx: futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>,
pub _server: Option<lsp::FakeLanguageServer>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BinaryStatus {
None,
CheckingForUpdate,
Downloading,
Failed { error: String },
}
#[derive(Clone)]
pub struct AvailableLanguage {
id: LanguageId,
name: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
loaded: bool,
}
impl AvailableLanguage {
pub fn name(&self) -> LanguageName {
self.name.clone()
}
pub fn matcher(&self) -> &LanguageMatcher {
&self.matcher
}
pub fn hidden(&self) -> bool {
self.hidden
}
}
enum AvailableGrammar {
Native(tree_sitter::Language),
Loaded(#[allow(unused)] PathBuf, tree_sitter::Language),
Loading(
#[allow(unused)] PathBuf,
Vec<oneshot::Sender<Result<tree_sitter::Language, Arc<anyhow::Error>>>>,
),
Unloaded(PathBuf),
LoadFailed(Arc<anyhow::Error>),
}
#[derive(Debug)]
pub struct LanguageNotFound;
impl std::fmt::Display for LanguageNotFound {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "language not found")
}
}
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),
("runnables", |q| &mut q.runnables),
("textobjects", |q| &mut q.text_objects),
];
/// 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>>,
pub runnables: Option<Cow<'static, str>>,
pub text_objects: Option<Cow<'static, str>>,
}
#[derive(Clone, Default)]
struct BinaryStatusSender {
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(SharedString, BinaryStatus)>>>>,
}
pub struct LoadedLanguage {
pub config: LanguageConfig,
pub queries: LanguageQueries,
pub context_provider: Option<Arc<dyn ContextProvider>>,
pub toolchain_provider: Option<Arc<dyn ToolchainLister>>,
}
impl LanguageRegistry {
pub fn new(executor: BackgroundExecutor) -> Self {
let this = Self {
state: RwLock::new(LanguageRegistryState {
next_language_server_id: 0,
languages: Vec::new(),
available_languages: Vec::new(),
grammars: Default::default(),
language_settings: Default::default(),
loading_languages: Default::default(),
lsp_adapters: Default::default(),
all_lsp_adapters: Default::default(),
available_lsp_adapters: HashMap::default(),
subscription: watch::channel(),
theme: Default::default(),
version: 0,
reload_count: 0,
#[cfg(any(test, feature = "test-support"))]
fake_server_entries: Default::default(),
}),
language_server_download_dir: None,
lsp_binary_status_tx: Default::default(),
dap_binary_status_tx: Default::default(),
executor,
};
this.add(PLAIN_TEXT.clone());
this
}
#[cfg(any(test, feature = "test-support"))]
pub fn test(executor: BackgroundExecutor) -> Self {
let mut this = Self::new(executor);
this.language_server_download_dir = Some(Path::new("/the-download-dir").into());
this
}
/// Clears out all of the loaded languages and reload them from scratch.
pub fn reload(&self) {
self.state.write().reload();
}
/// Reorders the list of language servers for the given language.
///
/// Uses the provided list of ordered [`CachedLspAdapters`] as the desired order.
///
/// Any existing language servers not present in `ordered_lsp_adapters` will be
/// appended to the end.
pub fn reorder_language_servers(
&self,
language: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
self.state
.write()
.reorder_language_servers(language, ordered_lsp_adapters);
}
/// Removes the specified languages and grammars from the registry.
pub fn remove_languages(
&self,
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
self.state
.write()
.remove_languages(languages_to_remove, grammars_to_remove)
}
pub fn remove_lsp_adapter(&self, language_name: &LanguageName, name: &LanguageServerName) {
let mut state = self.state.write();
if let Some(adapters) = state.lsp_adapters.get_mut(language_name) {
adapters.retain(|adapter| &adapter.name != name)
}
state.version += 1;
state.reload_count += 1;
*state.subscription.0.borrow_mut() = ();
}
#[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(),
config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: Default::default(),
toolchain_provider: None,
context_provider: None,
})
}),
)
}
/// Registers an available language server adapter.
///
/// The language server is registered under the language server name, but
/// not bound to a particular language.
///
/// When a language wants to load this particular language server, it will
/// invoke the `load` function.
pub fn register_available_lsp_adapter(
&self,
name: LanguageServerName,
load: impl Fn() -> Arc<dyn LspAdapter> + 'static + Send + Sync,
) {
self.state.write().available_lsp_adapters.insert(
name,
Arc::new(move || {
let lsp_adapter = load();
CachedLspAdapter::new(lsp_adapter)
}),
);
}
/// Loads the language server adapter for the language server with the given name.
pub fn load_available_lsp_adapter(
&self,
name: &LanguageServerName,
) -> Option<Arc<CachedLspAdapter>> {
let state = self.state.read();
let load_lsp_adapter = state.available_lsp_adapters.get(name)?;
Some(load_lsp_adapter())
}
pub fn register_lsp_adapter(
&self,
language_name: LanguageName,
adapter: Arc<dyn LspAdapter>,
) -> Arc<CachedLspAdapter> {
let cached = CachedLspAdapter::new(adapter);
let mut state = self.state.write();
state
.lsp_adapters
.entry(language_name)
.or_default()
.push(cached.clone());
state
.all_lsp_adapters
.insert(cached.name.clone(), cached.clone());
cached
}
pub fn get_or_register_lsp_adapter(
&self,
language_name: LanguageName,
server_name: LanguageServerName,
build_adapter: impl FnOnce() -> Arc<dyn LspAdapter> + 'static,
) -> Arc<CachedLspAdapter> {
let registered = self
.state
.write()
.lsp_adapters
.entry(language_name.clone())
.or_default()
.iter()
.find(|cached_adapter| cached_adapter.name == server_name)
.cloned();
if let Some(found) = registered {
found
} else {
let adapter = build_adapter();
self.register_lsp_adapter(language_name, adapter)
}
}
/// Register a fake language server and adapter
/// The returned channel receives a new instance of the language server every time it is started
#[cfg(any(feature = "test-support", test))]
pub fn register_fake_lsp(
&self,
language_name: impl Into<LanguageName>,
mut adapter: crate::FakeLspAdapter,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
let language_name = language_name.into();
let adapter_name = LanguageServerName(adapter.name.into());
let capabilities = adapter.capabilities.clone();
let initializer = adapter.initializer.take();
let adapter = CachedLspAdapter::new(Arc::new(adapter));
{
let mut state = self.state.write();
state
.lsp_adapters
.entry(language_name.clone())
.or_default()
.push(adapter.clone());
state.all_lsp_adapters.insert(adapter.name(), adapter);
}
self.register_fake_language_server(adapter_name, capabilities, initializer)
}
/// Register a fake lsp adapter (without the language server)
/// The returned channel receives a new instance of the language server every time it is started
#[cfg(any(feature = "test-support", test))]
pub fn register_fake_lsp_adapter(
&self,
language_name: impl Into<LanguageName>,
adapter: crate::FakeLspAdapter,
) {
let language_name = language_name.into();
let mut state = self.state.write();
let cached_adapter = CachedLspAdapter::new(Arc::new(adapter));
state
.lsp_adapters
.entry(language_name.clone())
.or_default()
.push(cached_adapter.clone());
state
.all_lsp_adapters
.insert(cached_adapter.name(), cached_adapter);
}
/// Register a fake language server (without the adapter)
/// The returned channel receives a new instance of the language server every time it is started
#[cfg(any(feature = "test-support", test))]
pub fn register_fake_language_server(
&self,
lsp_name: LanguageServerName,
capabilities: lsp::ServerCapabilities,
initializer: Option<Box<dyn Fn(&mut lsp::FakeLanguageServer) + Send + Sync>>,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
self.state.write().fake_server_entries.insert(
lsp_name,
FakeLanguageServerEntry {
tx: servers_tx,
capabilities,
initializer,
_server: None,
},
);
servers_rx
}
/// Adds a language to the registry, which can be loaded if needed.
pub fn register_language(
&self,
name: LanguageName,
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
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.load = load;
return;
}
}
state.available_languages.push(AvailableLanguage {
id: LanguageId::new(),
name,
grammar: grammar_name,
matcher,
load,
hidden,
loaded: false,
});
state.version += 1;
state.reload_count += 1;
*state.subscription.0.borrow_mut() = ();
}
/// 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>>, impl Into<tree_sitter::Language>)>,
) {
self.state.write().grammars.extend(
grammars
.into_iter()
.map(|(name, grammar)| (name.into(), AvailableGrammar::Native(grammar.into()))),
);
}
/// 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)>,
) {
let mut state = self.state.write();
state.grammars.extend(
grammars
.into_iter()
.map(|(name, path)| (name.into(), AvailableGrammar::Unloaded(path))),
);
state.version += 1;
state.reload_count += 1;
*state.subscription.0.borrow_mut() = ();
}
pub fn language_settings(&self) -> AllLanguageSettingsContent {
self.state.read().language_settings.clone()
}
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 grammar_names(&self) -> Vec<Arc<str>> {
let state = self.state.read();
let mut result = state.grammars.keys().cloned().collect::<Vec<_>>();
result.sort_unstable_by_key(|grammar_name| grammar_name.to_lowercase());
result
}
/// Add a pre-loaded language to the registry.
pub fn add(&self, language: Arc<Language>) {
let mut state = self.state.write();
state.available_languages.push(AvailableLanguage {
id: language.id,
name: language.name(),
grammar: language.config.grammar.clone(),
matcher: language.config.matcher.clone(),
hidden: language.config.hidden,
load: Arc::new(|| Err(anyhow!("already loaded"))),
loaded: true,
});
state.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,
) -> impl Future<Output = Result<Arc<Language>>> + use<> {
let name = UniCase::new(name);
let rx = self.get_or_load_language(|language_name, _| {
if UniCase::new(&language_name.0) == name {
1
} else {
0
}
});
async move { rx.await? }
}
pub fn language_for_name_or_extension(
self: &Arc<Self>,
string: &str,
) -> impl Future<Output = Result<Arc<Language>>> {
let string = UniCase::new(string);
let rx = self.get_or_load_language(|name, config| {
if UniCase::new(&name.0) == string
|| config
.path_suffixes
.iter()
.any(|suffix| UniCase::new(suffix) == string)
{
1
} else {
0
}
});
async move { rx.await? }
}
pub fn available_language_for_name(self: &Arc<Self>, name: &str) -> Option<AvailableLanguage> {
let state = self.state.read();
state
.available_languages
.iter()
.find(|l| l.name.0.as_ref() == name)
.cloned()
}
pub fn language_for_file(
self: &Arc<Self>,
file: &Arc<dyn File>,
content: Option<&Rope>,
cx: &App,
) -> Option<AvailableLanguage> {
let user_file_types = all_language_settings(Some(file), cx);
self.language_for_file_internal(
&file.full_path(cx),
content,
Some(&user_file_types.file_types),
)
}
pub fn language_for_file_path<'a>(
self: &Arc<Self>,
path: &'a Path,
) -> impl Future<Output = Result<Arc<Language>>> + 'a {
let available_language = self.language_for_file_internal(path, None, None);
let this = self.clone();
async move {
if let Some(language) = available_language {
this.load_language(&language).await?
} else {
Err(anyhow!(LanguageNotFound))
}
}
}
fn language_for_file_internal(
self: &Arc<Self>,
path: &Path,
content: Option<&Rope>,
user_file_types: Option<&HashMap<Arc<str>, GlobSet>>,
) -> Option<AvailableLanguage> {
let filename = path.file_name().and_then(|name| name.to_str());
// `Path.extension()` returns None for files with a leading '.'
// and no other extension which is not the desired behavior here,
// as we want `.zshrc` to result in extension being `Some("zshrc")`
let extension = filename.and_then(|filename| filename.split('.').next_back());
let path_suffixes = [extension, filename, path.to_str()];
let empty = GlobSet::empty();
self.find_matching_language(move |language_name, config| {
let path_matches_default_suffix = config
.path_suffixes
.iter()
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
let custom_suffixes = user_file_types
.and_then(|types| types.get(language_name.as_ref()))
.unwrap_or(&empty);
let path_matches_custom_suffix = path_suffixes
.iter()
.map(|suffix| suffix.unwrap_or(""))
.any(|suffix| custom_suffixes.is_match(suffix));
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)
},
);
if path_matches_custom_suffix {
2
} else if path_matches_default_suffix || content_matches {
1
} else {
0
}
})
}
fn find_matching_language(
self: &Arc<Self>,
callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
) -> Option<AvailableLanguage> {
let state = self.state.read();
let available_language = state
.available_languages
.iter()
.filter_map(|language| {
let score = callback(&language.name, &language.matcher);
if score > 0 {
Some((language.clone(), score))
} else {
None
}
})
.max_by_key(|e| e.1)
.clone()
.map(|(available_language, _)| available_language);
drop(state);
available_language
}
pub fn load_language(
self: &Arc<Self>,
language: &AvailableLanguage,
) -> oneshot::Receiver<Result<Arc<Language>>> {
let (tx, rx) = oneshot::channel();
let mut state = self.state.write();
// If the language is already loaded, resolve with it immediately.
for loaded_language in state.languages.iter() {
if loaded_language.id == language.id {
tx.send(Ok(loaded_language.clone())).unwrap();
return rx;
}
}
match state.loading_languages.entry(language.id) {
// If the language is already being loaded, then add this
// channel to a list that will be sent to when the load completes.
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
// Otherwise, start loading the language.
hash_map::Entry::Vacant(entry) => {
let this = self.clone();
let id = language.id;
let name = language.name.clone();
let language_load = language.load.clone();
self.executor
.spawn(async move {
let language = async {
let loaded_language = (language_load)()?;
if let Some(grammar) = loaded_language.config.grammar.clone() {
let grammar = Some(this.get_or_load_grammar(grammar).await?);
Language::new_with_id(id, loaded_language.config, grammar)
.with_context_provider(loaded_language.context_provider)
.with_toolchain_lister(loaded_language.toolchain_provider)
.with_queries(loaded_language.queries)
} else {
Ok(Language::new_with_id(id, loaded_language.config, None)
.with_context_provider(loaded_language.context_provider)
.with_toolchain_lister(loaded_language.toolchain_provider))
}
}
.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]);
}
}
drop(state);
rx
}
fn get_or_load_language(
self: &Arc<Self>,
callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
) -> oneshot::Receiver<Result<Arc<Language>>> {
let Some(language) = self.find_matching_language(callback) else {
let (tx, rx) = oneshot::channel();
let _ = tx.send(Err(anyhow!(LanguageNotFound)));
return rx;
};
self.load_language(&language)
}
fn get_or_load_grammar(
self: &Arc<Self>,
name: Arc<str>,
) -> impl Future<Output = 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::LoadFailed(error) => {
tx.send(Err(error.clone())).ok();
}
AvailableGrammar::Native(grammar) | AvailableGrammar::Loaded(_, grammar) => {
tx.send(Ok(grammar.clone())).ok();
}
AvailableGrammar::Loading(_, txs) => {
txs.push(tx);
}
AvailableGrammar::Unloaded(wasm_path) => {
let this = self.clone();
let wasm_path = wasm_path.clone();
*grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
self.executor
.spawn(async move {
let grammar_result = maybe!({
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"))?;
anyhow::Ok(with_parser(|parser| {
let mut store = parser.take_wasm_store().unwrap();
let grammar = store.load_language(grammar_name, &wasm_bytes);
parser.set_wasm_store(store).unwrap();
grammar
})?)
})
.map_err(Arc::new);
let value = match &grammar_result {
Ok(grammar) => AvailableGrammar::Loaded(wasm_path, grammar.clone()),
Err(error) => AvailableGrammar::LoadFailed(error.clone()),
};
let old_value = this.state.write().grammars.insert(name, value);
if let Some(AvailableGrammar::Loading(_, txs)) = old_value {
for tx in txs {
tx.send(grammar_result.clone()).ok();
}
}
})
.detach();
}
}
} else {
tx.send(Err(Arc::new(anyhow!("no such grammar {}", name))))
.ok();
}
async move { rx.await?.map_err(|e| anyhow!(e)) }
}
pub fn to_vec(&self) -> Vec<Arc<Language>> {
self.state.read().languages.to_vec()
}
pub fn lsp_adapters(&self, language_name: &LanguageName) -> Vec<Arc<CachedLspAdapter>> {
self.state
.read()
.lsp_adapters
.get(language_name)
.cloned()
.unwrap_or_default()
}
pub fn all_lsp_adapters(&self) -> Vec<Arc<CachedLspAdapter>> {
self.state
.read()
.all_lsp_adapters
.values()
.cloned()
.collect()
}
pub fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
self.state.read().all_lsp_adapters.get(name).cloned()
}
pub fn update_lsp_status(&self, server_name: LanguageServerName, status: BinaryStatus) {
self.lsp_binary_status_tx.send(server_name.0, status);
}
pub fn update_dap_status(&self, server_name: LanguageServerName, status: BinaryStatus) {
self.dap_binary_status_tx.send(server_name.0, status);
}
pub fn next_language_server_id(&self) -> LanguageServerId {
self.state.write().next_language_server_id()
}
pub fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>> {
self.language_server_download_dir
.as_ref()
.map(|dir| Arc::from(dir.join(name.0.as_ref())))
}
#[cfg(any(test, feature = "test-support"))]
pub fn create_fake_language_server(
&self,
server_id: LanguageServerId,
name: &LanguageServerName,
binary: lsp::LanguageServerBinary,
cx: &mut gpui::AsyncApp,
) -> Option<lsp::LanguageServer> {
use gpui::AppContext as _;
let mut state = self.state.write();
let fake_entry = state.fake_server_entries.get_mut(&name)?;
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
server_id,
binary,
name.0.to_string(),
fake_entry.capabilities.clone(),
cx,
);
fake_entry._server = Some(fake_server.clone());
if let Some(initializer) = &fake_entry.initializer {
initializer(&mut fake_server);
}
let tx = fake_entry.tx.clone();
cx.background_spawn(async move {
if fake_server
.try_receive_notification::<lsp::notification::Initialized>()
.await
.is_some()
{
tx.unbounded_send(fake_server.clone()).ok();
}
})
.detach();
Some(server)
}
pub fn language_server_binary_statuses(
&self,
) -> mpsc::UnboundedReceiver<(SharedString, BinaryStatus)> {
self.lsp_binary_status_tx.subscribe()
}
pub fn dap_server_binary_statuses(
&self,
) -> mpsc::UnboundedReceiver<(SharedString, BinaryStatus)> {
self.dap_binary_status_tx.subscribe()
}
pub async fn delete_server_container(&self, name: LanguageServerName) {
log::info!("deleting server container");
let Some(dir) = self.language_server_download_dir(&name) else {
return;
};
smol::fs::remove_dir_all(dir)
.await
.context("server container removal")
.log_err();
}
}
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.language_settings.languages.insert(
language.name(),
LanguageSettingsContent {
tab_size: language.config.tab_size,
hard_tabs: language.config.hard_tabs,
soft_wrap: language.config.soft_wrap,
auto_indent_on_paste: language.config.auto_indent_on_paste,
..Default::default()
}
.clone(),
);
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() = ();
}
/// Reorders the list of language servers for the given language.
///
/// Uses the provided list of ordered [`CachedLspAdapters`] as the desired order.
///
/// Any existing language servers not present in `ordered_lsp_adapters` will be
/// appended to the end.
fn reorder_language_servers(
&mut self,
language_name: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
let Some(lsp_adapters) = self.lsp_adapters.get_mut(language_name) else {
return;
};
let ordered_lsp_adapter_ids = ordered_lsp_adapters
.iter()
.map(|lsp_adapter| lsp_adapter.name.clone())
.collect::<HashSet<_>>();
let mut new_lsp_adapters = ordered_lsp_adapters;
for adapter in lsp_adapters.iter() {
if !ordered_lsp_adapter_ids.contains(&adapter.name) {
new_lsp_adapters.push(adapter.clone());
}
}
*lsp_adapters = new_lsp_adapters;
}
fn remove_languages(
&mut self,
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
if languages_to_remove.is_empty() && grammars_to_remove.is_empty() {
return;
}
self.languages
.retain(|language| !languages_to_remove.contains(&language.name()));
self.available_languages
.retain(|language| !languages_to_remove.contains(&language.name));
self.grammars
.retain(|name, _| !grammars_to_remove.contains(name));
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: LanguageId) {
for language in &mut self.available_languages {
if language.id == id {
language.loaded = true;
break;
}
}
}
}
impl BinaryStatusSender {
fn subscribe(&self) -> mpsc::UnboundedReceiver<(SharedString, BinaryStatus)> {
let (tx, rx) = mpsc::unbounded();
self.txs.lock().push(tx);
rx
}
fn send(&self, name: SharedString, status: BinaryStatus) {
let mut txs = self.txs.lock();
txs.retain(|tx| tx.unbounded_send((name.clone(), status.clone())).is_ok());
}
}