Skip to content

Commit 8445839

Browse files
committed
Reuse doc completion, hover, and definition for @see
This PR improves handling of `@see` contents by reusing machinery introduced for parsing documentation comments. The behaviour is backwards-compatible with the old syntax (`@see class#member`). To avoid code duplication, I've adjusted `TkDocSeeContent` to only include symbols that are valid in cross-references. The rest is pushed into `Description`, [which was already present in AST][desc], but remained empty due to `TkDocSeeContent` consuming the entire line. Fix #671 [desc]: https://github.com/EmmyLuaLs/emmylua-analyzer-rust/blob/2bb63fa4dca5ddd617b70c08f74bf11a15f2b6f8/crates/emmylua_parser/src/grammar/doc/tag.rs#L386
1 parent 5c6f4e1 commit 8445839

File tree

14 files changed

+299
-62
lines changed

14 files changed

+299
-62
lines changed

crates/emmylua_code_analysis/src/compilation/analyzer/doc/type_ref_tags.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,16 @@ pub fn analyze_see(analyzer: &mut DocAnalyzer, tag: LuaDocTagSee) -> Option<()>
389389
let owner = get_owner_id_or_report(analyzer, &tag)?;
390390
let content = tag.get_see_content()?;
391391
let text = content.get_text();
392-
393-
analyzer
394-
.db
395-
.get_property_index_mut()
396-
.add_see(analyzer.file_id, owner, text.to_string());
392+
let descriptions = tag
393+
.get_description()
394+
.map(|description| description.get_description_text());
395+
396+
analyzer.db.get_property_index_mut().add_see(
397+
analyzer.file_id,
398+
owner,
399+
text.to_string(),
400+
descriptions,
401+
);
397402

398403
Some(())
399404
}

