mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-28 04:09:05 +00:00
feat(els): implement extract into function
This commit is contained in:
parent
b8b312caad
commit
bb1dd5eb8e
4 changed files with 83 additions and 15 deletions
|
@ -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 = ¶ms.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 = ¶ms.context.diagnostics;
|
let diags = ¶ms.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(¶ms)?,
|
||||||
None => self.send_normal_action(params)?,
|
None => self.send_normal_action(¶ms)?,
|
||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ¶ms.command[..] {
|
match ¶ms.command[..] {
|
||||||
other => {
|
other => {
|
||||||
send_log(format!("unknown command: {other}"))?;
|
_log!("unknown command {other}: {params:?}");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue