Detailed changes
@@ -8455,6 +8455,15 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-c-sharp"
+version = "0.20.0"
+source = "git+https://github.com/tree-sitter/tree-sitter-c-sharp?rev=dd5e59721a5f8dae34604060833902b882023aaf#dd5e59721a5f8dae34604060833902b882023aaf"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-cpp"
version = "0.20.0"
@@ -9813,6 +9822,7 @@ dependencies = [
"tree-sitter",
"tree-sitter-bash",
"tree-sitter-c",
+ "tree-sitter-c-sharp",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-elixir",
@@ -136,6 +136,7 @@ uuid = { version = "1.1.2", features = ["v4"] }
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
tree-sitter-c = "0.20.1"
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
+tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"}
@@ -115,6 +115,7 @@ tree-sitter.workspace = true
tree-sitter-bash.workspace = true
tree-sitter-c.workspace = true
tree-sitter-cpp.workspace = true
+tree-sitter-c-sharp.workspace = true
tree-sitter-css.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-elm.workspace = true
@@ -10,6 +10,7 @@ use util::{asset_str, paths::PLUGINS_DIR};
use self::{deno::DenoSettings, elixir::ElixirSettings};
mod c;
+mod csharp;
mod css;
mod deno;
mod elixir;
@@ -73,6 +74,11 @@ pub fn init(
tree_sitter_cpp::language(),
vec![Arc::new(c::CLspAdapter)],
);
+ language(
+ "csharp",
+ tree_sitter_c_sharp::language(),
+ vec![Arc::new(csharp::OmniSharpAdapter {})],
+ );
language(
"css",
tree_sitter_css::language(),
@@ -0,0 +1,144 @@
+use anyhow::{anyhow, Context, Result};
+use async_compression::futures::bufread::GzipDecoder;
+use async_tar::Archive;
+use async_trait::async_trait;
+use futures::{io::BufReader, StreamExt};
+use language::{LanguageServerName, LspAdapterDelegate};
+use lsp::LanguageServerBinary;
+use smol::fs;
+use std::env::consts::ARCH;
+use std::ffi::OsString;
+use std::{any::Any, path::PathBuf};
+use util::async_maybe;
+use util::github::latest_github_release;
+use util::{github::GitHubLspBinaryVersion, ResultExt};
+
+pub struct OmniSharpAdapter;
+
+#[async_trait]
+impl super::LspAdapter for OmniSharpAdapter {
+ fn name(&self) -> LanguageServerName {
+ LanguageServerName("OmniSharp".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "OmniSharp"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ let release =
+ latest_github_release("OmniSharp/omnisharp-roslyn", false, delegate.http_client())
+ .await?;
+
+ let mapped_arch = match ARCH {
+ "aarch64" => Some("arm64"),
+ "x86_64" => Some("x64"),
+ _ => None,
+ };
+
+ match mapped_arch {
+ None => Ok(Box::new(())),
+ Some(arch) => {
+ let asset_name = format!("omnisharp-osx-{}-net6.0.tar.gz", 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<_>)
+ }
+ }
+ }
+
+ 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 binary_path = container_dir.join("omnisharp");
+
+ 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 decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
+ let archive = Archive::new(decompressed_bytes);
+ archive.unpack(container_dir).await?;
+ }
+
+ fs::set_permissions(
+ &binary_path,
+ <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
+ )
+ .await?;
+ Ok(LanguageServerBinary {
+ path: binary_path,
+ arguments: server_binary_arguments(),
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_server_binary(container_dir).await
+ }
+
+ async fn installation_test_binary(
+ &self,
+ container_dir: PathBuf,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_server_binary(container_dir)
+ .await
+ .map(|mut binary| {
+ binary.arguments = vec!["--help".into()];
+ binary
+ })
+ }
+}
+
+async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
+ async_maybe!({
+ 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 == "omnisharp")
+ {
+ last_binary_path = Some(entry.path());
+ }
+ }
+
+ if let Some(path) = last_binary_path {
+ Ok(LanguageServerBinary {
+ path,
+ arguments: server_binary_arguments(),
+ })
+ } else {
+ Err(anyhow!("no cached binary"))
+ }
+ })
+ .await
+ .log_err()
+}
+
+fn server_binary_arguments() -> Vec<OsString> {
+ vec!["-lsp".into()]
+}
@@ -0,0 +1,12 @@
+name = "CSharp"
+path_suffixes = ["cs"]
+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"] },
+ { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
+ { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
+]
@@ -0,0 +1,254 @@
+;; Methods
+(method_declaration name: (identifier) @function)
+(local_function_statement name: (identifier) @function)
+
+;; Types
+(interface_declaration name: (identifier) @type)
+(class_declaration name: (identifier) @type)
+(enum_declaration name: (identifier) @type)
+(struct_declaration (identifier) @type)
+(record_declaration (identifier) @type)
+(record_struct_declaration (identifier) @type)
+(namespace_declaration name: (identifier) @type)
+
+(constructor_declaration name: (identifier) @constructor)
+(destructor_declaration name: (identifier) @constructor)
+
+[
+ (implicit_type)
+ (predefined_type)
+] @type.builtin
+
+(_ type: (identifier) @type)
+
+;; Enum
+(enum_member_declaration (identifier) @property)
+
+;; Literals
+[
+ (real_literal)
+ (integer_literal)
+] @number
+
+[
+ (character_literal)
+ (string_literal)
+ (verbatim_string_literal)
+ (interpolated_string_text)
+ (interpolated_verbatim_string_text)
+ "\""
+ "$\""
+ "@$\""
+ "$@\""
+ ] @string
+
+[
+ (boolean_literal)
+ (null_literal)
+] @constant
+
+;; Comments
+(comment) @comment
+
+;; Tokens
+[
+ ";"
+ "."
+ ","
+] @punctuation.delimiter
+
+[
+ "--"
+ "-"
+ "-="
+ "&"
+ "&="
+ "&&"
+ "+"
+ "++"
+ "+="
+ "<"
+ "<="
+ "<<"
+ "<<="
+ "="
+ "=="
+ "!"
+ "!="
+ "=>"
+ ">"
+ ">="
+ ">>"
+ ">>="
+ ">>>"
+ ">>>="
+ "|"
+ "|="
+ "||"
+ "?"
+ "??"
+ "??="
+ "^"
+ "^="
+ "~"
+ "*"
+ "*="
+ "/"
+ "/="
+ "%"
+ "%="
+ ":"
+] @operator
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
+;; Keywords
+(modifier) @keyword
+(this_expression) @keyword
+(escape_sequence) @keyword
+
+[
+ "add"
+ "alias"
+ "as"
+ "base"
+ "break"
+ "case"
+ "catch"
+ "checked"
+ "class"
+ "continue"
+ "default"
+ "delegate"
+ "do"
+ "else"
+ "enum"
+ "event"
+ "explicit"
+ "extern"
+ "finally"
+ "for"
+ "foreach"
+ "global"
+ "goto"
+ "if"
+ "implicit"
+ "interface"
+ "is"
+ "lock"
+ "namespace"
+ "notnull"
+ "operator"
+ "params"
+ "return"
+ "remove"
+ "sizeof"
+ "stackalloc"
+ "static"
+ "struct"
+ "switch"
+ "throw"
+ "try"
+ "typeof"
+ "unchecked"
+ "using"
+ "while"
+ "new"
+ "await"
+ "in"
+ "yield"
+ "get"
+ "set"
+ "when"
+ "out"
+ "ref"
+ "from"
+ "where"
+ "select"
+ "record"
+ "init"
+ "with"
+ "let"
+] @keyword
+
+
+;; Linq
+(from_clause (identifier) @variable)
+(group_clause (identifier) @variable)
+(order_by_clause (identifier) @variable)
+(join_clause (identifier) @variable)
+(select_clause (identifier) @variable)
+(query_continuation (identifier) @variable) @keyword
+
+;; Record
+(with_expression
+ (with_initializer_expression
+ (simple_assignment_expression
+ (identifier) @variable)))
+
+;; Exprs
+(binary_expression (identifier) @variable (identifier) @variable)
+(binary_expression (identifier)* @variable)
+(conditional_expression (identifier) @variable)
+(prefix_unary_expression (identifier) @variable)
+(postfix_unary_expression (identifier)* @variable)
+(assignment_expression (identifier) @variable)
+(cast_expression (_) (identifier) @variable)
+
+;; Class
+(base_list (identifier) @type) ;; applies to record_base too
+(property_declaration (generic_name))
+(property_declaration
+ name: (identifier) @variable)
+(property_declaration
+ name: (identifier) @variable)
+(property_declaration
+ name: (identifier) @variable)
+
+;; Lambda
+(lambda_expression) @variable
+
+;; Attribute
+(attribute) @attribute
+
+;; Parameter
+(parameter
+ name: (identifier) @variable)
+(parameter (identifier) @variable)
+(parameter_modifier) @keyword
+
+;; Variable declarations
+(variable_declarator (identifier) @variable)
+(for_each_statement left: (identifier) @variable)
+(catch_declaration (_) (identifier) @variable)
+
+;; Return
+(return_statement (identifier) @variable)
+(yield_statement (identifier) @variable)
+
+;; Type
+(generic_name (identifier) @type)
+(type_parameter (identifier) @property)
+(type_argument_list (identifier) @type)
+(as_expression right: (identifier) @type)
+(is_expression right: (identifier) @type)
+
+;; Type constraints
+(type_parameter_constraints_clause (identifier) @property)
+
+;; Switch
+(switch_statement (identifier) @variable)
+(switch_expression (identifier) @variable)
+
+;; Lock statement
+(lock_statement (identifier) @variable)
+
+;; Method calls
+(invocation_expression (member_access_expression name: (identifier) @function))
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
@@ -0,0 +1,38 @@
+(class_declaration
+ "class" @context
+ name: (identifier) @name
+) @item
+
+(constructor_declaration
+ name: (identifier) @name
+) @item
+
+(property_declaration
+ type: (identifier)? @context
+ type: (predefined_type)? @context
+ name: (identifier) @name
+) @item
+
+(field_declaration
+ (variable_declaration) @context
+) @item
+
+(method_declaration
+ name: (identifier) @name
+ parameters: (parameter_list) @context
+) @item
+
+(enum_declaration
+ "enum" @context
+ name: (identifier) @name
+) @item
+
+(namespace_declaration
+ "namespace" @context
+ name: (qualified_name) @name
+) @item
+
+(interface_declaration
+ "interface" @context
+ name: (identifier) @name
+) @item
@@ -0,0 +1,4 @@
+# C#
+
+- Tree Sitter: [tree-sitter-c-sharp](https://github.com/tree-sitter/tree-sitter-c-sharp)
+- Language Server: [OmniSharp](https://github.com/OmniSharp/omnisharp-roslyn)