diff --git a/README.md b/README.md index 62d7d06..139691e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,12 @@ Here is a common `settings.json` including the above mentioned configurations: } ``` +## Project Symbol Search + +The extension supports project-wide symbol search with syntax-highlighted results. This feature is powered by JDTLS and can be accessed via Zed's symbol search. + +JDTLS uses **CamelCase fuzzy matching** for symbol queries. For example, searching for `EmpMe` would match `EmptyMedia`. The pattern works like `Emp*Me*`, matching the capital letters of CamelCase names. + ## Debugger Debug support is enabled via our [Fork of Java Debug](https://github.com/zed-industries/java-debug), which the extension will automatically download and start for you. Please refer to the [Zed Documentation](https://zed.dev/docs/debugger#getting-started) for general information about how debugging works in Zed. diff --git a/src/java.rs b/src/java.rs index 76dc8e2..05f7438 100644 --- a/src/java.rs +++ b/src/java.rs @@ -16,7 +16,7 @@ use zed_extension_api::{ self as zed, CodeLabel, CodeLabelSpan, DebugAdapterBinary, DebugTaskDefinition, Extension, LanguageServerId, LanguageServerInstallationStatus, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, Worktree, - lsp::{Completion, CompletionKind}, + lsp::{Completion, CompletionKind, Symbol, SymbolKind}, register_extension, serde_json::{Value, json}, set_language_server_installation_status, @@ -535,6 +535,71 @@ impl Extension for Java { _ => None, }) } + + fn label_for_symbol( + &self, + _language_server_id: &LanguageServerId, + symbol: Symbol, + ) -> Option { + let name = &symbol.name; + + match symbol.kind { + SymbolKind::Class | SymbolKind::Interface | SymbolKind::Enum => { + let keyword = match symbol.kind { + SymbolKind::Class => "class ", + SymbolKind::Interface => "interface ", + SymbolKind::Enum => "enum ", + _ => unreachable!(), + }; + let code = format!("{keyword}{name} {{}}"); + + Some(CodeLabel { + spans: vec![CodeLabelSpan::code_range(0..keyword.len() + name.len())], + filter_range: (keyword.len()..keyword.len() + name.len()).into(), + code, + }) + } + SymbolKind::Method | SymbolKind::Function => { + // jdtls: "methodName(Type, Type) : ReturnType" or "methodName(Type)" + // display: "ReturnType methodName(Type, Type)" (Java declaration order) + let method_name = name.split('(').next().unwrap_or(name); + let after_name = &name[method_name.len()..]; + + let (params, return_type) = if let Some((p, r)) = after_name.split_once(" : ") { + (p, Some(r)) + } else { + (after_name, None) + }; + + let ret = return_type.unwrap_or("void"); + let class_open = "class _ { "; + let code = format!("{class_open}{ret} {method_name}() {{}} }}"); + + let ret_start = class_open.len(); + let name_start = ret_start + ret.len() + 1; + + // Display: "void methodName(String, int)" + let mut spans = vec![ + CodeLabelSpan::code_range(ret_start..ret_start + ret.len()), + CodeLabelSpan::literal(" ".to_string(), None), + CodeLabelSpan::code_range(name_start..name_start + method_name.len()), + ]; + if !params.is_empty() { + spans.push(CodeLabelSpan::literal(params.to_string(), None)); + } + + // filter on "methodName(params)" portion of displayed text + let type_prefix_len = ret.len() + 1; // "void " + let filter_end = type_prefix_len + method_name.len() + params.len(); + Some(CodeLabel { + spans, + filter_range: (type_prefix_len..filter_end).into(), + code, + }) + } + _ => None, + } + } } register_extension!(Java);