diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/code_style/preferred_local_alias.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/code_style/preferred_local_alias.rs index bbbcbf85a..1c3925d8f 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/code_style/preferred_local_alias.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/code_style/preferred_local_alias.rs @@ -5,6 +5,7 @@ use emmylua_parser::{ LuaSyntaxKind, }; use rowan::{NodeOrToken, TextRange}; +use serde_json::json; use crate::{ DiagnosticCode, LuaDeclId, LuaSemanticDeclId, SemanticDeclLevel, SemanticModel, @@ -247,7 +248,9 @@ fn check_index_expr_preference( name = alias_info.preferred_name ) .to_string(), - None, + Some(json!({ + "preferredAlias": alias_info.preferred_name.clone(), + })), ); } diff --git a/crates/emmylua_ls/locales/action/zh_CN.yaml b/crates/emmylua_ls/locales/action/zh_CN.yaml index d4e9eb591..3c3a90de2 100644 --- a/crates/emmylua_ls/locales/action/zh_CN.yaml +++ b/crates/emmylua_ls/locales/action/zh_CN.yaml @@ -19,3 +19,6 @@ Do you want to modify the require path?: | Modify: | 修改 + +Replace with local alias '%{name}': | + 替换为本地变量别名 '%{name}' diff --git a/crates/emmylua_ls/src/handlers/code_actions/actions/build_fix_code.rs b/crates/emmylua_ls/src/handlers/code_actions/actions/build_fix_code.rs index 12028e9bb..ba2a9a6e9 100644 --- a/crates/emmylua_ls/src/handlers/code_actions/actions/build_fix_code.rs +++ b/crates/emmylua_ls/src/handlers/code_actions/actions/build_fix_code.rs @@ -75,3 +75,29 @@ pub fn build_add_doc_tag( Some(()) } + +pub fn build_preferred_local_alias_fix( + semantic_model: &SemanticModel, + actions: &mut Vec, + range: Range, + data: &Option, +) -> Option<()> { + let alias_name = data.as_ref()?.get("preferredAlias")?.as_str()?; + let document = semantic_model.get_document(); + let text_edit = TextEdit { + range, + new_text: alias_name.to_string(), + }; + + actions.push(CodeActionOrCommand::CodeAction(CodeAction { + title: t!("Replace with local alias '%{name}'", name = alias_name).to_string(), + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(WorkspaceEdit { + changes: Some(HashMap::from([(document.get_uri(), vec![text_edit])])), + ..Default::default() + }), + ..Default::default() + })); + + Some(()) +} diff --git a/crates/emmylua_ls/src/handlers/code_actions/build_actions.rs b/crates/emmylua_ls/src/handlers/code_actions/build_actions.rs index e84304ae2..2e06f1e0c 100644 --- a/crates/emmylua_ls/src/handlers/code_actions/build_actions.rs +++ b/crates/emmylua_ls/src/handlers/code_actions/build_actions.rs @@ -8,11 +8,9 @@ use lsp_types::{ use super::actions::{ build_add_doc_tag, build_disable_file_changes, build_disable_next_line_changes, + build_need_check_nil, build_preferred_local_alias_fix, }; -use crate::handlers::{ - code_actions::actions::build_need_check_nil, - command::{DisableAction, make_disable_code_command}, -}; +use crate::handlers::command::{DisableAction, make_disable_code_command}; pub fn build_actions( semantic_model: &SemanticModel, @@ -71,6 +69,9 @@ fn add_fix_code_action( match diagnostic_code { DiagnosticCode::NeedCheckNil => build_need_check_nil(semantic_model, actions, range, data), DiagnosticCode::UnknownDocTag => build_add_doc_tag(semantic_model, actions, range, data), + DiagnosticCode::PreferredLocalAlias => { + build_preferred_local_alias_fix(semantic_model, actions, range, data) + } _ => Some(()), } } diff --git a/crates/emmylua_ls/src/handlers/document_symbol/builder.rs b/crates/emmylua_ls/src/handlers/document_symbol/builder.rs index 6edfc5e55..dfee3d129 100644 --- a/crates/emmylua_ls/src/handlers/document_symbol/builder.rs +++ b/crates/emmylua_ls/src/handlers/document_symbol/builder.rs @@ -45,25 +45,48 @@ impl<'a> DocumentSymbolBuilder<'a> { .clone() } - pub fn add_node_symbol(&mut self, node: LuaSyntaxNode, symbol: LuaSymbol) { + pub fn add_node_symbol( + &mut self, + node: LuaSyntaxNode, + symbol: LuaSymbol, + parent: Option, + ) -> LuaSyntaxId { let syntax_id = LuaSyntaxId::new(node.kind(), node.text_range()); self.document_symbols.insert(syntax_id, Box::new(symbol)); - let mut node = node; - while let Some(parent) = node.parent() { - let parent_syntax_id = LuaSyntaxId::new(parent.kind(), parent.text_range()); - if let Some(symbol) = self.document_symbols.get_mut(&parent_syntax_id) { - symbol.add_child(syntax_id); + + if let Some(parent_id) = parent { + self.link_parent_child(parent_id, syntax_id); + return syntax_id; + } + + let mut current = node; + while let Some(parent_node) = current.parent() { + let parent_syntax_id = LuaSyntaxId::new(parent_node.kind(), parent_node.text_range()); + if let Some(parent_symbol) = self.document_symbols.get_mut(&parent_syntax_id) { + parent_symbol.add_child(syntax_id); break; } - node = parent; + current = parent_node; } + + syntax_id } - pub fn add_token_symbol(&mut self, token: LuaSyntaxToken, symbol: LuaSymbol) { + pub fn add_token_symbol( + &mut self, + token: LuaSyntaxToken, + symbol: LuaSymbol, + parent: Option, + ) -> LuaSyntaxId { let syntax_id = LuaSyntaxId::new(token.kind(), token.text_range()); self.document_symbols.insert(syntax_id, Box::new(symbol)); + if let Some(parent_id) = parent { + self.link_parent_child(parent_id, syntax_id); + return syntax_id; + } + let mut node = token.parent(); while let Some(parent_node) = node { let parent_syntax_id = LuaSyntaxId::new(parent_node.kind(), parent_node.text_range()); @@ -74,6 +97,27 @@ impl<'a> DocumentSymbolBuilder<'a> { node = parent_node.parent(); } + + syntax_id + } + + pub fn contains_symbol(&self, id: &LuaSyntaxId) -> bool { + self.document_symbols.contains_key(id) + } + + pub fn with_symbol_mut(&mut self, id: &LuaSyntaxId, func: F) -> Option<()> + where + F: FnOnce(&mut LuaSymbol), + { + let symbol = self.document_symbols.get_mut(id)?; + func(symbol); + Some(()) + } + + fn link_parent_child(&mut self, parent: LuaSyntaxId, child: LuaSyntaxId) { + if let Some(parent_symbol) = self.document_symbols.get_mut(&parent) { + parent_symbol.add_child(child); + } } #[allow(deprecated)] @@ -241,4 +285,12 @@ impl LuaSymbol { pub fn add_child(&mut self, child: LuaSyntaxId) { self.children.push(child); } + + pub fn set_kind(&mut self, kind: SymbolKind) { + self.kind = kind; + } + + pub fn set_detail(&mut self, detail: Option) { + self.detail = detail; + } } diff --git a/crates/emmylua_ls/src/handlers/document_symbol/comment.rs b/crates/emmylua_ls/src/handlers/document_symbol/comment.rs index 5df2d49b5..39c4fcff2 100644 --- a/crates/emmylua_ls/src/handlers/document_symbol/comment.rs +++ b/crates/emmylua_ls/src/handlers/document_symbol/comment.rs @@ -1,4 +1,4 @@ -use emmylua_parser::{LuaAstNode, LuaComment, LuaTokenKind}; +use emmylua_parser::{LuaAstNode, LuaComment, LuaSyntaxId, LuaTokenKind}; use lsp_types::SymbolKind; use rowan::NodeOrToken; @@ -7,7 +7,8 @@ use super::builder::{DocumentSymbolBuilder, LuaSymbol}; pub fn build_doc_region_symbol( builder: &mut DocumentSymbolBuilder, comment: LuaComment, -) -> Option<()> { + parent_id: LuaSyntaxId, +) -> Option { let mut region_token = None; for child in comment.syntax().children_with_tokens() { if let NodeOrToken::Token(token) = child { @@ -18,10 +19,7 @@ pub fn build_doc_region_symbol( } } - let region_token = match region_token { - Some(token) => token, - None => return Some(()), - }; + let region_token = region_token?; let description = comment .get_description() @@ -46,7 +44,7 @@ pub fn build_doc_region_symbol( selection_range, ); - builder.add_node_symbol(comment.syntax().clone(), symbol); + let symbol_id = builder.add_node_symbol(comment.syntax().clone(), symbol, Some(parent_id)); - Some(()) + Some(symbol_id) } diff --git a/crates/emmylua_ls/src/handlers/document_symbol/expr.rs b/crates/emmylua_ls/src/handlers/document_symbol/expr.rs index 2d678376a..a1a0397c6 100644 --- a/crates/emmylua_ls/src/handlers/document_symbol/expr.rs +++ b/crates/emmylua_ls/src/handlers/document_symbol/expr.rs @@ -1,5 +1,7 @@ use emmylua_code_analysis::LuaDeclId; -use emmylua_parser::{LuaAstNode, LuaClosureExpr, LuaIndexKey, LuaSyntaxKind, LuaTableExpr}; +use emmylua_parser::{ + LuaAstNode, LuaClosureExpr, LuaIndexKey, LuaSyntaxId, LuaSyntaxKind, LuaTableExpr, +}; use lsp_types::SymbolKind; use super::builder::{DocumentSymbolBuilder, LuaSymbol}; @@ -7,25 +9,63 @@ use super::builder::{DocumentSymbolBuilder, LuaSymbol}; pub fn build_closure_expr_symbol( builder: &mut DocumentSymbolBuilder, closure: LuaClosureExpr, -) -> Option<()> { - let parent = closure.syntax().parent()?; - if !matches!( - parent.kind().into(), - LuaSyntaxKind::LocalFuncStat | LuaSyntaxKind::FuncStat - ) { + parent_id: LuaSyntaxId, +) -> Option { + let parent_kind = closure.syntax().parent().map(|parent| parent.kind().into()); + let convert_parent_to_function = matches!( + parent_kind, + Some(LuaSyntaxKind::TableFieldAssign | LuaSyntaxKind::TableFieldValue) + ); + let needs_own_symbol = match parent_kind { + Some(LuaSyntaxKind::LocalFuncStat | LuaSyntaxKind::FuncStat) => false, + Some(_) if convert_parent_to_function => false, + _ => true, + }; + + let param_list = closure.get_params_list()?; + let params: Vec<_> = param_list.get_params().collect(); + let detail_text = format!( + "({})", + params + .iter() + .map(|param| { + if param.is_dots() { + "...".to_string() + } else { + param + .get_name_token() + .map(|token| token.get_name_text().to_string()) + .unwrap_or_default() + } + }) + .filter(|name| !name.is_empty()) + .collect::>() + .join(", ") + ); + let detail = Some(detail_text.clone()); + + let mut effective_parent = parent_id; + + if needs_own_symbol { let symbol = LuaSymbol::new( "closure".to_string(), - None, + detail.clone(), SymbolKind::MODULE, closure.get_range(), ); - builder.add_node_symbol(closure.syntax().clone(), symbol); + effective_parent = + builder.add_node_symbol(closure.syntax().clone(), symbol, Some(parent_id)); + } else if convert_parent_to_function { + let detail_clone = detail.clone(); + builder.with_symbol_mut(&parent_id, |symbol| { + symbol.set_kind(SymbolKind::FUNCTION); + symbol.set_detail(detail_clone); + })?; } let file_id = builder.get_file_id(); - let param_list = closure.get_params_list()?; - for param in param_list.get_params() { + for param in params { let decl_id = LuaDeclId::new(file_id, param.get_position()); let decl = builder.get_decl(&decl_id)?; let typ = builder.get_type(decl_id.into()); @@ -37,21 +77,30 @@ pub fn build_closure_expr_symbol( decl.get_range(), ); - builder.add_node_symbol(param.syntax().clone(), symbol); + builder.add_node_symbol(param.syntax().clone(), symbol, Some(effective_parent)); } - Some(()) + Some(effective_parent) } -pub fn build_table_symbol(builder: &mut DocumentSymbolBuilder, table: LuaTableExpr) -> Option<()> { - let symbol = LuaSymbol::new( - "table".to_string(), - None, - SymbolKind::STRUCT, - table.get_range(), - ); +pub fn build_table_symbol( + builder: &mut DocumentSymbolBuilder, + table: LuaTableExpr, + parent_id: LuaSyntaxId, + inline_to_parent: bool, +) -> Option { + let table_id = if inline_to_parent { + parent_id + } else { + let symbol = LuaSymbol::new( + "table".to_string(), + None, + SymbolKind::STRUCT, + table.get_range(), + ); - builder.add_node_symbol(table.syntax().clone(), symbol); + builder.add_node_symbol(table.syntax().clone(), symbol, Some(parent_id)) + }; if table.is_object() { for field in table.get_fields() { @@ -65,9 +114,9 @@ pub fn build_table_symbol(builder: &mut DocumentSymbolBuilder, table: LuaTableEx let symbol = LuaSymbol::new(str_key, None, SymbolKind::FIELD, field.get_range()); - builder.add_node_symbol(field.syntax().clone(), symbol); + builder.add_node_symbol(field.syntax().clone(), symbol, Some(table_id)); } } - Some(()) + Some(table_id) } diff --git a/crates/emmylua_ls/src/handlers/document_symbol/mod.rs b/crates/emmylua_ls/src/handlers/document_symbol/mod.rs index a55bd1ff5..d7cae3243 100644 --- a/crates/emmylua_ls/src/handlers/document_symbol/mod.rs +++ b/crates/emmylua_ls/src/handlers/document_symbol/mod.rs @@ -3,20 +3,21 @@ mod comment; mod expr; mod stats; -use std::collections::HashSet; - use builder::{DocumentSymbolBuilder, LuaSymbol}; use emmylua_code_analysis::SemanticModel; -use emmylua_parser::{LuaAst, LuaAstNode, LuaChunk, LuaExpr, LuaTableExpr}; +use emmylua_parser::{ + LuaAstNode, LuaBlock, LuaChunk, LuaComment, LuaExpr, LuaSingleArgExpr, LuaStat, LuaSyntaxId, + LuaSyntaxNode, +}; use expr::{build_closure_expr_symbol, build_table_symbol}; use lsp_types::{ ClientCapabilities, DocumentSymbol, DocumentSymbolOptions, DocumentSymbolParams, DocumentSymbolResponse, OneOf, ServerCapabilities, SymbolKind, }; use stats::{ - build_assign_stat_symbol, build_for_range_stat_symbol, build_for_stat_symbol, - build_func_stat_symbol, build_if_stat_symbol, build_local_func_stat_symbol, - build_local_stat_symbol, + IfSymbolContext, build_assign_stat_symbol, build_do_stat_symbol, build_for_range_stat_symbol, + build_for_stat_symbol, build_func_stat_symbol, build_if_stat_symbol, + build_local_func_stat_symbol, build_local_stat_symbol, }; use tokio_util::sync::CancellationToken; @@ -53,8 +54,8 @@ fn build_document_symbol(semantic_model: &SemanticModel) -> Option Option Option<()> { + process_chunk(builder, root, root_id) +} + +fn process_chunk( + builder: &mut DocumentSymbolBuilder, + chunk: &LuaChunk, + parent_id: LuaSyntaxId, ) -> Option<()> { - let mut skip_table_exprs: HashSet = HashSet::new(); + for node in chunk.syntax().children() { + match node { + comment if LuaComment::can_cast(comment.kind().into()) => { + let comment = LuaComment::cast(comment.clone())?; + process_comment(builder, &comment, parent_id); + } + block if LuaBlock::can_cast(block.kind().into()) => { + let block = LuaBlock::cast(block.clone())?; + process_block(builder, block, parent_id); + } + _ => {} + } + } + + Some(()) +} + +fn process_comment( + builder: &mut DocumentSymbolBuilder, + comment: &LuaComment, + parent_id: LuaSyntaxId, +) { + build_doc_region_symbol(builder, comment.clone(), parent_id); +} - for child in root.descendants::() { +fn process_block( + builder: &mut DocumentSymbolBuilder, + block: LuaBlock, + parent_id: LuaSyntaxId, +) -> Option<()> { + for child in block.syntax().children() { match child { - LuaAst::LuaLocalStat(local_stat) => { - for value_expr in local_stat.get_value_exprs() { - if let LuaExpr::TableExpr(table_expr) = value_expr { - skip_table_exprs.insert(table_expr); - } + comment if LuaComment::can_cast(comment.kind().into()) => { + let comment = LuaComment::cast(comment.clone())?; + process_comment(builder, &comment, parent_id); + } + stat if LuaStat::can_cast(stat.kind().into()) => { + let stat = LuaStat::cast(stat.clone())?; + process_stat(builder, stat, parent_id)?; + } + _ => {} + } + } + + Some(()) +} + +fn process_stat( + builder: &mut DocumentSymbolBuilder, + stat: LuaStat, + parent_id: LuaSyntaxId, +) -> Option<()> { + match stat { + LuaStat::LocalStat(local_stat) => { + let bindings = build_local_stat_symbol(builder, local_stat, parent_id)?; + for binding in bindings { + if let Some(expr) = binding.value_expr { + process_expr(builder, expr, binding.symbol_id, true)?; } - build_local_stat_symbol(builder, local_stat); } - LuaAst::LuaAssignStat(assign_stat) => { - let (_, exprs) = assign_stat.get_var_and_expr_list(); - for expr in exprs { - if let LuaExpr::TableExpr(table_expr) = expr { - skip_table_exprs.insert(table_expr); - } + } + LuaStat::AssignStat(assign_stat) => { + let bindings = build_assign_stat_symbol(builder, assign_stat.clone(), parent_id)?; + for binding in bindings { + if let Some(expr) = binding.value_expr { + process_expr(builder, expr, binding.symbol_id, true)?; + } + } + } + LuaStat::FuncStat(func_stat) => { + let func_id = build_func_stat_symbol(builder, func_stat.clone(), parent_id)?; + if let Some(closure) = func_stat.get_closure() { + let scope_parent = build_closure_expr_symbol(builder, closure.clone(), func_id)?; + if let Some(block) = closure.get_block() { + process_block(builder, block, scope_parent)?; } - build_assign_stat_symbol(builder, assign_stat); } - LuaAst::LuaForStat(for_stat) => { - build_for_stat_symbol(builder, for_stat); + } + LuaStat::LocalFuncStat(local_func) => { + let func_id = build_local_func_stat_symbol(builder, local_func.clone(), parent_id)?; + if let Some(closure) = local_func.get_closure() { + let scope_parent = build_closure_expr_symbol(builder, closure.clone(), func_id)?; + if let Some(block) = closure.get_block() { + process_block(builder, block, scope_parent)?; + } } - LuaAst::LuaForRangeStat(for_range_stat) => { - build_for_range_stat_symbol(builder, for_range_stat); + } + LuaStat::ForStat(for_stat) => { + let for_id = build_for_stat_symbol(builder, for_stat.clone(), parent_id)?; + process_exprs(builder, for_stat.syntax(), for_id)?; + if let Some(block) = for_stat.get_block() { + process_block(builder, block, for_id)?; } - LuaAst::LuaLocalFuncStat(local_func) => { - build_local_func_stat_symbol(builder, local_func); + } + LuaStat::ForRangeStat(for_range_stat) => { + let for_range_id = + build_for_range_stat_symbol(builder, for_range_stat.clone(), parent_id)?; + process_exprs(builder, for_range_stat.syntax(), for_range_id)?; + if let Some(block) = for_range_stat.get_block() { + process_block(builder, block, for_range_id)?; } - LuaAst::LuaFuncStat(func) => { - build_func_stat_symbol(builder, func); + } + LuaStat::IfStat(if_stat) => { + let ctx = build_if_stat_symbol(builder, if_stat.clone(), parent_id)?; + if let Some(condition) = if_stat.get_condition_expr() { + process_expr(builder, condition, ctx.if_id, false)?; } - LuaAst::LuaClosureExpr(closure) => { - build_closure_expr_symbol(builder, closure); + if let Some(block) = if_stat.get_block() { + process_block(builder, block, ctx.if_id)?; } - LuaAst::LuaTableExpr(table) => { - if !skip_table_exprs.contains(&table) { - build_table_symbol(builder, table); - } + process_if_clauses(builder, ctx)?; + } + LuaStat::WhileStat(while_stat) => { + if let Some(condition) = while_stat.get_condition_expr() { + process_expr(builder, condition, parent_id, false)?; + } + if let Some(block) = while_stat.get_block() { + process_block(builder, block, parent_id)?; + } + } + LuaStat::RepeatStat(repeat_stat) => { + if let Some(block) = repeat_stat.get_block() { + process_block(builder, block, parent_id)?; + } + if let Some(condition) = repeat_stat.get_condition_expr() { + process_expr(builder, condition, parent_id, false)?; } - LuaAst::LuaComment(comment) => { - build_doc_region_symbol(builder, comment); + } + LuaStat::DoStat(do_stat) => { + let do_id = build_do_stat_symbol(builder, do_stat.clone(), parent_id)?; + if let Some(block) = do_stat.get_block() { + process_block(builder, block, do_id)?; } - LuaAst::LuaIfStat(if_stat) => { - build_if_stat_symbol(builder, if_stat); + } + LuaStat::CallExprStat(call_stat) => { + process_exprs(builder, call_stat.syntax(), parent_id)?; + } + LuaStat::ReturnStat(return_stat) => { + process_exprs(builder, return_stat.syntax(), parent_id)?; + } + LuaStat::GlobalStat(global_stat) => { + process_exprs(builder, global_stat.syntax(), parent_id)?; + } + LuaStat::GotoStat(_) + | LuaStat::BreakStat(_) + | LuaStat::LabelStat(_) + | LuaStat::EmptyStat(_) => {} + } + + Some(()) +} + +fn process_if_clauses(builder: &mut DocumentSymbolBuilder, ctx: IfSymbolContext) -> Option<()> { + for (clause, clause_id) in ctx.clause_symbols { + if let Some(condition) = clause.get_condition_expr() { + process_expr(builder, condition, clause_id, false)?; + } + if let Some(block) = clause.get_block() { + process_block(builder, block, clause_id)?; + } + } + + Some(()) +} + +fn process_exprs( + builder: &mut DocumentSymbolBuilder, + syntax: &LuaSyntaxNode, + parent_id: LuaSyntaxId, +) -> Option<()> { + for child in syntax.children() { + match child { + expr if LuaExpr::can_cast(expr.kind().into()) => { + let expr = LuaExpr::cast(expr.clone())?; + process_expr(builder, expr, parent_id, false)?; } _ => {} } } + Some(()) +} + +fn process_expr( + builder: &mut DocumentSymbolBuilder, + expr: LuaExpr, + parent_id: LuaSyntaxId, + inline_table_to_parent: bool, +) -> Option<()> { + match expr { + LuaExpr::TableExpr(table) => { + if !inline_table_to_parent { + if table.is_object() { + for field in table.get_fields() { + if let Some(value_expr) = field.get_value_expr() { + process_expr(builder, value_expr, parent_id, false)?; + } + } + } + return Some(()); + } + let table_id = + build_table_symbol(builder, table.clone(), parent_id, inline_table_to_parent)?; + for field in table.get_fields() { + if let Some(value_expr) = field.get_value_expr() { + let field_id = + LuaSyntaxId::new(field.syntax().kind(), field.syntax().text_range()); + let next_parent = if builder.contains_symbol(&field_id) { + field_id + } else { + table_id + }; + process_expr(builder, value_expr, next_parent, true)?; + } + } + } + LuaExpr::ClosureExpr(closure) => { + if !inline_table_to_parent { + return Some(()); + } + let scope_parent = build_closure_expr_symbol(builder, closure.clone(), parent_id)?; + if let Some(block) = closure.get_block() { + process_block(builder, block, scope_parent)?; + } + } + LuaExpr::BinaryExpr(binary) => { + if let Some((left, right)) = binary.get_exprs() { + process_expr(builder, left, parent_id, inline_table_to_parent)?; + process_expr(builder, right, parent_id, inline_table_to_parent)?; + } + } + LuaExpr::UnaryExpr(unary) => { + if let Some(inner) = unary.get_expr() { + process_expr(builder, inner, parent_id, inline_table_to_parent)?; + } + } + LuaExpr::ParenExpr(paren) => { + if let Some(inner) = paren.get_expr() { + process_expr(builder, inner, parent_id, inline_table_to_parent)?; + } + } + LuaExpr::CallExpr(call) => { + if let Some(prefix) = call.get_prefix_expr() { + process_expr(builder, prefix, parent_id, inline_table_to_parent)?; + } + if let Some(args) = call.get_args_list() { + let collected: Vec<_> = args.get_args().collect(); + if collected.is_empty() && args.is_single_arg_no_parens() { + if let Some(single) = args.get_single_arg_expr() { + if let LuaSingleArgExpr::TableExpr(table) = single { + process_expr( + builder, + LuaExpr::TableExpr(table), + parent_id, + inline_table_to_parent, + )?; + } + } + } else { + for arg in collected { + process_expr(builder, arg, parent_id, inline_table_to_parent)?; + } + } + } + } + LuaExpr::IndexExpr(index_expr) => { + if let Some(prefix) = index_expr.get_prefix_expr() { + process_expr(builder, prefix, parent_id, inline_table_to_parent)?; + } + } + LuaExpr::NameExpr(_) | LuaExpr::LiteralExpr(_) => {} + } Some(()) } diff --git a/crates/emmylua_ls/src/handlers/document_symbol/stats.rs b/crates/emmylua_ls/src/handlers/document_symbol/stats.rs index 6bd238fdc..5bab096e0 100644 --- a/crates/emmylua_ls/src/handlers/document_symbol/stats.rs +++ b/crates/emmylua_ls/src/handlers/document_symbol/stats.rs @@ -1,21 +1,35 @@ use emmylua_code_analysis::{LuaDeclId, LuaSignatureId, LuaType}; use emmylua_parser::{ - LuaAssignStat, LuaAstNode, LuaAstToken, LuaForRangeStat, LuaForStat, LuaFuncStat, - LuaIfClauseStat, LuaIfStat, LuaLocalFuncStat, LuaLocalStat, + LuaAssignStat, LuaAstNode, LuaAstToken, LuaDoStat, LuaExpr, LuaForRangeStat, LuaForStat, + LuaFuncStat, LuaIfClauseStat, LuaIfStat, LuaLocalFuncStat, LuaLocalStat, LuaSyntaxId, }; use lsp_types::SymbolKind; use super::builder::{DocumentSymbolBuilder, LuaSymbol}; +#[derive(Clone)] +pub struct SymbolBinding { + pub symbol_id: LuaSyntaxId, + pub value_expr: Option, +} + +pub struct IfSymbolContext { + pub if_id: LuaSyntaxId, + pub clause_symbols: Vec<(LuaIfClauseStat, LuaSyntaxId)>, +} + pub fn build_local_stat_symbol( builder: &mut DocumentSymbolBuilder, local_stat: LuaLocalStat, -) -> Option<()> { + parent_id: LuaSyntaxId, +) -> Option> { let file_id = builder.get_file_id(); let local_names: Vec<_> = local_stat.get_local_name_list().collect(); + let local_values: Vec<_> = local_stat.get_value_exprs().collect(); let simple_local = local_names.len() == 1; + let mut bindings = Vec::new(); - for local_name in local_names { + for (index, local_name) in local_names.into_iter().enumerate() { let decl_id = LuaDeclId::new(file_id, local_name.get_position()); let decl = builder.get_decl(&decl_id)?; let typ = builder.get_type(decl_id.into()); @@ -27,21 +41,29 @@ pub fn build_local_stat_symbol( }; let symbol = LuaSymbol::new(decl.get_name().to_string(), desc.1, desc.0, range); - - builder.add_node_symbol(local_name.syntax().clone(), symbol); + let symbol_id = + builder.add_node_symbol(local_name.syntax().clone(), symbol, Some(parent_id)); + let value_expr = local_values.get(index).cloned(); + bindings.push(SymbolBinding { + symbol_id, + value_expr, + }); } - Some(()) + Some(bindings) } pub fn build_assign_stat_symbol( builder: &mut DocumentSymbolBuilder, assign_stat: LuaAssignStat, -) -> Option<()> { + parent_id: LuaSyntaxId, +) -> Option> { let file_id = builder.get_file_id(); - let (vars, _) = assign_stat.get_var_and_expr_list(); + let (vars, exprs) = assign_stat.get_var_and_expr_list(); let simple_var = vars.len() == 1; - for var in vars { + let mut bindings = Vec::new(); + + for (index, var) in vars.into_iter().enumerate() { let decl_id = LuaDeclId::new(file_id, var.get_position()); let decl = match builder.get_decl(&decl_id) { Some(decl) => decl, @@ -56,16 +78,22 @@ pub fn build_assign_stat_symbol( let desc = builder.get_symbol_kind_and_detail(Some(&typ)); let symbol = LuaSymbol::new(decl.get_name().to_string(), desc.1, desc.0, range); - builder.add_node_symbol(var.syntax().clone(), symbol); + let symbol_id = builder.add_node_symbol(var.syntax().clone(), symbol, Some(parent_id)); + let value_expr = exprs.get(index).cloned(); + bindings.push(SymbolBinding { + symbol_id, + value_expr, + }); } - Some(()) + Some(bindings) } pub fn build_for_stat_symbol( builder: &mut DocumentSymbolBuilder, for_stat: LuaForStat, -) -> Option<()> { + parent_id: LuaSyntaxId, +) -> Option { let file_id = builder.get_file_id(); let for_symbol = LuaSymbol::new( "for".to_string(), @@ -73,7 +101,8 @@ pub fn build_for_stat_symbol( SymbolKind::MODULE, for_stat.get_range(), ); - builder.add_node_symbol(for_stat.syntax().clone(), for_symbol); + let for_symbol_id = + builder.add_node_symbol(for_stat.syntax().clone(), for_symbol, Some(parent_id)); let iter_token = for_stat.get_var_name()?; let decl_id = LuaDeclId::new(file_id, iter_token.get_position()); @@ -87,14 +116,15 @@ pub fn build_for_stat_symbol( decl.get_range(), ); - builder.add_token_symbol(iter_token.syntax().clone(), symbol); - Some(()) + builder.add_token_symbol(iter_token.syntax().clone(), symbol, Some(for_symbol_id)); + Some(for_symbol_id) } pub fn build_for_range_stat_symbol( builder: &mut DocumentSymbolBuilder, for_range_stat: LuaForRangeStat, -) -> Option<()> { + parent_id: LuaSyntaxId, +) -> Option { let file_id = builder.get_file_id(); let for_symbol = LuaSymbol::new( "for in".to_string(), @@ -103,7 +133,8 @@ pub fn build_for_range_stat_symbol( for_range_stat.get_range(), ); - builder.add_node_symbol(for_range_stat.syntax().clone(), for_symbol); + let for_in_id = + builder.add_node_symbol(for_range_stat.syntax().clone(), for_symbol, Some(parent_id)); let vars = for_range_stat.get_var_name_list(); for var in vars { @@ -118,16 +149,17 @@ pub fn build_for_range_stat_symbol( decl.get_range(), ); - builder.add_token_symbol(var.syntax().clone(), symbol); + builder.add_token_symbol(var.syntax().clone(), symbol, Some(for_in_id)); } - Some(()) + Some(for_in_id) } pub fn build_local_func_stat_symbol( builder: &mut DocumentSymbolBuilder, local_func: LuaLocalFuncStat, -) -> Option<()> { + parent_id: LuaSyntaxId, +) -> Option { let file_id = builder.get_file_id(); let func_name = local_func.get_local_name()?; let decl_id = LuaDeclId::new(file_id, func_name.get_position()); @@ -146,14 +178,15 @@ pub fn build_local_func_stat_symbol( name_range, ); - builder.add_node_symbol(local_func.syntax().clone(), symbol); - Some(()) + let func_id = builder.add_node_symbol(local_func.syntax().clone(), symbol, Some(parent_id)); + Some(func_id) } pub fn build_func_stat_symbol( builder: &mut DocumentSymbolBuilder, func: LuaFuncStat, -) -> Option<()> { + parent_id: LuaSyntaxId, +) -> Option { let file_id = builder.get_file_id(); let func_name = func.get_func_name()?; let name = func_name.syntax().text().to_string(); @@ -167,11 +200,15 @@ pub fn build_func_stat_symbol( let symbol = LuaSymbol::with_selection_range(name, desc.1, desc.0, full_range, name_range); - builder.add_node_symbol(func.syntax().clone(), symbol); - Some(()) + let func_id = builder.add_node_symbol(func.syntax().clone(), symbol, Some(parent_id)); + Some(func_id) } -pub fn build_if_stat_symbol(builder: &mut DocumentSymbolBuilder, if_stat: LuaIfStat) -> Option<()> { +pub fn build_if_stat_symbol( + builder: &mut DocumentSymbolBuilder, + if_stat: LuaIfStat, + parent_id: LuaSyntaxId, +) -> Option { let if_symbol = LuaSymbol::new( "if".to_string(), None, @@ -179,7 +216,8 @@ pub fn build_if_stat_symbol(builder: &mut DocumentSymbolBuilder, if_stat: LuaIfS if_stat.get_range(), ); - builder.add_node_symbol(if_stat.syntax().clone(), if_symbol); + let if_id = builder.add_node_symbol(if_stat.syntax().clone(), if_symbol, Some(parent_id)); + let mut clause_symbols = Vec::new(); for branch in if_stat.get_all_clause() { let name = match &branch { @@ -194,8 +232,28 @@ pub fn build_if_stat_symbol(builder: &mut DocumentSymbolBuilder, if_stat: LuaIfS branch.get_range(), ); - builder.add_node_symbol(branch.syntax().clone(), symbol); + let clause_id = builder.add_node_symbol(branch.syntax().clone(), symbol, Some(if_id)); + clause_symbols.push((branch, clause_id)); } - Some(()) + Some(IfSymbolContext { + if_id, + clause_symbols, + }) +} + +pub fn build_do_stat_symbol( + builder: &mut DocumentSymbolBuilder, + do_stat: LuaDoStat, + parent_id: LuaSyntaxId, +) -> Option { + let symbol = LuaSymbol::new( + "do end".to_string(), + None, + SymbolKind::MODULE, + do_stat.get_range(), + ); + + let do_id = builder.add_node_symbol(do_stat.syntax().clone(), symbol, Some(parent_id)); + Some(do_id) }