This commit is contained in:
Max Brunsfeld
2022-03-29 11:06:08 -07:00
parent dd1c88afa5
commit d466768eed
35 changed files with 523 additions and 372 deletions
+125
View File
@@ -0,0 +1,125 @@
use client::http::{self, HttpClient, Method};
use gpui::Task;
pub use language::*;
use rust_embed::RustEmbed;
use serde::Deserialize;
use std::{borrow::Cow, str, sync::Arc};
mod c;
mod json;
mod rust;
mod typescript;
#[derive(RustEmbed)]
#[folder = "src/languages"]
#[exclude = "*.rs"]
struct LanguageDir;
#[derive(Deserialize)]
struct GithubRelease {
name: String,
assets: Vec<GithubReleaseAsset>,
}
#[derive(Deserialize)]
struct GithubReleaseAsset {
name: String,
browser_download_url: http::Url,
}
pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry {
let languages = LanguageRegistry::new(login_shell_env_loaded);
for (name, grammar, lsp_adapter) in [
(
"c",
tree_sitter_c::language(),
Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
),
(
"json",
tree_sitter_json::language(),
Some(Arc::new(json::JsonLspAdapter)),
),
(
"markdown",
tree_sitter_markdown::language(),
None, //
),
(
"rust",
tree_sitter_rust::language(),
Some(Arc::new(rust::RustLspAdapter)),
),
(
"tsx",
tree_sitter_typescript::language_tsx(),
None, //
),
(
"typescript",
tree_sitter_typescript::language_typescript(),
Some(Arc::new(typescript::TypeScriptLspAdapter)),
),
] {
languages.add(Arc::new(language(name, grammar, lsp_adapter)));
}
languages
}
fn language(
name: &str,
grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<dyn LspAdapter>>,
) -> Language {
let config = toml::from_slice(
&LanguageDir::get(&format!("{}/config.toml", name))
.unwrap()
.data,
)
.unwrap();
let mut language = Language::new(config, Some(grammar));
if let Some(query) = load_query(name, "/highlights") {
language = language
.with_highlights_query(query.as_ref())
.expect("failed to evaluate highlights query");
}
if let Some(query) = load_query(name, "/brackets") {
language = language
.with_brackets_query(query.as_ref())
.expect("failed to load brackets query");
}
if let Some(query) = load_query(name, "/indents") {
language = language
.with_indents_query(query.as_ref())
.expect("failed to load indents query");
}
if let Some(query) = load_query(name, "/outline") {
language = language
.with_outline_query(query.as_ref())
.expect("failed to load outline query");
}
if let Some(lsp_adapter) = lsp_adapter {
language = language.with_lsp_adapter(lsp_adapter)
}
language
}
fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {
let mut result = None;
for path in LanguageDir::iter() {
if let Some(remainder) = path.strip_prefix(name) {
if remainder.starts_with(filename_prefix) {
let contents = match LanguageDir::get(path.as_ref()).unwrap().data {
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
};
match &mut result {
None => result = Some(contents),
Some(r) => r.to_mut().push_str(contents.as_ref()),
}
}
}
}
result
}
+136
View File
@@ -0,0 +1,136 @@
use anyhow::{anyhow, Result};
use client::http::{self, HttpClient, Method};
use futures::{future::BoxFuture, FutureExt, StreamExt};
pub use language::*;
use smol::fs::{self, File};
use std::{path::PathBuf, str, sync::Arc};
use util::{ResultExt, TryFutureExt};
use super::GithubRelease;
pub struct CLspAdapter;
impl super::LspAdapter for CLspAdapter {
fn name(&self) -> &'static str {
"clangd"
}
fn fetch_latest_server_version(
&self,
http: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<LspBinaryVersion>> {
async move {
let release = http
.send(
surf::RequestBuilder::new(
Method::Get,
http::Url::parse(
"https://api.github.com/repos/clangd/clangd/releases/latest",
)
.unwrap(),
)
.middleware(surf::middleware::Redirect::default())
.build(),
)
.await
.map_err(|err| anyhow!("error fetching latest release: {}", err))?
.body_json::<GithubRelease>()
.await
.map_err(|err| anyhow!("error parsing latest release: {}", err))?;
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 release found matching {:?}", asset_name))?;
Ok(LspBinaryVersion {
name: release.name,
url: Some(asset.browser_download_url.clone()),
})
}
.boxed()
}
fn fetch_server_binary(
&self,
version: LspBinaryVersion,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
) -> BoxFuture<'static, Result<PathBuf>> {
async move {
let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
let version_dir = container_dir.join(format!("clangd_{}", version.name));
let binary_path = version_dir.join("bin/clangd");
if fs::metadata(&binary_path).await.is_err() {
let response = http
.send(
surf::RequestBuilder::new(Method::Get, version.url.unwrap())
.middleware(surf::middleware::Redirect::default())
.build(),
)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
let mut file = File::create(&zip_path).await?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response, &mut file).await?;
let unzip_status = smol::process::Command::new("unzip")
.current_dir(&container_dir)
.arg(&zip_path)
.output()
.await?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip clangd archive"))?;
}
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() != version_dir {
fs::remove_dir_all(&entry_path).await.log_err();
}
}
}
}
}
Ok(binary_path)
}
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_clangd_dir = 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_dir() {
last_clangd_dir = Some(entry.path());
}
}
let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let clangd_bin = clangd_dir.join("bin/clangd");
if clangd_bin.exists() {
Ok(clangd_bin)
} else {
Err(anyhow!(
"missing clangd binary in directory {:?}",
clangd_dir
))
}
}
.log_err()
.boxed()
}
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
}
+3
View File
@@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)
+11
View File
@@ -0,0 +1,11 @@
name = "C"
path_suffixes = ["c", "h"]
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 },
]
+101
View File
@@ -0,0 +1,101 @@
[
"break"
"case"
"const"
"continue"
"default"
"do"
"else"
"enum"
"extern"
"for"
"if"
"inline"
"return"
"sizeof"
"static"
"struct"
"switch"
"typedef"
"union"
"volatile"
"while"
] @keyword
[
"#define"
"#elif"
"#else"
"#endif"
"#if"
"#ifdef"
"#ifndef"
"#include"
(preproc_directive)
] @keyword
[
"--"
"-"
"-="
"->"
"="
"!="
"*"
"&"
"&&"
"+"
"++"
"+="
"<"
"=="
">"
"||"
] @operator
[
"."
";"
] @delimiter
[
(string_literal)
(system_lib_string)
(char_literal)
] @string
(comment) @comment
(null) @constant
(number_literal) @number
[
(true)
(false)
(null)
] @constant
(identifier) @variable
((identifier) @constant
(#match? @constant "^[A-Z][A-Z\\d_]*$"))
(call_expression
function: (identifier) @function)
(call_expression
function: (field_expression
field: (field_identifier) @function))
(function_declarator
declarator: (identifier) @function)
(preproc_function_def
name: (identifier) @function.special)
(field_identifier) @property
(statement_identifier) @label
[
(type_identifier)
(primitive_type)
(sized_type_specifier)
] @type
+7
View File
@@ -0,0 +1,7 @@
[
(field_expression)
(assignment_expression)
] @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent
+30
View File
@@ -0,0 +1,30 @@
(preproc_def
"#define" @context
name: (_) @name) @item
(preproc_function_def
"#define" @context
name: (_) @name
parameters: (preproc_params
"(" @context
")" @context)) @item
(type_definition
"typedef" @context
declarator: (_) @name) @item
(declaration
type: (_) @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))) @item
(function_definition
type: (_) @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))) @item
+131
View File
@@ -0,0 +1,131 @@
use anyhow::{anyhow, Context, Result};
use client::http::HttpClient;
use futures::{future::BoxFuture, FutureExt, StreamExt};
use language::{LspAdapter, LspBinaryVersion};
use serde::Deserialize;
use serde_json::json;
use smol::fs;
use std::{path::PathBuf, sync::Arc};
use util::ResultExt;
pub struct JsonLspAdapter;
impl JsonLspAdapter {
const BIN_PATH: &'static str =
"node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
}
impl LspAdapter for JsonLspAdapter {
fn name(&self) -> &'static str {
"vscode-json-languageserver"
}
fn server_args(&self) -> &[&str] {
&["--stdio"]
}
fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<LspBinaryVersion>> {
async move {
#[derive(Deserialize)]
struct NpmInfo {
versions: Vec<String>,
}
let output = smol::process::Command::new("npm")
.args(["info", "vscode-json-languageserver", "--json"])
.output()
.await?;
if !output.status.success() {
Err(anyhow!("failed to execute npm info"))?;
}
let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
Ok(LspBinaryVersion {
name: info
.versions
.pop()
.ok_or_else(|| anyhow!("no versions found in npm info"))?,
url: Default::default(),
})
}
.boxed()
}
fn fetch_server_binary(
&self,
version: LspBinaryVersion,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
) -> BoxFuture<'static, Result<PathBuf>> {
async move {
let version_dir = container_dir.join(&version.name);
fs::create_dir_all(&version_dir)
.await
.context("failed to create version directory")?;
let binary_path = version_dir.join(Self::BIN_PATH);
if fs::metadata(&binary_path).await.is_err() {
let output = smol::process::Command::new("npm")
.current_dir(&version_dir)
.arg("install")
.arg(format!("vscode-json-languageserver@{}", version.name))
.output()
.await
.context("failed to run npm install")?;
if !output.status.success() {
Err(anyhow!("failed to install vscode-json-languageserver"))?;
}
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() != version_dir {
fs::remove_dir_all(&entry_path).await.log_err();
}
}
}
}
}
Ok(binary_path)
}
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_version_dir = 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_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let bin_path = last_version_dir.join(Self::BIN_PATH);
if bin_path.exists() {
Ok(bin_path)
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
}
.log_err()
.boxed()
}
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
fn initialization_options(&self) -> Option<serde_json::Value> {
Some(json!({
"provideFormatter": true
}))
}
}
@@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)
@@ -0,0 +1,8 @@
name = "JSON"
path_suffixes = ["json"]
autoclose_before = ",]}"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false },
]
@@ -0,0 +1,12 @@
(string) @string
(pair
key: (string) @property)
(number) @number
[
(true)
(false)
(null)
] @constant
@@ -0,0 +1,2 @@
(array "]" @end) @indent
(object "}" @end) @indent
@@ -0,0 +1,2 @@
(pair
key: (string (string_content) @name)) @item
@@ -0,0 +1,8 @@
name = "Markdown"
path_suffixes = ["md"]
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "<", end = ">", close = true, newline = true },
]
@@ -0,0 +1,24 @@
(emphasis) @emphasis
(strong_emphasis) @emphasis.strong
[
(atx_heading)
(setext_heading)
] @title
[
(list_marker_plus)
(list_marker_minus)
(list_marker_star)
(list_marker_dot)
(list_marker_parenthesis)
] @list_marker
[
(indented_code_block)
(fenced_code_block)
(code_span)
] @text.literal
(link_destination) @link_uri
(link_text) @link_text
@@ -1,37 +1,17 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Result};
use async_compression::futures::bufread::GzipDecoder;
use client::http::{self, HttpClient, Method};
use futures::{future::BoxFuture, FutureExt, StreamExt};
use gpui::Task;
pub use language::*;
use lazy_static::lazy_static;
use regex::Regex;
use rust_embed::RustEmbed;
use serde::Deserialize;
use serde_json::json;
use smol::fs::{self, File};
use std::{borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
use util::{ResultExt, TryFutureExt};
#[derive(RustEmbed)]
#[folder = "languages"]
struct LanguageDir;
use super::GithubRelease;
struct RustLspAdapter;
struct CLspAdapter;
struct JsonLspAdapter;
#[derive(Deserialize)]
struct GithubRelease {
name: String,
assets: Vec<GithubReleaseAsset>,
}
#[derive(Deserialize)]
struct GithubReleaseAsset {
name: String,
browser_download_url: http::Url,
}
pub struct RustLspAdapter;
impl LspAdapter for RustLspAdapter {
fn name(&self) -> &'static str {
@@ -287,353 +267,11 @@ impl LspAdapter for RustLspAdapter {
}
}
impl LspAdapter for CLspAdapter {
fn name(&self) -> &'static str {
"clangd"
}
fn fetch_latest_server_version(
&self,
http: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<LspBinaryVersion>> {
async move {
let release = http
.send(
surf::RequestBuilder::new(
Method::Get,
http::Url::parse(
"https://api.github.com/repos/clangd/clangd/releases/latest",
)
.unwrap(),
)
.middleware(surf::middleware::Redirect::default())
.build(),
)
.await
.map_err(|err| anyhow!("error fetching latest release: {}", err))?
.body_json::<GithubRelease>()
.await
.map_err(|err| anyhow!("error parsing latest release: {}", err))?;
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 release found matching {:?}", asset_name))?;
Ok(LspBinaryVersion {
name: release.name,
url: Some(asset.browser_download_url.clone()),
})
}
.boxed()
}
fn fetch_server_binary(
&self,
version: LspBinaryVersion,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
) -> BoxFuture<'static, Result<PathBuf>> {
async move {
let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
let version_dir = container_dir.join(format!("clangd_{}", version.name));
let binary_path = version_dir.join("bin/clangd");
if fs::metadata(&binary_path).await.is_err() {
let response = http
.send(
surf::RequestBuilder::new(Method::Get, version.url.unwrap())
.middleware(surf::middleware::Redirect::default())
.build(),
)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
let mut file = File::create(&zip_path).await?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response, &mut file).await?;
let unzip_status = smol::process::Command::new("unzip")
.current_dir(&container_dir)
.arg(&zip_path)
.output()
.await?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip clangd archive"))?;
}
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() != version_dir {
fs::remove_dir_all(&entry_path).await.log_err();
}
}
}
}
}
Ok(binary_path)
}
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_clangd_dir = 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_dir() {
last_clangd_dir = Some(entry.path());
}
}
let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let clangd_bin = clangd_dir.join("bin/clangd");
if clangd_bin.exists() {
Ok(clangd_bin)
} else {
Err(anyhow!(
"missing clangd binary in directory {:?}",
clangd_dir
))
}
}
.log_err()
.boxed()
}
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
}
impl JsonLspAdapter {
const BIN_PATH: &'static str =
"node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
}
impl LspAdapter for JsonLspAdapter {
fn name(&self) -> &'static str {
"vscode-json-languageserver"
}
fn server_args(&self) -> &[&str] {
&["--stdio"]
}
fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<LspBinaryVersion>> {
async move {
#[derive(Deserialize)]
struct NpmInfo {
versions: Vec<String>,
}
let output = smol::process::Command::new("npm")
.args(["info", "vscode-json-languageserver", "--json"])
.output()
.await?;
if !output.status.success() {
Err(anyhow!("failed to execute npm info"))?;
}
let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
Ok(LspBinaryVersion {
name: info
.versions
.pop()
.ok_or_else(|| anyhow!("no versions found in npm info"))?,
url: Default::default(),
})
}
.boxed()
}
fn fetch_server_binary(
&self,
version: LspBinaryVersion,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
) -> BoxFuture<'static, Result<PathBuf>> {
async move {
let version_dir = container_dir.join(&version.name);
fs::create_dir_all(&version_dir)
.await
.context("failed to create version directory")?;
let binary_path = version_dir.join(Self::BIN_PATH);
if fs::metadata(&binary_path).await.is_err() {
let output = smol::process::Command::new("npm")
.current_dir(&version_dir)
.arg("install")
.arg(format!("vscode-json-languageserver@{}", version.name))
.output()
.await
.context("failed to run npm install")?;
if !output.status.success() {
Err(anyhow!("failed to install vscode-json-languageserver"))?;
}
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() != version_dir {
fs::remove_dir_all(&entry_path).await.log_err();
}
}
}
}
}
Ok(binary_path)
}
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_version_dir = 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_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let bin_path = last_version_dir.join(Self::BIN_PATH);
if bin_path.exists() {
Ok(bin_path)
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
}
.log_err()
.boxed()
}
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
fn initialization_options(&self) -> Option<serde_json::Value> {
Some(json!({
"provideFormatter": true
}))
}
}
pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry {
let languages = LanguageRegistry::new(login_shell_env_loaded);
for (name, grammar, lsp_adapter) in [
(
"c",
tree_sitter_c::language(),
Some(Arc::new(CLspAdapter) as Arc<dyn LspAdapter>),
),
(
"json",
tree_sitter_json::language(),
Some(Arc::new(JsonLspAdapter)),
),
(
"markdown",
tree_sitter_markdown::language(),
None, //
),
(
"rust",
tree_sitter_rust::language(),
Some(Arc::new(RustLspAdapter)),
),
(
"tsx",
tree_sitter_typescript::language_tsx(),
None, //
),
(
"typescript",
tree_sitter_typescript::language_typescript(),
None, //
),
] {
languages.add(Arc::new(language(name, grammar, lsp_adapter)));
}
languages
}
fn language(
name: &str,
grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<dyn LspAdapter>>,
) -> Language {
let config = toml::from_slice(
&LanguageDir::get(&format!("{}/config.toml", name))
.unwrap()
.data,
)
.unwrap();
let mut language = Language::new(config, Some(grammar));
if let Some(query) = load_query(name, "/highlights") {
language = language
.with_highlights_query(query.as_ref())
.expect("failed to evaluate highlights query");
}
if let Some(query) = load_query(name, "/brackets") {
language = language
.with_brackets_query(query.as_ref())
.expect("failed to load brackets query");
}
if let Some(query) = load_query(name, "/indents") {
language = language
.with_indents_query(query.as_ref())
.expect("failed to load indents query");
}
if let Some(query) = load_query(name, "/outline") {
language = language
.with_outline_query(query.as_ref())
.expect("failed to load outline query");
}
if let Some(lsp_adapter) = lsp_adapter {
language = language.with_lsp_adapter(lsp_adapter)
}
language
}
fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {
let mut result = None;
for path in LanguageDir::iter() {
if let Some(remainder) = path.strip_prefix(name) {
if remainder.starts_with(filename_prefix) {
let contents = match LanguageDir::get(path.as_ref()).unwrap().data {
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
};
match &mut result {
None => result = Some(contents),
Some(r) => r.to_mut().push_str(contents.as_ref()),
}
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::languages::{language, LspAdapter};
use gpui::color::Color;
use language::LspAdapter;
use theme::SyntaxTheme;
#[test]
@@ -0,0 +1,6 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)
+16
View File
@@ -0,0 +1,16 @@
name = "Rust"
path_suffixes = ["rs"]
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 = false, newline = true },
{ start = "\"", end = "\"", close = true, newline = false },
{ start = "/*", end = " */", close = true, newline = false },
]
[language_server]
disk_based_diagnostic_sources = ["rustc"]
disk_based_diagnostics_progress_token = "rustAnalyzer/cargo check"
@@ -0,0 +1,83 @@
(type_identifier) @type
(primitive_type) @type.builtin
(field_identifier) @property
(call_expression
function: [
(identifier) @function
(scoped_identifier
name: (identifier) @function)
(field_expression
field: (field_identifier) @function.method)
])
(function_item name: (identifier) @function.definition)
(function_signature_item name: (identifier) @function.definition)
; Identifier conventions
; Assume uppercase names are enum constructors
((identifier) @variant
(#match? @variant "^[A-Z]"))
; Assume that uppercase names in paths are types
((scoped_identifier
path: (identifier) @type)
(#match? @type "^[A-Z]"))
((scoped_identifier
path: (scoped_identifier
name: (identifier) @type))
(#match? @type "^[A-Z]"))
; Assume all-caps names are constants
((identifier) @constant
(#match? @constant "^[A-Z][A-Z\\d_]+$"))
[
"as"
"async"
"break"
"const"
"continue"
"default"
"dyn"
"else"
"enum"
"extern"
"for"
"fn"
"if"
"in"
"impl"
"let"
"loop"
"macro_rules!"
"match"
"mod"
"move"
"pub"
"return"
"static"
"struct"
"trait"
"type"
"use"
"where"
"while"
"union"
"unsafe"
(mutable_specifier)
(super)
] @keyword
[
(string_literal)
(raw_string_literal)
(char_literal)
] @string
[
(line_comment)
(block_comment)
] @comment
+12
View File
@@ -0,0 +1,12 @@
[
((where_clause) _ @end)
(field_expression)
(call_expression)
(assignment_expression)
(let_declaration)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent
+63
View File
@@ -0,0 +1,63 @@
(struct_item
(visibility_modifier)? @context
"struct" @context
name: (_) @name) @item
(enum_item
(visibility_modifier)? @context
"enum" @context
name: (_) @name) @item
(enum_variant
(visibility_modifier)? @context
name: (_) @name) @item
(impl_item
"impl" @context
trait: (_)? @name
"for"? @context
type: (_) @name) @item
(trait_item
(visibility_modifier)? @context
"trait" @context
name: (_) @name) @item
(function_item
(visibility_modifier)? @context
(function_modifiers)? @context
"fn" @context
name: (_) @name) @item
(function_signature_item
(visibility_modifier)? @context
(function_modifiers)? @context
"fn" @context
name: (_) @name) @item
(macro_definition
. "macro_rules!" @context
name: (_) @name) @item
(mod_item
(visibility_modifier)? @context
"mod" @context
name: (_) @name) @item
(type_item
(visibility_modifier)? @context
"type" @context
name: (_) @name) @item
(associated_type
"type" @context
name: (_) @name) @item
(const_item
(visibility_modifier)? @context
"const" @context
name: (_) @name) @item
(field_declaration
(visibility_modifier)? @context
name: (_) @name) @item
+1
View File
@@ -0,0 +1 @@
../typescript/brackets.scm
+12
View File
@@ -0,0 +1,12 @@
name = "TSX"
path_suffixes = ["tsx"]
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 = false, newline = true },
{ start = "\"", end = "\"", close = true, newline = false },
{ start = "/*", end = " */", close = true, newline = false },
]
+1
View File
@@ -0,0 +1 @@
../typescript/highlights.scm
+1
View File
@@ -0,0 +1 @@
../typescript/indents.scm
+1
View File
@@ -0,0 +1 @@
../typescript/outline.scm
+121
View File
@@ -0,0 +1,121 @@
pub struct TypeScriptLspAdapter;
impl TypeScriptLspAdapter {
const BIN_PATH: &'static str =
"node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
}
impl super::LspAdapter for TypeScriptLspAdapter {
fn name(&self) -> &'static str {
"typescript-language-server"
}
fn server_args(&self) -> &[&str] {
&["--stdio"]
}
fn fetch_latest_server_version(
&self,
_: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<LspBinaryVersion>> {
async move {
#[derive(Deserialize)]
struct NpmInfo {
versions: Vec<String>,
}
let output = smol::process::Command::new("npm")
.args(["info", "vscode-json-languageserver", "--json"])
.output()
.await?;
if !output.status.success() {
Err(anyhow!("failed to execute npm info"))?;
}
let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
Ok(LspBinaryVersion {
name: info
.versions
.pop()
.ok_or_else(|| anyhow!("no versions found in npm info"))?,
url: Default::default(),
})
}
.boxed()
}
fn fetch_server_binary(
&self,
version: LspBinaryVersion,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
) -> BoxFuture<'static, Result<PathBuf>> {
async move {
let version_dir = container_dir.join(&version.name);
fs::create_dir_all(&version_dir)
.await
.context("failed to create version directory")?;
let binary_path = version_dir.join(Self::BIN_PATH);
if fs::metadata(&binary_path).await.is_err() {
let output = smol::process::Command::new("npm")
.current_dir(&version_dir)
.arg("install")
.arg(format!("vscode-json-languageserver@{}", version.name))
.output()
.await
.context("failed to run npm install")?;
if !output.status.success() {
Err(anyhow!("failed to install vscode-json-languageserver"))?;
}
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() != version_dir {
fs::remove_dir_all(&entry_path).await.log_err();
}
}
}
}
}
Ok(binary_path)
}
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_version_dir = 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_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let bin_path = last_version_dir.join(Self::BIN_PATH);
if bin_path.exists() {
Ok(bin_path)
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
}
.log_err()
.boxed()
}
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
fn initialization_options(&self) -> Option<serde_json::Value> {
Some(json!({
"provideFormatter": true
}))
}
}
@@ -0,0 +1,5 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
@@ -0,0 +1,12 @@
name = "TypeScript"
path_suffixes = ["ts"]
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 = false, newline = true },
{ start = "\"", end = "\"", close = true, newline = false },
{ start = "/*", end = " */", close = true, newline = false },
]
@@ -0,0 +1,219 @@
; Variables
(identifier) @variable
; Properties
(property_identifier) @property
; Function and method calls
(call_expression
function: (identifier) @function)
(call_expression
function: (member_expression
property: (property_identifier) @function.method))
; Function and method definitions
(function
name: (identifier) @function)
(function_declaration
name: (identifier) @function)
(method_definition
name: (property_identifier) @function.method)
(pair
key: (property_identifier) @function.method
value: [(function) (arrow_function)])
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: [(function) (arrow_function)])
(variable_declarator
name: (identifier) @function
value: [(function) (arrow_function)])
(assignment_expression
left: (identifier) @function
right: [(function) (arrow_function)])
; Special identifiers
((identifier) @constructor
(#match? @constructor "^[A-Z]"))
([
(identifier)
(shorthand_property_identifier)
(shorthand_property_identifier_pattern)
] @constant
(#match? @constant "^[A-Z_][A-Z\\d_]+$"))
; Literals
(this) @variable.builtin
(super) @variable.builtin
[
(true)
(false)
(null)
(undefined)
] @constant.builtin
(comment) @comment
[
(string)
(template_string)
] @string
(regex) @string.special
(number) @number
; Tokens
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
[
";"
"?."
"."
","
] @punctuation.delimiter
[
"-"
"--"
"-="
"+"
"++"
"+="
"*"
"*="
"**"
"**="
"/"
"/="
"%"
"%="
"<"
"<="
"<<"
"<<="
"="
"=="
"==="
"!"
"!="
"!=="
"=>"
">"
">="
">>"
">>="
">>>"
">>>="
"~"
"^"
"&"
"|"
"^="
"&="
"|="
"&&"
"||"
"??"
"&&="
"||="
"??="
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
"as"
"async"
"await"
"break"
"case"
"catch"
"class"
"const"
"continue"
"debugger"
"default"
"delete"
"do"
"else"
"export"
"extends"
"finally"
"for"
"from"
"function"
"get"
"if"
"import"
"in"
"instanceof"
"let"
"new"
"of"
"return"
"set"
"static"
"switch"
"target"
"throw"
"try"
"typeof"
"var"
"void"
"while"
"with"
"yield"
] @keyword
; Types
(type_identifier) @type
(predefined_type) @type.builtin
((identifier) @type
(#match? @type "^[A-Z]"))
(type_arguments
"<" @punctuation.bracket
">" @punctuation.bracket)
; Keywords
[ "abstract"
"declare"
"enum"
"export"
"implements"
"interface"
"keyof"
"namespace"
"private"
"protected"
"public"
"type"
"readonly"
"override"
] @keyword
@@ -0,0 +1,15 @@
[
(call_expression)
(assignment_expression)
(member_expression)
(lexical_declaration)
(variable_declaration)
(assignment_expression)
(if_statement)
(for_statement)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent
@@ -0,0 +1,55 @@
(internal_module
"namespace" @context
name: (_) @name) @item
(enum_declaration
"enum" @context
name: (_) @name) @item
(function_declaration
"async"? @context
"function" @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item
(interface_declaration
"interface" @context
name: (_) @name) @item
(program
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (_) @name) @item))
(class_declaration
"class" @context
name: (_) @name) @item
(method_definition
[
"get"
"set"
"async"
"*"
"readonly"
"static"
(override_modifier)
(accessibility_modifier)
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item
(public_field_definition
[
"declare"
"readonly"
"abstract"
"static"
(accessibility_modifier)
]* @context
name: (_) @name) @item
+3 -3
View File
@@ -19,7 +19,7 @@ use workspace::{
AppState, OpenNew, OpenParams, OpenPaths, Settings,
};
use zed::{
self, assets::Assets, build_window_options, build_workspace, fs::RealFs, language, menus,
self, assets::Assets, build_window_options, build_workspace, fs::RealFs, languages, menus,
};
fn main() {
@@ -34,7 +34,7 @@ fn main() {
let default_settings = Settings::new("Zed Mono", &app.font_cache(), theme)
.unwrap()
.with_overrides(
language::PLAIN_TEXT.name(),
languages::PLAIN_TEXT.name(),
settings::LanguageOverride {
soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
..Default::default()
@@ -60,7 +60,7 @@ fn main() {
app.run(move |cx| {
let http = http::client();
let client = client::Client::new(http.clone());
let mut languages = language::build_language_registry(login_shell_env_loaded);
let mut languages = languages::build_language_registry(login_shell_env_loaded);
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
let channel_list =
cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx));
+3 -3
View File
@@ -1,5 +1,5 @@
pub mod assets;
pub mod language;
pub mod languages;
pub mod menus;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
@@ -557,7 +557,7 @@ mod tests {
assert_eq!(editor.title(cx), "untitled");
assert!(Arc::ptr_eq(
editor.language(cx).unwrap(),
&language::PLAIN_TEXT
&languages::PLAIN_TEXT
));
editor.handle_input(&editor::Input("hi".into()), cx);
assert!(editor.is_dirty(cx));
@@ -647,7 +647,7 @@ mod tests {
editor.update(cx, |editor, cx| {
assert!(Arc::ptr_eq(
editor.language(cx).unwrap(),
&language::PLAIN_TEXT
&languages::PLAIN_TEXT
));
editor.handle_input(&editor::Input("hi".into()), cx);
assert!(editor.is_dirty(cx.as_ref()));