From e48ac6b592744e422a9ec2b634a1416db4ff3d45 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 21 Oct 2023 12:00:58 +0200 Subject: [PATCH] Relocate and refactor quickfix code into its own module We're going to have more quickfixes, to it's best not to overload the 'server' module. Plus, there's a lot of boilerplate around the quickfixes so we might want to factor it out. --- crates/aiken-lsp/src/lib.rs | 1 + crates/aiken-lsp/src/quickfix.rs | 66 +++++++++++++++++++ crates/aiken-lsp/src/server.rs | 105 ++++++++----------------------- 3 files changed, 92 insertions(+), 80 deletions(-) create mode 100644 crates/aiken-lsp/src/quickfix.rs diff --git a/crates/aiken-lsp/src/lib.rs b/crates/aiken-lsp/src/lib.rs index a8f6fc95..b627081f 100644 --- a/crates/aiken-lsp/src/lib.rs +++ b/crates/aiken-lsp/src/lib.rs @@ -8,6 +8,7 @@ mod cast; mod edits; pub mod error; mod line_numbers; +mod quickfix; pub mod server; mod utils; diff --git a/crates/aiken-lsp/src/quickfix.rs b/crates/aiken-lsp/src/quickfix.rs new file mode 100644 index 00000000..3e5f57c4 --- /dev/null +++ b/crates/aiken-lsp/src/quickfix.rs @@ -0,0 +1,66 @@ +use crate::{edits, server::lsp_project::LspProject}; +use std::collections::HashMap; + +const UNKNOWN_VARIABLE: &str = "aiken::check::unknown::variable"; + +const UNKNOWN_MODULE: &str = "aiken::check::unknown::module"; + +/// Errors for which we can provide quickfixes +pub enum Quickfix { + UnknownVariable, +} + +fn match_code(diagnostic: &lsp_types::Diagnostic, expected: &str) -> bool { + diagnostic.code == Some(lsp_types::NumberOrString::String(expected.to_string())) +} + +pub fn assert(diagnostic: &lsp_types::Diagnostic) -> Option { + let is_error = diagnostic.severity == Some(lsp_types::DiagnosticSeverity::ERROR); + + if is_error && match_code(diagnostic, UNKNOWN_VARIABLE) { + return Some(Quickfix::UnknownVariable); + } + + if is_error && match_code(diagnostic, UNKNOWN_MODULE) { + todo!() + } + + None +} + +pub fn unknown_variable( + compiler: &LspProject, + text_document: &lsp_types::TextDocumentIdentifier, + diagnostic: &lsp_types::Diagnostic, +) -> Vec { + let mut actions = Vec::new(); + + if let Some(parsed_document) = edits::parse_document(text_document) { + if let Some(serde_json::Value::String(ref var_name)) = diagnostic.data { + for module in compiler.project.modules() { + let mut changes = HashMap::new(); + if module.ast.has_definition(var_name) { + if let Some((title, edit)) = parsed_document.import(&module, Some(var_name)) { + changes.insert(text_document.uri.clone(), vec![edit]); + actions.push(lsp_types::CodeAction { + title, + kind: Some(lsp_types::CodeActionKind::QUICKFIX), + diagnostics: Some(vec![diagnostic.clone()]), + is_preferred: Some(true), + disabled: None, + data: None, + command: None, + edit: Some(lsp_types::WorkspaceEdit { + changes: Some(changes), + document_changes: None, + change_annotations: None, + }), + }); + } + } + } + } + } + + actions +} diff --git a/crates/aiken-lsp/src/server.rs b/crates/aiken-lsp/src/server.rs index 62b8ef54..56f7f5b5 100644 --- a/crates/aiken-lsp/src/server.rs +++ b/crates/aiken-lsp/src/server.rs @@ -33,9 +33,9 @@ use miette::Diagnostic; use crate::{ cast::{cast_notification, cast_request}, - edits, error::Error as ServerError, line_numbers::LineNumbers, + quickfix::{self, Quickfix}, utils::{ path_to_uri, span_to_lsp_range, text_edit_replace, uri_to_module_name, COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN, @@ -44,11 +44,9 @@ use crate::{ use self::lsp_project::LspProject; -mod lsp_project; +pub mod lsp_project; pub mod telemetry; -const UNKNOWN_VARIABLE: &str = "aiken::check::unknown::variable"; - #[allow(dead_code)] pub struct Server { // Project root directory @@ -331,45 +329,32 @@ impl Server { } CodeActionRequest::METHOD => { - let params = - cast_request::(request).expect("cast code action request"); + let mut actions = Vec::new(); - // Identify any diagnostic which refers to an unknown variable. In which case, we - // might want to provide some imports suggestion. - let unknown_variables = params - .context - .diagnostics - .into_iter() - .filter(|diagnostic| { - let is_error = - diagnostic.severity == Some(lsp_types::DiagnosticSeverity::ERROR); - let is_unknown_variable = diagnostic.code - == Some(lsp_types::NumberOrString::String( - UNKNOWN_VARIABLE.to_string(), - )); + if let Some(ref compiler) = self.compiler { + let params = cast_request::(request) + .expect("cast code action request"); - is_error && is_unknown_variable - }) - .collect::>(); - - match unknown_variables.first() { - Some(diagnostic) => Ok(lsp_server::Response { - id, - error: None, - result: Some(serde_json::to_value( - self.quickfix_unknown_variable( - params.text_document, - diagnostic.to_owned(), - ) - .unwrap_or(vec![]), - )?), - }), - None => Ok(lsp_server::Response { - id, - error: None, - result: Some(serde_json::Value::Null), - }), + for diagnostic in params.context.diagnostics.iter() { + match quickfix::assert(diagnostic) { + Some(Quickfix::UnknownVariable) => { + let quickfixes = quickfix::unknown_variable( + compiler, + ¶ms.text_document, + diagnostic, + ); + actions.extend(quickfixes); + } + None => (), + } + } } + + Ok(lsp_server::Response { + id, + error: None, + result: Some(serde_json::to_value(actions)?), + }) } unsupported => Err(ServerError::UnsupportedLspRequest { @@ -403,46 +388,6 @@ impl Server { } } - fn quickfix_unknown_variable( - &self, - text_document: lsp_types::TextDocumentIdentifier, - diagnostic: lsp_types::Diagnostic, - ) -> Option> { - let compiler = self.compiler.as_ref()?; - - let parsed_document = edits::parse_document(&text_document)?; - - let mut actions = Vec::new(); - - if let Some(serde_json::Value::String(ref var_name)) = diagnostic.data { - for module in compiler.project.modules() { - let mut changes = HashMap::new(); - - if module.ast.has_definition(var_name) { - if let Some((title, edit)) = parsed_document.import(&module, Some(var_name)) { - changes.insert(text_document.uri.clone(), vec![edit]); - actions.push(lsp_types::CodeAction { - title, - kind: Some(lsp_types::CodeActionKind::QUICKFIX), - diagnostics: Some(vec![diagnostic.clone()]), - is_preferred: Some(true), - disabled: None, - data: None, - command: None, - edit: Some(lsp_types::WorkspaceEdit { - changes: Some(changes), - document_changes: None, - change_annotations: None, - }), - }); - } - } - } - } - - Some(actions) - } - fn completion_for_import(&self) -> Option> { let compiler = self.compiler.as_ref()?;