diff --git a/Cargo.lock b/Cargo.lock index 0d2c47efc8430bf04b1e65ce41e4f76193abbe99..214ece842b4621fc26a5225e6876e12f3391eb96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5214,9 +5214,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", @@ -5279,6 +5279,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-python" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713170684ba94376b784b0c6dd23693461e15f96a806ed1848e40996e3cda7c7" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-rust" version = "0.20.1" @@ -6032,6 +6042,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/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/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/Cargo.toml b/crates/zed/Cargo.toml index a866e2b92e85a7afb28556ca86e244d5ec24ed32..cc268beb3c581faf6fe9859e0056da088ab6696c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -87,13 +87,14 @@ 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" } 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.1" 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..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; @@ -43,6 +44,11 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi tree_sitter_markdown::language(), None, // ), + ( + "python", + tree_sitter_python::language(), + Some(Arc::new(python::PythonLspAdapter)), + ), ( "rust", tree_sitter_rust::language(), diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs new file mode 100644 index 0000000000000000000000000000000000000000..be5b58b4b521a5f7c50aee325f0c4e0e69e64a6a --- /dev/null +++ b/crates/zed/src/languages/python.rs @@ -0,0 +1,153 @@ +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() + } + + 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/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/config.toml b/crates/zed/src/languages/python/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..da49d3709abd4ddda460ad6ed36f8789410d98bc --- /dev/null +++ b/crates/zed/src/languages/python/config.toml @@ -0,0 +1,11 @@ +name = "Python" +path_suffixes = ["py", "pyi"] +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..118af92aaaff8fdf28ba75aa0456931f04fb5aa2 --- /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) @type + (#match? @type "^[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..ad262fd5013bfe458257b6ba51232c7a546ce1f2 --- /dev/null +++ b/crates/zed/src/languages/python/indents.scm @@ -0,0 +1,4 @@ +(_ (block)) @indent +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent 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 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,