diff --git a/starlark_bin/bin/bazel.rs b/starlark_bin/bin/bazel.rs index 07610e800..335391655 100644 --- a/starlark_bin/bin/bazel.rs +++ b/starlark_bin/bin/bazel.rs @@ -141,6 +141,7 @@ pub(crate) fn main( prelude: &[PathBuf], dialect: Dialect, globals: Globals, + eager: bool, ) -> anyhow::Result<()> { if !lsp { return Err(anyhow::anyhow!("Bazel mode only supports `--lsp`")); @@ -154,6 +155,7 @@ pub(crate) fn main( is_interactive, dialect, globals, + eager, )?; ctx.mode = ContextMode::Check; @@ -173,6 +175,7 @@ pub(crate) struct BazelContext { pub(crate) globals: Globals, pub(crate) builtin_docs: HashMap, pub(crate) builtin_symbols: HashMap, + pub(crate) eager: bool, } impl BazelContext { @@ -187,6 +190,7 @@ impl BazelContext { module: bool, dialect: Dialect, globals: Globals, + eager: bool, ) -> anyhow::Result { let prelude: Vec<_> = prelude .iter() @@ -263,6 +267,7 @@ impl BazelContext { }), external_output_base: output_base .map(|output_base| PathBuf::from(output_base).join("external")), + eager, }) } @@ -886,4 +891,8 @@ impl LspContext for BazelContext { Ok(names) } + + fn is_eager(&self) -> bool { + self.eager + } } diff --git a/starlark_bin/bin/eval.rs b/starlark_bin/bin/eval.rs index a4028046f..963b635dd 100644 --- a/starlark_bin/bin/eval.rs +++ b/starlark_bin/bin/eval.rs @@ -70,6 +70,7 @@ pub(crate) struct Context { pub(crate) globals: Globals, pub(crate) builtin_docs: HashMap, pub(crate) builtin_symbols: HashMap, + pub(crate) eager: bool, } /// The outcome of evaluating (checking, parsing or running) given starlark code. @@ -101,6 +102,7 @@ impl Context { module: bool, dialect: Dialect, globals: Globals, + eager: bool, ) -> anyhow::Result { let prelude: Vec<_> = prelude .iter() @@ -143,6 +145,7 @@ impl Context { globals, builtin_docs, builtin_symbols, + eager, }) } @@ -379,4 +382,8 @@ impl LspContext for Context { fn get_environment(&self, _uri: &LspUrl) -> DocModule { DocModule::default() } + + fn is_eager(&self) -> bool { + self.eager + } } diff --git a/starlark_bin/bin/main.rs b/starlark_bin/bin/main.rs index ca35d06a4..5e4243024 100644 --- a/starlark_bin/bin/main.rs +++ b/starlark_bin/bin/main.rs @@ -142,6 +142,9 @@ struct Args { )] files: Vec, + #[arg(long = "eager", help = "In LSP mode, eagerly load all files.")] + eager: bool, + #[arg( long = "bazel", help = "Run in Bazel mode (temporary, will be removed)" @@ -298,6 +301,7 @@ fn main() -> anyhow::Result<()> { &prelude, dialect, globals, + args.eager, )?; return Ok(()); } @@ -313,6 +317,7 @@ fn main() -> anyhow::Result<()> { is_interactive, dialect, globals, + args.eager, )?; if args.lsp { diff --git a/starlark_lsp/src/server.rs b/starlark_lsp/src/server.rs index faac435fd..be9dcd530 100644 --- a/starlark_lsp/src/server.rs +++ b/starlark_lsp/src/server.rs @@ -39,22 +39,30 @@ use lsp_server::Response; use lsp_server::ResponseError; use lsp_types::notification::DidChangeTextDocument; use lsp_types::notification::DidCloseTextDocument; +use lsp_types::notification::DidCreateFiles; +use lsp_types::notification::DidDeleteFiles; use lsp_types::notification::DidOpenTextDocument; +use lsp_types::notification::DidRenameFiles; use lsp_types::notification::LogMessage; use lsp_types::notification::PublishDiagnostics; use lsp_types::request::Completion; +use lsp_types::request::DocumentSymbolRequest; use lsp_types::request::GotoDefinition; use lsp_types::request::HoverRequest; +use lsp_types::request::WorkspaceSymbolRequest; use lsp_types::CompletionItem; use lsp_types::CompletionItemKind; use lsp_types::CompletionOptions; use lsp_types::CompletionParams; use lsp_types::CompletionResponse; use lsp_types::DefinitionOptions; +use lsp_types::DeleteFilesParams; use lsp_types::Diagnostic; use lsp_types::DidChangeTextDocumentParams; use lsp_types::DidCloseTextDocumentParams; use lsp_types::DidOpenTextDocumentParams; +use lsp_types::DocumentSymbolParams; +use lsp_types::DocumentSymbolResponse; use lsp_types::Documentation; use lsp_types::GotoDefinitionParams; use lsp_types::GotoDefinitionResponse; @@ -64,6 +72,7 @@ use lsp_types::HoverParams; use lsp_types::HoverProviderCapability; use lsp_types::InitializeParams; use lsp_types::LanguageString; +use lsp_types::Location; use lsp_types::LocationLink; use lsp_types::LogMessageParams; use lsp_types::MarkedString; @@ -74,6 +83,7 @@ use lsp_types::OneOf; use lsp_types::Position; use lsp_types::PublishDiagnosticsParams; use lsp_types::Range; +use lsp_types::RenameFilesParams; use lsp_types::ServerCapabilities; use lsp_types::TextDocumentSyncCapability; use lsp_types::TextDocumentSyncKind; @@ -81,6 +91,9 @@ use lsp_types::TextEdit; use lsp_types::Url; use lsp_types::WorkDoneProgressOptions; use lsp_types::WorkspaceFolder; +use lsp_types::WorkspaceSymbol; +use lsp_types::WorkspaceSymbolParams; +use lsp_types::WorkspaceSymbolResponse; use serde::de::DeserializeOwned; use serde::Deserialize; use serde::Deserializer; @@ -107,6 +120,7 @@ use crate::definition::IdentifierDefinition; use crate::definition::LspModule; use crate::inspect::AstModuleInspect; use crate::inspect::AutocompleteType; +use crate::symbols; use crate::symbols::find_symbols_at_location; /// The request to get the file contents for a starlark: URI @@ -367,6 +381,11 @@ pub trait LspContext { let _unused = (document_uri, kind, current_value, workspace_root); Ok(Vec::new()) } + + /// Should the LSP eagerly load all files in the workspace? Otherwise, only files that are + /// opened will be parsed. This can be useful for large workspaces, but eager loading + /// adds support for workspace symbols, etc. + fn is_eager(&self) -> bool; } /// Errors when [`LspContext::resolve_load()`] cannot resolve a given path. @@ -395,7 +414,7 @@ pub(crate) struct Backend { /// The logic implementations of stuff impl Backend { - fn server_capabilities(settings: LspServerSettings) -> ServerCapabilities { + fn server_capabilities(context: &T, settings: LspServerSettings) -> ServerCapabilities { let definition_provider = settings.enable_goto_definition.then_some({ OneOf::Right(DefinitionOptions { work_done_progress_options: WorkDoneProgressOptions { @@ -408,6 +427,12 @@ impl Backend { definition_provider, completion_provider: Some(CompletionOptions::default()), hover_provider: Some(HoverProviderCapability::Simple(true)), + document_symbol_provider: Some(OneOf::Left(true)), + workspace_symbol_provider: if context.is_eager() { + Some(OneOf::Left(true)) + } else { + None + }, ..ServerCapabilities::default() } } @@ -431,9 +456,8 @@ impl Backend { Ok(module) } - fn validate(&self, uri: Url, version: Option, text: String) -> anyhow::Result<()> { - let uri = uri.try_into()?; - let eval_result = self.context.parse_file_with_contents(&uri, text); + fn validate(&self, uri: &LspUrl, version: Option, text: String) -> anyhow::Result<()> { + let eval_result = self.context.parse_file_with_contents(uri, text); if let Some(ast) = eval_result.ast { let module = Arc::new(LspModule::new(ast)); let mut last_valid_parse = self.last_valid_parse.write().unwrap(); @@ -444,8 +468,9 @@ impl Backend { } fn did_open(&self, params: DidOpenTextDocumentParams) -> anyhow::Result<()> { + let uri = params.text_document.uri.try_into()?; self.validate( - params.text_document.uri, + &uri, Some(params.text_document.version as i64), params.text_document.text, ) @@ -454,14 +479,16 @@ impl Backend { fn did_change(&self, params: DidChangeTextDocumentParams) -> anyhow::Result<()> { // We asked for Sync full, so can just grab all the text from params let change = params.content_changes.into_iter().next().unwrap(); - self.validate( - params.text_document.uri, - Some(params.text_document.version as i64), - change.text, - ) + let uri = params.text_document.uri.try_into()?; + self.validate(&uri, Some(params.text_document.version as i64), change.text) } fn did_close(&self, params: DidCloseTextDocumentParams) -> anyhow::Result<()> { + // If in eager mode, don't remove the file from the `last_valid_parse`. + if self.context.is_eager() { + return Ok(()); + } + { let mut last_valid_parse = self.last_valid_parse.write().unwrap(); last_valid_parse.remove(¶ms.text_document.uri.clone().try_into()?); @@ -470,6 +497,32 @@ impl Backend { Ok(()) } + fn did_rename(&self, params: RenameFilesParams) -> anyhow::Result<()> { + let mut last_valid_parse = self.last_valid_parse.write().unwrap(); + for file in params.files { + let old_uri = Url::parse(&file.old_uri).unwrap(); + let old_uri = old_uri.try_into()?; + let new_uri = Url::parse(&file.new_uri).unwrap(); + let new_uri = new_uri.try_into()?; + + if let Some(ast) = last_valid_parse.remove(&old_uri) { + last_valid_parse.insert(new_uri, ast); + } + } + Ok(()) + } + + fn did_delete(&self, params: DeleteFilesParams) -> anyhow::Result<()> { + let mut last_valid_parse = self.last_valid_parse.write().unwrap(); + for file in params.files { + let uri = Url::parse(&file.uri).unwrap(); + let uri = uri.try_into()?; + last_valid_parse.remove(&uri); + self.publish_diagnostics(uri.try_into()?, Vec::new(), None); + } + Ok(()) + } + /// Go to the definition of the symbol at the current cursor if that definition is in /// the same file. /// @@ -506,6 +559,17 @@ impl Backend { self.send_response(new_response(id, self.hover_info(params, initialize_params))); } + /// Offer an overview of symbols in the current document. + fn document_symbols(&self, id: RequestId, params: DocumentSymbolParams) { + self.send_response(new_response(id, self.get_document_symbols(params))); + } + + /// Offer an overview of all symbols in the current workspace. + /// Only supported in eager mode. + fn workspace_symbols(&self, id: RequestId, params: WorkspaceSymbolParams) { + self.send_response(new_response(id, self.get_workspace_symbols(params))); + } + /// Get the file contents of a starlark: URI. fn get_starlark_file_contents(&self, id: RequestId, params: StarlarkFileContentsParams) { let response: anyhow::Result<_> = match params.uri { @@ -1166,6 +1230,72 @@ impl Backend { }) } + fn get_document_symbols( + &self, + params: DocumentSymbolParams, + ) -> anyhow::Result { + let uri = params.text_document.uri.try_into()?; + + let document = match self.get_ast(&uri) { + Some(document) => document, + None => return Ok(DocumentSymbolResponse::Nested(vec![])), + }; + + let result = symbols::get_document_symbols( + document.ast.codemap(), + document.ast.statement(), + symbols::DocumentSymbolMode::IncludeLoads, + ); + + Ok(result.into()) + } + + fn get_workspace_symbols( + &self, + params: WorkspaceSymbolParams, + ) -> anyhow::Result { + let query = ¶ms.query.to_lowercase(); + + let symbols = self + .last_valid_parse + .read() + .unwrap() + .iter() + .flat_map(|(uri, document)| { + let uri: Url = uri.try_into().unwrap(); + + symbols::get_document_symbols( + document.ast.codemap(), + document.ast.statement(), + symbols::DocumentSymbolMode::OmitLoads, + ) + .into_iter() + .filter_map(move |symbol| { + if !query.is_empty() && !symbol.name.to_lowercase().contains(query) { + return None; + } + + let location = Location { + uri: uri.clone(), + range: symbol.range, + }; + + Some(WorkspaceSymbol { + name: symbol.name, + kind: symbol.kind, + location: OneOf::Left(location), + container_name: None, + tags: None, + // TODO? + data: None, + }) + }) + }) + .collect(); + + Ok(WorkspaceSymbolResponse::Nested(symbols)) + } + fn get_workspace_root( workspace_roots: Option<&Vec>, target: &LspUrl, @@ -1210,6 +1340,7 @@ impl Backend { fn main_loop(&self, initialize_params: InitializeParams) -> anyhow::Result<()> { self.log_message(MessageType::INFO, "Starlark server initialised"); + self.preload_workspace(&initialize_params); for msg in &self.connection.receiver { match msg { Message::Request(req) => { @@ -1223,6 +1354,10 @@ impl Backend { self.completion(req.id, params, &initialize_params); } else if let Some(params) = as_request::(&req) { self.hover(req.id, params, &initialize_params); + } else if let Some(params) = as_request::(&req) { + self.document_symbols(req.id, params); + } else if let Some(params) = as_request::(&req) { + self.workspace_symbols(req.id, params); } else if self.connection.handle_shutdown(&req)? { return Ok(()); } @@ -1235,6 +1370,20 @@ impl Backend { self.did_change(params)?; } else if let Some(params) = as_notification::(&x) { self.did_close(params)?; + } else if let Some(params) = as_notification::(&x) { + if self.context.is_eager() { + for file in params.files { + let url = Url::parse(&file.uri).unwrap(); + let path = url.to_file_path().ok(); + if let Some(path) = path { + self.preload_file_if_relevant(&LspUrl::File(path))?; + } + } + } + } else if let Some(params) = as_notification::(&x) { + self.did_delete(params)?; + } else if let Some(params) = as_notification::(&x) { + self.did_rename(params)?; } } Message::Response(_) => { @@ -1244,6 +1393,90 @@ impl Backend { } Ok(()) } + + fn preload_workspace(&self, initialize_params: &InitializeParams) { + if !self.context.is_eager() { + return; + } + + // Figure out the roots to crawl from + let workspace_roots: Vec<_> = + if let Some(workspace_folders) = initialize_params.workspace_folders.as_ref() { + workspace_folders + .iter() + .filter_map(|folder| folder.uri.to_file_path().ok()) + .collect() + } else if let Some(root_uri) = initialize_params.root_uri.as_ref() { + root_uri.to_file_path().ok().into_iter().collect() + } else { + #[allow(deprecated)] + initialize_params + .root_path + .as_ref() + .map(PathBuf::from) + .into_iter() + .collect() + }; + + for root in workspace_roots { + if let Err(e) = self.preload_directory(&root) { + self.log_message( + MessageType::WARNING, + &format!("Error preloading workspace: {:#}", e), + ); + } + } + } + + fn preload_directory(&self, dir: &Path) -> anyhow::Result<()> { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let file_type = entry.file_type()?; + if file_type.is_file() { + self.preload_file_if_relevant(&LspUrl::File(entry.path()))?; + } else if file_type.is_dir() { + self.preload_directory(&entry.path())?; + } + } + + Ok(()) + } + + fn preload_file_if_relevant(&self, file: &LspUrl) -> anyhow::Result<()> { + if !self.context.is_eager() { + return Ok(()); + } + + // Check if it's a Starlark-ish file. + match file { + LspUrl::File(path) => { + let ext = path.extension().and_then(|ext| ext.to_str()); + let file_name = path.file_name().and_then(|name| name.to_str()); + let is_starlark = matches!( + (ext, file_name), + (Some("star"), _) + | (Some("bzl"), _) + | (Some("bzlmod"), _) + | (Some("bazel"), _) + | (_, Some("BUILD")) + | (_, Some("WORKSPACE")) + ); + + if !is_starlark { + return Ok(()); + } + } + LspUrl::Starlark(_) | LspUrl::Other(_) => return Ok(()), + } + + let contents = self.context.get_load_contents(file)?; + if let Some(contents) = contents { + // NOTE: This also inserts the AST into the cache. + self.validate(file, None, contents)?; + } + + Ok(()) + } } /// Instantiate an LSP server that reads on stdin, and writes to stdout @@ -1274,11 +1507,11 @@ pub fn server_with_connection( .as_ref() .and_then(|opts| serde_json::from_value(opts.clone()).ok()) .unwrap_or_default(); - let capabilities_payload = Backend::::server_capabilities(server_settings); + let capabilities_payload = Backend::server_capabilities(&context, server_settings); let server_capabilities = serde_json::to_value(capabilities_payload).unwrap(); let initialize_data = serde_json::json!({ - "capabilities": server_capabilities, + "capabilities": server_capabilities, }); connection.initialize_finish(init_request_id, initialize_data)?; diff --git a/starlark_lsp/src/symbols.rs b/starlark_lsp/src/symbols.rs index 0f63052c9..798bb1ee1 100644 --- a/starlark_lsp/src/symbols.rs +++ b/starlark_lsp/src/symbols.rs @@ -18,12 +18,21 @@ //! Find which symbols are in scope at a particular point. use std::collections::HashMap; +use std::ops::Deref; +use lsp_types::DocumentSymbol; +use lsp_types::SymbolKind as LspSymbolKind; use starlark::codemap::CodeMap; +use starlark::codemap::Span; use starlark::docs::DocItem; use starlark::docs::DocParam; use starlark_syntax::codemap::ResolvedPos; +use starlark_syntax::syntax::ast::ArgumentP; use starlark_syntax::syntax::ast::AssignP; +use starlark_syntax::syntax::ast::AssignTargetP; +use starlark_syntax::syntax::ast::AstAssignIdentP; +use starlark_syntax::syntax::ast::AstExprP; +use starlark_syntax::syntax::ast::AstLiteral; use starlark_syntax::syntax::ast::AstPayload; use starlark_syntax::syntax::ast::AstStmtP; use starlark_syntax::syntax::ast::ExprP; @@ -161,16 +170,259 @@ pub(crate) fn find_symbols_at_location( symbols } +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum DocumentSymbolMode { + IncludeLoads, + OmitLoads, +} + +pub fn get_document_symbols( + codemap: &CodeMap, + ast: &AstStmtP

