Skip to content

Commit

Permalink
feat(els): impl document link
Browse files Browse the repository at this point in the history
  • Loading branch information
mtshiba committed Oct 12, 2024
1 parent 23142af commit 17b3fbc
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 27 deletions.
24 changes: 15 additions & 9 deletions crates/els/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ use erg_compiler::erg_parser::parse::Parsable;
use lsp_types::request::{
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
DocumentHighlightRequest, DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest,
GotoDefinition, GotoImplementation, GotoImplementationParams, GotoTypeDefinition,
GotoTypeDefinitionParams, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References,
ResolveCompletionItem, SelectionRangeRequest, SemanticTokensFullRequest, SignatureHelpRequest,
WillRenameFiles, WorkspaceSymbol,
DocumentHighlightRequest, DocumentLinkRequest, DocumentSymbolRequest, ExecuteCommand,
FoldingRangeRequest, GotoDefinition, GotoImplementation, GotoImplementationParams,
GotoTypeDefinition, GotoTypeDefinitionParams, HoverRequest, InlayHintRequest,
InlayHintResolveRequest, References, ResolveCompletionItem, SelectionRangeRequest,
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol,
};
use lsp_types::{
CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
CodeAction, CodeActionParams, CodeLensParams, CompletionItem, CompletionParams,
DocumentHighlightParams, DocumentSymbolParams, ExecuteCommandParams, FoldingRangeParams,
GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams, ReferenceParams,
RenameFilesParams, SelectionRangeParams, SemanticTokensParams, SignatureHelpParams,
WorkspaceSymbolParams,
DocumentHighlightParams, DocumentLinkParams, DocumentSymbolParams, ExecuteCommandParams,
FoldingRangeParams, GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams,
ReferenceParams, RenameFilesParams, SelectionRangeParams, SemanticTokensParams,
SignatureHelpParams, WorkspaceSymbolParams,
};

use crate::server::Server;
Expand Down Expand Up @@ -61,6 +61,7 @@ pub struct SendChannels {
folding_range: mpsc::Sender<WorkerMessage<FoldingRangeParams>>,
selection_range: mpsc::Sender<WorkerMessage<SelectionRangeParams>>,
document_highlight: mpsc::Sender<WorkerMessage<DocumentHighlightParams>>,
document_link: mpsc::Sender<WorkerMessage<DocumentLinkParams>>,
pub(crate) health_check: mpsc::Sender<WorkerMessage<()>>,
}

Expand Down Expand Up @@ -90,6 +91,7 @@ impl SendChannels {
let (tx_folding_range, rx_folding_range) = mpsc::channel();
let (tx_selection_range, rx_selection_range) = mpsc::channel();
let (tx_document_highlight, rx_document_highlight) = mpsc::channel();
let (tx_document_link, rx_document_link) = mpsc::channel();
let (tx_health_check, rx_health_check) = mpsc::channel();
(
Self {
Expand Down Expand Up @@ -117,6 +119,7 @@ impl SendChannels {
folding_range: tx_folding_range,
selection_range: tx_selection_range,
document_highlight: tx_document_highlight,
document_link: tx_document_link,
health_check: tx_health_check,
},
ReceiveChannels {
Expand Down Expand Up @@ -144,6 +147,7 @@ impl SendChannels {
folding_range: rx_folding_range,
selection_range: rx_selection_range,
document_highlight: rx_document_highlight,
document_link: rx_document_link,
health_check: rx_health_check,
},
)
Expand Down Expand Up @@ -206,6 +210,7 @@ pub struct ReceiveChannels {
pub(crate) folding_range: mpsc::Receiver<WorkerMessage<FoldingRangeParams>>,
pub(crate) selection_range: mpsc::Receiver<WorkerMessage<SelectionRangeParams>>,
pub(crate) document_highlight: mpsc::Receiver<WorkerMessage<DocumentHighlightParams>>,
pub(crate) document_link: mpsc::Receiver<WorkerMessage<DocumentLinkParams>>,
pub(crate) health_check: mpsc::Receiver<WorkerMessage<()>>,
}

Expand Down Expand Up @@ -292,3 +297,4 @@ impl_sendable!(
DocumentHighlightParams,
document_highlight
);
impl_sendable!(DocumentLinkRequest, DocumentLinkParams, document_link);
276 changes: 276 additions & 0 deletions crates/els/doc_link.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
use std::path::Path;

use erg_common::Str;
use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::ast::{ClassAttr, Expr, Literal};
use erg_compiler::erg_parser::parse::Parsable;

use erg_compiler::erg_parser::token::{Token, TokenKind};
use erg_compiler::ty::Type;
use erg_compiler::varinfo::VarInfo;
use lsp_types::{DocumentLink, DocumentLinkParams, Position, Range, Url};

use crate::_log;
use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::NormalizedUrl;

/// programming related words are not considered usual words (e.g. `if`, `while`)
fn is_usual_word(word: &str) -> bool {
matches!(
word,
"a" | "the"
| "an"
| "is"
| "are"
| "was"
| "were"
| "be"
| "been"
| "being"
| "am"
| "does"
| "did"
| "done"
| "doing"
| "have"
| "has"
| "had"
| "having"
| "will"
| "shall"
| "would"
| "should"
| "may"
| "might"
| "must"
| "can"
| "could"
| "need"
| "used"
| "to"
| "of"
| "in"
| "on"
| "at"
| "by"
| "with"
| "from"
| "about"
| "between"
| "among"
| "into"
| "onto"
| "upon"
| "over"
| "under"
| "above"
| "below"
| "behind"
| "beside"
| "before"
| "after"
| "during"
| "through"
| "across"
| "against"
| "towards"
| "around"
| "besides"
| "like"
| "near"
| "till"
| "until"
| "throughout"
| "within"
| "without"
| "according"
| "though"
| "whereas"
| "whether"
| "so"
| "although"
)
}

impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn handle_document_link(
&mut self,
params: DocumentLinkParams,
) -> ELSResult<Option<Vec<DocumentLink>>> {
_log!(self, "document link requested: {params:?}");
let uri = NormalizedUrl::new(params.text_document.uri);
let mut res = vec![];
res.extend(self.get_document_link(&uri));
Ok(Some(res))
}

fn get_document_link(&self, uri: &NormalizedUrl) -> Vec<DocumentLink> {
let mut res = vec![];
if let Some(ast) = self.get_ast(uri) {
let mut comments = vec![];
for chunk in ast.block().iter() {
comments.extend(self.get_doc_comments(chunk));
}
for comment in comments {
res.extend(self.gen_doc_link(uri, comment));
}
} else if let Ok(ast) = self.build_ast(uri) {
let mut comments = vec![];
for chunk in ast.block().iter() {
comments.extend(self.get_doc_comments(chunk));
}
for comment in comments {
res.extend(self.gen_doc_link(uri, comment));
}
}
for comment in self.get_comments(uri) {
res.extend(self.gen_doc_link(uri, &comment));
}
res
}

fn gen_doc_link(&self, uri: &NormalizedUrl, comment: &Literal) -> Vec<DocumentLink> {
let mut res = vec![];
let Some(mod_ctx) = self.get_mod_ctx(uri) else {
return vec![];
};
let mut line = comment.token.lineno.saturating_sub(1);
let mut col = comment.token.col_begin;
for li in comment.token.content.split('\n') {
let li = Str::rc(li);
let words = li.split_with(&[" ", "'", "\"", "`"]);
for word in words {
if word.trim().is_empty() || is_usual_word(word) {
col += word.len() as u32 + 1;
continue;
}
let range = Range {
start: Position {
line,
character: col,
},
end: Position {
line,
character: col + word.len() as u32,
},
};
let typ = Type::Mono(Str::rc(word));
if let Some(path) = self.cfg.input.resolve_path(Path::new(word), &self.cfg) {
let target = Url::from_file_path(path).ok();
res.push(DocumentLink {
range,
target,
tooltip: Some(format!("module {word}")),
data: None,
});
} else if let Some((_, vi)) = mod_ctx.context.get_type_info(&typ) {
res.push(self.gen_doc_link_from_vi(word, range, vi));
}
col += word.len() as u32 + 1;
}
line += 1;
col = 0;
}
res
}

fn gen_doc_link_from_vi(&self, name: &str, range: Range, vi: &VarInfo) -> DocumentLink {
let mut target = if let Some(path) = vi.t.module_path() {
Url::from_file_path(path).ok()
} else {
vi.def_loc
.module
.as_ref()
.and_then(|path| Url::from_file_path(path).ok())
};
if let Some(target) = target.as_mut() {
target.set_fragment(
vi.def_loc
.loc
.ln_begin()
.map(|l| format!("L{l}"))
.as_deref(),
);
}
let tooltip = format!("{name}: {}", vi.t);
DocumentLink {
range,
target,
tooltip: Some(tooltip),
data: None,
}
}

#[allow(clippy::only_used_in_recursion)]
fn get_doc_comments<'e>(&self, expr: &'e Expr) -> Vec<&'e Literal> {
match expr {
Expr::Literal(lit) if lit.is_doc_comment() => vec![lit],
Expr::Def(def) => {
let mut comments = vec![];
for chunk in def.body.block.iter() {
comments.extend(self.get_doc_comments(chunk));
}
comments
}
Expr::Methods(methods) => {
let mut comments = vec![];
for chunk in methods.attrs.iter() {
match chunk {
ClassAttr::Def(def) => {
for chunk in def.body.block.iter() {
comments.extend(self.get_doc_comments(chunk));
}
}
ClassAttr::Doc(lit) => {
comments.push(lit);
}
_ => {}
}
}
comments
}
Expr::Call(call) => {
let mut comments = vec![];
for arg in call.args.pos_args() {
comments.extend(self.get_doc_comments(&arg.expr));
}
comments
}
Expr::Lambda(lambda) => {
let mut comments = vec![];
for chunk in lambda.body.iter() {
comments.extend(self.get_doc_comments(chunk));
}
comments
}
Expr::Dummy(dummy) => {
let mut comments = vec![];
for chunk in dummy.exprs.iter() {
comments.extend(self.get_doc_comments(chunk));
}
comments
}
Expr::InlineModule(module) => {
let mut comments = vec![];
for chunk in module.ast.module.block().iter() {
comments.extend(self.get_doc_comments(chunk));
}
comments
}
_ => vec![],
}
}

fn get_comments(&self, uri: &NormalizedUrl) -> Vec<Literal> {
let mut lines = vec![];
if let Ok(code) = self.file_cache.get_entire_code(uri) {
for (line, li) in code.lines().enumerate() {
if let Some(li) = li.strip_prefix("#") {
let token = Token::new(TokenKind::DocComment, Str::rc(li), line as u32 + 1, 1);
lines.push(Literal::new(token));
}
}
}
lines
}
}
1 change: 1 addition & 0 deletions crates/els/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod definition;
mod diagnostics;
mod diff;
mod doc_highlight;
mod doc_link;
mod file_cache;
mod folding_range;
mod hir_visitor;
Expand Down
1 change: 1 addition & 0 deletions crates/els/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod definition;
mod diagnostics;
mod diff;
mod doc_highlight;
mod doc_link;
mod file_cache;
mod folding_range;
mod hir_visitor;
Expand Down
18 changes: 6 additions & 12 deletions crates/els/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,20 +173,14 @@ impl Scheduler {
/// Only pending tasks can be cancelled
/// TODO: cancel executing tasks
pub fn cancel(&self, id: TaskID) -> Option<Task> {
let idx = self
.pending
.borrow()
.iter()
.position(|task| task.id == id)?;
self.pending.borrow_mut().remove(idx)
let mut lock = self.pending.borrow_mut();
let idx = lock.iter().position(|task| task.id == id)?;
lock.remove(idx)
}

pub fn finish(&self, id: TaskID) -> Option<Task> {
let idx = self
.executing
.borrow()
.iter()
.position(|task| task.id == id)?;
Some(self.executing.borrow_mut().remove(idx))
let mut lock = self.executing.borrow_mut();
let idx = lock.iter().position(|task| task.id == id)?;
Some(lock.remove(idx))
}
}
Loading

0 comments on commit 17b3fbc

Please sign in to comment.