From e01b62d8ffb564c7076be82b5b18214434afbad0 Mon Sep 17 00:00:00 2001 From: He1pa <18012015693@163.com> Date: Wed, 23 Aug 2023 14:36:53 +0800 Subject: [PATCH 1/2] feat: integrate kcl fmt tools to lsp. Suport formmat single file --- kclvm/tools/src/LSP/src/capabilities.rs | 1 + kclvm/tools/src/LSP/src/request.rs | 33 ++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/kclvm/tools/src/LSP/src/capabilities.rs b/kclvm/tools/src/LSP/src/capabilities.rs index 9938bcbed..dfd3c417b 100644 --- a/kclvm/tools/src/LSP/src/capabilities.rs +++ b/kclvm/tools/src/LSP/src/capabilities.rs @@ -36,6 +36,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti }) }), ), + document_formatting_provider: Some(OneOf::Left(true)), ..Default::default() } } diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index 69836f88d..7162e5d9d 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -1,9 +1,8 @@ -use std::{collections::HashMap, time::Instant}; - -use anyhow::Ok; use crossbeam_channel::Sender; -use lsp_types::{CodeAction, CodeActionKind, CodeActionOrCommand, TextEdit}; +use kclvm_tools::format::{format_source, FormatOptions}; +use lsp_types::{CodeAction, CodeActionKind, CodeActionOrCommand, Position, Range, TextEdit}; use ra_ap_vfs::VfsPath; +use std::{collections::HashMap, time::Instant}; use crate::{ completion::completion, @@ -47,6 +46,7 @@ impl LanguageServerState { .on::(handle_hover)? .on::(handle_document_symbol)? .on::(handle_code_action)? + .on::(handle_formatting)? .finish(); Ok(()) @@ -67,6 +67,31 @@ impl LanguageServerSnapshot { } } +pub(crate) fn handle_formatting( + _snap: LanguageServerSnapshot, + params: lsp_types::DocumentFormattingParams, + sender: Sender, +) -> anyhow::Result>> { + let file = file_path_from_url(¶ms.text_document.uri)?; + let src = std::fs::read_to_string(file.clone())?; + match format_source(&file, &src, &FormatOptions::default()) { + Ok((source, is_formatted)) => { + if is_formatted { + Ok(Some(vec![TextEdit { + range: Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX)), + new_text: source, + }])) + } else { + Ok(None) + } + } + Err(err) => { + log_message(format!("Format failed: {err}"), &sender)?; + Ok(None) + } + } +} + /// Called when a `GotoDefinition` request was received. pub(crate) fn handle_code_action( _snap: LanguageServerSnapshot, From 5fac5897736a2c64273ea054589dba1e7149a6ab Mon Sep 17 00:00:00 2001 From: He1pa <18012015693@163.com> Date: Wed, 23 Aug 2023 16:41:20 +0800 Subject: [PATCH 2/2] test: add lsp fmt uint test --- kclvm/tools/src/LSP/src/formatting.rs | 25 +++++++++ kclvm/tools/src/LSP/src/lib.rs | 1 + kclvm/tools/src/LSP/src/main.rs | 1 + kclvm/tools/src/LSP/src/request.rs | 20 ++------ kclvm/tools/src/LSP/src/tests.rs | 74 ++++++++++++++++++++++++++- 5 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 kclvm/tools/src/LSP/src/formatting.rs diff --git a/kclvm/tools/src/LSP/src/formatting.rs b/kclvm/tools/src/LSP/src/formatting.rs new file mode 100644 index 000000000..d991a0c2b --- /dev/null +++ b/kclvm/tools/src/LSP/src/formatting.rs @@ -0,0 +1,25 @@ +use kclvm_tools::format::{format_source, FormatOptions}; +use lsp_types::{Position, Range, TextEdit}; + +pub(crate) fn format_single_file( + file: String, + src: String, +) -> anyhow::Result>> { + let (source, is_formatted) = format_source( + &file, + &src, + &FormatOptions { + omit_errors: true, + ..Default::default() + }, + ) + .map_err(|err| anyhow::anyhow!("Formmatting failed: {}", err))?; + if is_formatted { + Ok(Some(vec![TextEdit { + range: Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX)), + new_text: source, + }])) + } else { + Ok(None) + } +} diff --git a/kclvm/tools/src/LSP/src/lib.rs b/kclvm/tools/src/LSP/src/lib.rs index 3af069b3c..75677bf7f 100644 --- a/kclvm/tools/src/LSP/src/lib.rs +++ b/kclvm/tools/src/LSP/src/lib.rs @@ -4,6 +4,7 @@ mod config; mod db; mod dispatcher; mod find_ref; +mod formatting; mod from_lsp; mod notification; mod state; diff --git a/kclvm/tools/src/LSP/src/main.rs b/kclvm/tools/src/LSP/src/main.rs index 5d092e12c..196c693ac 100644 --- a/kclvm/tools/src/LSP/src/main.rs +++ b/kclvm/tools/src/LSP/src/main.rs @@ -19,6 +19,7 @@ mod state; mod to_lsp; mod util; +mod formatting; #[cfg(test)] mod tests; diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index 7162e5d9d..e85d8add6 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -1,5 +1,5 @@ use crossbeam_channel::Sender; -use kclvm_tools::format::{format_source, FormatOptions}; + use lsp_types::{CodeAction, CodeActionKind, CodeActionOrCommand, Position, Range, TextEdit}; use ra_ap_vfs::VfsPath; use std::{collections::HashMap, time::Instant}; @@ -9,6 +9,7 @@ use crate::{ db::AnalysisDatabase, dispatcher::RequestDispatcher, document_symbol::document_symbol, + formatting::format_single_file, from_lsp::{self, file_path_from_url, kcl_pos}, goto_def::goto_definition, hover, quick_fix, @@ -74,22 +75,7 @@ pub(crate) fn handle_formatting( ) -> anyhow::Result>> { let file = file_path_from_url(¶ms.text_document.uri)?; let src = std::fs::read_to_string(file.clone())?; - match format_source(&file, &src, &FormatOptions::default()) { - Ok((source, is_formatted)) => { - if is_formatted { - Ok(Some(vec![TextEdit { - range: Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX)), - new_text: source, - }])) - } else { - Ok(None) - } - } - Err(err) => { - log_message(format!("Format failed: {err}"), &sender)?; - Ok(None) - } - } + format_single_file(file, src) } /// Called when a `GotoDefinition` request was received. diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index dc83c909a..e6185a737 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::env; use std::path::PathBuf; use std::process::Command; @@ -33,6 +32,7 @@ use parking_lot::RwLock; use crate::completion::KCLCompletionItem; use crate::document_symbol::document_symbol; +use crate::formatting::format_single_file; use crate::from_lsp::file_path_from_url; use crate::hover::hover; use crate::quick_fix::quick_fix; @@ -1241,3 +1241,75 @@ fn goto_import_external_file_test() { let res = goto_definition(&program, &pos, &prog_scope); assert!(res.is_some()); } + +#[test] +fn formmat_signle_file_test() { + const FILE_INPUT_SUFFIX: &str = ".input"; + const FILE_OUTPUT_SUFFIX: &str = ".golden"; + const TEST_CASES: &[&str; 17] = &[ + "assert", + "check", + "blankline", + "breakline", + "codelayout", + "collection_if", + "comment", + "comp_for", + // "empty", + "import", + "indent", + "inline_comment", + "lambda", + "quant", + "schema", + "string", + "type_alias", + "unary", + ]; + + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let test_file = path; + let test_dir = test_file + .parent() + .unwrap() + .join("format") + .join("test_data") + .join("format_data"); + for case in TEST_CASES { + let test_file = test_dir + .join(format!("{}{}", case, FILE_INPUT_SUFFIX)) + .to_str() + .unwrap() + .to_string(); + let test_src = std::fs::read_to_string(&test_file).unwrap(); + let got = format_single_file(test_file, test_src).unwrap().unwrap(); + let data_output = std::fs::read_to_string( + &test_dir + .join(format!("{}{}", case, FILE_OUTPUT_SUFFIX)) + .to_str() + .unwrap() + .to_string(), + ) + .unwrap(); + + #[cfg(target_os = "windows")] + let data_output = data_output.replace("\r\n", "\n"); + + let expect = vec![TextEdit { + range: Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX)), + new_text: data_output, + }]; + + assert_eq!(expect, got); + } + + // empty test case, without change after fmt + let test_file = test_dir + .join(format!("{}{}", "empty", FILE_INPUT_SUFFIX)) + .to_str() + .unwrap() + .to_string(); + let test_src = std::fs::read_to_string(&test_file).unwrap(); + let got = format_single_file(test_file, test_src).unwrap(); + assert_eq!(got, None) +}