diff --git a/kls/src/formatter/defsrc_layout/get_layout.rs b/kls/src/formatter/defsrc_layout/get_layout.rs new file mode 100644 index 0000000..ee01c73 --- /dev/null +++ b/kls/src/formatter/defsrc_layout/get_layout.rs @@ -0,0 +1,108 @@ +use super::{parse_into_ext_tree_and_root_span, ExtParseTree}; +use crate::{path_to_url, WorkspaceOptions}; +use anyhow::{anyhow, Ok}; +use lsp_types::{TextDocumentItem, Url}; +use std::{collections::BTreeMap, iter, path::PathBuf, str::FromStr}; + +pub fn get_defsrc_layout( + workspace_options: &WorkspaceOptions, + documents: &BTreeMap, + tab_size: u32, + file_uri: &Url, // of current file + tree: &ExtParseTree, // of current file +) -> anyhow::Result>>> { + match workspace_options { + WorkspaceOptions::Single => { + if tree.includes()?.is_empty() { + tree.defsrc_layout(tab_size) + } else { + Ok(None) + } + } + WorkspaceOptions::Workspace { + main_config_file, + root, + } => { + let main_config_file_path = PathBuf::from_str(main_config_file) + .map_err(|e| anyhow!("main_config_file is an invalid path: {}", e))?; + let main_config_file_url = path_to_url(&main_config_file_path, root) + .map_err(|e| anyhow!("failed to convert main_config_file_path to url: {}", e))?; + + let main_tree: ExtParseTree = if main_config_file_url == *file_uri { + // currently opened file is the main file + tree.clone() // TODO: prevent clone + } else { + // currently opened file is non-main file, and probably an included file. + let text = &documents + .get(file_uri) + .map(|doc| &doc.text) + .ok_or_else(|| { + anyhow!( + "included file is not present in the workspace: {}", + file_uri.to_string() + ) + })?; + + parse_into_ext_tree_and_root_span(text) + .map(|x| x.0) + .map_err(|e| anyhow!("parse_into_ext_tree_and_root_span failed: {}", e.msg))? + }; + + let includes = main_tree + .includes() + .map_err(|e| anyhow!("workspace [main = {main_config_file_url}]: {e}"))? + .iter() + .map(|path| path_to_url(path, root)) + .collect::>>() + .map_err(|e| anyhow!("path_to_url: {e}"))?; + + // make sure that all includes collectively contain only 1 defsrc + let mut defsrc_layout = None; + for file_url in includes.iter().chain(iter::once(&main_config_file_url)) { + let text = &documents + .get(file_url) + .expect("document should be cached") + .text; + + let tree = parse_into_ext_tree_and_root_span(text) + .map(|x| x.0) + .map_err(|e| { + anyhow!( + "parse_into_ext_tree_and_root_span failed for file '{file_uri}': {}", + e.msg + ) + })?; + + if let Some(layout) = tree + .defsrc_layout(tab_size) + .map_err(|e| anyhow!("tree.defsrc_layout for '{file_url}' failed: {e}"))? + { + defsrc_layout = Some(layout); + } + } + Ok(defsrc_layout) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn workspace_single_basic_main_config() { + let src = "(defsrc 1 2) (deflayer base 3 4)"; + let layout = get_defsrc_layout( + &WorkspaceOptions::Single, + &BTreeMap::new(), + 4, + &Url::from_str("file:///main.kbd").unwrap(), + &parse_into_ext_tree_and_root_span(src).unwrap().0, + ) + .unwrap() + .ok_or("should be some") + .unwrap(); + + assert_eq!(layout, vec![vec![3], vec![1]]); + } +} diff --git a/kls/src/formatter/use_defsrc_layout_on_deflayers.rs b/kls/src/formatter/defsrc_layout/mod.rs similarity index 93% rename from kls/src/formatter/use_defsrc_layout_on_deflayers.rs rename to kls/src/formatter/defsrc_layout/mod.rs index 0c0f5c4..0cfa30d 100644 --- a/kls/src/formatter/use_defsrc_layout_on_deflayers.rs +++ b/kls/src/formatter/defsrc_layout/mod.rs @@ -1,7 +1,11 @@ use super::ext_tree::*; use crate::log; +use anyhow::{anyhow, Ok}; use unicode_segmentation::*; +pub mod get_layout; +pub use get_layout::*; + impl ExtParseTree { // TODO: maybe don't format if an atom in defsrc/deflayer is too large. // TODO: respect `tab_size`. @@ -78,9 +82,11 @@ impl ExtParseTree { } /// Obtains defsrc layout from a given [`ExtParseTree`]. - /// Returns None if found 0 defsrc blocks or found more than 1 defsrc. - /// It doesn't search includes. - pub fn defsrc_layout<'a>(&'a self, tab_size: u32) -> Option>> { + /// * It doesn't search includes. + /// * Returns `Err` if found more than 1 defsrc, or `defsrc` contains a list. + /// * Returns `Ok(None)` if found 0 defsrc blocks. + /// * Returns `Ok(Some)` otherwise. + pub fn defsrc_layout<'a>(&'a self, tab_size: u32) -> anyhow::Result>>> { let mut defsrc: Option<&'a NodeList> = None; for top_level_item in self.0.iter() { @@ -102,11 +108,7 @@ impl ExtParseTree { if let "defsrc" = first_atom.as_str() { match defsrc { Some(_) => { - log!( - "Formatting `deflayer`s failed: config file \ - contains multiple `defsrc` definitions." - ); - return None; + return Err(anyhow!("multiple `defsrc` definitions in a single file")); } None => { defsrc = Some(top_level_list); @@ -115,15 +117,12 @@ impl ExtParseTree { } } - let defsrc = if let Some(x) = defsrc { - x - } else { - log!( - "Formatting `deflayer`s failed: `defsrc` not found in this file. \ - NOTE: includes (or the main file, if this file is non-main) haven't \ - been checked, because it's not implemented yet." - ); - return None; + let defsrc = match defsrc { + Some(x) => x, + None => { + // defsrc not found in this file, but it may be in another. + return Ok(None); + } }; // Get number of atoms from `defsrc` now to prevent additional allocations @@ -136,11 +135,7 @@ impl ExtParseTree { // Read the layout from `defsrc` for (i, defsrc_item) in defsrc.iter().skip(1).enumerate() { if let Expr::List(_) = defsrc_item.expr { - log!( - "Formatting `deflayer`s failed: there shouldn't \ - be any lists in `defsrc`." - ); - return None; + return Err(anyhow!("found a list in `defsrc`")); } let defsrc_item_as_str = defsrc_item.expr.to_string(); @@ -186,7 +181,7 @@ impl ExtParseTree { } // Layout no longer needs to be mutable. - Some(layout) + Ok(Some(layout)) } } @@ -306,7 +301,7 @@ mod tests { fn formats_correctly(input: &str, expected_output: &str) { let mut tree = parse_into_ext_tree(input).expect("parses"); let tab_size = 4; - if let Some(layout) = tree.defsrc_layout(tab_size) { + if let Some(layout) = tree.defsrc_layout(tab_size).expect("no err") { tree.use_defsrc_layout_on_deflayers(&layout, tab_size, true); }; assert_eq!( diff --git a/kls/src/formatter/ext_tree.rs b/kls/src/formatter/ext_tree.rs index a09d7ac..3442ca4 100644 --- a/kls/src/formatter/ext_tree.rs +++ b/kls/src/formatter/ext_tree.rs @@ -66,10 +66,6 @@ impl ExtParseTree { let mut result = vec![]; for top_level_block in self.0.iter() { if let Expr::List(NodeList::NonEmptyList(xs)) = &top_level_block.expr { - if xs.len() != 2 { - return Err(anyhow!("an include block contains more than 2 items")); - } - match &xs[0].expr { Expr::Atom(x) => match x.as_str() { "include" => {} @@ -78,6 +74,17 @@ impl ExtParseTree { _ => continue, }; + if xs.len() != 2 { + return Err(anyhow!( + "an include block items: 2 != {}; block: \n{}", + xs.len(), + xs.iter().fold(String::new(), |mut acc, x| { + acc.push_str(&x.to_string()); + acc + }) + )); + } + if let Expr::Atom(x) = &xs[1].expr { result.push(PathBuf::from_str(x.as_str().trim_matches('\"'))?) } diff --git a/kls/src/formatter/mod.rs b/kls/src/formatter/mod.rs index fb63b68..c148817 100644 --- a/kls/src/formatter/mod.rs +++ b/kls/src/formatter/mod.rs @@ -1,8 +1,10 @@ pub mod ext_tree; use ext_tree::*; +use crate::log; + +pub mod defsrc_layout; mod remove_excessive_newlines; -mod use_defsrc_layout_on_deflayers; pub struct Formatter { // Additional options @@ -32,6 +34,7 @@ impl Formatter { } if self.options.use_defsrc_layout_on_deflayers { + log!("formatting defsrc - layout: {:?}", defsrc_layout); if let Some(layout) = defsrc_layout { tree.use_defsrc_layout_on_deflayers( layout, diff --git a/kls/src/lib.rs b/kls/src/lib.rs index 9447cc3..18b8c16 100644 --- a/kls/src/lib.rs +++ b/kls/src/lib.rs @@ -1,7 +1,4 @@ -use crate::{ - formatter::ext_tree::ExtParseTree, - helpers::{lsp_range_from_span, HashSet}, -}; +use crate::helpers::{lsp_range_from_span, HashSet}; use anyhow::{anyhow, bail}; use formatter::Formatter; use kanata_parser::cfg::{FileContentProvider, ParseError}; @@ -21,7 +18,6 @@ use serde_wasm_bindgen::{from_value, to_value}; use std::{ collections::BTreeMap, fmt::Display, - iter::once, path::{self, Path, PathBuf}, str::{FromStr, Split}, }; @@ -431,116 +427,19 @@ impl KanataLanguageServer { let range = lsp_range_from_span(&root_span.into()); - let start = helpers::now(); - - let defsrc_layout = match &self.workspace_options { - WorkspaceOptions::Single => { - let includes = match tree.includes() { - Ok(x) => x, - Err(e) => { - log!("{}", e); - return None; - } - }; - if includes.is_empty() { - tree.defsrc_layout(params.options.tab_size) - } else { - None - } - } - WorkspaceOptions::Workspace { - main_config_file, - root, - } => { - let main_config_file_path = match PathBuf::from_str(main_config_file) { - Ok(x) => x, - Err(_) => { - log!("main_config_file is an invalid path"); - return None; - } - }; - let main_config_file_url = match path_to_url(&main_config_file_path, root) { - Ok(x) => x, - Err(_) => { - log!("failed to convert main_config_file_path to url"); - return None; - } - }; - let main_tree: ExtParseTree = if main_config_file_url == params.text_document.uri { - // currently opened file is the main file - tree.clone() // TODO: prevent clone - } else { - // currently opened file is non-main file, and probably an included file. - let text = &match self.documents.get(¶ms.text_document.uri) { - Some(doc) => &doc.text, - None => { - log!("included file is not present in the workspace"); - return Some(vec![]); - } - }; - match formatter::ext_tree::parse_into_ext_tree_and_root_span(text) { - Ok(x) => x.0, - Err(_) => { - log!("main file is not found in the workspace"); - return None; - } - } - }; - - let includes = match main_tree.includes() { - Ok(x) => x, - Err(e) => { - log!("{}", e); - return None; - } - }; - let includes = includes - .iter() - .map(|path| path_to_url(path, root)) - .collect::>>(); - let includes = match includes { - Ok(x) => x, - Err(e) => { - log!("include path_to_url: {}", e); - return None; - } - }; - - // make sure that all includes collectively contain only 1 defsrc - let mut defsrc_layout = None; - for file_url in includes.iter().chain(once(&main_config_file_url)) { - let text = &self - .documents - .get(file_url) - .expect("document should be cached") - .text; - - let (tree, _) = - match formatter::ext_tree::parse_into_ext_tree_and_root_span(text) { - Ok(x) => x, - Err(_e) => { - log!("failed to parse current file into tree"); - return None; - } - }; - if let Some(layout) = tree.defsrc_layout(params.options.tab_size) { - if defsrc_layout.is_none() { - defsrc_layout = Some(layout); - } else { - log!("multiple defsrc definitions across includes"); - return None; - } - } - } - defsrc_layout - } - }; + let defsrc_layout = formatter::defsrc_layout::get_defsrc_layout( + &self.workspace_options, + &self.documents, + params.options.tab_size, + ¶ms.text_document.uri, + &tree, + ) + .map_err(|e| log!("format: get_defsrc_layout: {}", e)) + .ok()?; self.formatter .format(&mut tree, ¶ms.options, defsrc_layout.as_deref()); - log!("format in {:.3?}", helpers::now().duration_since(start)); - Some(vec![TextEdit { range, new_text: tree.to_string(),