Skip to content

Commit 17b3fbc

Browse files
committed
feat(els): impl document link
1 parent 23142af commit 17b3fbc

File tree

9 files changed

+323
-27
lines changed

9 files changed

+323
-27
lines changed

crates/els/channels.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ use erg_compiler::erg_parser::parse::Parsable;
66
use lsp_types::request::{
77
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
88
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
9-
DocumentHighlightRequest, DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest,
10-
GotoDefinition, GotoImplementation, GotoImplementationParams, GotoTypeDefinition,
11-
GotoTypeDefinitionParams, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References,
12-
ResolveCompletionItem, SelectionRangeRequest, SemanticTokensFullRequest, SignatureHelpRequest,
13-
WillRenameFiles, WorkspaceSymbol,
9+
DocumentHighlightRequest, DocumentLinkRequest, DocumentSymbolRequest, ExecuteCommand,
10+
FoldingRangeRequest, GotoDefinition, GotoImplementation, GotoImplementationParams,
11+
GotoTypeDefinition, GotoTypeDefinitionParams, HoverRequest, InlayHintRequest,
12+
InlayHintResolveRequest, References, ResolveCompletionItem, SelectionRangeRequest,
13+
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol,
1414
};
1515
use lsp_types::{
1616
CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
1717
CodeAction, CodeActionParams, CodeLensParams, CompletionItem, CompletionParams,
18-
DocumentHighlightParams, DocumentSymbolParams, ExecuteCommandParams, FoldingRangeParams,
19-
GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams, ReferenceParams,
20-
RenameFilesParams, SelectionRangeParams, SemanticTokensParams, SignatureHelpParams,
21-
WorkspaceSymbolParams,
18+
DocumentHighlightParams, DocumentLinkParams, DocumentSymbolParams, ExecuteCommandParams,
19+
FoldingRangeParams, GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams,
20+
ReferenceParams, RenameFilesParams, SelectionRangeParams, SemanticTokensParams,
21+
SignatureHelpParams, WorkspaceSymbolParams,
2222
};
2323

2424
use crate::server::Server;
@@ -61,6 +61,7 @@ pub struct SendChannels {
6161
folding_range: mpsc::Sender<WorkerMessage<FoldingRangeParams>>,
6262
selection_range: mpsc::Sender<WorkerMessage<SelectionRangeParams>>,
6363
document_highlight: mpsc::Sender<WorkerMessage<DocumentHighlightParams>>,
64+
document_link: mpsc::Sender<WorkerMessage<DocumentLinkParams>>,
6465
pub(crate) health_check: mpsc::Sender<WorkerMessage<()>>,
6566
}
6667

@@ -90,6 +91,7 @@ impl SendChannels {
9091
let (tx_folding_range, rx_folding_range) = mpsc::channel();
9192
let (tx_selection_range, rx_selection_range) = mpsc::channel();
9293
let (tx_document_highlight, rx_document_highlight) = mpsc::channel();
94+
let (tx_document_link, rx_document_link) = mpsc::channel();
9395
let (tx_health_check, rx_health_check) = mpsc::channel();
9496
(
9597
Self {
@@ -117,6 +119,7 @@ impl SendChannels {
117119
folding_range: tx_folding_range,
118120
selection_range: tx_selection_range,
119121
document_highlight: tx_document_highlight,
122+
document_link: tx_document_link,
120123
health_check: tx_health_check,
121124
},
122125
ReceiveChannels {
@@ -144,6 +147,7 @@ impl SendChannels {
144147
folding_range: rx_folding_range,
145148
selection_range: rx_selection_range,
146149
document_highlight: rx_document_highlight,
150+
document_link: rx_document_link,
147151
health_check: rx_health_check,
148152
},
149153
)
@@ -206,6 +210,7 @@ pub struct ReceiveChannels {
206210
pub(crate) folding_range: mpsc::Receiver<WorkerMessage<FoldingRangeParams>>,
207211
pub(crate) selection_range: mpsc::Receiver<WorkerMessage<SelectionRangeParams>>,
208212
pub(crate) document_highlight: mpsc::Receiver<WorkerMessage<DocumentHighlightParams>>,
213+
pub(crate) document_link: mpsc::Receiver<WorkerMessage<DocumentLinkParams>>,
209214
pub(crate) health_check: mpsc::Receiver<WorkerMessage<()>>,
210215
}
211216

@@ -292,3 +297,4 @@ impl_sendable!(
292297
DocumentHighlightParams,
293298
document_highlight
294299
);
300+
impl_sendable!(DocumentLinkRequest, DocumentLinkParams, document_link);

crates/els/doc_link.rs

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
use std::path::Path;
2+
3+
use erg_common::Str;
4+
use erg_compiler::artifact::BuildRunnable;
5+
use erg_compiler::erg_parser::ast::{ClassAttr, Expr, Literal};
6+
use erg_compiler::erg_parser::parse::Parsable;
7+
8+
use erg_compiler::erg_parser::token::{Token, TokenKind};
9+
use erg_compiler::ty::Type;
10+
use erg_compiler::varinfo::VarInfo;
11+
use lsp_types::{DocumentLink, DocumentLinkParams, Position, Range, Url};
12+
13+
use crate::_log;
14+
use crate::server::{ELSResult, RedirectableStdout, Server};
15+
use crate::util::NormalizedUrl;
16+
17+
/// programming related words are not considered usual words (e.g. `if`, `while`)
18+
fn is_usual_word(word: &str) -> bool {
19+
matches!(
20+
word,
21+
"a" | "the"
22+
| "an"
23+
| "is"
24+
| "are"
25+
| "was"
26+
| "were"
27+
| "be"
28+
| "been"
29+
| "being"
30+
| "am"
31+
| "does"
32+
| "did"
33+
| "done"
34+
| "doing"
35+
| "have"
36+
| "has"
37+
| "had"
38+
| "having"
39+
| "will"
40+
| "shall"
41+
| "would"
42+
| "should"
43+
| "may"
44+
| "might"
45+
| "must"
46+
| "can"
47+
| "could"
48+
| "need"
49+
| "used"
50+
| "to"
51+
| "of"
52+
| "in"
53+
| "on"
54+
| "at"
55+
| "by"
56+
| "with"
57+
| "from"
58+
| "about"
59+
| "between"
60+
| "among"
61+
| "into"
62+
| "onto"
63+
| "upon"
64+
| "over"
65+
| "under"
66+
| "above"
67+
| "below"
68+
| "behind"
69+
| "beside"
70+
| "before"
71+
| "after"
72+
| "during"
73+
| "through"
74+
| "across"
75+
| "against"
76+
| "towards"
77+
| "around"
78+
| "besides"
79+
| "like"
80+
| "near"
81+
| "till"
82+
| "until"
83+
| "throughout"
84+
| "within"
85+
| "without"
86+
| "according"
87+
| "though"
88+
| "whereas"
89+
| "whether"
90+
| "so"
91+
| "although"
92+
)
93+
}
94+
95+
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
96+
pub(crate) fn handle_document_link(
97+
&mut self,
98+
params: DocumentLinkParams,
99+
) -> ELSResult<Option<Vec<DocumentLink>>> {
100+
_log!(self, "document link requested: {params:?}");
101+
let uri = NormalizedUrl::new(params.text_document.uri);
102+
let mut res = vec![];
103+
res.extend(self.get_document_link(&uri));
104+
Ok(Some(res))
105+
}
106+
107+
fn get_document_link(&self, uri: &NormalizedUrl) -> Vec<DocumentLink> {
108+
let mut res = vec![];
109+
if let Some(ast) = self.get_ast(uri) {
110+
let mut comments = vec![];
111+
for chunk in ast.block().iter() {
112+
comments.extend(self.get_doc_comments(chunk));
113+
}
114+
for comment in comments {
115+
res.extend(self.gen_doc_link(uri, comment));
116+
}
117+
} else if let Ok(ast) = self.build_ast(uri) {
118+
let mut comments = vec![];
119+
for chunk in ast.block().iter() {
120+
comments.extend(self.get_doc_comments(chunk));
121+
}
122+
for comment in comments {
123+
res.extend(self.gen_doc_link(uri, comment));
124+
}
125+
}
126+
for comment in self.get_comments(uri) {
127+
res.extend(self.gen_doc_link(uri, &comment));
128+
}
129+
res
130+
}
131+
132+
fn gen_doc_link(&self, uri: &NormalizedUrl, comment: &Literal) -> Vec<DocumentLink> {
133+
let mut res = vec![];
134+
let Some(mod_ctx) = self.get_mod_ctx(uri) else {
135+
return vec![];
136+
};
137+
let mut line = comment.token.lineno.saturating_sub(1);
138+
let mut col = comment.token.col_begin;
139+
for li in comment.token.content.split('\n') {
140+
let li = Str::rc(li);
141+
let words = li.split_with(&[" ", "'", "\"", "`"]);
142+
for word in words {
143+
if word.trim().is_empty() || is_usual_word(word) {
144+
col += word.len() as u32 + 1;
145+
continue;
146+
}
147+
let range = Range {
148+
start: Position {
149+
line,
150+
character: col,
151+
},
152+
end: Position {
153+
line,
154+
character: col + word.len() as u32,
155+
},
156+
};
157+
let typ = Type::Mono(Str::rc(word));
158+
if let Some(path) = self.cfg.input.resolve_path(Path::new(word), &self.cfg) {
159+
let target = Url::from_file_path(path).ok();
160+
res.push(DocumentLink {
161+
range,
162+
target,
163+
tooltip: Some(format!("module {word}")),
164+
data: None,
165+
});
166+
} else if let Some((_, vi)) = mod_ctx.context.get_type_info(&typ) {
167+
res.push(self.gen_doc_link_from_vi(word, range, vi));
168+
}
169+
col += word.len() as u32 + 1;
170+
}
171+
line += 1;
172+
col = 0;
173+
}
174+
res
175+
}
176+
177+
fn gen_doc_link_from_vi(&self, name: &str, range: Range, vi: &VarInfo) -> DocumentLink {
178+
let mut target = if let Some(path) = vi.t.module_path() {
179+
Url::from_file_path(path).ok()
180+
} else {
181+
vi.def_loc
182+
.module
183+
.as_ref()
184+
.and_then(|path| Url::from_file_path(path).ok())
185+
};
186+
if let Some(target) = target.as_mut() {
187+
target.set_fragment(
188+
vi.def_loc
189+
.loc
190+
.ln_begin()
191+
.map(|l| format!("L{l}"))
192+
.as_deref(),
193+
);
194+
}
195+
let tooltip = format!("{name}: {}", vi.t);
196+
DocumentLink {
197+
range,
198+
target,
199+
tooltip: Some(tooltip),
200+
data: None,
201+
}
202+
}
203+
204+
#[allow(clippy::only_used_in_recursion)]
205+
fn get_doc_comments<'e>(&self, expr: &'e Expr) -> Vec<&'e Literal> {
206+
match expr {
207+
Expr::Literal(lit) if lit.is_doc_comment() => vec![lit],
208+
Expr::Def(def) => {
209+
let mut comments = vec![];
210+
for chunk in def.body.block.iter() {
211+
comments.extend(self.get_doc_comments(chunk));
212+
}
213+
comments
214+
}
215+
Expr::Methods(methods) => {
216+
let mut comments = vec![];
217+
for chunk in methods.attrs.iter() {
218+
match chunk {
219+
ClassAttr::Def(def) => {
220+
for chunk in def.body.block.iter() {
221+
comments.extend(self.get_doc_comments(chunk));
222+
}
223+
}
224+
ClassAttr::Doc(lit) => {
225+
comments.push(lit);
226+
}
227+
_ => {}
228+
}
229+
}
230+
comments
231+
}
232+
Expr::Call(call) => {
233+
let mut comments = vec![];
234+
for arg in call.args.pos_args() {
235+
comments.extend(self.get_doc_comments(&arg.expr));
236+
}
237+
comments
238+
}
239+
Expr::Lambda(lambda) => {
240+
let mut comments = vec![];
241+
for chunk in lambda.body.iter() {
242+
comments.extend(self.get_doc_comments(chunk));
243+
}
244+
comments
245+
}
246+
Expr::Dummy(dummy) => {
247+
let mut comments = vec![];
248+
for chunk in dummy.exprs.iter() {
249+
comments.extend(self.get_doc_comments(chunk));
250+
}
251+
comments
252+
}
253+
Expr::InlineModule(module) => {
254+
let mut comments = vec![];
255+
for chunk in module.ast.module.block().iter() {
256+
comments.extend(self.get_doc_comments(chunk));
257+
}
258+
comments
259+
}
260+
_ => vec![],
261+
}
262+
}
263+
264+
fn get_comments(&self, uri: &NormalizedUrl) -> Vec<Literal> {
265+
let mut lines = vec![];
266+
if let Ok(code) = self.file_cache.get_entire_code(uri) {
267+
for (line, li) in code.lines().enumerate() {
268+
if let Some(li) = li.strip_prefix("#") {
269+
let token = Token::new(TokenKind::DocComment, Str::rc(li), line as u32 + 1, 1);
270+
lines.push(Literal::new(token));
271+
}
272+
}
273+
}
274+
lines
275+
}
276+
}

crates/els/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod definition;
88
mod diagnostics;
99
mod diff;
1010
mod doc_highlight;
11+
mod doc_link;
1112
mod file_cache;
1213
mod folding_range;
1314
mod hir_visitor;

crates/els/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod definition;
88
mod diagnostics;
99
mod diff;
1010
mod doc_highlight;
11+
mod doc_link;
1112
mod file_cache;
1213
mod folding_range;
1314
mod hir_visitor;

crates/els/scheduler.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -173,20 +173,14 @@ impl Scheduler {
173173
/// Only pending tasks can be cancelled
174174
/// TODO: cancel executing tasks
175175
pub fn cancel(&self, id: TaskID) -> Option<Task> {
176-
let idx = self
177-
.pending
178-
.borrow()
179-
.iter()
180-
.position(|task| task.id == id)?;
181-
self.pending.borrow_mut().remove(idx)
176+
let mut lock = self.pending.borrow_mut();
177+
let idx = lock.iter().position(|task| task.id == id)?;
178+
lock.remove(idx)
182179
}
183180

184181
pub fn finish(&self, id: TaskID) -> Option<Task> {
185-
let idx = self
186-
.executing
187-
.borrow()
188-
.iter()
189-
.position(|task| task.id == id)?;
190-
Some(self.executing.borrow_mut().remove(idx))
182+
let mut lock = self.executing.borrow_mut();
183+
let idx = lock.iter().position(|task| task.id == id)?;
184+
Some(lock.remove(idx))
191185
}
192186
}

0 commit comments

Comments
 (0)