crates/emmylua_code_analysis/src/db_index/property/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,19 @@ impl LuaPropertyIndex {
160160
&mut self,
161161
file_id: FileId,
162162
owner_id: LuaSemanticDeclId,
163-
see_content: String,
163+
mut see_content: String,
164+
see_description: Option<String>,
164165
) -> Option<()> {
165166
let property = self.get_or_create_property(owner_id.clone())?;
166167
let tag_content = property
167168
.tag_content
168169
.get_or_insert_with(|| Box::new(LuaTagContent::new()));
169170

171+
if let Some(see_description) = see_description {
172+
see_content += " ";
173+
see_content += &see_description;
174+
}
175+
170176
tag_content.add_tag("see".into(), see_content);
171177

172178
self.in_filed_owner

crates/emmylua_ls/src/handlers/completion/providers/desc_provider.rs

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::handlers::completion::providers::member_provider::add_completions_for
66
use crate::handlers::completion::providers::module_path_provider::add_modules;
77
use crate::util::{find_comment_scope, find_ref_at, resolve_ref};
88
use emmylua_code_analysis::{LuaType, WorkspaceId};
9-
use emmylua_parser::{LuaAstNode, LuaDocDescription};
10-
use emmylua_parser_desc::LuaDescRefPathItem;
9+
use emmylua_parser::{LuaAstNode, LuaDocDescription, LuaTokenKind};
10+
use emmylua_parser_desc::{LuaDescRefPathItem, parse_ref_target};
1111
use rowan::TextRange;
1212
use std::collections::HashSet;
1313

@@ -18,28 +18,41 @@ pub fn add_completions(builder: &mut CompletionBuilder) -> Option<()> {
1818

1919
let semantic_model = &builder.semantic_model;
2020
let document = semantic_model.get_document();
21-
let description = LuaDocDescription::cast(builder.trigger_token.parent()?)?;
2221

23-
// Quickly scan the line before actually parsing comment.
24-
let line = document.get_line(builder.position_offset)?;
25-
let line_range = document.get_line_range(line)?;
26-
let line_text =
27-
&document.get_text()[line_range.intersect(TextRange::up_to(builder.position_offset))?];
22+
let path = if let Some(description) = builder
23+
.trigger_token
24+
.parent()
25+
.and_then(LuaDocDescription::cast)
26+
{
27+
// Quickly scan the line before actually parsing comment.
28+
let line = document.get_line(builder.position_offset)?;
29+
let line_range = document.get_line_range(line)?;
30+
let line_text = &document.get_text()
31+
[line_range.intersect(TextRange::up_to(builder.position_offset))?];
32+
33+
if !line_text.contains('`') {
34+
return None;
35+
}
2836

29-
if !line_text.contains('`') {
37+
find_ref_at(
38+
semantic_model
39+
.get_module()
40+
.map(|m| m.workspace_id)
41+
.unwrap_or(WorkspaceId::MAIN),
42+
semantic_model.get_emmyrc(),
43+
document.get_text(),
44+
description,
45+
builder.position_offset,
46+
)?
47+
} else if builder.trigger_token.kind() == LuaTokenKind::TkDocSeeContent.into() {
48+
parse_ref_target(
49+
document.get_text(),
50+
builder.trigger_token.text_range(),
51+
builder.position_offset,
52+
)?
53+
} else {
3054
return None;
31-
}
32-
33-
let path = find_ref_at(
34-
semantic_model
35-
.get_module()
36-
.map(|m| m.workspace_id)
37-
.unwrap_or(WorkspaceId::MAIN),
38-
semantic_model.get_emmyrc(),
39-
document.get_text(),
40-
description.clone(),
41-
builder.position_offset,
42-
)?;
55+
};
4356

4457
if path.is_empty() {
4558
add_global_completions(builder);

crates/emmylua_ls/src/handlers/completion/providers/doc_type_provider.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ fn check_can_add_type_completion(builder: &CompletionBuilder) -> Option<()> {
6363
LuaTokenKind::TkWhitespace => {
6464
let left_token = builder.trigger_token.prev_token()?;
6565
match left_token.kind().into() {
66-
LuaTokenKind::TkTagReturn | LuaTokenKind::TkTagType | LuaTokenKind::TkTagSee => {
66+
LuaTokenKind::TkTagReturn | LuaTokenKind::TkTagType => {
6767
return Some(());
6868
}
6969
LuaTokenKind::TkName => {

crates/emmylua_ls/src/handlers/definition/goto_doc_see.rs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,41 @@
1-
use emmylua_code_analysis::{LuaMemberKey, LuaSemanticDeclId, LuaType, SemanticModel};
1+
use crate::handlers::definition::goto_path::goto_path;
2+
use emmylua_code_analysis::{
3+
LuaCompilation, LuaMemberKey, LuaSemanticDeclId, LuaType, SemanticModel,
4+
};
25
use emmylua_parser::{LuaAstToken, LuaGeneralToken};
6+
use emmylua_parser_desc::parse_ref_target;
37
use lsp_types::GotoDefinitionResponse;
8+
use rowan::TextSize;
49

510
pub fn goto_doc_see(
611
semantic_model: &SemanticModel,
12+
compilation: &LuaCompilation,
713
content_token: LuaGeneralToken,
14+
position_offset: TextSize,
815
) -> Option<GotoDefinitionResponse> {
916
let text = content_token.get_text();
1017
let name_parts = text.split('#').collect::<Vec<_>>();
1118

1219
match name_parts.len() {
13-
1 => {
14-
let name = &name_parts[0];
15-
return goto_type(semantic_model, &name);
16-
}
17-
2 => {
20+
0 => {}
21+
// Legacy handler for format like `@see type#member`
22+
2 if !name_parts[1].is_empty() && !name_parts[1].starts_with([' ', '\t']) => {
1823
let type_name = &name_parts[0];
1924
let member_name = &name_parts[1];
2025
return goto_type_member(semantic_model, &type_name, &member_name);
2126
}
22-
_ => {}
23-
}
27+
_ => {
28+
let path = parse_ref_target(
29+
semantic_model.get_document().get_text(),
30+
content_token.get_range(),
31+
position_offset,
32+
)?;
2433

25-
None
26-
}
27-
28-
fn goto_type(semantic_model: &SemanticModel, type_name: &str) -> Option<GotoDefinitionResponse> {
29-
let file_id = semantic_model.get_file_id();
30-
let type_decl = semantic_model
31-
.get_db()
32-
.get_type_index()
33-
.find_type_decl(file_id, type_name)?;
34-
let locations = type_decl.get_locations();
35-
let mut result = Vec::new();
36-
for location in locations {
37-
let document = semantic_model.get_document_by_file_id(location.file_id)?;
38-
let lsp_location = document.to_lsp_location(location.range)?;
39-
result.push(lsp_location);
34+
return goto_path(semantic_model, compilation, &path, content_token.syntax());
35+
}
4036
}
4137

42-
Some(GotoDefinitionResponse::Array(result))
38+
None
4339
}
4440

4541
fn goto_type_member(

crates/emmylua_ls/src/handlers/definition/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,12 @@ pub fn definition(
9494
} else if token.kind() == LuaTokenKind::TkDocSeeContent.into() {
9595
let general_token = LuaGeneralToken::cast(token.clone())?;
9696
if let Some(_) = general_token.get_parent::<LuaDocTagSee>() {
97-
return goto_doc_see(&semantic_model, general_token);
97+
return goto_doc_see(
98+
&semantic_model,
99+
&analysis.compilation,
100+
general_token,
101+
position_offset,
102+
);
98103
}
99104
} else if token.kind() == LuaTokenKind::TkDocDetail.into() {
100105
let parent = token.parent()?;

crates/emmylua_ls/src/handlers/hover/hover_builder.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub struct HoverBuilder<'a> {
2323
pub annotation_description: Vec<MarkedString>,
2424
/// Type expansion, often used for alias types
2525
pub type_expansion: Option<Vec<String>>,
26-
/// see
26+
/// For `@see` and unknown tags tags
2727
tag_content: Option<Vec<(String, String)>>,
2828

2929
pub is_completion: bool,
@@ -239,6 +239,9 @@ impl<'a> HoverBuilder<'a> {
239239
}
240240

241241
if let Some(tag_content) = &self.tag_content {
242+
if !tag_content.is_empty() {
243+
content.push_str("\n---\n");
244+
}
242245
for (tag_name, description) in tag_content {
243246
content.push_str(&format!("\n@*{}* {}\n", tag_name, description));
244247
}

crates/emmylua_ls/src/handlers/hover/mod.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ mod hover_humanize;
66
mod keyword_hover;
77
mod std_hover;
88

9+
use super::RegisterCapabilities;
10+
use crate::context::ServerContextSnapshot;
11+
use crate::util::{find_ref_at, resolve_ref_single};
912
pub use build_hover::build_hover_content_for_completion;
1013
use build_hover::build_semantic_info_hover;
1114
use emmylua_code_analysis::{EmmyLuaAnalysis, FileId, WorkspaceId};
12-
use emmylua_parser::{LuaAstNode, LuaDocDescription, LuaKind, LuaTokenKind};
15+
use emmylua_parser::{LuaAstNode, LuaDocDescription, LuaTokenKind};
16+
use emmylua_parser_desc::parse_ref_target;
1317
pub use find_origin::{find_all_same_named_members, find_member_origin_owner};
1418
pub use hover_builder::HoverBuilder;
1519
pub use hover_humanize::infer_prefix_global_name;
@@ -22,10 +26,6 @@ use rowan::TokenAtOffset;
2226
pub use std_hover::{hover_std_description, is_std};
2327
use tokio_util::sync::CancellationToken;
2428

25-
use super::RegisterCapabilities;
26-
use crate::context::ServerContextSnapshot;
27-
use crate::util::{find_ref_at, resolve_ref_single};
28-
2929
pub async fn on_hover(
3030
context: ServerContextSnapshot,
3131
params: HoverParams,
@@ -82,7 +82,7 @@ pub fn hover(analysis: &EmmyLuaAnalysis, file_id: FileId, position: Position) ->
8282
range: document.to_lsp_range(keywords.text_range()),
8383
});
8484
}
85-
detail if detail.kind() == LuaKind::Token(LuaTokenKind::TkDocDetail) => {
85+
detail if detail.kind() == LuaTokenKind::TkDocDetail.into() => {
8686
let parent = detail.parent()?;
8787
let description = LuaDocDescription::cast(parent)?;
8888
let document = semantic_model.get_document();
@@ -111,6 +111,25 @@ pub fn hover(analysis: &EmmyLuaAnalysis, file_id: FileId, position: Position) ->
111111
path.last()?.1,
112112
)
113113
}
114+
doc_see if doc_see.kind() == LuaTokenKind::TkDocSeeContent.into() => {
115+
let document = semantic_model.get_document();
116+
117+
let path =
118+
parse_ref_target(document.get_text(), doc_see.text_range(), position_offset)?;
119+
120+
let db = analysis.compilation.get_db();
121+
let semantic_info = resolve_ref_single(db, file_id, &path, &doc_see)?;
122+
123+
build_semantic_info_hover(
124+
&analysis.compilation,
125+
&semantic_model,
126+
db,
127+
&document,
128+
doc_see,
129+
semantic_info,
130+
path.last()?.1,
131+
)
132+
}
114133
_ => {
115134
let semantic_info = semantic_model.get_semantic_info(token.clone().into())?;
116135
let db = semantic_model.get_db();

crates/emmylua_ls/src/handlers/semantic_token/build_semantic_tokens.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,20 @@ fn build_tokens_semantic_token(
216216
SemanticTokenModifier::DOCUMENTATION,
217217
);
218218
}
219-
LuaTokenKind::TKDocPath | LuaTokenKind::TkDocSeeContent => {
219+
LuaTokenKind::TKDocPath => {
220220
builder.push_with_modifier(
221221
token,
222222
SemanticTokenType::STRING,
223223
SemanticTokenModifier::DOCUMENTATION,
224224
);
225225
}
226+
LuaTokenKind::TkDocSeeContent => {
227+
builder.push_with_modifier(
228+
token,
229+
SemanticTokenType::VARIABLE,
230+
SemanticTokenModifier::DOCUMENTATION,
231+
);
232+
}
226233
LuaTokenKind::TkDocRegion | LuaTokenKind::TkDocEndRegion => {
227234
builder.push(token, SemanticTokenType::COMMENT);
228235
}

crates/emmylua_ls/src/handlers/test/completion_test.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,4 +2082,37 @@ mod tests {
20822082
));
20832083
Ok(())
20842084
}
2085+
2086+
#[gtest]
2087+
fn test_see_completion() -> Result<()> {
2088+
let mut ws = ProviderVirtualWorkspace::new();
2089+
ws.def(
2090+
r#"
2091+
---@class Meep
2092+
"#,
2093+
);
2094+
check!(ws.check_completion(
2095+
r#"
2096+
--- @see M<??>
2097+
"#,
2098+
vec![
2099+
VirtualCompletionItem {
2100+
label: "Meep".to_string(),
2101+
kind: CompletionItemKind::CLASS,
2102+
..Default::default()
2103+
},
2104+
VirtualCompletionItem {
2105+
label: "virtual_0".to_string(),
2106+
kind: CompletionItemKind::FILE,
2107+
..Default::default()
2108+
},
2109+
VirtualCompletionItem {
2110+
label: "virtual_1".to_string(),
2111+
kind: CompletionItemKind::FILE,
2112+
..Default::default()
2113+
},
2114+
],
2115+
));
2116+
Ok(())
2117+
}
20852118
}

0 commit comments

Comments
 (0)