feat(els): support code lens

This commit is contained in:
Shunsuke Shibayama 2023-02-26 01:16:28 +09:00
parent 0f0d042020
commit 948f123ce8
9 changed files with 194 additions and 40 deletions

56
crates/els/code_lens.rs Normal file
View 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
View 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 &params.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]),
})
}
}

View file

@ -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>(

View file

@ -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() {

View file

@ -1,4 +1,6 @@
mod code_action;
mod code_lens;
mod command;
mod completion;
mod definition;
mod diagnostics;

View file

@ -1,4 +1,6 @@
mod code_action;
mod code_lens;
mod command;
mod completion;
mod definition;
mod diagnostics;

View file

@ -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
}
}

View file

@ -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")),
}
}

View file

@ -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 {