mirror of
https://github.com/erg-lang/erg.git
synced 2025-08-04 18:58:30 +00:00
feat(els): support code lens
This commit is contained in:
parent
0f0d042020
commit
948f123ce8
9 changed files with 194 additions and 40 deletions
56
crates/els/code_lens.rs
Normal file
56
crates/els/code_lens.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
|
||||
use erg_compiler::artifact::BuildRunnable;
|
||||
use erg_compiler::hir::Expr;
|
||||
|
||||
use lsp_types::{CodeLens, CodeLensParams, Url};
|
||||
|
||||
use crate::server::{ELSResult, Server};
|
||||
use crate::util;
|
||||
|
||||
impl<Checker: BuildRunnable> Server<Checker> {
|
||||
pub(crate) fn show_code_lens(&mut self, msg: &Value) -> ELSResult<()> {
|
||||
Self::send_log("code lens requested")?;
|
||||
let params = CodeLensParams::deserialize(&msg["params"])?;
|
||||
let uri = util::normalize_url(params.text_document.uri);
|
||||
// TODO: parallelize
|
||||
let result = [
|
||||
self.send_trait_impls_lens(&uri)?,
|
||||
self.send_class_inherits_lens(&uri)?,
|
||||
]
|
||||
.concat();
|
||||
Self::send(
|
||||
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": result }),
|
||||
)
|
||||
}
|
||||
|
||||
fn send_trait_impls_lens(&mut self, uri: &Url) -> ELSResult<Vec<CodeLens>> {
|
||||
let mut result = vec![];
|
||||
if let Some(artifact) = self.artifacts.get(uri) {
|
||||
if let Some(hir) = &artifact.object {
|
||||
for chunk in hir.module.iter() {
|
||||
match chunk {
|
||||
Expr::Def(def) if def.def_kind().is_trait() => {
|
||||
let trait_loc = &def.sig.ident().vi.def_loc;
|
||||
let command = self.gen_show_trait_impls_command(trait_loc.clone())?;
|
||||
let lens = CodeLens {
|
||||
range: util::loc_to_range(trait_loc.loc).unwrap(),
|
||||
command: Some(command),
|
||||
data: None,
|
||||
};
|
||||
result.push(lens);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn send_class_inherits_lens(&mut self, _uri: &Url) -> ELSResult<Vec<CodeLens>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
59
crates/els/command.rs
Normal file
59
crates/els/command.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use erg_compiler::varinfo::AbsLocation;
|
||||
use lsp_types::Command;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
|
||||
use erg_compiler::artifact::BuildRunnable;
|
||||
use erg_compiler::hir::Expr;
|
||||
|
||||
use lsp_types::{ExecuteCommandParams, Location, Url};
|
||||
|
||||
use crate::server::{ELSResult, Server};
|
||||
use crate::util;
|
||||
|
||||
impl<Checker: BuildRunnable> Server<Checker> {
|
||||
pub(crate) fn execute_command(&mut self, msg: &Value) -> ELSResult<()> {
|
||||
let params = ExecuteCommandParams::deserialize(&msg["params"])?;
|
||||
Self::send_log(format!("command requested: {}", params.command))?;
|
||||
#[allow(clippy::match_single_binding)]
|
||||
match ¶ms.command[..] {
|
||||
other => {
|
||||
Self::send_log(format!("unknown command: {other}"))?;
|
||||
Self::send(
|
||||
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": Value::Null }),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_show_trait_impls_command(
|
||||
&self,
|
||||
trait_loc: AbsLocation,
|
||||
) -> ELSResult<Command> {
|
||||
let refs = self.get_refs_from_abs_loc(&trait_loc);
|
||||
let uri =
|
||||
util::normalize_url(Url::from_file_path(trait_loc.module.as_ref().unwrap()).unwrap());
|
||||
let opt_visitor = self.get_visitor(&uri);
|
||||
let filter = |loc: Location| {
|
||||
let token = self.file_cache.get_token(&loc.uri, loc.range.start)?;
|
||||
let min_expr = opt_visitor
|
||||
.as_ref()
|
||||
.and_then(|visitor| visitor.get_min_expr(&token))?;
|
||||
matches!(min_expr, Expr::ClassDef(_)).then_some(loc)
|
||||
};
|
||||
let impls = refs.into_iter().filter_map(filter).collect::<Vec<_>>();
|
||||
let impl_len = impls.len();
|
||||
let locations = serde_json::to_value(impls)?;
|
||||
let uri = util::normalize_url(Url::from_file_path(trait_loc.module.unwrap()).unwrap());
|
||||
let uri = serde_json::to_value(uri)?;
|
||||
let position = util::loc_to_pos(trait_loc.loc).unwrap();
|
||||
let position = serde_json::to_value(position)?;
|
||||
Ok(Command {
|
||||
title: format!("{impl_len} implementations"),
|
||||
// the command is defined in: https://github.com/erg-lang/vscode-erg/blob/20e6e2154b045ab56fedbc8769d03633acfd12e0/src/extension.ts#L92-L94
|
||||
command: "erg.showReferences".to_string(),
|
||||
arguments: Some(vec![uri, position, locations]),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -176,7 +176,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
Expr::BinOp(bin) => self.get_expr_from_bin(expr, bin, token),
|
||||
Expr::UnaryOp(unary) => self.get_expr(&unary.expr, token),
|
||||
Expr::Call(call) => self.get_expr_from_call(expr, call, token),
|
||||
Expr::ClassDef(class_def) => self.get_expr_from_class_def(class_def, token),
|
||||
Expr::ClassDef(class_def) => self.get_expr_from_class_def(expr, class_def, token),
|
||||
Expr::Def(def) => self.get_expr_from_def(expr, def, token),
|
||||
Expr::PatchDef(patch_def) => self.get_expr_from_patch_def(expr, patch_def, token),
|
||||
Expr::Lambda(lambda) => self.get_expr_from_lambda(expr, lambda, token),
|
||||
|
@ -268,10 +268,12 @@ impl<'a> HIRVisitor<'a> {
|
|||
) -> Option<&Expr> {
|
||||
self.return_expr_if_same(expr, def.sig.ident().name.token(), token)
|
||||
.or_else(|| self.get_expr_from_block(&def.body.block, token))
|
||||
.or_else(|| def.loc().contains(token.loc()).then_some(expr))
|
||||
}
|
||||
|
||||
fn get_expr_from_class_def<'e>(
|
||||
&'e self,
|
||||
expr: &'e Expr,
|
||||
class_def: &'e ClassDef,
|
||||
token: &Token,
|
||||
) -> Option<&Expr> {
|
||||
|
@ -280,6 +282,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
.as_ref()
|
||||
.and_then(|req_sup| self.get_expr(req_sup, token))
|
||||
.or_else(|| self.get_expr_from_block(&class_def.methods, token))
|
||||
.or_else(|| class_def.loc().contains(token.loc()).then_some(expr))
|
||||
}
|
||||
|
||||
fn get_expr_from_block<'e>(&'e self, block: &'e Block, token: &Token) -> Option<&Expr> {
|
||||
|
@ -319,6 +322,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
self.return_expr_if_same(expr, patch_def.sig.ident().name.token(), token)
|
||||
.or_else(|| self.get_expr(&patch_def.base, token))
|
||||
.or_else(|| self.get_expr_from_block(&patch_def.methods, token))
|
||||
.or_else(|| patch_def.loc().contains(token.loc()).then_some(expr))
|
||||
}
|
||||
|
||||
fn get_expr_from_lambda<'e>(
|
||||
|
|
|
@ -12,6 +12,14 @@ use lsp_types::{HoverContents, HoverParams, MarkedString, Url};
|
|||
use crate::server::{ELSResult, Server};
|
||||
use crate::util;
|
||||
|
||||
const PROG_LANG: &str = if cfg!(feature = "py_compatible") {
|
||||
"python"
|
||||
} else {
|
||||
"erg"
|
||||
};
|
||||
|
||||
const ERG_LANG: &str = "erg";
|
||||
|
||||
fn lang_code(code: &str) -> LanguageCode {
|
||||
code.lines()
|
||||
.next()
|
||||
|
@ -92,11 +100,6 @@ macro_rules! next {
|
|||
impl<Checker: BuildRunnable> Server<Checker> {
|
||||
pub(crate) fn show_hover(&mut self, msg: &Value) -> ELSResult<()> {
|
||||
Self::send_log(format!("hover requested : {msg}"))?;
|
||||
let lang = if cfg!(feature = "py_compatible") {
|
||||
"python"
|
||||
} else {
|
||||
"erg"
|
||||
};
|
||||
let params = HoverParams::deserialize(&msg["params"])?;
|
||||
let uri = util::normalize_url(params.text_document_position_params.text_document.uri);
|
||||
let pos = params.text_document_position_params.position;
|
||||
|
@ -156,11 +159,12 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
let definition = MarkedString::from_language_code(lang.into(), code_block);
|
||||
let definition =
|
||||
MarkedString::from_language_code(PROG_LANG.into(), code_block);
|
||||
contents.push(definition);
|
||||
}
|
||||
let typ = MarkedString::from_language_code(
|
||||
lang.into(),
|
||||
ERG_LANG.into(),
|
||||
format!("{}: {}", token.content, vi.t),
|
||||
);
|
||||
contents.push(typ);
|
||||
|
@ -171,7 +175,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
if let Some(visitor) = self.get_visitor(&uri) {
|
||||
if let Some(typ) = visitor.get_min_expr(&token) {
|
||||
let typ = MarkedString::from_language_code(
|
||||
lang.into(),
|
||||
ERG_LANG.into(),
|
||||
format!("{}: {typ}", token.content),
|
||||
);
|
||||
contents.push(typ);
|
||||
|
@ -224,10 +228,14 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
let code_block = eliminate_top_indent(
|
||||
code_block.trim_start_matches(lang.as_str()).to_string(),
|
||||
);
|
||||
let marked = if lang.is_erg() {
|
||||
MarkedString::from_language_code("erg".into(), code_block)
|
||||
} else {
|
||||
MarkedString::from_markdown(code_block)
|
||||
let marked = match lang {
|
||||
LanguageCode::Erg => {
|
||||
MarkedString::from_language_code("erg".into(), code_block)
|
||||
}
|
||||
LanguageCode::Python => {
|
||||
MarkedString::from_language_code("python".into(), code_block)
|
||||
}
|
||||
_ => MarkedString::from_markdown(code_block),
|
||||
};
|
||||
contents.push(marked);
|
||||
if lang.is_erg() {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
mod code_action;
|
||||
mod code_lens;
|
||||
mod command;
|
||||
mod completion;
|
||||
mod definition;
|
||||
mod diagnostics;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
mod code_action;
|
||||
mod code_lens;
|
||||
mod command;
|
||||
mod completion;
|
||||
mod definition;
|
||||
mod diagnostics;
|
||||
|
|
|
@ -3,8 +3,9 @@ use serde_json::json;
|
|||
use serde_json::Value;
|
||||
|
||||
use erg_compiler::artifact::BuildRunnable;
|
||||
use erg_compiler::varinfo::AbsLocation;
|
||||
|
||||
use lsp_types::{ReferenceParams, Url};
|
||||
use lsp_types::{Position, ReferenceParams, Url};
|
||||
|
||||
use crate::server::{ELSResult, Server};
|
||||
use crate::util;
|
||||
|
@ -14,32 +15,37 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
let params = ReferenceParams::deserialize(&msg["params"])?;
|
||||
let uri = util::normalize_url(params.text_document_position.text_document.uri);
|
||||
let pos = params.text_document_position.position;
|
||||
if let Some(tok) = self.file_cache.get_token(&uri, pos) {
|
||||
let result = self.show_refs_inner(&uri, pos);
|
||||
Self::send(
|
||||
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": result }),
|
||||
)
|
||||
}
|
||||
|
||||
fn show_refs_inner(&self, uri: &Url, pos: Position) -> Vec<lsp_types::Location> {
|
||||
if let Some(tok) = self.file_cache.get_token(uri, pos) {
|
||||
// Self::send_log(format!("token: {tok}"))?;
|
||||
if let Some(visitor) = self.get_visitor(&uri) {
|
||||
if let Some(visitor) = self.get_visitor(uri) {
|
||||
if let Some(vi) = visitor.get_info(&tok) {
|
||||
let mut refs = vec![];
|
||||
if let Some(value) = self.get_index().get_refs(&vi.def_loc) {
|
||||
// Self::send_log(format!("referrers: {referrers:?}"))?;
|
||||
for referrer in value.referrers.iter() {
|
||||
if let (Some(path), Some(range)) =
|
||||
(&referrer.module, util::loc_to_range(referrer.loc))
|
||||
{
|
||||
let ref_uri =
|
||||
util::normalize_url(Url::from_file_path(path).unwrap());
|
||||
refs.push(lsp_types::Location::new(ref_uri, range));
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::send(
|
||||
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": refs }),
|
||||
)?;
|
||||
return Ok(());
|
||||
return self.get_refs_from_abs_loc(&vi.def_loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::send(
|
||||
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": Value::Null }),
|
||||
)
|
||||
vec![]
|
||||
}
|
||||
|
||||
pub(crate) fn get_refs_from_abs_loc(&self, referee: &AbsLocation) -> Vec<lsp_types::Location> {
|
||||
let mut refs = vec![];
|
||||
if let Some(value) = self.get_index().get_refs(referee) {
|
||||
// Self::send_log(format!("referrers: {referrers:?}"))?;
|
||||
for referrer in value.referrers.iter() {
|
||||
if let (Some(path), Some(range)) =
|
||||
(&referrer.module, util::loc_to_range(referrer.loc))
|
||||
{
|
||||
let ref_uri = util::normalize_url(Url::from_file_path(path).unwrap());
|
||||
refs.push(lsp_types::Location::new(ref_uri, range));
|
||||
}
|
||||
}
|
||||
}
|
||||
refs
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@ use erg_compiler::ty::HasType;
|
|||
|
||||
use lsp_types::{
|
||||
ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
|
||||
CompletionOptions, DidChangeTextDocumentParams, ExecuteCommandOptions, HoverProviderCapability,
|
||||
InitializeResult, OneOf, Position, SemanticTokenType, SemanticTokensFullOptions,
|
||||
SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities,
|
||||
ServerCapabilities, SignatureHelpOptions, Url, WorkDoneProgressOptions,
|
||||
CodeLensOptions, CompletionOptions, DidChangeTextDocumentParams, ExecuteCommandOptions,
|
||||
HoverProviderCapability, InitializeResult, OneOf, Position, SemanticTokenType,
|
||||
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
|
||||
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions, Url,
|
||||
WorkDoneProgressOptions,
|
||||
};
|
||||
|
||||
use crate::file_cache::FileCache;
|
||||
|
@ -243,6 +244,9 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
work_done_progress: None,
|
||||
},
|
||||
});
|
||||
result.capabilities.code_lens_provider = Some(CodeLensOptions {
|
||||
resolve_provider: Some(false),
|
||||
});
|
||||
Self::send(&json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": id,
|
||||
|
@ -360,7 +364,9 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
"textDocument/inlayHint" => self.get_inlay_hint(msg),
|
||||
"textDocument/codeAction" => self.send_code_action(msg),
|
||||
"textDocument/signatureHelp" => self.show_signature_help(msg),
|
||||
"textDocument/codeLens" => self.show_code_lens(msg),
|
||||
"workspace/willRenameFiles" => self.rename_files(msg),
|
||||
"workspace/executeCommand" => self.execute_command(msg),
|
||||
other => Self::send_error(Some(id), -32600, format!("{other} is not supported")),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ pub enum LanguageCode {
|
|||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
Erg,
|
||||
Python,
|
||||
}
|
||||
|
||||
impl FromStr for LanguageCode {
|
||||
|
@ -18,6 +19,7 @@ impl FromStr for LanguageCode {
|
|||
"simplified_chinese" | "zh-CN" => Ok(Self::SimplifiedChinese),
|
||||
"traditional_chinese" | "zh-TW" => Ok(Self::TraditionalChinese),
|
||||
"erg" => Ok(Self::Erg),
|
||||
"python" => Ok(Self::Python),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +33,7 @@ impl From<LanguageCode> for &str {
|
|||
LanguageCode::SimplifiedChinese => "simplified_chinese",
|
||||
LanguageCode::TraditionalChinese => "traditional_chinese",
|
||||
LanguageCode::Erg => "erg",
|
||||
LanguageCode::Python => "python",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +54,9 @@ impl LanguageCode {
|
|||
pub const fn erg_patterns() -> [&'static str; 2] {
|
||||
["erg", "erg"]
|
||||
}
|
||||
pub const fn python_patterns() -> [&'static str; 2] {
|
||||
["python", "python"]
|
||||
}
|
||||
pub const fn patterns(&self) -> [&'static str; 2] {
|
||||
match self {
|
||||
Self::English => Self::en_patterns(),
|
||||
|
@ -58,6 +64,7 @@ impl LanguageCode {
|
|||
Self::SimplifiedChinese => Self::zh_cn_patterns(),
|
||||
Self::TraditionalChinese => Self::zh_tw_patterns(),
|
||||
Self::Erg => Self::erg_patterns(),
|
||||
Self::Python => Self::python_patterns(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +83,9 @@ impl LanguageCode {
|
|||
pub const fn is_erg(&self) -> bool {
|
||||
matches!(self, Self::Erg)
|
||||
}
|
||||
pub const fn is_python(&self) -> bool {
|
||||
matches!(self, Self::Python)
|
||||
}
|
||||
|
||||
pub const fn matches_feature(&self) -> bool {
|
||||
match self {
|
||||
|
@ -88,6 +98,7 @@ impl LanguageCode {
|
|||
Self::SimplifiedChinese => cfg!(feature = "simplified_chinese"),
|
||||
Self::TraditionalChinese => cfg!(feature = "traditional_chinese"),
|
||||
Self::Erg => true,
|
||||
Self::Python => cfg!(feature = "py_compatible"),
|
||||
}
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue