feat(els): implement extract into function

This commit is contained in:
Shunsuke Shibayama 2023-05-28 14:14:27 +09:00
parent b8b312caad
commit bb1dd5eb8e
4 changed files with 83 additions and 15 deletions

View file

@ -1,5 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use erg_common::consts::ERG_MODE;
use erg_common::deepen_indent;
use erg_common::traits::Locational; use erg_common::traits::Locational;
use erg_compiler::artifact::BuildRunnable; use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::token::{Token, TokenKind}; use erg_compiler::erg_parser::token::{Token, TokenKind};
@ -7,7 +9,7 @@ use erg_compiler::hir::Expr;
use lsp_types::{ use lsp_types::{
CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse, CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse,
TextEdit, Url, WorkspaceEdit, Position, Range, TextEdit, Url, WorkspaceEdit,
}; };
use crate::server::{send_log, ELSResult, Server}; use crate::server::{send_log, ELSResult, Server};
@ -16,10 +18,10 @@ use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable> Server<Checker> { impl<Checker: BuildRunnable> Server<Checker> {
fn gen_eliminate_unused_vars_action( fn gen_eliminate_unused_vars_action(
&self, &self,
params: CodeActionParams, params: &CodeActionParams,
) -> ELSResult<Option<CodeAction>> { ) -> ELSResult<Option<CodeAction>> {
let uri = NormalizedUrl::new(params.text_document.uri); let uri = NormalizedUrl::new(params.text_document.uri.clone());
let diags = params.context.diagnostics; let diags = &params.context.diagnostics;
let Some(diag) = diags.get(0).cloned() else { let Some(diag) = diags.get(0).cloned() else {
return Ok(None); return Ok(None);
}; };
@ -134,7 +136,17 @@ impl<Checker: BuildRunnable> Server<Checker> {
Some(action) Some(action)
} }
fn send_normal_action(&self, params: CodeActionParams) -> ELSResult<Vec<CodeAction>> { fn gen_extract_function_action(&self, params: &CodeActionParams) -> Option<CodeAction> {
let action = CodeAction {
title: "Extract into function".to_string(),
kind: Some(CodeActionKind::REFACTOR_EXTRACT),
data: Some(serde_json::to_value(params.clone()).unwrap()),
..Default::default()
};
Some(action)
}
fn send_normal_action(&self, params: &CodeActionParams) -> ELSResult<Vec<CodeAction>> {
let mut actions = vec![]; let mut actions = vec![];
let uri = NormalizedUrl::new(params.text_document.uri.clone()); let uri = NormalizedUrl::new(params.text_document.uri.clone());
if let Some(token) = self.file_cache.get_token(&uri, params.range.start) { if let Some(token) = self.file_cache.get_token(&uri, params.range.start) {
@ -144,10 +156,11 @@ impl<Checker: BuildRunnable> Server<Checker> {
} }
} }
actions.extend(self.send_quick_fix(params)?); actions.extend(self.send_quick_fix(params)?);
actions.extend(self.gen_extract_function_action(params));
Ok(actions) Ok(actions)
} }
fn send_quick_fix(&self, params: CodeActionParams) -> ELSResult<Vec<CodeAction>> { fn send_quick_fix(&self, params: &CodeActionParams) -> ELSResult<Vec<CodeAction>> {
let mut result: Vec<CodeAction> = vec![]; let mut result: Vec<CodeAction> = vec![];
let diags = &params.context.diagnostics; let diags = &params.context.diagnostics;
if diags.is_empty() { if diags.is_empty() {
@ -174,8 +187,8 @@ impl<Checker: BuildRunnable> Server<Checker> {
.as_ref() .as_ref()
.and_then(|kinds| kinds.first().map(|s| s.as_str())) .and_then(|kinds| kinds.first().map(|s| s.as_str()))
{ {
Some("quickfix") => self.send_quick_fix(params)?, Some("quickfix") => self.send_quick_fix(&params)?,
None => self.send_normal_action(params)?, None => self.send_normal_action(&params)?,
Some(other) => { Some(other) => {
send_log(&format!("Unknown code action requested: {other}"))?; send_log(&format!("Unknown code action requested: {other}"))?;
vec![] vec![]
@ -188,4 +201,42 @@ impl<Checker: BuildRunnable> Server<Checker> {
.collect(), .collect(),
)) ))
} }
pub(crate) fn handle_code_action_resolve(
&mut self,
action: CodeAction,
) -> ELSResult<CodeAction> {
send_log(format!("code action resolve requested: {action:?}"))?;
match &action.title[..] {
"Extract into function" => self.resolve_extract_function_action(action),
_ => Ok(action),
}
}
fn resolve_extract_function_action(&self, mut action: CodeAction) -> ELSResult<CodeAction> {
let params = action
.data
.take()
.and_then(|v| serde_json::from_value::<CodeActionParams>(v).ok())
.ok_or("invalid params")?;
let uri = NormalizedUrl::new(params.text_document.uri);
let range = Range::new(Position::new(params.range.start.line, 0), params.range.end);
let code = self.file_cache.get_ranged(&uri, range)?.unwrap_or_default();
let indent_len = code.chars().take_while(|c| *c == ' ').count();
let code = deepen_indent(code);
let new_func = if ERG_MODE {
format!("{}new_func() =\n{code}\n\n", " ".repeat(indent_len))
} else {
format!("{}def new_func():\n{code}\n\n", " ".repeat(indent_len))
};
let edit1 = TextEdit::new(range, new_func);
let edit2 = TextEdit::new(
Range::new(range.end, range.end),
format!("{}new_func()", " ".repeat(indent_len)),
);
let mut changes = HashMap::new();
changes.insert(uri.raw(), vec![edit1, edit2]);
action.edit = Some(WorkspaceEdit::new(changes));
Ok(action)
}
} }

View file

@ -6,7 +6,8 @@ use erg_compiler::hir::Expr;
use lsp_types::{Command, ExecuteCommandParams, Location, Url}; use lsp_types::{Command, ExecuteCommandParams, Location, Url};
use crate::server::{send_log, ELSResult, Server}; use crate::_log;
use crate::server::{ELSResult, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable> Server<Checker> { impl<Checker: BuildRunnable> Server<Checker> {
@ -14,11 +15,11 @@ impl<Checker: BuildRunnable> Server<Checker> {
&mut self, &mut self,
params: ExecuteCommandParams, params: ExecuteCommandParams,
) -> ELSResult<Option<Value>> { ) -> ELSResult<Option<Value>> {
send_log(format!("command requested: {}", params.command))?; _log!("command requested: {}", params.command);
#[allow(clippy::match_single_binding)] #[allow(clippy::match_single_binding)]
match &params.command[..] { match &params.command[..] {
other => { other => {
send_log(format!("unknown command: {other}"))?; _log!("unknown command {other}: {params:?}");
Ok(None) Ok(None)
} }
} }

View file

@ -25,9 +25,9 @@ use erg_compiler::module::{SharedCompilerResource, SharedModuleIndex};
use erg_compiler::ty::HasType; use erg_compiler::ty::HasType;
use lsp_types::request::{ use lsp_types::request::{
CodeActionRequest, CodeLensRequest, Completion, ExecuteCommand, GotoDefinition, HoverRequest, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, ExecuteCommand,
InlayHintRequest, References, Rename, Request, ResolveCompletionItem, GotoDefinition, HoverRequest, InlayHintRequest, References, Rename, Request,
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
}; };
use lsp_types::{ use lsp_types::{
ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
@ -328,7 +328,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
} else { } else {
let options = CodeActionProviderCapability::Options(CodeActionOptions { let options = CodeActionProviderCapability::Options(CodeActionOptions {
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX, CodeActionKind::REFACTOR]), code_action_kinds: Some(vec![CodeActionKind::QUICKFIX, CodeActionKind::REFACTOR]),
resolve_provider: Some(false), resolve_provider: Some(true),
work_done_progress_options: WorkDoneProgressOptions::default(), work_done_progress_options: WorkDoneProgressOptions::default(),
}); });
Some(options) Some(options)
@ -491,6 +491,9 @@ impl<Checker: BuildRunnable> Server<Checker> {
CodeActionRequest::METHOD => { CodeActionRequest::METHOD => {
self.wrap::<CodeActionRequest>(id, msg, Self::handle_code_action) self.wrap::<CodeActionRequest>(id, msg, Self::handle_code_action)
} }
CodeActionResolveRequest::METHOD => {
self.wrap::<CodeActionResolveRequest>(id, msg, Self::handle_code_action_resolve)
}
SignatureHelpRequest::METHOD => { SignatureHelpRequest::METHOD => {
self.wrap::<SignatureHelpRequest>(id, msg, Self::handle_signature_help) self.wrap::<SignatureHelpRequest>(id, msg, Self::handle_signature_help)
} }

View file

@ -189,3 +189,16 @@ pub fn trim_eliminate_top_indent(code: String) -> String {
} }
result result
} }
pub fn deepen_indent(code: String) -> String {
let mut result = String::new();
for line in code.lines() {
result.push_str(" ");
result.push_str(line);
result.push('\n');
}
if !result.is_empty() {
result.pop();
}
result
}