From 34351c0a51cad8f354f628d23a0dc04e4112d04b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 21 Jun 2022 17:04:54 -0700 Subject: [PATCH 1/7] Start work on Python support --- Cargo.lock | 11 ++ crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 5 + crates/zed/src/languages/python/config.toml | 11 ++ .../zed/src/languages/python/highlights.scm | 125 ++++++++++++++++++ crates/zed/src/languages/python/indents.scm | 6 + styles/src/themes/common/base16.ts | 4 + 7 files changed, 163 insertions(+) create mode 100644 crates/zed/src/languages/python/config.toml create mode 100644 crates/zed/src/languages/python/highlights.scm create mode 100644 crates/zed/src/languages/python/indents.scm diff --git a/Cargo.lock b/Cargo.lock index b96bcce58167661d73830530f0cdcb414dc8b6e5..427db02e74e911bc34ea21691b4be53e61ae0e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5277,6 +5277,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-python" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d315475c65416274539dd1db9fa2de94918a6ef399dc1287be7cb7bc83dd6d" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-rust" version = "0.20.1" @@ -6030,6 +6040,7 @@ dependencies = [ "tree-sitter-go", "tree-sitter-json 0.20.0", "tree-sitter-markdown", + "tree-sitter-python", "tree-sitter-rust", "tree-sitter-toml", "tree-sitter-typescript", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index a866e2b92e85a7afb28556ca86e244d5ec24ed32..3e43f45c7af67b5c831e52e936fcd2a0ae6f761b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -94,6 +94,7 @@ tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" } tree-sitter-rust = "0.20.1" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } +tree-sitter-python = "0.20.0" tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } tree-sitter-typescript = "0.20.1" url = "2.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 825925c07f41a9bcb5d80547b97fb689ac1a58e8..f3c1a83f25a4ea9d141507b42cd6d3ced3aa56fd 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -43,6 +43,11 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi tree_sitter_markdown::language(), None, // ), + ( + "python", + tree_sitter_python::language(), + None, // + ), ( "rust", tree_sitter_rust::language(), diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..beddf578f2ba325291c738f02aea54fccf78706b --- /dev/null +++ b/crates/zed/src/languages/python/config.toml @@ -0,0 +1,11 @@ +name = "Python" +path_suffixes = ["py"] +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 = false, newline = false }, +] diff --git a/crates/zed/src/languages/python/highlights.scm b/crates/zed/src/languages/python/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..c752e586c7efa1d9510a2076dec6abdf5c15b06a --- /dev/null +++ b/crates/zed/src/languages/python/highlights.scm @@ -0,0 +1,125 @@ +(attribute attribute: (identifier) @property) +(type (identifier) @type) + +; Function calls + +(decorator) @function + +(call + function: (attribute attribute: (identifier) @function.method)) +(call + function: (identifier) @function) + +; Function definitions + +(function_definition + name: (identifier) @function) + +; Identifier naming conventions + +((identifier) @constructor + (#match? @constructor "^[A-Z]")) + +((identifier) @constant + (#match? @constant "^[A-Z][A-Z_]*$")) + +; Builtin functions + +((call + function: (identifier) @function.builtin) + (#match? + @function.builtin + "^(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes|callable|chr|classmethod|compile|complex|delattr|dict|dir|divmod|enumerate|eval|exec|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|isinstance|issubclass|iter|len|list|locals|map|max|memoryview|min|next|object|oct|open|ord|pow|print|property|range|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|vars|zip|__import__)$")) + +; Literals + +[ + (none) + (true) + (false) +] @constant.builtin + +[ + (integer) + (float) +] @number + +(comment) @comment +(string) @string +(escape_sequence) @escape + +(interpolation + "{" @punctuation.special + "}" @punctuation.special) @embedded + +[ + "-" + "-=" + "!=" + "*" + "**" + "**=" + "*=" + "/" + "//" + "//=" + "/=" + "&" + "%" + "%=" + "^" + "+" + "->" + "+=" + "<" + "<<" + "<=" + "<>" + "=" + ":=" + "==" + ">" + ">=" + ">>" + "|" + "~" + "and" + "in" + "is" + "not" + "or" +] @operator + +[ + "as" + "assert" + "async" + "await" + "break" + "class" + "continue" + "def" + "del" + "elif" + "else" + "except" + "exec" + "finally" + "for" + "from" + "global" + "if" + "import" + "lambda" + "nonlocal" + "pass" + "print" + "raise" + "return" + "try" + "while" + "with" + "yield" + "match" + "case" +] @keyword \ No newline at end of file diff --git a/crates/zed/src/languages/python/indents.scm b/crates/zed/src/languages/python/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..efc93466a7e39e2f3a0051376648ace62a33fbb0 --- /dev/null +++ b/crates/zed/src/languages/python/indents.scm @@ -0,0 +1,6 @@ +(class_definition body: (block)) @indent +(function_definition body: (block)) @indent + +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent diff --git a/styles/src/themes/common/base16.ts b/styles/src/themes/common/base16.ts index 0bdf455be3a79b435d1dc2b5c2a9d5edcfbfefe2..52715bd5445306a0bf51b2f6b11a61e5f938a0e3 100644 --- a/styles/src/themes/common/base16.ts +++ b/styles/src/themes/common/base16.ts @@ -171,6 +171,10 @@ export function createTheme( color: sample(ramps.cyan, 0.5), weight: fontWeights.normal, }, + constructor: { + color: sample(ramps.blue, 0.5), + weight: fontWeights.normal, + }, variant: { color: sample(ramps.blue, 0.5), weight: fontWeights.normal, From 074caa09c2156180fc89a3c8d6c4d1c5d84e0d24 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 22 Jun 2022 16:18:52 -0700 Subject: [PATCH 2/7] Make python indent query more general Upgrade Tree-sitter for a query bugfix that I found while writing this indent query. --- Cargo.lock | 4 ++-- crates/zed/Cargo.toml | 2 +- crates/zed/src/languages/python/indents.scm | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 427db02e74e911bc34ea21691b4be53e61ae0e30..4b76e517e950ff07337c5e235d8752cf25a83ccf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5212,9 +5212,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3b781640108d29892e8b9684642d2cda5ea05951fd58f0fea1db9edeb9b71" +checksum = "549a9faf45679ad50b7f603253635598cf5e007d8ceb806a23f95355938f76a0" dependencies = [ "cc", "regex", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 3e43f45c7af67b5c831e52e936fcd2a0ae6f761b..47a2df6a9a074f820b93d1208f22ca22f6104870 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -87,7 +87,7 @@ tempdir = { version = "0.3.7" } thiserror = "1.0.29" tiny_http = "0.8" toml = "0.5" -tree-sitter = "0.20.6" +tree-sitter = "0.20.7" tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } diff --git a/crates/zed/src/languages/python/indents.scm b/crates/zed/src/languages/python/indents.scm index efc93466a7e39e2f3a0051376648ace62a33fbb0..ad262fd5013bfe458257b6ba51232c7a546ce1f2 100644 --- a/crates/zed/src/languages/python/indents.scm +++ b/crates/zed/src/languages/python/indents.scm @@ -1,6 +1,4 @@ -(class_definition body: (block)) @indent -(function_definition body: (block)) @indent - +(_ (block)) @indent (_ "[" "]" @end) @indent (_ "{" "}" @end) @indent (_ "(" ")" @end) @indent From c0dbd8f9b9e6be0ef1522d49c29e11e6d80f795f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 22 Jun 2022 16:28:09 -0700 Subject: [PATCH 3/7] Add python outline and bracket queries --- crates/language/src/buffer.rs | 2 +- crates/zed/src/languages/python/brackets.scm | 3 +++ crates/zed/src/languages/python/outline.scm | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 crates/zed/src/languages/python/brackets.scm create mode 100644 crates/zed/src/languages/python/outline.scm diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 69d51ce9dbeb482eca2026f5f1d3055cd4d7b14d..7fb414166d94c9726c7eb64d1a5c8574ce85fab6 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1902,7 +1902,7 @@ impl BufferSnapshot { } while stack.last().map_or(false, |prev_range| { - !prev_range.contains(&item_range.start) || !prev_range.contains(&item_range.end) + prev_range.start > item_range.start || prev_range.end < item_range.end }) { stack.pop(); } diff --git a/crates/zed/src/languages/python/brackets.scm b/crates/zed/src/languages/python/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..191fd9c084a52eced37428281971ff9e569a4932 --- /dev/null +++ b/crates/zed/src/languages/python/brackets.scm @@ -0,0 +1,3 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) diff --git a/crates/zed/src/languages/python/outline.scm b/crates/zed/src/languages/python/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..373c7c7c68b3b2a97e753c4fca09b6c2e42c68e6 --- /dev/null +++ b/crates/zed/src/languages/python/outline.scm @@ -0,0 +1,9 @@ +(class_definition + "class" @context + name: (identifier) @name + ) @item + +(function_definition + "async"? @context + "def" @context + name: (_) @name) @item \ No newline at end of file From 11f73bfa4e63be14523d8fb78d7e3aa7e1158a2c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 22 Jun 2022 16:58:19 -0700 Subject: [PATCH 4/7] Integrate pyright language server --- crates/lsp/src/lsp.rs | 7 ++- crates/zed/src/languages.rs | 3 +- crates/zed/src/languages/python.rs | 97 ++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 crates/zed/src/languages/python.rs diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index b1b97b945570c91b63da0b5b4ca929acedffa14d..90a6ff529ae564d049c73bfc6d0f3893579c221d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -251,7 +251,7 @@ impl LanguageServer { let params = InitializeParams { process_id: Default::default(), root_path: Default::default(), - root_uri: Some(root_uri), + root_uri: Some(root_uri.clone()), initialization_options: options, capabilities: ClientCapabilities { workspace: Some(WorkspaceClientCapabilities { @@ -312,7 +312,10 @@ impl LanguageServer { ..Default::default() }, trace: Default::default(), - workspace_folders: Default::default(), + workspace_folders: Some(vec![WorkspaceFolder { + uri: root_uri, + name: Default::default(), + }]), client_info: Default::default(), locale: Default::default(), }; diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index f3c1a83f25a4ea9d141507b42cd6d3ced3aa56fd..d792660d20793ce2a0cd2b6e9cd4658d7bc3c440 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -7,6 +7,7 @@ mod c; mod go; mod installation; mod json; +mod python; mod rust; mod typescript; @@ -46,7 +47,7 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi ( "python", tree_sitter_python::language(), - None, // + Some(Arc::new(python::PythonLspAdapter)), ), ( "rust", diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs new file mode 100644 index 0000000000000000000000000000000000000000..84f5eda3f782fca963907927fa553c2dc8d5f428 --- /dev/null +++ b/crates/zed/src/languages/python.rs @@ -0,0 +1,97 @@ +use super::installation::{npm_install_packages, npm_package_latest_version}; +use anyhow::{anyhow, Context, Result}; +use client::http::HttpClient; +use futures::{future::BoxFuture, FutureExt, StreamExt}; +use language::{LanguageServerName, LspAdapter}; +use smol::fs; +use std::{ + any::Any, + path::{Path, PathBuf}, + sync::Arc, +}; +use util::{ResultExt, TryFutureExt}; + +pub struct PythonLspAdapter; + +impl PythonLspAdapter { + const BIN_PATH: &'static str = "node_modules/pyright/langserver.index.js"; +} + +impl LspAdapter for PythonLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("pyright".into()) + } + + fn server_args(&self) -> &[&str] { + &["--stdio"] + } + + fn fetch_latest_server_version( + &self, + _: Arc, + ) -> BoxFuture<'static, Result>> { + async move { Ok(Box::new(npm_package_latest_version("pyright").await?) as Box<_>) }.boxed() + } + + fn fetch_server_binary( + &self, + version: Box, + _: Arc, + container_dir: Arc, + ) -> BoxFuture<'static, Result> { + let version = version.downcast::().unwrap(); + async move { + let version_dir = container_dir.join(version.as_str()); + 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() { + npm_install_packages([("pyright", version.as_str())], &version_dir).await?; + + 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: Arc, + ) -> BoxFuture<'static, Option> { + 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() + } +} From 981e53784dce19fdd0a4f64bc0984d24d349ebac Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 23 Jun 2022 11:13:30 -0700 Subject: [PATCH 5/7] Upgrade tree-sitter-python for error recovery improvement --- Cargo.lock | 4 ++-- crates/zed/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b76e517e950ff07337c5e235d8752cf25a83ccf..d62f8d2df285ee9181d4dfca7502fe9cc514fd57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5279,9 +5279,9 @@ dependencies = [ [[package]] name = "tree-sitter-python" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d315475c65416274539dd1db9fa2de94918a6ef399dc1287be7cb7bc83dd6d" +checksum = "713170684ba94376b784b0c6dd23693461e15f96a806ed1848e40996e3cda7c7" dependencies = [ "cc", "tree-sitter", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 47a2df6a9a074f820b93d1208f22ca22f6104870..cc268beb3c581faf6fe9859e0056da088ab6696c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -94,7 +94,7 @@ tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" } tree-sitter-rust = "0.20.1" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } -tree-sitter-python = "0.20.0" +tree-sitter-python = "0.20.1" tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } tree-sitter-typescript = "0.20.1" url = "2.2" From 4d4db6ec4ba5c589d7beaecebe7bf712730b2c11 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 23 Jun 2022 11:14:01 -0700 Subject: [PATCH 6/7] Syntax-highlight Python project symbols and completions --- crates/zed/src/languages/python.rs | 56 +++++++++++++++++++ .../zed/src/languages/python/highlights.scm | 4 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 84f5eda3f782fca963907927fa553c2dc8d5f428..be5b58b4b521a5f7c50aee325f0c4e0e69e64a6a 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -94,4 +94,60 @@ impl LspAdapter for PythonLspAdapter { .log_err() .boxed() } + + fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &language::Language, + ) -> Option { + let label = &item.label; + let grammar = language.grammar()?; + let highlight_id = match item.kind? { + lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, + lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?, + lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?, + lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, + _ => return None, + }; + Some(language::CodeLabel { + text: label.clone(), + runs: vec![(0..label.len(), highlight_id)], + filter_range: 0..label.len(), + }) + } + + fn label_for_symbol( + &self, + name: &str, + kind: lsp::SymbolKind, + language: &language::Language, + ) -> Option { + let (text, filter_range, display_range) = match kind { + lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { + let text = format!("def {}():\n", name); + let filter_range = 4..4 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + lsp::SymbolKind::CLASS => { + let text = format!("class {}:", name); + let filter_range = 6..6 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + lsp::SymbolKind::CONSTANT => { + let text = format!("{} = 0", name); + let filter_range = 0..name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + _ => return None, + }; + + Some(language::CodeLabel { + runs: language.highlight_text(&text.as_str().into(), display_range.clone()), + text: text[display_range].to_string(), + filter_range, + }) + } } diff --git a/crates/zed/src/languages/python/highlights.scm b/crates/zed/src/languages/python/highlights.scm index c752e586c7efa1d9510a2076dec6abdf5c15b06a..118af92aaaff8fdf28ba75aa0456931f04fb5aa2 100644 --- a/crates/zed/src/languages/python/highlights.scm +++ b/crates/zed/src/languages/python/highlights.scm @@ -17,8 +17,8 @@ ; Identifier naming conventions -((identifier) @constructor - (#match? @constructor "^[A-Z]")) +((identifier) @type + (#match? @type "^[A-Z]")) ((identifier) @constant (#match? @constant "^[A-Z][A-Z_]*$")) From dc056fc46f0d52fb0809af061eba114db482d67f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 23 Jun 2022 11:14:11 -0700 Subject: [PATCH 7/7] Treat .pyi files as Python --- crates/zed/src/languages/python/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml index beddf578f2ba325291c738f02aea54fccf78706b..da49d3709abd4ddda460ad6ed36f8789410d98bc 100644 --- a/crates/zed/src/languages/python/config.toml +++ b/crates/zed/src/languages/python/config.toml @@ -1,5 +1,5 @@ name = "Python" -path_suffixes = ["py"] +path_suffixes = ["py", "pyi"] line_comment = "# " autoclose_before = ";:.,=}])>" brackets = [