From 113a2d2c4dbf376ddc217bb87e7b1d5e0b46124e Mon Sep 17 00:00:00 2001 From: Auca Coyan Date: Fri, 9 Jun 2023 17:27:06 -0300 Subject: [PATCH] make `nufmt` keep the comments (#18) * :construction: working on keeping comments * :construction: splitted the comment from the code I need to fix having multiple comments, the split works with token.span.start that may by larger than the content length. Afterwards, I need to deal with returning the code and the comment, indicating which is which and how to re-assemble once it is formatted * :sparkles: finish comments in `nufmt` --------- Co-authored-by: Auca Maillot --- src/formatting.rs | 80 ++++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 8 ++--- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index 5923bb4..ea61295 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,31 +1,67 @@ use crate::config::Config; use log::trace; use nu_parser::{flatten_block, parse, FlatShape}; -use nu_protocol::engine::{self, StateWorkingSet}; +use nu_protocol::{ + ast::Block, + engine::{self, StateWorkingSet}, + Span, +}; -// Format an entire crate (or subset of the module tree) +// Format an array of bytes +// +// Reading the file gives you a list of bytes pub fn format_inner(contents: &[u8], _config: &Config) -> Vec { - // nice place to measure parsing and formatting time + // nice place to measure formatting time // let mut timer = Timer::start(); - // parsing starts + // parsing starts let engine_state = engine::EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); let parsed_block = parse(&mut working_set, None, contents, false); - trace!("parsed block:\n{:?}\n", &parsed_block); + trace!("parsed block:\n{:?}", &parsed_block); + + // check if the block has at least 1 pipeline + if !block_has_pipilnes(&parsed_block) { + trace!("block has no pipelines!"); + println!("File has no code to format."); + return contents.to_vec(); + } // flat is a list of (Span , Flatshape) // // Span is the piece of code. You can stringfy the contents. // Flatshape is an enum of the type of token read by the AST. let flat = flatten_block(&working_set, &parsed_block); - trace!("flattened block:\n{:#?}\n", &flat); + trace!("flattened block:\n{:?}", &flat); // timer = timer.done_parsing() // formatting starts let mut out: Vec = vec![]; - for (span, shape) in flat { + let mut start = 0; + let end_of_file = contents.len(); + + for (span, shape) in flat.clone().into_iter() { + // check if span skipped some bytes before the current span + if span.start > start { + trace!( + "Span didn't started on the beginning! span {0}, start: {1}", + span.start, + start + ); + let skipped_contents = &contents[start..span.start]; + let printable = String::from_utf8_lossy(skipped_contents).to_string(); + trace!("contents: {:?}", printable); + if skipped_contents.contains(&b'#') { + trace!("This have a comment. Writing."); + out.extend(trim_ascii_whitespace(skipped_contents)); + out.push(b'\n'); + } else { + trace!("The contents doesn't have a '#'. Skipping.") + } + } + + // get the span contents and format it let mut c_bites = working_set.get_span_contents(span); let content = String::from_utf8_lossy(c_bites).to_string(); trace!("shape is {shape}"); @@ -73,6 +109,28 @@ pub fn format_inner(contents: &[u8], _config: &Config) -> Vec { _ => out.extend(c_bites), } + + // check if span skipped some bytes between the final spann and the end of file + if is_last_span(span, &flat) && span.end < end_of_file { + trace!( + "The last span doesn't end the file! span: {0}, end: {1}", + span.end, + end_of_file + ); + let remaining_contents = &contents[span.end..end_of_file]; + let printable = String::from_utf8_lossy(remaining_contents).to_string(); + trace!("contents: {:?}", printable); + if remaining_contents.contains(&b'#') { + trace!("This have a comment. Writing."); + out.push(b'\n'); + out.extend(trim_ascii_whitespace(remaining_contents)); + } else { + trace!("The contents doesn't have a '#'. Skipping.") + } + } + + // cleanup + start = span.end + 1; } // just before writing, append a new line to the file. out = insert_newline(out); @@ -97,3 +155,11 @@ pub fn trim_ascii_whitespace(x: &[u8]) -> &[u8] { let to = x.iter().rposition(|x| !x.is_ascii_whitespace()).unwrap(); &x[from..=to] } + +fn block_has_pipilnes(block: &Block) -> bool { + !block.pipelines.is_empty() +} + +fn is_last_span(span: Span, flat: &[(Span, FlatShape)]) -> bool { + span == flat.last().unwrap().0 +} diff --git a/src/lib.rs b/src/lib.rs index bc56d10..081ca3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,6 @@ pub fn format_single_file(file: &PathBuf, config: &Config) { // write down the file to path let mut writer = File::create(file).unwrap(); let file_bites = formatted_bytes.as_slice(); - trace!("writing {:?}", formatted_bytes); writer .write_all(file_bites) .expect("something went wrong writing"); @@ -79,10 +78,11 @@ mod test { } #[test] - #[ignore = "comments aren't a part of Spans,"] fn ignore_comments() { - let nu = String::from("# this is a comment"); - let expected = String::from("# this is a comment"); + let nu = String::from( + "# beginning of script comment\n\n let one = 1\ndef my-func [\n param1:int # inline comment\n]{ print(param1) \n}\nmyfunc(one)\n\n\n\n\n\n# final comment\n\n\n"); + let expected = String::from( + "# beginning of script comment\nlet one = 1\ndef my-func [\n param1:int # inline comment\n]{ print(param1) \n}\nmyfunc(one) \n# final comment\n"); assert_eq!(expected, format_string(&nu, &Config::default())); }