, + mode: DocumentSymbolMode, +) -> Vec { + let mut symbols = Vec::new(); + match &ast.node { + StmtP::Expression(expr) => { + if let Some(symbol) = get_document_symbol_for_expr(codemap, None, expr, ast.span) { + symbols.push(symbol); + } + } + StmtP::Assign(assign) => { + if let Some(symbol) = get_document_symbol_for_expr( + codemap, + match &assign.lhs.node { + AssignTargetP::Tuple(_) + | AssignTargetP::Index(_) + | AssignTargetP::Dot(_, _) => None, + AssignTargetP::Identifier(ident) => Some(ident), + }, + &assign.rhs, + ast.span, + ) { + symbols.push(symbol); + } + } + StmtP::Statements(statements) => { + for stmt in statements { + symbols.extend(get_document_symbols(codemap, stmt, mode)); + } + } + StmtP::If(_, body) => { + symbols.extend(get_document_symbols(codemap, body, mode)); + } + StmtP::IfElse(_, bodies) => { + let (if_body, else_body) = bodies.deref(); + symbols.extend(get_document_symbols(codemap, if_body, mode)); + symbols.extend(get_document_symbols(codemap, else_body, mode)); + } + StmtP::For(for_) => { + symbols.extend(get_document_symbols(codemap, &for_.body, mode)); + } + StmtP::Def(def) => { + symbols.push(make_document_symbol( + def.name.ident.clone(), + LspSymbolKind::FUNCTION, + ast.span, + def.name.span, + codemap, + Some( + def.params + .iter() + .filter_map(|param| get_document_symbol_for_parameter(codemap, param)) + .chain(get_document_symbols(codemap, &def.body, mode)) + .collect(), + ), + )); + } + StmtP::Load(load) => { + if mode == DocumentSymbolMode::IncludeLoads { + symbols.push(make_document_symbol( + load.module.node.clone(), + LspSymbolKind::MODULE, + ast.span, + load.module.span, + codemap, + Some( + load.args + .iter() + .map(|loaded_symbol| { + make_document_symbol( + loaded_symbol.local.ident.clone(), + LspSymbolKind::METHOD, + loaded_symbol.span(), + loaded_symbol.local.span, + codemap, + None, + ) + }) + .collect(), + ), + )); + } + } + + // These don't produce any symbols. + StmtP::Break + | StmtP::Continue + | StmtP::Pass + | StmtP::Return(_) + | StmtP::AssignModify(_, _, _) => {} + } + + symbols +} + +fn get_document_symbol_for_parameter( + codemap: &CodeMap, + param: &ParameterP

, +) -> Option { + match param { + ParameterP::NoArgs => None, + ParameterP::Normal(p, _) + | ParameterP::WithDefaultValue(p, _, _) + | ParameterP::Args(p, _) + | ParameterP::KwArgs(p, _) => Some(make_document_symbol( + p.ident.clone(), + LspSymbolKind::VARIABLE, + p.span, + p.span, + codemap, + None, + )), + } +} + +fn get_document_symbol_for_expr( + codemap: &CodeMap, + name: Option<&AstAssignIdentP

>, + expr: &AstExprP

, + outer_range: Span, +) -> Option { + match &expr.node { + ExprP::Call(call, args) => { + if let ExprP::Identifier(func_name) = &call.node { + // Look for a call to `struct`. We'll require passing in a name from the assignment + // expression. The outer range is the range of the entire assignment expression. + if &func_name.node.ident == "struct" { + name.map(|name| { + make_document_symbol( + name.ident.clone(), + LspSymbolKind::STRUCT, + outer_range, + name.span, + codemap, + Some( + args.iter() + .filter_map(|arg| match &arg.node { + ArgumentP::Named(name, _) => Some(make_document_symbol( + name.node.clone(), + LspSymbolKind::FIELD, + arg.span, + name.span, + codemap, + None, + )), + _ => None, + }) + .collect(), + ), + ) + }) + } else { + // Check if this call has a named argument called "name". If so, we'll assume + // that this is a buildable target, and expose it. + args.iter() + .find_map(|arg| match &arg.node { + ArgumentP::Named(name, value) => match (name, &value.node) { + (name, ExprP::Literal(AstLiteral::String(value))) + if &name.node == "name" => + { + Some(value) + } + _ => None, + }, + _ => None, + }) + .map(|target_name| { + make_document_symbol( + target_name.node.clone(), + LspSymbolKind::CONSTANT, + expr.span, + target_name.span, + codemap, + None, + ) + }) + } + } else { + None + } + } + ExprP::Lambda(lambda) => name.map(|name| { + make_document_symbol( + name.ident.clone(), + LspSymbolKind::FUNCTION, + expr.span, + expr.span, + codemap, + Some( + lambda + .params + .iter() + .filter_map(|param| get_document_symbol_for_parameter(codemap, param)) + .chain(get_document_symbol_for_expr( + codemap, + None, + &lambda.body, + lambda.body.span, + )) + .collect(), + ), + ) + }), + + _ => None, + } +} + +fn make_document_symbol( + name: String, + kind: LspSymbolKind, + range: Span, + selection_range: Span, + codemap: &CodeMap, + children: Option>, +) -> DocumentSymbol { + #[allow(deprecated)] + DocumentSymbol { + name, + detail: None, + kind, + tags: None, + deprecated: None, + range: codemap.resolve_span(range).into(), + selection_range: codemap.resolve_span(selection_range).into(), + children, + } +} + #[cfg(test)] mod tests { use std::collections::HashMap; + use lsp_types::DocumentSymbol; + use lsp_types::Position; + use lsp_types::Range; + use lsp_types::SymbolKind as LspSymbolKind; use starlark::syntax::AstModule; use starlark::syntax::Dialect; use starlark_syntax::codemap::ResolvedPos; use starlark_syntax::syntax::module::AstModuleFields; use super::find_symbols_at_location; + use super::get_document_symbols; + use super::DocumentSymbolMode; use super::Symbol; use super::SymbolKind; @@ -317,4 +569,637 @@ my_var = True ]) ); } + + #[test] + fn document_symbols() { + let ast_module = AstModule::parse( + "t.star", + r#"load("foo.star", "exported_a", renamed = "exported_b") + +def method(param): + foo = struct(field = "value") + bar = lambda x: x + 1 + return lambda y: y + 1 + +baz = struct(field = "value") + +some_rule(name = "qux") + "# + .to_owned(), + &Dialect::Standard, + ) + .unwrap(); + + assert_eq!( + get_document_symbols( + ast_module.codemap(), + ast_module.statement(), + DocumentSymbolMode::IncludeLoads + ), + vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "foo.star".to_owned(), + detail: None, + kind: LspSymbolKind::MODULE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0 + }, + end: Position { + line: 0, + character: 54 + } + }, + selection_range: Range { + start: Position { + line: 0, + character: 5 + }, + end: Position { + line: 0, + character: 15 + } + }, + children: Some(vec![ + DocumentSymbol { + name: "exported_a".to_owned(), + detail: None, + kind: LspSymbolKind::METHOD, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 17 + }, + end: Position { + line: 0, + character: 29 + } + }, + selection_range: Range { + start: Position { + line: 0, + character: 17 + }, + end: Position { + line: 0, + character: 29 + } + }, + children: None + }, + DocumentSymbol { + name: "renamed".to_owned(), + detail: None, + kind: LspSymbolKind::METHOD, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 31 + }, + end: Position { + line: 0, + character: 53 + } + }, + selection_range: Range { + start: Position { + line: 0, + character: 31 + }, + end: Position { + line: 0, + character: 38 + } + }, + children: None + } + ]) + }, + #[allow(deprecated)] + DocumentSymbol { + name: "method".to_owned(), + detail: None, + kind: LspSymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 0 + }, + end: Position { + line: 7, + character: 0 + } + }, + selection_range: Range { + start: Position { + line: 2, + character: 4 + }, + end: Position { + line: 2, + character: 10 + } + }, + children: Some(vec![ + DocumentSymbol { + name: "param".to_owned(), + detail: None, + kind: LspSymbolKind::VARIABLE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 11 + }, + end: Position { + line: 2, + character: 16 + } + }, + selection_range: Range { + start: Position { + line: 2, + character: 11 + }, + end: Position { + line: 2, + character: 16 + } + }, + children: None + }, + DocumentSymbol { + name: "foo".to_owned(), + detail: None, + kind: LspSymbolKind::STRUCT, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 4 + }, + end: Position { + line: 3, + character: 33 + } + }, + selection_range: Range { + start: Position { + line: 3, + character: 4 + }, + end: Position { + line: 3, + character: 7 + } + }, + children: Some(vec![DocumentSymbol { + name: "field".to_owned(), + detail: None, + kind: LspSymbolKind::FIELD, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 17 + }, + end: Position { + line: 3, + character: 32 + } + }, + selection_range: Range { + start: Position { + line: 3, + character: 17 + }, + end: Position { + line: 3, + character: 22 + } + }, + children: None + }]) + }, + DocumentSymbol { + name: "bar".to_owned(), + detail: None, + kind: LspSymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 4, + character: 10 + }, + end: Position { + line: 4, + character: 25 + } + }, + selection_range: Range { + start: Position { + line: 4, + character: 10 + }, + end: Position { + line: 4, + character: 25 + } + }, + children: Some(vec![DocumentSymbol { + name: "x".to_owned(), + detail: None, + kind: LspSymbolKind::VARIABLE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 4, + character: 17 + }, + end: Position { + line: 4, + character: 18 + } + }, + selection_range: Range { + start: Position { + line: 4, + character: 17 + }, + end: Position { + line: 4, + character: 18 + } + }, + children: None + }]) + } + ]) + }, + #[allow(deprecated)] + DocumentSymbol { + name: "baz".to_owned(), + detail: None, + kind: LspSymbolKind::STRUCT, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 7, + character: 0 + }, + end: Position { + line: 7, + character: 29 + } + }, + selection_range: Range { + start: Position { + line: 7, + character: 0 + }, + end: Position { + line: 7, + character: 3 + } + }, + children: Some(vec![DocumentSymbol { + name: "field".to_owned(), + detail: None, + kind: LspSymbolKind::FIELD, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 7, + character: 13 + }, + end: Position { + line: 7, + character: 28 + } + }, + selection_range: Range { + start: Position { + line: 7, + character: 13 + }, + end: Position { + line: 7, + character: 18 + } + }, + children: None + }]) + }, + #[allow(deprecated)] + DocumentSymbol { + name: "qux".to_owned(), + detail: None, + kind: LspSymbolKind::CONSTANT, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 9, + character: 0 + }, + end: Position { + line: 9, + character: 23 + } + }, + selection_range: Range { + start: Position { + line: 9, + character: 17 + }, + end: Position { + line: 9, + character: 22 + } + }, + children: None + } + ] + ); + + assert_eq!( + get_document_symbols( + ast_module.codemap(), + ast_module.statement(), + DocumentSymbolMode::OmitLoads + ), + vec![ + #[allow(deprecated)] + DocumentSymbol { + name: "method".to_owned(), + detail: None, + kind: LspSymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 0 + }, + end: Position { + line: 7, + character: 0 + } + }, + selection_range: Range { + start: Position { + line: 2, + character: 4 + }, + end: Position { + line: 2, + character: 10 + } + }, + children: Some(vec![ + DocumentSymbol { + name: "param".to_owned(), + detail: None, + kind: LspSymbolKind::VARIABLE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 11 + }, + end: Position { + line: 2, + character: 16 + } + }, + selection_range: Range { + start: Position { + line: 2, + character: 11 + }, + end: Position { + line: 2, + character: 16 + } + }, + children: None + }, + DocumentSymbol { + name: "foo".to_owned(), + detail: None, + kind: LspSymbolKind::STRUCT, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 4 + }, + end: Position { + line: 3, + character: 33 + } + }, + selection_range: Range { + start: Position { + line: 3, + character: 4 + }, + end: Position { + line: 3, + character: 7 + } + }, + children: Some(vec![DocumentSymbol { + name: "field".to_owned(), + detail: None, + kind: LspSymbolKind::FIELD, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 17 + }, + end: Position { + line: 3, + character: 32 + } + }, + selection_range: Range { + start: Position { + line: 3, + character: 17 + }, + end: Position { + line: 3, + character: 22 + } + }, + children: None + }]) + }, + DocumentSymbol { + name: "bar".to_owned(), + detail: None, + kind: LspSymbolKind::FUNCTION, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 4, + character: 10 + }, + end: Position { + line: 4, + character: 25 + } + }, + selection_range: Range { + start: Position { + line: 4, + character: 10 + }, + end: Position { + line: 4, + character: 25 + } + }, + children: Some(vec![DocumentSymbol { + name: "x".to_owned(), + detail: None, + kind: LspSymbolKind::VARIABLE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 4, + character: 17 + }, + end: Position { + line: 4, + character: 18 + } + }, + selection_range: Range { + start: Position { + line: 4, + character: 17 + }, + end: Position { + line: 4, + character: 18 + } + }, + children: None + }]) + } + ]) + }, + #[allow(deprecated)] + DocumentSymbol { + name: "baz".to_owned(), + detail: None, + kind: LspSymbolKind::STRUCT, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 7, + character: 0 + }, + end: Position { + line: 7, + character: 29 + } + }, + selection_range: Range { + start: Position { + line: 7, + character: 0 + }, + end: Position { + line: 7, + character: 3 + } + }, + children: Some(vec![DocumentSymbol { + name: "field".to_owned(), + detail: None, + kind: LspSymbolKind::FIELD, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 7, + character: 13 + }, + end: Position { + line: 7, + character: 28 + } + }, + selection_range: Range { + start: Position { + line: 7, + character: 13 + }, + end: Position { + line: 7, + character: 18 + } + }, + children: None + }]) + }, + #[allow(deprecated)] + DocumentSymbol { + name: "qux".to_owned(), + detail: None, + kind: LspSymbolKind::CONSTANT, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 9, + character: 0 + }, + end: Position { + line: 9, + character: 23 + } + }, + selection_range: Range { + start: Position { + line: 9, + character: 17 + }, + end: Position { + line: 9, + character: 22 + } + }, + children: None + } + ] + ); + } } diff --git a/starlark_lsp/src/test.rs b/starlark_lsp/src/test.rs index e58a815de..61c81c2f5 100644 --- a/starlark_lsp/src/test.rs +++ b/starlark_lsp/src/test.rs @@ -300,6 +300,10 @@ impl LspContext for TestServerContext { .collect(), } } + + fn is_eager(&self) -> bool { + false + } } /// A server for use in testing that provides helpers for sending requests, correlating