diff --git a/crates/els/code_action.rs b/crates/els/code_action.rs index eb5e991c..15ef747d 100644 --- a/crates/els/code_action.rs +++ b/crates/els/code_action.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use erg_common::consts::ERG_MODE; +use erg_common::deepen_indent; use erg_common::traits::Locational; use erg_compiler::artifact::BuildRunnable; use erg_compiler::erg_parser::token::{Token, TokenKind}; @@ -7,7 +9,7 @@ use erg_compiler::hir::Expr; use lsp_types::{ CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse, - TextEdit, Url, WorkspaceEdit, + Position, Range, TextEdit, Url, WorkspaceEdit, }; use crate::server::{send_log, ELSResult, Server}; @@ -16,10 +18,10 @@ use crate::util::{self, NormalizedUrl}; impl Server { fn gen_eliminate_unused_vars_action( &self, - params: CodeActionParams, + params: &CodeActionParams, ) -> ELSResult> { - let uri = NormalizedUrl::new(params.text_document.uri); - let diags = params.context.diagnostics; + let uri = NormalizedUrl::new(params.text_document.uri.clone()); + let diags = ¶ms.context.diagnostics; let Some(diag) = diags.get(0).cloned() else { return Ok(None); }; @@ -134,7 +136,17 @@ impl Server { Some(action) } - fn send_normal_action(&self, params: CodeActionParams) -> ELSResult> { + fn gen_extract_function_action(&self, params: &CodeActionParams) -> Option { + 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> { let mut actions = vec![]; let uri = NormalizedUrl::new(params.text_document.uri.clone()); if let Some(token) = self.file_cache.get_token(&uri, params.range.start) { @@ -144,10 +156,11 @@ impl Server { } } actions.extend(self.send_quick_fix(params)?); + actions.extend(self.gen_extract_function_action(params)); Ok(actions) } - fn send_quick_fix(&self, params: CodeActionParams) -> ELSResult> { + fn send_quick_fix(&self, params: &CodeActionParams) -> ELSResult> { let mut result: Vec = vec![]; let diags = ¶ms.context.diagnostics; if diags.is_empty() { @@ -174,8 +187,8 @@ impl Server { .as_ref() .and_then(|kinds| kinds.first().map(|s| s.as_str())) { - Some("quickfix") => self.send_quick_fix(params)?, - None => self.send_normal_action(params)?, + Some("quickfix") => self.send_quick_fix(¶ms)?, + None => self.send_normal_action(¶ms)?, Some(other) => { send_log(&format!("Unknown code action requested: {other}"))?; vec![] @@ -188,4 +201,42 @@ impl Server { .collect(), )) } + + pub(crate) fn handle_code_action_resolve( + &mut self, + action: CodeAction, + ) -> ELSResult { + 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 { + let params = action + .data + .take() + .and_then(|v| serde_json::from_value::(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) + } } diff --git a/crates/els/command.rs b/crates/els/command.rs index 2bc860ca..f0f90bb5 100644 --- a/crates/els/command.rs +++ b/crates/els/command.rs @@ -6,7 +6,8 @@ use erg_compiler::hir::Expr; 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}; impl Server { @@ -14,11 +15,11 @@ impl Server { &mut self, params: ExecuteCommandParams, ) -> ELSResult> { - send_log(format!("command requested: {}", params.command))?; + _log!("command requested: {}", params.command); #[allow(clippy::match_single_binding)] match ¶ms.command[..] { other => { - send_log(format!("unknown command: {other}"))?; + _log!("unknown command {other}: {params:?}"); Ok(None) } } diff --git a/crates/els/server.rs b/crates/els/server.rs index df6db2fd..108a7ff0 100644 --- a/crates/els/server.rs +++ b/crates/els/server.rs @@ -25,9 +25,9 @@ use erg_compiler::module::{SharedCompilerResource, SharedModuleIndex}; use erg_compiler::ty::HasType; use lsp_types::request::{ - CodeActionRequest, CodeLensRequest, Completion, ExecuteCommand, GotoDefinition, HoverRequest, - InlayHintRequest, References, Rename, Request, ResolveCompletionItem, - SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, + CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, ExecuteCommand, + GotoDefinition, HoverRequest, InlayHintRequest, References, Rename, Request, + ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, }; use lsp_types::{ ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, @@ -328,7 +328,7 @@ impl Server { } else { let options = CodeActionProviderCapability::Options(CodeActionOptions { code_action_kinds: Some(vec![CodeActionKind::QUICKFIX, CodeActionKind::REFACTOR]), - resolve_provider: Some(false), + resolve_provider: Some(true), work_done_progress_options: WorkDoneProgressOptions::default(), }); Some(options) @@ -491,6 +491,9 @@ impl Server { CodeActionRequest::METHOD => { self.wrap::(id, msg, Self::handle_code_action) } + CodeActionResolveRequest::METHOD => { + self.wrap::(id, msg, Self::handle_code_action_resolve) + } SignatureHelpRequest::METHOD => { self.wrap::(id, msg, Self::handle_signature_help) } diff --git a/crates/erg_common/lib.rs b/crates/erg_common/lib.rs index 46560785..34bbd65d 100644 --- a/crates/erg_common/lib.rs +++ b/crates/erg_common/lib.rs @@ -189,3 +189,16 @@ pub fn trim_eliminate_top_indent(code: String) -> String { } 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 +}