mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 10:29:35 +03:00
Add Clojure language support with tree-sitter and LSP (#6988)
Current limitations: * Not able to navigate into JAR files Release Notes: - Added Clojure language support --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
parent
b4b59f8706
commit
a159183f52
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -9149,6 +9149,15 @@ dependencies = [
|
|||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-clojure"
|
||||||
|
version = "0.0.9"
|
||||||
|
source = "git+https://github.com/prcastro/tree-sitter-clojure?branch=update-ts#38b4f8d264248b2fd09575fbce66f7c22e8929d5"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"tree-sitter",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter-cpp"
|
name = "tree-sitter-cpp"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
@ -10841,6 +10850,7 @@ dependencies = [
|
|||||||
"tree-sitter-beancount",
|
"tree-sitter-beancount",
|
||||||
"tree-sitter-c",
|
"tree-sitter-c",
|
||||||
"tree-sitter-c-sharp",
|
"tree-sitter-c-sharp",
|
||||||
|
"tree-sitter-clojure",
|
||||||
"tree-sitter-cpp",
|
"tree-sitter-cpp",
|
||||||
"tree-sitter-css",
|
"tree-sitter-css",
|
||||||
"tree-sitter-elixir",
|
"tree-sitter-elixir",
|
||||||
|
@ -226,6 +226,7 @@ tree-sitter = { version = "0.20", features = ["wasm"] }
|
|||||||
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
|
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
|
||||||
tree-sitter-beancount = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "da1bf8c6eb0ae7a97588affde7227630bcd678b6" }
|
tree-sitter-beancount = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "da1bf8c6eb0ae7a97588affde7227630bcd678b6" }
|
||||||
tree-sitter-c = "0.20.1"
|
tree-sitter-c = "0.20.1"
|
||||||
|
tree-sitter-clojure = { git = "https://github.com/prcastro/tree-sitter-clojure", branch = "update-ts"}
|
||||||
tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
|
tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
|
||||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
|
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
|
||||||
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
||||||
|
@ -444,7 +444,6 @@ impl Copilot {
|
|||||||
|_, _| { /* Silence the notification */ },
|
|_, _| { /* Silence the notification */ },
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let server = cx.update(|cx| server.initialize(None, cx))?.await?;
|
let server = cx.update(|cx| server.initialize(None, cx))?.await?;
|
||||||
|
|
||||||
let status = server
|
let status = server
|
||||||
|
@ -28,7 +28,7 @@ use futures::{
|
|||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
|
AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
|
||||||
Model, ModelContext, Task, WeakModel,
|
Model, ModelContext, PromptLevel, Task, WeakModel,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
@ -47,7 +47,8 @@ use language::{
|
|||||||
use log::error;
|
use log::error;
|
||||||
use lsp::{
|
use lsp::{
|
||||||
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
|
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
|
||||||
DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf,
|
DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId,
|
||||||
|
MessageActionItem, OneOf,
|
||||||
};
|
};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
@ -213,12 +214,37 @@ enum ProjectClientState {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A prompt requested by LSP server.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LanguageServerPromptRequest {
|
||||||
|
pub level: PromptLevel,
|
||||||
|
pub message: String,
|
||||||
|
pub actions: Vec<MessageActionItem>,
|
||||||
|
response_channel: Sender<MessageActionItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServerPromptRequest {
|
||||||
|
pub async fn respond(self, index: usize) -> Option<()> {
|
||||||
|
if let Some(response) = self.actions.into_iter().nth(index) {
|
||||||
|
self.response_channel.send(response).await.ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialEq for LanguageServerPromptRequest {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.message == other.message && self.actions == other.actions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
LanguageServerAdded(LanguageServerId),
|
LanguageServerAdded(LanguageServerId),
|
||||||
LanguageServerRemoved(LanguageServerId),
|
LanguageServerRemoved(LanguageServerId),
|
||||||
LanguageServerLog(LanguageServerId, String),
|
LanguageServerLog(LanguageServerId, String),
|
||||||
Notification(String),
|
Notification(String),
|
||||||
|
LanguageServerPrompt(LanguageServerPromptRequest),
|
||||||
ActiveEntryChanged(Option<ProjectEntryId>),
|
ActiveEntryChanged(Option<ProjectEntryId>),
|
||||||
ActivateProjectPanel,
|
ActivateProjectPanel,
|
||||||
WorktreeAdded,
|
WorktreeAdded,
|
||||||
@ -3105,6 +3131,42 @@ impl Project {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
language_server
|
||||||
|
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
||||||
|
let this = this.clone();
|
||||||
|
move |params, mut cx| {
|
||||||
|
let this = this.clone();
|
||||||
|
async move {
|
||||||
|
if let Some(actions) = params.actions {
|
||||||
|
let (tx, mut rx) = smol::channel::bounded(1);
|
||||||
|
let request = LanguageServerPromptRequest {
|
||||||
|
level: match params.typ {
|
||||||
|
lsp::MessageType::ERROR => PromptLevel::Critical,
|
||||||
|
lsp::MessageType::WARNING => PromptLevel::Warning,
|
||||||
|
_ => PromptLevel::Info,
|
||||||
|
},
|
||||||
|
message: params.message,
|
||||||
|
actions,
|
||||||
|
response_channel: tx,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(_) = this.update(&mut cx, |_, cx| {
|
||||||
|
cx.emit(Event::LanguageServerPrompt(request));
|
||||||
|
}) {
|
||||||
|
let response = rx.next().await;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
let disk_based_diagnostics_progress_token =
|
let disk_based_diagnostics_progress_token =
|
||||||
adapter.disk_based_diagnostics_progress_token.clone();
|
adapter.disk_based_diagnostics_progress_token.clone();
|
||||||
|
|
||||||
|
@ -571,6 +571,27 @@ impl Workspace {
|
|||||||
cx.new_view(|_| MessageNotification::new(message.clone()))
|
cx.new_view(|_| MessageNotification::new(message.clone()))
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
project::Event::LanguageServerPrompt(request) => {
|
||||||
|
let request = request.clone();
|
||||||
|
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
let messages = request
|
||||||
|
.actions
|
||||||
|
.iter()
|
||||||
|
.map(|action| action.title.as_str())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let index = cx
|
||||||
|
.update(|cx| {
|
||||||
|
cx.prompt(request.level, "", Some(&request.message), &messages)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
request.respond(index).await;
|
||||||
|
|
||||||
|
Result::<(), anyhow::Error>::Ok(())
|
||||||
|
})
|
||||||
|
.detach()
|
||||||
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
cx.notify()
|
cx.notify()
|
||||||
|
@ -112,6 +112,7 @@ tree-sitter-bash.workspace = true
|
|||||||
tree-sitter-beancount.workspace = true
|
tree-sitter-beancount.workspace = true
|
||||||
tree-sitter-c-sharp.workspace = true
|
tree-sitter-c-sharp.workspace = true
|
||||||
tree-sitter-c.workspace = true
|
tree-sitter-c.workspace = true
|
||||||
|
tree-sitter-clojure.workspace = true
|
||||||
tree-sitter-cpp.workspace = true
|
tree-sitter-cpp.workspace = true
|
||||||
tree-sitter-css.workspace = true
|
tree-sitter-css.workspace = true
|
||||||
tree-sitter-elixir.workspace = true
|
tree-sitter-elixir.workspace = true
|
||||||
|
@ -10,6 +10,7 @@ use util::asset_str;
|
|||||||
use self::{deno::DenoSettings, elixir::ElixirSettings};
|
use self::{deno::DenoSettings, elixir::ElixirSettings};
|
||||||
|
|
||||||
mod c;
|
mod c;
|
||||||
|
mod clojure;
|
||||||
mod csharp;
|
mod csharp;
|
||||||
mod css;
|
mod css;
|
||||||
mod deno;
|
mod deno;
|
||||||
@ -68,6 +69,7 @@ pub fn init(
|
|||||||
("beancount", tree_sitter_beancount::language()),
|
("beancount", tree_sitter_beancount::language()),
|
||||||
("c", tree_sitter_c::language()),
|
("c", tree_sitter_c::language()),
|
||||||
("c_sharp", tree_sitter_c_sharp::language()),
|
("c_sharp", tree_sitter_c_sharp::language()),
|
||||||
|
("clojure", tree_sitter_clojure::language()),
|
||||||
("cpp", tree_sitter_cpp::language()),
|
("cpp", tree_sitter_cpp::language()),
|
||||||
("css", tree_sitter_css::language()),
|
("css", tree_sitter_css::language()),
|
||||||
("elixir", tree_sitter_elixir::language()),
|
("elixir", tree_sitter_elixir::language()),
|
||||||
@ -131,6 +133,7 @@ pub fn init(
|
|||||||
language("bash", vec![]);
|
language("bash", vec![]);
|
||||||
language("beancount", vec![]);
|
language("beancount", vec![]);
|
||||||
language("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
|
language("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
|
||||||
|
language("clojure", vec![Arc::new(clojure::ClojureLspAdapter)]);
|
||||||
language("cpp", vec![Arc::new(c::CLspAdapter)]);
|
language("cpp", vec![Arc::new(c::CLspAdapter)]);
|
||||||
language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]);
|
language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]);
|
||||||
language(
|
language(
|
||||||
|
136
crates/zed/src/languages/clojure.rs
Normal file
136
crates/zed/src/languages/clojure.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
pub use language::*;
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use smol::fs::{self, File};
|
||||||
|
use std::{any::Any, env::consts, path::PathBuf};
|
||||||
|
use util::{
|
||||||
|
fs::remove_matching,
|
||||||
|
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct ClojureLspAdapter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl super::LspAdapter for ClojureLspAdapter {
|
||||||
|
fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("clojure-lsp".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"clojure"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
let release = latest_github_release(
|
||||||
|
"clojure-lsp/clojure-lsp",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
delegate.http_client(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let platform = match consts::ARCH {
|
||||||
|
"x86_64" => "amd64",
|
||||||
|
"aarch64" => "aarch64",
|
||||||
|
other => bail!("Running on unsupported platform: {other}"),
|
||||||
|
};
|
||||||
|
let asset_name = format!("clojure-lsp-native-macos-{platform}.zip");
|
||||||
|
let asset = release
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|asset| asset.name == asset_name)
|
||||||
|
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||||
|
let version = GitHubLspBinaryVersion {
|
||||||
|
name: release.tag_name.clone(),
|
||||||
|
url: asset.browser_download_url.clone(),
|
||||||
|
};
|
||||||
|
Ok(Box::new(version) as Box<_>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
|
let zip_path = container_dir.join(format!("clojure-lsp_{}.zip", version.name));
|
||||||
|
let folder_path = container_dir.join("bin");
|
||||||
|
let binary_path = folder_path.join("clojure-lsp");
|
||||||
|
|
||||||
|
if fs::metadata(&binary_path).await.is_err() {
|
||||||
|
let mut response = delegate
|
||||||
|
.http_client()
|
||||||
|
.get(&version.url, Default::default(), true)
|
||||||
|
.await
|
||||||
|
.context("error downloading release")?;
|
||||||
|
let mut file = File::create(&zip_path)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to create file {}", zip_path.display()))?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"download failed with status {}",
|
||||||
|
response.status().to_string()
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||||
|
|
||||||
|
fs::create_dir_all(&folder_path)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to create directory {}", folder_path.display()))?;
|
||||||
|
|
||||||
|
let unzip_status = smol::process::Command::new("unzip")
|
||||||
|
.arg(&zip_path)
|
||||||
|
.arg("-d")
|
||||||
|
.arg(&folder_path)
|
||||||
|
.output()
|
||||||
|
.await?
|
||||||
|
.status;
|
||||||
|
if !unzip_status.success() {
|
||||||
|
return Err(anyhow!("failed to unzip elixir-ls archive"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_matching(&container_dir, |entry| entry != folder_path).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: binary_path,
|
||||||
|
arguments: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
let binary_path = container_dir.join("bin").join("clojure-lsp");
|
||||||
|
if binary_path.exists() {
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: binary_path,
|
||||||
|
arguments: vec![],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
let binary_path = container_dir.join("bin").join("clojure-lsp");
|
||||||
|
if binary_path.exists() {
|
||||||
|
Some(LanguageServerBinary {
|
||||||
|
path: binary_path,
|
||||||
|
arguments: vec!["--version".into()],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
crates/zed/src/languages/clojure/brackets.scm
Normal file
3
crates/zed/src/languages/clojure/brackets.scm
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
("(" @open ")" @close)
|
||||||
|
("[" @open "]" @close)
|
||||||
|
("{" @open "}" @close)
|
12
crates/zed/src/languages/clojure/config.toml
Normal file
12
crates/zed/src/languages/clojure/config.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
name = "Clojure"
|
||||||
|
grammar = "clojure"
|
||||||
|
path_suffixes = ["clj", "cljs"]
|
||||||
|
line_comments = [";; "]
|
||||||
|
autoclose_before = "}])"
|
||||||
|
brackets = [
|
||||||
|
{ start = "{", end = "}", close = true, newline = true },
|
||||||
|
{ start = "[", end = "]", close = true, newline = true },
|
||||||
|
{ start = "(", end = ")", close = true, newline = true },
|
||||||
|
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||||
|
]
|
||||||
|
word_characters = ["-"]
|
41
crates/zed/src/languages/clojure/highlights.scm
Normal file
41
crates/zed/src/languages/clojure/highlights.scm
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
;; Literals
|
||||||
|
|
||||||
|
(num_lit) @number
|
||||||
|
|
||||||
|
[
|
||||||
|
(char_lit)
|
||||||
|
(str_lit)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
[
|
||||||
|
(bool_lit)
|
||||||
|
(nil_lit)
|
||||||
|
] @constant.builtin
|
||||||
|
|
||||||
|
(kwd_lit) @constant
|
||||||
|
|
||||||
|
;; Comments
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
;; Treat quasiquotation as operators for the purpose of highlighting.
|
||||||
|
|
||||||
|
[
|
||||||
|
"'"
|
||||||
|
"`"
|
||||||
|
"~"
|
||||||
|
"@"
|
||||||
|
"~@"
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
|
||||||
|
(list_lit
|
||||||
|
.
|
||||||
|
(sym_lit) @function)
|
||||||
|
|
||||||
|
(list_lit
|
||||||
|
.
|
||||||
|
(sym_lit) @keyword
|
||||||
|
(#match? @keyword
|
||||||
|
"^(do|if|let|var|fn|fn*|loop*|recur|throw|try|catch|finally|set!|new|quote|->|->>)$"
|
||||||
|
))
|
3
crates/zed/src/languages/clojure/indents.scm
Normal file
3
crates/zed/src/languages/clojure/indents.scm
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
(_ "[" "]") @indent
|
||||||
|
(_ "{" "}") @indent
|
||||||
|
(_ "(" ")") @indent
|
1
crates/zed/src/languages/clojure/outline.scm
Normal file
1
crates/zed/src/languages/clojure/outline.scm
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
4
docs/src/languages/clojure.md
Normal file
4
docs/src/languages/clojure.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Clojure
|
||||||
|
|
||||||
|
- Tree Sitter: [tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure)
|
||||||
|
- Language Server: [clojure-lsp](https://github.com/clojure-lsp/clojure-lsp)
|
Loading…
Reference in New Issue
Block a user