From db68fc51303e5bf9621246c0bb4edb8233641510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Molina=20Rebolledo?= Date: Sun, 28 Jan 2024 16:44:50 -0600 Subject: [PATCH] Add PureScript LSP/Highlighting support (#6911) This PR adds basic support for the language using the `purescript-language-server`. Release Notes: - Added support for PureScript. --- Cargo.lock | 10 ++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 8 + crates/zed/src/languages/purescript.rs | 139 +++++++++++++++++ .../zed/src/languages/purescript/brackets.scm | 3 + .../zed/src/languages/purescript/config.toml | 13 ++ .../src/languages/purescript/highlights.scm | 144 ++++++++++++++++++ .../zed/src/languages/purescript/indents.scm | 3 + 9 files changed, 322 insertions(+) create mode 100644 crates/zed/src/languages/purescript.rs create mode 100644 crates/zed/src/languages/purescript/brackets.scm create mode 100644 crates/zed/src/languages/purescript/config.toml create mode 100644 crates/zed/src/languages/purescript/highlights.scm create mode 100644 crates/zed/src/languages/purescript/indents.scm diff --git a/Cargo.lock b/Cargo.lock index cfeb83bf5c..21d97b093e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8549,6 +8549,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-purescript" +version = "1.0.0" +source = "git+https://github.com/ivanmoreau/tree-sitter-purescript?rev=a37140f0c7034977b90faa73c94fcb8a5e45ed08#a37140f0c7034977b90faa73c94fcb8a5e45ed08" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-python" version = "0.20.4" @@ -9739,6 +9748,7 @@ dependencies = [ "tree-sitter-nix", "tree-sitter-nu", "tree-sitter-php", + "tree-sitter-purescript", "tree-sitter-python", "tree-sitter-racket", "tree-sitter-ruby", diff --git a/Cargo.toml b/Cargo.toml index 1af796ae79..eaa09c4c8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,6 +148,7 @@ tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", re tree-sitter-rust = "0.20.3" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } tree-sitter-php = "0.21.1" +tree-sitter-purescript = { git = "https://github.com/ivanmoreau/tree-sitter-purescript", rev = "a37140f0c7034977b90faa73c94fcb8a5e45ed08" } tree-sitter-python = "0.20.2" tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 3ad3595044..97c5a6e394 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -134,6 +134,7 @@ tree-sitter-ruby.workspace = true tree-sitter-haskell.workspace = true tree-sitter-html.workspace = true tree-sitter-php.workspace = true +tree-sitter-purescript.workspace = true tree-sitter-scheme.workspace = true tree-sitter-svelte.workspace = true tree-sitter-racket.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index a93b8b29a7..ac3d7f2ee8 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -23,6 +23,7 @@ mod language_plugin; mod lua; mod nu; mod php; +mod purescript; mod python; mod ruby; mod rust; @@ -258,6 +259,13 @@ pub fn init( ], ); + language( + "purescript", + tree_sitter_purescript::language(), + vec![Arc::new(purescript::PurescriptLspAdapter::new( + node_runtime.clone(), + ))], + ); language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); diff --git a/crates/zed/src/languages/purescript.rs b/crates/zed/src/languages/purescript.rs new file mode 100644 index 0000000000..bdf55f1b93 --- /dev/null +++ b/crates/zed/src/languages/purescript.rs @@ -0,0 +1,139 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use collections::HashMap; +use futures::StreamExt; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use node_runtime::NodeRuntime; +use serde_json::json; +use smol::fs; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; +use util::{async_maybe, ResultExt}; + +const SERVER_PATH: &'static str = "node_modules/.bin/purescript-language-server"; + +fn server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] +} + +pub struct PurescriptLspAdapter { + node: Arc, +} + +impl PurescriptLspAdapter { + pub fn new(node: Arc) -> Self { + Self { node } + } +} + +#[async_trait] +impl LspAdapter for PurescriptLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("purescript-language-server".into()) + } + + fn short_name(&self) -> &'static str { + "purescript" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new( + self.node + .npm_package_latest_version("purescript-language-server") + .await?, + ) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let server_path = container_dir.join(SERVER_PATH); + + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("purescript-language-server", version.as_str())], + ) + .await?; + } + + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary(container_dir, &*self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &*self.node).await + } + + fn initialization_options(&self) -> Option { + Some(json!({ + "purescript": { + "addSpagoSources": true + } + })) + } + + fn language_ids(&self) -> HashMap { + [("PureScript".into(), "purescript".into())] + .into_iter() + .collect() + } +} + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &dyn NodeRuntime, +) -> Option { + async_maybe!({ + 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 server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + }) + .await + .log_err() +} diff --git a/crates/zed/src/languages/purescript/brackets.scm b/crates/zed/src/languages/purescript/brackets.scm new file mode 100644 index 0000000000..191fd9c084 --- /dev/null +++ b/crates/zed/src/languages/purescript/brackets.scm @@ -0,0 +1,3 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) diff --git a/crates/zed/src/languages/purescript/config.toml b/crates/zed/src/languages/purescript/config.toml new file mode 100644 index 0000000000..f568ef4d1d --- /dev/null +++ b/crates/zed/src/languages/purescript/config.toml @@ -0,0 +1,13 @@ +name = "PureScript" +path_suffixes = ["purs"] +autoclose_before = ",=)}]" +line_comment = "-- " +block_comment = ["{- ", " -}"] +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 }, + { start = "`", end = "`", close = true, newline = false }, +] diff --git a/crates/zed/src/languages/purescript/highlights.scm b/crates/zed/src/languages/purescript/highlights.scm new file mode 100644 index 0000000000..b6e969af78 --- /dev/null +++ b/crates/zed/src/languages/purescript/highlights.scm @@ -0,0 +1,144 @@ +;; Copyright 2022 nvim-treesitter +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; You may obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS IS" BASIS, +;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;; See the License for the specific language governing permissions and +;; limitations under the License. + +;; ---------------------------------------------------------------------------- +;; Literals and comments + +(integer) @number +(exp_negation) @number +(exp_literal (number)) @float +(char) @character +[ + (string) + (triple_quote_string) +] @string + +(comment) @comment + + +;; ---------------------------------------------------------------------------- +;; Punctuation + +[ + "(" + ")" + "{" + "}" + "[" + "]" +] @punctuation.bracket + +[ + (comma) + ";" +] @punctuation.delimiter + + +;; ---------------------------------------------------------------------------- +;; Keywords, operators, includes + +[ + "forall" + "∀" +] @keyword + +;; (pragma) @constant + +[ + "if" + "then" + "else" + "case" + "of" +] @keyword + +[ + "import" + "module" +] @keyword + +[ + (operator) + (constructor_operator) + (type_operator) + (qualified_module) ; grabs the `.` (dot), ex: import System.IO + (all_names) + (wildcard) + "=" + "|" + "::" + "=>" + "->" + "<-" + "\\" + "`" + "@" + "∷" + "⇒" + "<=" + "⇐" + "→" + "←" +] @operator + +(module) @title + +[ + (where) + "let" + "in" + "class" + "instance" + "derive" + "foreign" + "data" + "newtype" + "type" + "as" + "hiding" + "do" + "ado" + "infix" + "infixl" + "infixr" +] @keyword + + +;; ---------------------------------------------------------------------------- +;; Functions and variables + +(variable) @variable +(pat_wildcard) @variable + +(signature name: (variable) @type) +(function + name: (variable) @function + patterns: (patterns)) + + +(exp_infix (exp_name) @function (#set! "priority" 101)) +(exp_apply . (exp_name (variable) @function)) +(exp_apply . (exp_name (qualified_variable (variable) @function))) + + +;; ---------------------------------------------------------------------------- +;; Types + +(type) @type +(type_variable) @type + +(constructor) @constructor + +; True or False +((constructor) @_bool (#match? @_bool "(True|False)")) @boolean diff --git a/crates/zed/src/languages/purescript/indents.scm b/crates/zed/src/languages/purescript/indents.scm new file mode 100644 index 0000000000..112b414aa4 --- /dev/null +++ b/crates/zed/src/languages/purescript/indents.scm @@ -0,0 +1,3 @@ +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent