Merge pull request #702 from zed-industries/typescript
Add support for JS/Typescript/TSX, allow language servers to support multiple languages
This commit is contained in:
commit
79bd8642e6
51 changed files with 2444 additions and 1641 deletions
|
@ -1,8 +1,7 @@
|
|||
pub use crate::{
|
||||
diagnostic_set::DiagnosticSet,
|
||||
highlight_map::{HighlightId, HighlightMap},
|
||||
proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, LanguageServerConfig,
|
||||
PLAIN_TEXT,
|
||||
proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT,
|
||||
};
|
||||
use crate::{
|
||||
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
||||
|
|
|
@ -34,6 +34,23 @@ pub struct Summary {
|
|||
count: usize,
|
||||
}
|
||||
|
||||
impl<T> DiagnosticEntry<T> {
|
||||
// Used to provide diagnostic context to lsp codeAction request
|
||||
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
|
||||
let code = self
|
||||
.diagnostic
|
||||
.code
|
||||
.clone()
|
||||
.map(lsp::NumberOrString::String);
|
||||
|
||||
lsp::Diagnostic {
|
||||
code,
|
||||
severity: Some(self.diagnostic.severity),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiagnosticSet {
|
||||
pub fn from_sorted_entries<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
|
||||
where
|
||||
|
|
|
@ -7,8 +7,8 @@ pub mod proto;
|
|||
mod tests;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::http::{self, HttpClient};
|
||||
use collections::HashSet;
|
||||
use client::http::HttpClient;
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
future::{BoxFuture, Shared},
|
||||
FutureExt, TryFutureExt,
|
||||
|
@ -20,6 +20,7 @@ use parking_lot::{Mutex, RwLock};
|
|||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
|
@ -51,7 +52,6 @@ lazy_static! {
|
|||
brackets: Default::default(),
|
||||
autoclose_before: Default::default(),
|
||||
line_comment: None,
|
||||
language_server: None,
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
@ -61,20 +61,18 @@ pub trait ToLspPosition {
|
|||
fn to_lsp_position(self) -> lsp::Position;
|
||||
}
|
||||
|
||||
pub struct LspBinaryVersion {
|
||||
pub name: String,
|
||||
pub url: Option<http::Url>,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LanguageServerName(pub Arc<str>);
|
||||
|
||||
pub trait LspAdapter: 'static + Send + Sync {
|
||||
fn name(&self) -> &'static str;
|
||||
fn name(&self) -> LanguageServerName;
|
||||
fn fetch_latest_server_version(
|
||||
&self,
|
||||
http: Arc<dyn HttpClient>,
|
||||
) -> BoxFuture<'static, Result<LspBinaryVersion>>;
|
||||
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>>;
|
||||
fn fetch_server_binary(
|
||||
&self,
|
||||
version: LspBinaryVersion,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
http: Arc<dyn HttpClient>,
|
||||
container_dir: PathBuf,
|
||||
) -> BoxFuture<'static, Result<PathBuf>>;
|
||||
|
@ -96,6 +94,14 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
fn initialization_options(&self) -> Option<Value> {
|
||||
None
|
||||
}
|
||||
|
||||
fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -113,7 +119,6 @@ pub struct LanguageConfig {
|
|||
#[serde(default)]
|
||||
pub autoclose_before: String,
|
||||
pub line_comment: Option<String>,
|
||||
pub language_server: Option<LanguageServerConfig>,
|
||||
}
|
||||
|
||||
impl Default for LanguageConfig {
|
||||
|
@ -124,25 +129,17 @@ impl Default for LanguageConfig {
|
|||
brackets: Default::default(),
|
||||
autoclose_before: Default::default(),
|
||||
line_comment: Default::default(),
|
||||
language_server: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct LanguageServerConfig {
|
||||
pub disk_based_diagnostic_sources: HashSet<String>,
|
||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[serde(skip)]
|
||||
fake_config: Option<FakeLanguageServerConfig>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
struct FakeLanguageServerConfig {
|
||||
servers_tx: mpsc::UnboundedSender<lsp::FakeLanguageServer>,
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
|
||||
pub struct FakeLspAdapter {
|
||||
pub name: &'static str,
|
||||
pub capabilities: lsp::ServerCapabilities,
|
||||
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
|
||||
pub disk_based_diagnostics_progress_token: Option<&'static str>,
|
||||
pub disk_based_diagnostics_sources: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
@ -157,7 +154,12 @@ pub struct Language {
|
|||
pub(crate) config: LanguageConfig,
|
||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||
pub(crate) adapter: Option<Arc<dyn LspAdapter>>,
|
||||
lsp_binary_path: Mutex<Option<Shared<BoxFuture<'static, Result<PathBuf, Arc<anyhow::Error>>>>>>,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_adapter: Option<(
|
||||
mpsc::UnboundedSender<lsp::FakeLanguageServer>,
|
||||
Arc<FakeLspAdapter>,
|
||||
)>,
|
||||
}
|
||||
|
||||
pub struct Grammar {
|
||||
|
@ -184,6 +186,12 @@ pub struct LanguageRegistry {
|
|||
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||
lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||
login_shell_env_loaded: Shared<Task<()>>,
|
||||
lsp_binary_paths: Mutex<
|
||||
HashMap<
|
||||
LanguageServerName,
|
||||
Shared<BoxFuture<'static, Result<PathBuf, Arc<anyhow::Error>>>>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl LanguageRegistry {
|
||||
|
@ -195,6 +203,7 @@ impl LanguageRegistry {
|
|||
lsp_binary_statuses_tx,
|
||||
lsp_binary_statuses_rx,
|
||||
login_shell_env_loaded: login_shell_env_loaded.shared(),
|
||||
lsp_binary_paths: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,7 +253,7 @@ impl LanguageRegistry {
|
|||
}
|
||||
|
||||
pub fn start_language_server(
|
||||
&self,
|
||||
self: &Arc<Self>,
|
||||
server_id: usize,
|
||||
language: Arc<Language>,
|
||||
root_path: Arc<Path>,
|
||||
|
@ -252,34 +261,20 @@ impl LanguageRegistry {
|
|||
cx: &mut MutableAppContext,
|
||||
) -> Option<Task<Result<lsp::LanguageServer>>> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if language
|
||||
.config
|
||||
.language_server
|
||||
.as_ref()
|
||||
.and_then(|config| config.fake_config.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
if language.fake_adapter.is_some() {
|
||||
let language = language.clone();
|
||||
return Some(cx.spawn(|mut cx| async move {
|
||||
let fake_config = language
|
||||
.config
|
||||
.language_server
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.fake_config
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let (server, mut fake_server) = cx.update(|cx| {
|
||||
lsp::LanguageServer::fake_with_capabilities(
|
||||
fake_config.capabilities.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
if let Some(initializer) = &fake_config.initializer {
|
||||
return Some(cx.spawn(|cx| async move {
|
||||
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
||||
let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities(
|
||||
fake_adapter.capabilities.clone(),
|
||||
cx.clone(),
|
||||
);
|
||||
|
||||
if let Some(initializer) = &fake_adapter.initializer {
|
||||
initializer(&mut fake_server);
|
||||
}
|
||||
|
||||
let servers_tx = fake_config.servers_tx.clone();
|
||||
let servers_tx = servers_tx.clone();
|
||||
cx.background()
|
||||
.spawn(async move {
|
||||
fake_server
|
||||
|
@ -298,16 +293,17 @@ impl LanguageRegistry {
|
|||
.ok_or_else(|| anyhow!("language server download directory has not been assigned"))
|
||||
.log_err()?;
|
||||
|
||||
let this = self.clone();
|
||||
let adapter = language.adapter.clone()?;
|
||||
let background = cx.background().clone();
|
||||
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||
Some(cx.background().spawn(async move {
|
||||
Some(cx.spawn(|cx| async move {
|
||||
login_shell_env_loaded.await;
|
||||
let server_binary_path = language
|
||||
.lsp_binary_path
|
||||
let server_binary_path = this
|
||||
.lsp_binary_paths
|
||||
.lock()
|
||||
.get_or_insert_with(|| {
|
||||
.entry(adapter.name())
|
||||
.or_insert_with(|| {
|
||||
get_server_binary_path(
|
||||
adapter.clone(),
|
||||
language.clone(),
|
||||
|
@ -329,8 +325,7 @@ impl LanguageRegistry {
|
|||
&server_binary_path,
|
||||
server_args,
|
||||
&root_path,
|
||||
adapter.initialization_options(),
|
||||
background,
|
||||
cx,
|
||||
)?;
|
||||
Ok(server)
|
||||
}))
|
||||
|
@ -350,7 +345,7 @@ async fn get_server_binary_path(
|
|||
download_dir: Arc<Path>,
|
||||
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||
) -> Result<PathBuf> {
|
||||
let container_dir = download_dir.join(adapter.name());
|
||||
let container_dir = download_dir.join(adapter.name().0.as_ref());
|
||||
if !container_dir.exists() {
|
||||
smol::fs::create_dir_all(&container_dir)
|
||||
.await
|
||||
|
@ -423,10 +418,16 @@ impl Language {
|
|||
})
|
||||
}),
|
||||
adapter: None,
|
||||
lsp_binary_path: Default::default(),
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_adapter: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lsp_adapter(&self) -> Option<Arc<dyn LspAdapter>> {
|
||||
self.adapter.clone()
|
||||
}
|
||||
|
||||
pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self
|
||||
.grammar
|
||||
|
@ -467,11 +468,23 @@ impl Language {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_lsp_adapter(mut self, lsp_adapter: impl LspAdapter) -> Self {
|
||||
self.adapter = Some(Arc::new(lsp_adapter));
|
||||
pub fn with_lsp_adapter(mut self, lsp_adapter: Arc<dyn LspAdapter>) -> Self {
|
||||
self.adapter = Some(lsp_adapter);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_fake_lsp_adapter(
|
||||
&mut self,
|
||||
fake_lsp_adapter: FakeLspAdapter,
|
||||
) -> mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
let (servers_tx, servers_rx) = mpsc::unbounded();
|
||||
let adapter = Arc::new(fake_lsp_adapter);
|
||||
self.fake_adapter = Some((servers_tx, adapter.clone()));
|
||||
self.adapter = Some(adapter);
|
||||
servers_rx
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Arc<str> {
|
||||
self.config.name.clone()
|
||||
}
|
||||
|
@ -480,18 +493,16 @@ impl Language {
|
|||
self.config.line_comment.as_deref()
|
||||
}
|
||||
|
||||
pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
|
||||
self.config
|
||||
.language_server
|
||||
.as_ref()
|
||||
.map(|config| &config.disk_based_diagnostic_sources)
|
||||
pub fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] {
|
||||
self.adapter.as_ref().map_or(&[] as &[_], |adapter| {
|
||||
adapter.disk_based_diagnostic_sources()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> {
|
||||
self.config
|
||||
.language_server
|
||||
pub fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> {
|
||||
self.adapter
|
||||
.as_ref()
|
||||
.and_then(|config| config.disk_based_diagnostics_progress_token.as_ref())
|
||||
.and_then(|adapter| adapter.disk_based_diagnostics_progress_token())
|
||||
}
|
||||
|
||||
pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
|
||||
|
@ -598,47 +609,70 @@ impl CodeLabel {
|
|||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl LanguageServerConfig {
|
||||
pub fn fake() -> (Self, mpsc::UnboundedReceiver<lsp::FakeLanguageServer>) {
|
||||
let (servers_tx, servers_rx) = mpsc::unbounded();
|
||||
(
|
||||
Self {
|
||||
fake_config: Some(FakeLanguageServerConfig {
|
||||
servers_tx,
|
||||
capabilities: lsp::LanguageServer::full_capabilities(),
|
||||
initializer: None,
|
||||
}),
|
||||
disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
servers_rx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_fake_capabilities(&mut self, capabilities: lsp::ServerCapabilities) {
|
||||
self.fake_config.as_mut().unwrap().capabilities = capabilities;
|
||||
}
|
||||
|
||||
pub fn set_fake_initializer(
|
||||
&mut self,
|
||||
initializer: impl 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer),
|
||||
) {
|
||||
self.fake_config.as_mut().unwrap().initializer = Some(Box::new(initializer));
|
||||
impl Default for FakeLspAdapter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "the-fake-language-server",
|
||||
capabilities: lsp::LanguageServer::full_capabilities(),
|
||||
initializer: None,
|
||||
disk_based_diagnostics_progress_token: None,
|
||||
disk_based_diagnostics_sources: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToLspPosition for PointUtf16 {
|
||||
fn to_lsp_position(self) -> lsp::Position {
|
||||
lsp::Position::new(self.row, self.column)
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl LspAdapter for FakeLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName(self.name.into())
|
||||
}
|
||||
|
||||
fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: Arc<dyn HttpClient>,
|
||||
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn fetch_server_binary(
|
||||
&self,
|
||||
_: Box<dyn 'static + Send + Any>,
|
||||
_: Arc<dyn HttpClient>,
|
||||
_: PathBuf,
|
||||
) -> BoxFuture<'static, Result<PathBuf>> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn cached_server_binary(&self, _: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] {
|
||||
self.disk_based_diagnostics_sources
|
||||
}
|
||||
|
||||
fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> {
|
||||
self.disk_based_diagnostics_progress_token
|
||||
}
|
||||
}
|
||||
|
||||
pub fn point_to_lsp(point: PointUtf16) -> lsp::Position {
|
||||
lsp::Position::new(point.row, point.column)
|
||||
}
|
||||
|
||||
pub fn point_from_lsp(point: lsp::Position) -> PointUtf16 {
|
||||
PointUtf16::new(point.line, point.character)
|
||||
}
|
||||
|
||||
pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
|
||||
let start = PointUtf16::new(range.start.line, range.start.character);
|
||||
let end = PointUtf16::new(range.end.line, range.end.character);
|
||||
start..end
|
||||
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
|
||||
lsp::Range {
|
||||
start: point_to_lsp(range.start),
|
||||
end: point_to_lsp(range.end),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
|
||||
point_from_lsp(range.start)..point_from_lsp(range.end)
|
||||
}
|
||||
|
|
|
@ -927,7 +927,6 @@ fn rust_lang() -> Language {
|
|||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
language_server: None,
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue