commit
87ba68e3ea
27 changed files with 1229 additions and 277 deletions
|
@ -4,6 +4,7 @@ use rust_embed::RustEmbed;
|
|||
use std::{borrow::Cow, str, sync::Arc};
|
||||
|
||||
mod c;
|
||||
mod go;
|
||||
mod installation;
|
||||
mod json;
|
||||
mod rust;
|
||||
|
@ -27,6 +28,11 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi
|
|||
tree_sitter_cpp::language(),
|
||||
Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
|
||||
),
|
||||
(
|
||||
"go",
|
||||
tree_sitter_go::language(),
|
||||
Some(Arc::new(go::GoLspAdapter) as Arc<dyn LspAdapter>),
|
||||
),
|
||||
(
|
||||
"json",
|
||||
tree_sitter_json::language(),
|
||||
|
|
|
@ -4,7 +4,11 @@ use client::http::HttpClient;
|
|||
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||
pub use language::*;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
any::Any,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub struct CLspAdapter;
|
||||
|
@ -19,10 +23,17 @@ impl super::LspAdapter for CLspAdapter {
|
|||
http: Arc<dyn HttpClient>,
|
||||
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
|
||||
async move {
|
||||
let version = latest_github_release("clangd/clangd", http, |release_name| {
|
||||
format!("clangd-mac-{release_name}.zip")
|
||||
})
|
||||
.await?;
|
||||
let release = latest_github_release("clangd/clangd", http).await?;
|
||||
let asset_name = format!("clangd-mac-{}.zip", release.name);
|
||||
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.name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
.boxed()
|
||||
|
@ -32,7 +43,7 @@ impl super::LspAdapter for CLspAdapter {
|
|||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
http: Arc<dyn HttpClient>,
|
||||
container_dir: PathBuf,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Result<PathBuf>> {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
async move {
|
||||
|
@ -81,7 +92,10 @@ impl super::LspAdapter for CLspAdapter {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
async move {
|
||||
let mut last_clangd_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
|
|
401
crates/zed/src/languages/go.rs
Normal file
401
crates/zed/src/languages/go.rs
Normal file
|
@ -0,0 +1,401 @@
|
|||
use super::installation::latest_github_release;
|
||||
use anyhow::{anyhow, Result};
|
||||
use client::http::HttpClient;
|
||||
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||
pub use language::*;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use smol::{fs, process};
|
||||
use std::{
|
||||
any::Any,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct GoLspAdapter;
|
||||
|
||||
lazy_static! {
|
||||
static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap();
|
||||
}
|
||||
|
||||
impl super::LspAdapter for GoLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("gopls".into())
|
||||
}
|
||||
|
||||
fn server_args(&self) -> &[&str] {
|
||||
&["-mode=stdio"]
|
||||
}
|
||||
|
||||
fn fetch_latest_server_version(
|
||||
&self,
|
||||
http: Arc<dyn HttpClient>,
|
||||
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
|
||||
async move {
|
||||
let release = latest_github_release("golang/tools", http).await?;
|
||||
let version: Option<String> = release.name.strip_prefix("gopls/v").map(str::to_string);
|
||||
if version.is_none() {
|
||||
log::warn!(
|
||||
"couldn't infer gopls version from github release name '{}'",
|
||||
release.name
|
||||
);
|
||||
}
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
_: Arc<dyn HttpClient>,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Result<PathBuf>> {
|
||||
let version = version.downcast::<Option<String>>().unwrap();
|
||||
let this = *self;
|
||||
|
||||
async move {
|
||||
if let Some(version) = *version {
|
||||
let binary_path = container_dir.join(&format!("gopls_{version}"));
|
||||
if let Ok(metadata) = fs::metadata(&binary_path).await {
|
||||
if metadata.is_file() {
|
||||
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
|
||||
while let Some(entry) = entries.next().await {
|
||||
if let Some(entry) = entry.log_err() {
|
||||
let entry_path = entry.path();
|
||||
if entry_path.as_path() != binary_path
|
||||
&& entry.file_name() != "gobin"
|
||||
{
|
||||
fs::remove_file(&entry_path).await.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(binary_path.to_path_buf());
|
||||
}
|
||||
}
|
||||
} else if let Some(path) = this.cached_server_binary(container_dir.clone()).await {
|
||||
return Ok(path.to_path_buf());
|
||||
}
|
||||
|
||||
let gobin_dir = container_dir.join("gobin");
|
||||
fs::create_dir_all(&gobin_dir).await?;
|
||||
let install_output = process::Command::new("go")
|
||||
.env("GO111MODULE", "on")
|
||||
.env("GOBIN", &gobin_dir)
|
||||
.args(["install", "golang.org/x/tools/gopls@latest"])
|
||||
.output()
|
||||
.await?;
|
||||
if !install_output.status.success() {
|
||||
Err(anyhow!("failed to install gopls"))?;
|
||||
}
|
||||
|
||||
let installed_binary_path = gobin_dir.join("gopls");
|
||||
let version_output = process::Command::new(&installed_binary_path)
|
||||
.arg("version")
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?;
|
||||
let version_stdout = str::from_utf8(&version_output.stdout)
|
||||
.map_err(|_| anyhow!("gopls version produced invalid utf8"))?;
|
||||
let version = GOPLS_VERSION_REGEX
|
||||
.find(version_stdout)
|
||||
.ok_or_else(|| anyhow!("failed to parse gopls version output"))?
|
||||
.as_str();
|
||||
let binary_path = container_dir.join(&format!("gopls_{version}"));
|
||||
fs::rename(&installed_binary_path, &binary_path).await?;
|
||||
|
||||
Ok(binary_path.to_path_buf())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
async move {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_file()
|
||||
&& entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map_or(false, |name| name.starts_with("gopls_"))
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(path.to_path_buf())
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
}
|
||||
.log_err()
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Language,
|
||||
) -> Option<CodeLabel> {
|
||||
let label = &completion.label;
|
||||
|
||||
// Gopls returns nested fields and methods as completions.
|
||||
// To syntax highlight these, combine their final component
|
||||
// with their detail.
|
||||
let name_offset = label.rfind(".").unwrap_or(0);
|
||||
|
||||
match completion.kind.zip(completion.detail.as_ref()) {
|
||||
Some((lsp::CompletionItemKind::MODULE, detail)) => {
|
||||
let text = format!("{label} {detail}");
|
||||
let source = Rope::from(format!("import {text}").as_str());
|
||||
let runs = language.highlight_text(&source, 7..7 + text.len());
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..label.len(),
|
||||
});
|
||||
}
|
||||
Some((
|
||||
lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE,
|
||||
detail,
|
||||
)) => {
|
||||
let text = format!("{label} {detail}");
|
||||
let source =
|
||||
Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 4..4 + text.len()),
|
||||
);
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..label.len(),
|
||||
});
|
||||
}
|
||||
Some((lsp::CompletionItemKind::STRUCT, _)) => {
|
||||
let text = format!("{label} struct {{}}");
|
||||
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..label.len(),
|
||||
});
|
||||
}
|
||||
Some((lsp::CompletionItemKind::INTERFACE, _)) => {
|
||||
let text = format!("{label} interface {{}}");
|
||||
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..label.len(),
|
||||
});
|
||||
}
|
||||
Some((lsp::CompletionItemKind::FIELD, detail)) => {
|
||||
let text = format!("{label} {detail}");
|
||||
let source =
|
||||
Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 16..16 + text.len()),
|
||||
);
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..label.len(),
|
||||
});
|
||||
}
|
||||
Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => {
|
||||
if let Some(signature) = detail.strip_prefix("func") {
|
||||
let text = format!("{label}{signature}");
|
||||
let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
return Some(CodeLabel {
|
||||
filter_range: 0..label.len(),
|
||||
text,
|
||||
runs,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn label_for_symbol(
|
||||
&self,
|
||||
name: &str,
|
||||
kind: lsp::SymbolKind,
|
||||
language: &Language,
|
||||
) -> Option<CodeLabel> {
|
||||
let (text, filter_range, display_range) = match kind {
|
||||
lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
|
||||
let text = format!("func {} () {{}}", name);
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::STRUCT => {
|
||||
let text = format!("type {} struct {{}}", name);
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..text.len();
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::INTERFACE => {
|
||||
let text = format!("type {} interface {{}}", name);
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..text.len();
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::CLASS => {
|
||||
let text = format!("type {} T", name);
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::CONSTANT => {
|
||||
let text = format!("const {} = nil", name);
|
||||
let filter_range = 6..6 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::VARIABLE => {
|
||||
let text = format!("var {} = nil", name);
|
||||
let filter_range = 4..4 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::MODULE => {
|
||||
let text = format!("package {}", name);
|
||||
let filter_range = 8..8 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_runs(
|
||||
delta: usize,
|
||||
mut runs: Vec<(Range<usize>, HighlightId)>,
|
||||
) -> Vec<(Range<usize>, HighlightId)> {
|
||||
for (range, _) in &mut runs {
|
||||
range.start += delta;
|
||||
range.end += delta;
|
||||
}
|
||||
runs
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::languages::language;
|
||||
use gpui::color::Color;
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
#[test]
|
||||
fn test_go_label_for_completion() {
|
||||
let language = language(
|
||||
"go",
|
||||
tree_sitter_go::language(),
|
||||
Some(Arc::new(GoLspAdapter)),
|
||||
);
|
||||
|
||||
let theme = SyntaxTheme::new(vec![
|
||||
("type".into(), Color::green().into()),
|
||||
("keyword".into(), Color::blue().into()),
|
||||
("function".into(), Color::red().into()),
|
||||
("number".into(), Color::yellow().into()),
|
||||
("property".into(), Color::white().into()),
|
||||
]);
|
||||
language.set_theme(&theme);
|
||||
|
||||
let grammar = language.grammar().unwrap();
|
||||
let highlight_function = grammar.highlight_id_for_name("function").unwrap();
|
||||
let highlight_type = grammar.highlight_id_for_name("type").unwrap();
|
||||
let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
|
||||
let highlight_number = grammar.highlight_id_for_name("number").unwrap();
|
||||
let highlight_field = grammar.highlight_id_for_name("property").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
language.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "Hello".to_string(),
|
||||
detail: Some("func(a B) c.D".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
Some(CodeLabel {
|
||||
text: "Hello(a B) c.D".to_string(),
|
||||
filter_range: 0..5,
|
||||
runs: vec![
|
||||
(0..5, highlight_function),
|
||||
(8..9, highlight_type),
|
||||
(13..14, highlight_type),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Nested methods
|
||||
assert_eq!(
|
||||
language.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::METHOD),
|
||||
label: "one.two.Three".to_string(),
|
||||
detail: Some("func() [3]interface{}".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
Some(CodeLabel {
|
||||
text: "one.two.Three() [3]interface{}".to_string(),
|
||||
filter_range: 0..13,
|
||||
runs: vec![
|
||||
(8..13, highlight_function),
|
||||
(17..18, highlight_number),
|
||||
(19..28, highlight_keyword),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Nested fields
|
||||
assert_eq!(
|
||||
language.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FIELD),
|
||||
label: "two.Three".to_string(),
|
||||
detail: Some("a.Bcd".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
Some(CodeLabel {
|
||||
text: "two.Three a.Bcd".to_string(),
|
||||
filter_range: 0..9,
|
||||
runs: vec![(4..9, highlight_field), (12..15, highlight_type)],
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
3
crates/zed/src/languages/go/brackets.scm
Normal file
3
crates/zed/src/languages/go/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
11
crates/zed/src/languages/go/config.toml
Normal file
11
crates/zed/src/languages/go/config.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
name = "Go"
|
||||
path_suffixes = ["go"]
|
||||
line_comment = "// "
|
||||
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 },
|
||||
{ start = "/*", end = " */", close = true, newline = false },
|
||||
]
|
107
crates/zed/src/languages/go/highlights.scm
Normal file
107
crates/zed/src/languages/go/highlights.scm
Normal file
|
@ -0,0 +1,107 @@
|
|||
(identifier) @variable
|
||||
(type_identifier) @type
|
||||
(field_identifier) @property
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @function)
|
||||
|
||||
(call_expression
|
||||
function: (selector_expression
|
||||
field: (field_identifier) @function.method))
|
||||
|
||||
(function_declaration
|
||||
name: (identifier) @function)
|
||||
|
||||
(method_declaration
|
||||
name: (field_identifier) @function.method)
|
||||
|
||||
[
|
||||
"--"
|
||||
"-"
|
||||
"-="
|
||||
":="
|
||||
"!"
|
||||
"!="
|
||||
"..."
|
||||
"*"
|
||||
"*"
|
||||
"*="
|
||||
"/"
|
||||
"/="
|
||||
"&"
|
||||
"&&"
|
||||
"&="
|
||||
"%"
|
||||
"%="
|
||||
"^"
|
||||
"^="
|
||||
"+"
|
||||
"++"
|
||||
"+="
|
||||
"<-"
|
||||
"<"
|
||||
"<<"
|
||||
"<<="
|
||||
"<="
|
||||
"="
|
||||
"=="
|
||||
">"
|
||||
">="
|
||||
">>"
|
||||
">>="
|
||||
"|"
|
||||
"|="
|
||||
"||"
|
||||
"~"
|
||||
] @operator
|
||||
|
||||
[
|
||||
"break"
|
||||
"case"
|
||||
"chan"
|
||||
"const"
|
||||
"continue"
|
||||
"default"
|
||||
"defer"
|
||||
"else"
|
||||
"fallthrough"
|
||||
"for"
|
||||
"func"
|
||||
"go"
|
||||
"goto"
|
||||
"if"
|
||||
"import"
|
||||
"interface"
|
||||
"map"
|
||||
"package"
|
||||
"range"
|
||||
"return"
|
||||
"select"
|
||||
"struct"
|
||||
"switch"
|
||||
"type"
|
||||
"var"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
(interpreted_string_literal)
|
||||
(raw_string_literal)
|
||||
(rune_literal)
|
||||
] @string
|
||||
|
||||
(escape_sequence) @escape
|
||||
|
||||
[
|
||||
(int_literal)
|
||||
(float_literal)
|
||||
(imaginary_literal)
|
||||
] @number
|
||||
|
||||
[
|
||||
(true)
|
||||
(false)
|
||||
(nil)
|
||||
(iota)
|
||||
] @constant.builtin
|
||||
|
||||
(comment) @comment
|
9
crates/zed/src/languages/go/indents.scm
Normal file
9
crates/zed/src/languages/go/indents.scm
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
(assignment_statement)
|
||||
(call_expression)
|
||||
(selector_expression)
|
||||
] @indent
|
||||
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
44
crates/zed/src/languages/go/outline.scm
Normal file
44
crates/zed/src/languages/go/outline.scm
Normal file
|
@ -0,0 +1,44 @@
|
|||
(type_declaration
|
||||
"type" @context
|
||||
(type_spec
|
||||
name: (_) @name)) @item
|
||||
|
||||
(function_declaration
|
||||
"func" @context
|
||||
name: (identifier) @name
|
||||
parameters: (parameter_list
|
||||
"(" @context
|
||||
")" @context)) @item
|
||||
|
||||
(method_declaration
|
||||
"func" @context
|
||||
receiver: (parameter_list
|
||||
"(" @context
|
||||
(parameter_declaration
|
||||
type: (_) @context)
|
||||
")" @context)
|
||||
name: (field_identifier) @name
|
||||
parameters: (parameter_list
|
||||
"(" @context
|
||||
")" @context)) @item
|
||||
|
||||
(const_declaration
|
||||
"const" @context
|
||||
(const_spec
|
||||
name: (identifier) @name) @item)
|
||||
|
||||
(source_file
|
||||
(var_declaration
|
||||
"var" @context
|
||||
(var_spec
|
||||
name: (identifier) @name) @item))
|
||||
|
||||
(method_spec
|
||||
name: (_) @name
|
||||
parameters: (parameter_list
|
||||
"(" @context
|
||||
")" @context)) @item
|
||||
|
||||
(field_declaration
|
||||
name: (_) @name
|
||||
type: (_) @context) @item
|
|
@ -25,14 +25,14 @@ struct NpmInfoDistTags {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct GithubRelease {
|
||||
name: String,
|
||||
assets: Vec<GithubReleaseAsset>,
|
||||
pub name: String,
|
||||
pub assets: Vec<GithubReleaseAsset>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct GithubReleaseAsset {
|
||||
name: String,
|
||||
browser_download_url: String,
|
||||
pub name: String,
|
||||
pub browser_download_url: String,
|
||||
}
|
||||
|
||||
pub async fn npm_package_latest_version(name: &str) -> Result<String> {
|
||||
|
@ -78,11 +78,10 @@ pub async fn npm_install_packages(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn latest_github_release(
|
||||
pub(crate) async fn latest_github_release(
|
||||
repo_name_with_owner: &str,
|
||||
http: Arc<dyn HttpClient>,
|
||||
asset_name: impl Fn(&str) -> String,
|
||||
) -> Result<GitHubLspBinaryVersion> {
|
||||
) -> Result<GithubRelease, anyhow::Error> {
|
||||
let mut response = http
|
||||
.get(
|
||||
&format!("https://api.github.com/repos/{repo_name_with_owner}/releases/latest"),
|
||||
|
@ -91,24 +90,13 @@ pub async fn latest_github_release(
|
|||
)
|
||||
.await
|
||||
.context("error fetching latest release")?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading latest release")?;
|
||||
|
||||
let release: GithubRelease =
|
||||
serde_json::from_slice(body.as_slice()).context("error deserializing latest release")?;
|
||||
let asset_name = asset_name(&release.name);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
Ok(GitHubLspBinaryVersion {
|
||||
name: release.name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
})
|
||||
Ok(release)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,11 @@ use language::{LanguageServerName, LspAdapter};
|
|||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use smol::fs;
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
any::Any,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub struct JsonLspAdapter;
|
||||
|
@ -56,7 +60,7 @@ impl LspAdapter for JsonLspAdapter {
|
|||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
_: Arc<dyn HttpClient>,
|
||||
container_dir: PathBuf,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Result<PathBuf>> {
|
||||
let version = version.downcast::<String>().unwrap();
|
||||
async move {
|
||||
|
@ -95,7 +99,10 @@ impl LspAdapter for JsonLspAdapter {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
|
|
|
@ -7,7 +7,14 @@ pub use language::*;
|
|||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
env::consts,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub struct RustLspAdapter;
|
||||
|
@ -22,10 +29,17 @@ impl LspAdapter for RustLspAdapter {
|
|||
http: Arc<dyn HttpClient>,
|
||||
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
|
||||
async move {
|
||||
let version = latest_github_release("rust-analyzer/rust-analyzer", http, |_| {
|
||||
format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH)
|
||||
})
|
||||
.await?;
|
||||
let release = latest_github_release("rust-analyzer/rust-analyzer", http).await?;
|
||||
let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH);
|
||||
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.name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
.boxed()
|
||||
|
@ -35,7 +49,7 @@ impl LspAdapter for RustLspAdapter {
|
|||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
http: Arc<dyn HttpClient>,
|
||||
container_dir: PathBuf,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Result<PathBuf>> {
|
||||
async move {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
|
@ -72,7 +86,10 @@ impl LspAdapter for RustLspAdapter {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
async move {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
|
|
|
@ -5,7 +5,11 @@ use futures::{future::BoxFuture, FutureExt, StreamExt};
|
|||
use language::{LanguageServerName, LspAdapter};
|
||||
use serde_json::json;
|
||||
use smol::fs;
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
any::Any,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub struct TypeScriptLspAdapter;
|
||||
|
@ -45,7 +49,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
|||
&self,
|
||||
versions: Box<dyn 'static + Send + Any>,
|
||||
_: Arc<dyn HttpClient>,
|
||||
container_dir: PathBuf,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Result<PathBuf>> {
|
||||
let versions = versions.downcast::<Versions>().unwrap();
|
||||
async move {
|
||||
|
@ -88,7 +92,10 @@ impl LspAdapter for TypeScriptLspAdapter {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: Arc<Path>,
|
||||
) -> BoxFuture<'static, Option<PathBuf>> {
|
||||
async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
|
|
|
@ -94,6 +94,14 @@ fn main() {
|
|||
..Default::default()
|
||||
},
|
||||
)
|
||||
.with_overrides(
|
||||
"Go",
|
||||
settings::LanguageOverride {
|
||||
tab_size: Some(4),
|
||||
hard_tabs: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.with_overrides(
|
||||
"Markdown",
|
||||
settings::LanguageOverride {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue