diff --git a/.gitignore b/.gitignore index 1c7ea24a83..239f4d05dc 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ editor/benches/resources/25000000_lines.roc editor/benches/resources/50000_lines.roc editor/benches/resources/500_lines.roc +# file editor creates when no arg is passed +new-roc-project + # rust cache (sccache folder) sccache_dir diff --git a/Cargo.lock b/Cargo.lock index ec0031bb77..5750a0a982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1215,6 +1215,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -3567,6 +3573,7 @@ dependencies = [ "confy", "copypasta", "env_logger 0.8.4", + "fs_extra", "futures", "glyph_brush", "im 15.0.0", @@ -3584,9 +3591,11 @@ dependencies = [ "quickcheck 1.0.3", "quickcheck_macros 1.0.0", "rand 0.8.4", + "roc_builtins", "roc_can", "roc_collections", "roc_fmt", + "roc_load", "roc_module", "roc_parse", "roc_problem", @@ -3598,6 +3607,8 @@ dependencies = [ "ropey", "serde", "snafu", + "tempfile", + "uuid", "ven_graph", "wgpu", "wgpu_glyph", @@ -4758,6 +4769,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.3", +] + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Earthfile b/Earthfile index fd77785c8a..164f50af88 100644 --- a/Earthfile +++ b/Earthfile @@ -65,7 +65,7 @@ check-rustfmt: RUN cargo fmt --all -- --check check-typos: - RUN cargo install --version 1.0.11 typos-cli + RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ RUN typos @@ -97,7 +97,6 @@ test-all: BUILD +test-zig BUILD +check-rustfmt BUILD +check-clippy - BUILD +check-typos BUILD +test-rust BUILD +verify-no-git-changes diff --git a/cli/src/main.rs b/cli/src/main.rs index 15a1de4b48..a2e91147b1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -34,7 +34,7 @@ fn main() -> io::Result<()> { } None => { - launch_editor(&[])?; + launch_editor(None)?; Ok(0) } @@ -91,16 +91,13 @@ If you're building the compiler from source you'll want to do `cargo run [FILE]` .subcommand_matches(CMD_EDIT) .unwrap() .values_of_os(DIRECTORY_OR_FILES) + .map(|mut values| values.next()) { - None => { - launch_editor(&[])?; + Some(Some(os_str)) => { + launch_editor(Some(Path::new(os_str)))?; } - Some(values) => { - let paths = values - .map(|os_str| Path::new(os_str)) - .collect::>(); - - launch_editor(&paths)?; + _ => { + launch_editor(None)?; } } @@ -187,8 +184,8 @@ fn roc_files_recursive>( } #[cfg(feature = "editor")] -fn launch_editor(filepaths: &[&Path]) -> io::Result<()> { - roc_editor::launch(filepaths) +fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> { + roc_editor::launch(project_dir_path) } #[cfg(not(feature = "editor"))] diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 0a6fafe939..66481d3db8 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1967,7 +1967,7 @@ fn update<'a>( ); } - if module_id == state.root_id && state.goal_phase == Phase::SolveTypes { + if is_host_exposed && state.goal_phase == Phase::SolveTypes { debug_assert!(work.is_empty()); debug_assert!(state.dependencies.solved_all()); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 6e825a9953..57d747d246 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -551,7 +551,53 @@ impl IdentIds { } } - /// Generates a unique, new name that's just a stringified integer + // necessary when the name of a value is changed in the editor + pub fn update_key( + &mut self, + old_ident_name: &str, + new_ident_name: &str, + ) -> Result { + let old_ident: Ident = old_ident_name.into(); + + let ident_id_ref_opt = self.by_ident.get(&old_ident); + + match ident_id_ref_opt { + Some(ident_id_ref) => { + let ident_id = *ident_id_ref; + + self.by_ident.remove(&old_ident); + self.by_ident.insert(new_ident_name.into(), ident_id); + + let by_id = &mut self.by_id; + let key_index_opt = by_id.iter().position(|x| *x == old_ident); + + if let Some(key_index) = key_index_opt { + if let Some(vec_elt) = by_id.get_mut(key_index) { + *vec_elt = new_ident_name.into(); + } else { + // we get the index from by_id + unreachable!() + } + + Ok(ident_id) + } else { + Err( + format!( + "Tried to find position of key {:?} in IdentIds.by_id but I could not find the key. IdentIds.by_id: {:?}", + old_ident_name, + self.by_id + ) + ) + } + } + None => Err(format!( + "Tried to update key in IdentIds ({:?}) but I could not find the key ({}).", + self.by_ident, old_ident_name + )), + } + } + + /// Generates a unique, new name that's just a strigified integer /// (e.g. "1" or "5"), using an internal counter. Since valid Roc variable /// names cannot begin with a number, this has no chance of colliding /// with actual user-defined variables. diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 460294c9b3..72298d6209 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -32,7 +32,7 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { #[inline(always)] pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located>>, SyntaxError<'a>> { - // force that we pare until the end of the input + // force that we parse until the end of the input let min_indent = 0; skip_second!( specialize( diff --git a/compiler/parse/src/test_helpers.rs b/compiler/parse/src/test_helpers.rs index 8f7682dc54..148b43fd54 100644 --- a/compiler/parse/src/test_helpers.rs +++ b/compiler/parse/src/test_helpers.rs @@ -1,6 +1,9 @@ use crate::ast; +use crate::module::module_defs; // use crate::module::module_defs; +use crate::parser::Parser; use crate::parser::{State, SyntaxError}; +use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use roc_region::all::Located; @@ -23,3 +26,15 @@ pub fn parse_loc_with<'a>( Err(fail) => Err(SyntaxError::Expr(fail)), } } + +pub fn parse_defs_with<'a>( + arena: &'a Bump, + input: &'a str, +) -> Result>>, SyntaxError<'a>> { + let state = State::new(input.trim().as_bytes()); + + match module_defs().parse(arena, state) { + Ok(tuple) => Ok(tuple.1), + Err(tuple) => Err(tuple.1), + } +} diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 5c4f71525f..f9207b2e38 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -9,6 +9,8 @@ exclude = ["src/shaders/*.spv"] [dependencies] roc_collections = { path = "../compiler/collections" } +roc_load = { path = "../compiler/load" } +roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } @@ -49,6 +51,9 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [ ], default-features = false } serde = { version = "1.0.123", features = ["derive"] } nonempty = "0.6.0" +tempfile = "3.2.0" +uuid = { version = "0.8", features = ["v4"] } +fs_extra = "1.2.0" [dependencies.bytemuck] version = "1.4" diff --git a/editor/src/editor/code_lines.rs b/editor/src/editor/code_lines.rs index 63f7b52472..4298c877f4 100644 --- a/editor/src/editor/code_lines.rs +++ b/editor/src/editor/code_lines.rs @@ -1,10 +1,10 @@ use crate::ui::text::lines::Lines; use crate::ui::text::selection::Selection; -use crate::ui::ui_error::UIResult; +use crate::ui::text::text_pos::TextPos; +use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::slice_get; use crate::ui::util::slice_get_mut; -use bumpalo::collections::String as BumpString; -use bumpalo::Bump; +use std::cmp::Ordering; use std::fmt; #[derive(Debug)] @@ -14,31 +14,102 @@ pub struct CodeLines { } impl CodeLines { - pub fn from_str(code_str: &str) -> CodeLines { - CodeLines { - lines: code_str - .split_inclusive('\n') - .map(|s| s.to_owned()) - .collect(), - nr_of_chars: code_str.len(), - } - } - pub fn insert_between_line( &mut self, line_nr: usize, index: usize, new_str: &str, ) -> UIResult<()> { - let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + let nr_of_lines = self.lines.len(); - line_ref.insert_str(index, new_str); + if line_nr < nr_of_lines { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.insert_str(index, new_str); + } else if line_nr >= self.lines.len() { + for _ in 0..((line_nr - nr_of_lines) + 1) { + self.push_empty_line(); + } + + self.insert_between_line(line_nr, index, new_str)?; + } else { + LineInsertionFailed { + line_nr, + nr_of_lines, + } + .fail()?; + } self.nr_of_chars += new_str.len(); Ok(()) } + pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> { + if line_nr <= self.lines.len() { + self.lines.insert(line_nr, String::new()); + + Ok(()) + } else { + OutOfBounds { + index: line_nr, + collection_name: "code_lines.lines".to_owned(), + len: self.lines.len(), + } + .fail() + } + } + + pub fn push_empty_line(&mut self) { + self.lines.push(String::new()) + } + + pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> { + // clippy prefers this over if-else + match line_nr.cmp(&self.lines.len()) { + Ordering::Less => { + self.insert_empty_line(line_nr + 1)?; + + let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr + + if col_nr < line_ref.len() { + let next_line_str: String = line_ref.drain(col_nr..).collect(); + + let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line + + *next_line_ref = next_line_str; + } + + Ok(()) + } + Ordering::Equal => self.insert_empty_line(line_nr + 1), + Ordering::Greater => OutOfBounds { + index: line_nr, + collection_name: "code_lines.lines".to_owned(), + len: self.lines.len(), + } + .fail(), + } + } + + pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + *line_ref = String::new(); + + Ok(()) + } + + pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> { + let line_len = self.line_len(line_nr)?; + + self.lines.remove(line_nr); + + self.nr_of_chars -= line_len; + + Ok(()) + } + pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { let line_ref = slice_get_mut(line_nr, &mut self.lines)?; @@ -49,6 +120,18 @@ impl CodeLines { Ok(()) } + pub fn del_range_at_line( + &mut self, + line_nr: usize, + col_range: std::ops::Range, + ) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.drain(col_range); + + Ok(()) + } + pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> { if selection.is_on_same_line() { let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?; @@ -60,17 +143,36 @@ impl CodeLines { Ok(()) } + + // last column of last line + pub fn end_txt_pos(&self) -> TextPos { + let last_line_nr = self.nr_of_lines() - 1; + + TextPos { + line: last_line_nr, + column: self.line_len(last_line_nr).unwrap(), // safe because we just calculated last_line + } + } +} + +impl Default for CodeLines { + fn default() -> Self { + CodeLines { + lines: Vec::new(), + nr_of_chars: 0, + } + } } impl Lines for CodeLines { - fn get_line(&self, line_nr: usize) -> UIResult<&str> { + fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> { let line_string = slice_get(line_nr, &self.lines)?; Ok(line_string) } fn line_len(&self, line_nr: usize) -> UIResult { - self.get_line(line_nr).map(|line| line.len()) + self.get_line_ref(line_nr).map(|line| line.len()) } fn nr_of_lines(&self) -> usize { @@ -81,14 +183,8 @@ impl Lines for CodeLines { self.nr_of_chars } - fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { - let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena); - - for line in &self.lines { - lines.push_str(line); - } - - lines + fn all_lines_as_string(&self) -> String { + self.lines.join("\n") } fn is_last_line(&self, line_nr: usize) -> bool { @@ -96,7 +192,7 @@ impl Lines for CodeLines { } fn last_char(&self, line_nr: usize) -> UIResult> { - Ok(self.get_line(line_nr)?.chars().last()) + Ok(self.get_line_ref(line_nr)?.chars().last()) } } @@ -105,14 +201,16 @@ impl fmt::Display for CodeLines { for row in &self.lines { let row_str = row .chars() - .map(|code_char| format!("'{}'", code_char)) + .map(|code_char| format!("{}", code_char)) .collect::>() - .join(", "); + .join(" "); - write!(f, "\n{}", row_str)?; + let escaped_row_str = row_str.replace("\n", "\\n"); + + write!(f, "\n{}", escaped_row_str)?; } - write!(f, " (code_lines)")?; + writeln!(f, " (code_lines, {:?} lines)", self.lines.len())?; Ok(()) } diff --git a/editor/src/editor/config.rs b/editor/src/editor/config.rs index aa44ca1b07..432bba0f41 100644 --- a/editor/src/editor/config.rs +++ b/editor/src/editor/config.rs @@ -5,6 +5,7 @@ use crate::editor::theme::EdTheme; #[derive(Serialize, Deserialize)] pub struct Config { pub code_font_size: f32, + pub debug_font_size: f32, pub ed_theme: EdTheme, } @@ -12,6 +13,7 @@ impl Default for Config { fn default() -> Self { Self { code_font_size: 30.0, + debug_font_size: 20.0, ed_theme: EdTheme::default(), } } diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index 2cbd9f35c7..82b53f6bce 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,3 +1,4 @@ +use crate::lang::parse::ASTNodeId; use crate::ui::ui_error::UIResult; use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; use colored::*; @@ -11,6 +12,24 @@ use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; #[derive(Debug, Snafu)] #[snafu(visibility(pub))] pub enum EdError { + #[snafu(display( + "ASTNodeIdWithoutDefId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(DefId)` .", + ast_node_id + ))] + ASTNodeIdWithoutDefId { + ast_node_id: ASTNodeId, + backtrace: Backtrace, + }, + + #[snafu(display( + "ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .", + ast_node_id + ))] + ASTNodeIdWithoutExprId { + ast_node_id: ASTNodeId, + backtrace: Backtrace, + }, + #[snafu(display( "CaretNotFound: No carets were found in the expected node with id {}", node_id @@ -43,6 +62,17 @@ pub enum EdError { backtrace: Backtrace, }, + #[snafu(display( + "EmptyCodeString: I need to have a code string (code_str) that contains either an app, interface or Package-Config header. The code string was empty.", + ))] + EmptyCodeString { backtrace: Backtrace }, + + #[snafu(display("FailedToUpdateIdentIdName: {}", err_str))] + FailedToUpdateIdentIdName { + err_str: String, + backtrace: Backtrace, + }, + #[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))] GetContentOnNestedNode { backtrace: Backtrace }, @@ -99,6 +129,12 @@ pub enum EdError { backtrace: Backtrace, }, + #[snafu(display("NoDefMarkNodeBeforeLineNr: I could not find a MarkupNode whose root parent points to a DefId located before the given line number: {}.", line_nr))] + NoDefMarkNodeBeforeLineNr { + line_nr: usize, + backtrace: Backtrace, + }, + #[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))] NodeWithoutAttributes { backtrace: Backtrace }, @@ -131,6 +167,17 @@ pub enum EdError { backtrace: Backtrace, }, + #[snafu(display( + "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", + required_pattern2, + encountered_pattern2, + ))] + UnexpectedPattern2Variant { + required_pattern2: String, + encountered_pattern2: String, + backtrace: Backtrace, + }, + #[snafu(display( "UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.", descriptive_vec_name @@ -154,7 +201,10 @@ pub enum EdError { }, #[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))] - ParseError { syntax_err: String }, + SrcParseError { + syntax_err: String, + backtrace: Backtrace, + }, #[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))] RecordWithoutFields { backtrace: Backtrace }, @@ -176,14 +226,6 @@ pub fn print_err(err: &EdError) { } } -pub fn print_ui_err(err: &UIError) { - eprintln!("{}", format!("{}", err).truecolor(255, 0, 0)); - - if let Some(backtrace) = ErrorCompat::backtrace(err) { - eprintln!("{}", color_backtrace(backtrace)); - } -} - fn color_backtrace(backtrace: &snafu::Backtrace) -> String { let backtrace_str = format!("{}", backtrace); let backtrace_split = backtrace_str.split('\n'); diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index f0abf111fe..2cece54f43 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -1,40 +1,28 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::NestedNodeWithoutChildren; -use crate::editor::ed_error::NodeIdNotInGridNodeMap; +use crate::editor::ed_error::{NoDefMarkNodeBeforeLineNr, NodeIdNotInGridNodeMap}; use crate::editor::mvc::ed_model::EdModel; use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::util::first_last_index_of; use crate::editor::util::index_of; -use crate::lang::ast::ExprId; +use crate::lang::parse::ASTNodeId; use crate::ui::text::selection::Selection; use crate::ui::text::text_pos::TextPos; -use crate::ui::ui_error::UIResult; +use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::{slice_get, slice_get_mut}; use snafu::OptionExt; +use std::cmp::Ordering; use std::fmt; +use super::markup::nodes::get_root_mark_node_id; + #[derive(Debug)] pub struct GridNodeMap { pub lines: Vec>, } impl GridNodeMap { - pub fn new() -> GridNodeMap { - GridNodeMap { - lines: vec![vec![]], - } - } - - pub fn add_to_line(&mut self, line_nr: usize, len: usize, node_id: MarkNodeId) -> UIResult<()> { - let line_ref = slice_get_mut(line_nr, &mut self.lines)?; - let mut new_cols_vec: Vec = std::iter::repeat(node_id).take(len).collect(); - - line_ref.append(&mut new_cols_vec); - - Ok(()) - } - pub fn insert_between_line( &mut self, line_nr: usize, @@ -42,18 +30,105 @@ impl GridNodeMap { len: usize, node_id: MarkNodeId, ) -> UIResult<()> { - let line_ref = slice_get_mut(line_nr, &mut self.lines)?; - let new_cols_vec: Vec = std::iter::repeat(node_id).take(len).collect(); + let nr_of_lines = self.lines.len(); - line_ref.splice(index..index, new_cols_vec); + if line_nr < nr_of_lines { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + let new_cols_vec: Vec = std::iter::repeat(node_id).take(len).collect(); + + line_ref.splice(index..index, new_cols_vec); + } else if line_nr >= nr_of_lines { + for _ in 0..((line_nr - nr_of_lines) + 1) { + self.push_empty_line(); + } + + self.insert_between_line(line_nr, index, len, node_id)?; + } else { + LineInsertionFailed { + line_nr, + nr_of_lines, + } + .fail()?; + } Ok(()) } - pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { + pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> { + if line_nr <= self.lines.len() { + self.lines.insert(line_nr, Vec::new()); + + Ok(()) + } else { + OutOfBounds { + index: line_nr, + collection_name: "code_lines.lines".to_owned(), + len: self.lines.len(), + } + .fail() + } + } + + pub fn push_empty_line(&mut self) { + self.lines.push(vec![]); + } + + pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> { + // clippy prefers this over if-else + match line_nr.cmp(&self.lines.len()) { + Ordering::Less => { + self.insert_empty_line(line_nr + 1)?; + + let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr + + if col_nr < line_ref.len() { + let next_line_str: Vec = line_ref.drain(col_nr..).collect(); + + let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line + + *next_line_ref = next_line_str; + } + + Ok(()) + } + Ordering::Equal => self.insert_empty_line(line_nr + 1), + Ordering::Greater => OutOfBounds { + index: line_nr, + collection_name: "grid_node_map.lines".to_owned(), + len: self.lines.len(), + } + .fail(), + } + } + + pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> { let line_ref = slice_get_mut(line_nr, &mut self.lines)?; - line_ref.remove(index); + *line_ref = vec![]; + + Ok(()) + } + + pub fn del_line(&mut self, line_nr: usize) { + self.lines.remove(line_nr); + } + + pub fn del_at_line(&mut self, line_nr: usize, column: usize) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.remove(column); + + Ok(()) + } + + pub fn del_range_at_line( + &mut self, + line_nr: usize, + col_range: std::ops::Range, + ) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.drain(col_range); Ok(()) } @@ -64,16 +139,12 @@ impl GridNodeMap { line_ref.drain(selection.start_pos.column..selection.end_pos.column); } else { - // TODO support multiline + unimplemented!("TODO support deleting multiline selection") } Ok(()) } - /*pub fn new_line(&mut self) { - self.lines.push(vec![]) - }*/ - pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult { let line = slice_get(caret_pos.line, &self.lines)?; let node_id = slice_get(caret_pos.column, line)?; @@ -133,15 +204,15 @@ impl GridNodeMap { } } - // returns start and end pos of Expr2, relevant AST node and MarkNodeId of the corresponding MarkupNode - pub fn get_expr_start_end_pos( + // returns start and end pos of Expr2/Def2, relevant AST node and MarkNodeId of the corresponding MarkupNode + pub fn get_block_start_end_pos( &self, caret_pos: TextPos, ed_model: &EdModel, - ) -> EdResult<(TextPos, TextPos, ExprId, MarkNodeId)> { + ) -> EdResult<(TextPos, TextPos, ASTNodeId, MarkNodeId)> { let line = slice_get(caret_pos.line, &self.lines)?; let node_id = slice_get(caret_pos.column, line)?; - let node = ed_model.markup_node_pool.get(*node_id); + let node = ed_model.mark_node_pool.get(*node_id); if node.is_nested() { let (start_pos, end_pos) = self.get_nested_start_end_pos(*node_id, ed_model)?; @@ -151,10 +222,7 @@ impl GridNodeMap { let (first_node_index, last_node_index) = first_last_index_of(*node_id, line)?; let curr_node_id = slice_get(first_node_index, line)?; - let curr_ast_node_id = ed_model - .markup_node_pool - .get(*curr_node_id) - .get_ast_node_id(); + let curr_ast_node_id = ed_model.mark_node_pool.get(*curr_node_id).get_ast_node_id(); let mut expr_start_index = first_node_index; let mut expr_end_index = last_node_index; @@ -165,7 +233,7 @@ impl GridNodeMap { for i in (0..first_node_index).rev() { let prev_pos_node_id = slice_get(i, line)?; let prev_ast_node_id = ed_model - .markup_node_pool + .mark_node_pool .get(*prev_pos_node_id) .get_ast_node_id(); @@ -187,7 +255,7 @@ impl GridNodeMap { for i in last_node_index..line.len() { let next_pos_node_id = slice_get(i, line)?; let next_ast_node_id = ed_model - .markup_node_pool + .mark_node_pool .get(*next_pos_node_id) .get_ast_node_id(); @@ -204,7 +272,7 @@ impl GridNodeMap { } let correct_mark_node_id = - GridNodeMap::get_top_node_with_expr_id(*curr_node_id, &ed_model.markup_node_pool); + GridNodeMap::get_top_node_with_expr_id(*curr_node_id, &ed_model.mark_node_pool); Ok(( TextPos { @@ -225,12 +293,12 @@ impl GridNodeMap { // `{` is not the entire Expr2 fn get_top_node_with_expr_id( curr_node_id: MarkNodeId, - markup_node_pool: &SlowPool, + mark_node_pool: &SlowPool, ) -> MarkNodeId { - let curr_node = markup_node_pool.get(curr_node_id); + let curr_node = mark_node_pool.get(curr_node_id); if let Some(parent_id) = curr_node.get_parent_id_opt() { - let parent = markup_node_pool.get(parent_id); + let parent = mark_node_pool.get(parent_id); if parent.get_ast_node_id() == curr_node.get_ast_node_id() { parent_id @@ -247,30 +315,109 @@ impl GridNodeMap { nested_node_id: MarkNodeId, ed_model: &EdModel, ) -> EdResult<(TextPos, TextPos)> { - let parent_mark_node = ed_model.markup_node_pool.get(nested_node_id); + let left_most_leaf = self.get_leftmost_leaf(nested_node_id, ed_model)?; - let all_child_ids = parent_mark_node.get_children_ids(); - let first_child_id = all_child_ids - .first() - .with_context(|| NestedNodeWithoutChildren { - node_id: nested_node_id, - })?; - let last_child_id = all_child_ids - .last() - .with_context(|| NestedNodeWithoutChildren { - node_id: nested_node_id, - })?; + let right_most_leaf = self.get_rightmost_leaf(nested_node_id, ed_model)?; let expr_start_pos = ed_model .grid_node_map - .get_node_position(*first_child_id, true)?; + .get_node_position(left_most_leaf, true)?; let expr_end_pos = ed_model .grid_node_map - .get_node_position(*last_child_id, false)? + .get_node_position(right_most_leaf, false)? .increment_col(); Ok((expr_start_pos, expr_end_pos)) } + + fn get_leftmost_leaf( + &self, + nested_node_id: MarkNodeId, + ed_model: &EdModel, + ) -> EdResult { + let mut children_ids = ed_model + .mark_node_pool + .get(nested_node_id) + .get_children_ids(); + let mut first_child_id = 0; + + while !children_ids.is_empty() { + first_child_id = *children_ids + .first() + .with_context(|| NestedNodeWithoutChildren { + node_id: nested_node_id, + })?; + + children_ids = ed_model + .mark_node_pool + .get(first_child_id) + .get_children_ids(); + } + + Ok(first_child_id) + } + + fn get_rightmost_leaf( + &self, + nested_node_id: MarkNodeId, + ed_model: &EdModel, + ) -> EdResult { + let mut children_ids = ed_model + .mark_node_pool + .get(nested_node_id) + .get_children_ids(); + let mut last_child_id = 0; + + while !children_ids.is_empty() { + last_child_id = *children_ids + .last() + .with_context(|| NestedNodeWithoutChildren { + node_id: nested_node_id, + })?; + + children_ids = ed_model + .mark_node_pool + .get(last_child_id) + .get_children_ids(); + } + + Ok(last_child_id) + } + + // get id of root mark_node whose ast_node_id points to a DefId + pub fn get_def_mark_node_id_before_line( + &self, + line_nr: usize, + mark_node_pool: &SlowPool, + ) -> EdResult { + for curr_line_nr in (0..line_nr).rev() { + let first_col_pos = TextPos { + line: curr_line_nr, + column: 0, + }; + + if self.node_exists_at_pos(first_col_pos) { + let mark_node_id = self.get_id_at_row_col(first_col_pos)?; + let root_mark_node_id = get_root_mark_node_id(mark_node_id, mark_node_pool); + + let ast_node_id = mark_node_pool.get(root_mark_node_id).get_ast_node_id(); + + if let ASTNodeId::ADefId(_) = ast_node_id { + return Ok(root_mark_node_id); + } + } + } + + NoDefMarkNodeBeforeLineNr { line_nr }.fail() + } +} + +impl Default for GridNodeMap { + fn default() -> Self { + GridNodeMap { + lines: vec![Vec::new()], + } + } } impl fmt::Display for GridNodeMap { @@ -282,10 +429,10 @@ impl fmt::Display for GridNodeMap { .collect::>() .join(", "); - write!(f, "{}", row_str)?; + writeln!(f, "{}", row_str)?; } - write!(f, " (grid_node_map)")?; + writeln!(f, "(grid_node_map, {:?} lines)", self.lines.len())?; Ok(()) } diff --git a/editor/src/editor/keyboard_input.rs b/editor/src/editor/keyboard_input.rs index a76ff9d24e..95538347a3 100644 --- a/editor/src/editor/keyboard_input.rs +++ b/editor/src/editor/keyboard_input.rs @@ -43,7 +43,7 @@ pub fn handle_keydown( } } - A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, + A | S | R | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, F11 => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index ad8294a9a6..9ab70d32ba 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -1,9 +1,8 @@ use super::keyboard_input; use super::style::CODE_TXT_XY; -use crate::editor::ed_error::print_ui_err; use crate::editor::mvc::ed_view; use crate::editor::mvc::ed_view::RenderedWgpu; -use crate::editor::resources::strings::NOTHING_OPENED; +use crate::editor::resources::strings::{HELLO_WORLD, NOTHING_OPENED}; use crate::editor::{ config::Config, ed_error::print_err, @@ -20,17 +19,23 @@ use crate::graphics::{ }; use crate::lang::expr::Env; use crate::lang::pool::Pool; -use crate::ui::ui_error::UIError::FileOpenFailed; -use crate::ui::util::slice_get; -use bumpalo::collections::String as BumpString; +use crate::ui::text::caret_w_select::CaretPos; +use crate::ui::util::path_to_string; use bumpalo::Bump; use cgmath::Vector2; +use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use pipelines::RectResources; -use roc_module::symbol::Interns; -use roc_module::symbol::{IdentIds, ModuleIds}; +use roc_can::builtins::builtin_defs_map; +use roc_collections::all::MutMap; +use roc_load; +use roc_load::file::LoadedModule; +use roc_module::symbol::IdentIds; use roc_types::subs::VarStore; +use std::collections::HashSet; +use std::fs::{self, File}; +use std::io::Write; use std::{error::Error, io, path::Path}; -use wgpu::{CommandEncoder, RenderPass, TextureView}; +use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView}; use wgpu_glyph::GlyphBrush; use winit::{ dpi::PhysicalSize, @@ -48,26 +53,13 @@ use winit::{ /// The editor is actually launched from the CLI if you pass it zero arguments, /// or if you provide it 1 or more files or directories to open on launch. -pub fn launch(filepaths: &[&Path]) -> io::Result<()> { - //TODO support using multiple filepaths - let first_path_opt = if !filepaths.is_empty() { - match slice_get(0, filepaths) { - Ok(path_ref_ref) => Some(*path_ref_ref), - Err(e) => { - eprintln!("{}", e); - None - } - } - } else { - None - }; - - run_event_loop(first_path_opt).expect("Error running event loop"); +pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> { + run_event_loop(project_dir_path_opt).expect("Error running event loop"); Ok(()) } -fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { +fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box> { env_logger::init(); // Open window and create a surface @@ -134,51 +126,37 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let env_arena = Bump::new(); let code_arena = Bump::new(); + let (file_path_str, code_str) = read_main_roc_file(project_dir_path_opt); + println!("Loading file {}...", file_path_str); + + let file_path = Path::new(&file_path_str); + + let loaded_module = load_module(file_path); + let mut var_store = VarStore::default(); let dep_idents = IdentIds::exposed_builtins(8); - let exposed_ident_ids = IdentIds::default(); - let mut module_ids = ModuleIds::default(); - let mod_id = module_ids.get_or_insert(&"ModId123".into()); - - let interns = Interns { - module_ids, - all_ident_ids: IdentIds::exposed_builtins(8), - }; + let module_ids = loaded_module.interns.module_ids.clone(); let env = Env::new( - mod_id, + loaded_module.module_id, &env_arena, &mut env_pool, &mut var_store, dep_idents, - &interns.module_ids, + &module_ids, exposed_ident_ids, ); - let mut code_str = BumpString::from_str_in("", &code_arena); - - let file_path = if let Some(file_path) = file_path_opt { - match std::fs::read_to_string(file_path) { - Ok(file_as_str) => { - code_str = BumpString::from_str_in(&file_as_str, &code_arena); - file_path - } - - Err(e) => { - print_ui_err(&FileOpenFailed { - path_str: file_path.to_string_lossy().to_string(), - err_msg: e.to_string(), - }); - Path::new("") - } - } - } else { - Path::new("") - }; - let ed_model_opt = { - let ed_model_res = ed_model::init_model(&code_str, file_path, env, &interns, &code_arena); + let ed_model_res = ed_model::init_model( + &code_str, + file_path, + env, + loaded_module, + &code_arena, + CaretPos::End, + ); match ed_model_res { Ok(mut ed_model) => { @@ -251,7 +229,8 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { event: event::WindowEvent::ReceivedCharacter(ch), .. } => { - let input_outcome_res = app_update::handle_new_char(&ch, &mut app_model); + let input_outcome_res = + app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers); if let Err(e) = input_outcome_res { print_err(&e) } else if let Ok(InputOutcome::Ignored) = input_outcome_res { @@ -314,23 +293,55 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { } if let Some(ref rendered_wgpu) = rendered_wgpu_opt { - for text_section in &rendered_wgpu.text_sections { + draw_rects( + &rendered_wgpu.rects_behind, + &mut encoder, + &frame.view, + &gpu_device, + &rect_resources, + wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)), + ); + + for text_section in &rendered_wgpu.text_sections_behind { let borrowed_text = text_section.to_borrowed(); glyph_brush.queue(borrowed_text); } - draw_all_rects( - &rendered_wgpu.rects, + // draw first layer of text + glyph_brush + .draw_queued( + &gpu_device, + &mut staging_belt, + &mut encoder, + &frame.view, + size.width, + size.height, + ) + .expect("Failed to draw first layer of text."); + + // draw rects on top of first text layer + draw_rects( + &rendered_wgpu.rects_front, &mut encoder, &frame.view, &gpu_device, &rect_resources, - &ed_theme, - ) + wgpu::LoadOp::Load, + ); + + for text_section in &rendered_wgpu.text_sections_front { + let borrowed_text = text_section.to_borrowed(); + + glyph_brush.queue(borrowed_text); + } } } else { - begin_render_pass(&mut encoder, &frame.view, &ed_theme); + begin_render_pass( + &mut encoder, + &frame.view, + wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)), + ); queue_no_file_text( &size, @@ -341,7 +352,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { ); } - // draw all text + // draw text glyph_brush .draw_queued( &gpu_device, @@ -351,7 +362,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { size.width, size.height, ) - .expect("Draw queued"); + .expect("Failed to draw queued text."); staging_belt.finish(); cmd_queue.submit(Some(encoder.finish())); @@ -374,17 +385,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { Ok(()) } -fn draw_all_rects( +fn draw_rects( all_rects: &[Rect], encoder: &mut CommandEncoder, texture_view: &TextureView, gpu_device: &wgpu::Device, rect_resources: &RectResources, - ed_theme: &EdTheme, + load_op: LoadOp, ) { let rect_buffers = create_rect_buffers(gpu_device, encoder, all_rects); - let mut render_pass = begin_render_pass(encoder, texture_view, ed_theme); + let mut render_pass = begin_render_pass(encoder, texture_view, load_op); render_pass.set_pipeline(&rect_resources.pipeline); render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); @@ -399,16 +410,14 @@ fn draw_all_rects( fn begin_render_pass<'a>( encoder: &'a mut CommandEncoder, texture_view: &'a TextureView, - ed_theme: &EdTheme, + load_op: LoadOp, ) -> RenderPass<'a> { - let bg_color = to_wgpu_color(ed_theme.background); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[wgpu::RenderPassColorAttachment { view: texture_view, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(bg_color), + load: load_op, store: true, }, }], @@ -417,6 +426,166 @@ fn begin_render_pass<'a>( }) } +type PathStr = String; + +fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathStr, String) { + if let Some(project_dir_path) = project_dir_path_opt { + let mut ls_config = HashSet::new(); + ls_config.insert(DirEntryAttr::FullName); + + let dir_items = ls(project_dir_path, &ls_config) + .unwrap_or_else(|err| panic!("Failed to list items in project directory: {:?}", err)) + .items; + + let file_names = dir_items + .iter() + .map(|info_hash_map| { + info_hash_map + .values() + .map(|dir_entry_value| { + if let DirEntryValue::String(file_name) = dir_entry_value { + Some(file_name) + } else { + None + } + }) + .flatten() // remove None + .collect::>() + }) + .flatten(); + + let roc_file_names: Vec<&String> = file_names + .filter(|file_name| file_name.contains(".roc")) + .collect(); + + let project_dir_path_str = path_to_string(project_dir_path); + + if let Some(&roc_file_name) = roc_file_names.first() { + let full_roc_file_path_str = vec![ + project_dir_path_str.clone(), + "/".to_owned(), + roc_file_name.clone(), + ] + .join(""); + let file_as_str = std::fs::read_to_string(&Path::new(&full_roc_file_path_str)) + .unwrap_or_else(|err| panic!("In the provided project {:?}, I found the roc file {}, but I failed to read it: {}", &project_dir_path_str, &full_roc_file_path_str, err)); + + (full_roc_file_path_str, file_as_str) + } else { + init_new_roc_project(&project_dir_path_str) + } + } else { + init_new_roc_project("new-roc-project") + } +} + +// returns path and content of app file +fn init_new_roc_project(project_dir_path_str: &str) -> (PathStr, String) { + let orig_platform_path = Path::new("./examples/hello-world/platform"); + + let project_dir_path = Path::new(project_dir_path_str); + + let roc_file_path_str = vec![project_dir_path_str, "/UntitledApp.roc"].join(""); + let roc_file_path = Path::new("./new-roc-project/UntitledApp.roc"); + + let project_platform_path_str = vec![project_dir_path_str, "/platform"].join(""); + let project_platform_path = Path::new(&project_platform_path_str); + + if !project_dir_path.exists() { + fs::create_dir(project_dir_path).expect("Failed to create dir for roc project."); + } + + copy_roc_platform_if_not_exists(orig_platform_path, project_platform_path, project_dir_path); + + let code_str = create_roc_file_if_not_exists(project_dir_path, roc_file_path); + + (roc_file_path_str, code_str) +} + +// returns contents of file +fn create_roc_file_if_not_exists(project_dir_path: &Path, roc_file_path: &Path) -> String { + if !roc_file_path.exists() { + let mut roc_file = File::create(roc_file_path).unwrap_or_else(|err| { + panic!("No roc file path was passed to the editor, so I wanted to create a new roc project with the file {:?}, but it failed: {}", roc_file_path, err) + }); + + write!(roc_file, "{}", HELLO_WORLD).unwrap_or_else(|err| { + panic!( + r#"No roc file path was passed to the editor, so I created a new roc project with the file {:?} + I wanted to write roc hello world to that file, but it failed: {:?}"#, + roc_file_path, + err + ) + }); + + HELLO_WORLD.to_string() + } else { + std::fs::read_to_string(roc_file_path).unwrap_or_else(|err| { + panic!( + "I detected an existing {:?} inside {:?}, but I failed to read from it: {}", + roc_file_path, project_dir_path, err + ) + }) + } +} + +fn copy_roc_platform_if_not_exists( + orig_platform_path: &Path, + project_platform_path: &Path, + project_dir_path: &Path, +) { + if !orig_platform_path.exists() && !project_platform_path.exists() { + panic!( + r#"No roc file path was passed to the editor, I wanted to create a new roc project but I could not find the platform at {:?}. + Are you at the root of the roc repository?"#, + orig_platform_path + ); + } else if !project_platform_path.exists() { + copy(orig_platform_path, project_dir_path, &CopyOptions::new()).unwrap_or_else(|err|{ + panic!(r#"No roc file path was passed to the editor, so I wanted to create a new roc project and roc projects require a platform, + I tried to copy the platform at {:?} to {:?} but it failed: {}"#, + orig_platform_path, + project_platform_path, + err + ) + }); + } +} + +pub fn load_module(src_file: &Path) -> LoadedModule { + let subs_by_module = MutMap::default(); + + let arena = Bump::new(); + let loaded = roc_load::file::load_and_typecheck( + &arena, + src_file.to_path_buf(), + arena.alloc(roc_builtins::std::standard_stdlib()), + src_file.parent().unwrap_or_else(|| { + panic!( + "src_file {:?} did not have a parent directory but I need to have one.", + src_file + ) + }), + subs_by_module, + 8, + builtin_defs_map, + ); + + match loaded { + Ok(x) => x, + Err(roc_load::file::LoadingProblem::FormattedReport(report)) => { + panic!( + "Failed to load module from src_file {:?}. Report: {:?}", + src_file, report + ); + } + Err(e) => panic!( + "Failed to load module from src_file {:?}: {:?}", + src_file, e + ), + } +} + fn queue_no_file_text( size: &PhysicalSize, text: &str, diff --git a/editor/src/editor/markup/common_nodes.rs b/editor/src/editor/markup/common_nodes.rs new file mode 100644 index 0000000000..f74648f3fa --- /dev/null +++ b/editor/src/editor/markup/common_nodes.rs @@ -0,0 +1,107 @@ +use crate::{ + editor::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}, + lang::{ast::ExprId, parse::ASTNodeId}, +}; + +use super::{attribute::Attributes, nodes, nodes::MarkupNode}; + +pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::EQUALS.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COMMA.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn_w_nls( + ast_node_id: ASTNodeId, + parent_id_opt: Option, + nr_of_newlines: usize, +) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: nr_of_newlines, + } +} + +pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COLON.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} diff --git a/editor/src/editor/markup/mod.rs b/editor/src/editor/markup/mod.rs index ecae51c6d2..de015ebd80 100644 --- a/editor/src/editor/markup/mod.rs +++ b/editor/src/editor/markup/mod.rs @@ -1,2 +1,3 @@ pub mod attribute; +pub mod common_nodes; pub mod nodes; diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs index 26047fa230..00b5358e88 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/editor/src/editor/markup/nodes.rs @@ -1,42 +1,62 @@ use super::attribute::Attributes; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::ExpectedTextNode; -use crate::editor::ed_error::GetContentOnNestedNode; use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; +use crate::editor::markup::common_nodes::new_blank_mn; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::markup::common_nodes::new_colon_mn; +use crate::editor::markup::common_nodes::new_comma_mn; +use crate::editor::markup::common_nodes::new_equals_mn; +use crate::editor::markup::common_nodes::new_left_accolade_mn; +use crate::editor::markup::common_nodes::new_left_square_mn; +use crate::editor::markup::common_nodes::new_right_accolade_mn; +use crate::editor::markup::common_nodes::new_right_square_mn; +use crate::editor::mvc::tld_value_update::tld_mark_node; use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; -use crate::lang::ast::{Expr2, ExprId, RecordField}; -use crate::lang::{expr::Env, pool::PoolStr}; +use crate::lang::ast::Def2; +use crate::lang::ast::DefId; +use crate::lang::ast::ExprId; +use crate::lang::ast::RecordField; +use crate::lang::ast::ValueDef; +use crate::lang::parse::ASTNodeId; +use crate::lang::parse::{AppHeader, AST}; +use crate::lang::pattern::get_identifier_string; +use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; use crate::ui::util::slice_get; use bumpalo::Bump; +use roc_module::symbol::Interns; use std::fmt; #[derive(Debug)] pub enum MarkupNode { Nested { - ast_node_id: ExprId, + ast_node_id: ASTNodeId, children_ids: Vec, parent_id_opt: Option, + newlines_at_end: usize, }, Text { content: String, - ast_node_id: ExprId, + ast_node_id: ASTNodeId, syn_high_style: HighlightStyle, attributes: Attributes, parent_id_opt: Option, + newlines_at_end: usize, }, Blank { - ast_node_id: ExprId, + ast_node_id: ASTNodeId, attributes: Attributes, syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank parent_id_opt: Option, + newlines_at_end: usize, }, } impl MarkupNode { - pub fn get_ast_node_id(&self) -> ExprId { + pub fn get_ast_node_id(&self) -> ASTNodeId { match self { MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, MarkupNode::Text { ast_node_id, .. } => *ast_node_id, @@ -60,9 +80,9 @@ impl MarkupNode { } } - pub fn get_sibling_ids(&self, markup_node_pool: &SlowPool) -> Vec { + pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec { if let Some(parent_id) = self.get_parent_id_opt() { - let parent_node = markup_node_pool.get(parent_id); + let parent_node = mark_node_pool.get(parent_id); parent_node.get_children_ids() } else { @@ -74,7 +94,7 @@ impl MarkupNode { pub fn get_child_indices( &self, child_id: MarkNodeId, - markup_node_pool: &SlowPool, + mark_node_pool: &SlowPool, ) -> EdResult<(usize, usize)> { match self { MarkupNode::Nested { children_ids, .. } => { @@ -87,7 +107,7 @@ impl MarkupNode { mark_child_index_opt = Some(indx); } - let child_mark_node = markup_node_pool.get(mark_child_id); + let child_mark_node = mark_node_pool.get(mark_child_id); // a node that points to the same ast_node as the parent is a ',', '[', ']' // those are not "real" ast children if child_mark_node.get_ast_node_id() != self_ast_id { @@ -147,15 +167,25 @@ impl MarkupNode { } } - // can't be &str, this creates borrowing issues - pub fn get_content(&self) -> EdResult { + pub fn get_content(&self) -> String { match self { - MarkupNode::Nested { .. } => GetContentOnNestedNode {}.fail(), - MarkupNode::Text { content, .. } => Ok(content.clone()), - MarkupNode::Blank { .. } => Ok(BLANK_PLACEHOLDER.to_owned()), + MarkupNode::Nested { .. } => "".to_owned(), + MarkupNode::Text { content, .. } => content.clone(), + MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), } } + // gets content and adds newline from newline_at_end + pub fn get_full_content(&self) -> String { + let mut full_content = self.get_content(); + + for _ in 0..self.get_newlines_at_end() { + full_content.push('\n') + } + + full_content + } + pub fn get_content_mut(&mut self) -> EdResult<&mut String> { match self { MarkupNode::Nested { .. } => ExpectedTextNode { @@ -172,11 +202,10 @@ impl MarkupNode { } } - pub fn is_all_alphanumeric(&self) -> EdResult { - Ok(self - .get_content()? + pub fn is_all_alphanumeric(&self) -> bool { + self.get_content() .chars() - .all(|chr| chr.is_ascii_alphanumeric())) + .all(|chr| chr.is_ascii_alphanumeric()) } pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> { @@ -209,6 +238,34 @@ impl MarkupNode { pub fn is_nested(&self) -> bool { matches!(self, MarkupNode::Nested { .. }) } + + pub fn get_newlines_at_end(&self) -> usize { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end, + } + } + + pub fn add_newline_at_end(&mut self) { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end += 1, + } + } } fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { @@ -223,12 +280,13 @@ pub const RIGHT_SQUARE_BR: &str = " ]"; pub const COLON: &str = ": "; pub const COMMA: &str = ", "; pub const STRING_QUOTES: &str = "\"\""; +pub const EQUALS: &str = " = "; fn new_markup_node( text: String, - node_id: ExprId, + node_id: ASTNodeId, highlight_style: HighlightStyle, - markup_node_pool: &mut SlowPool, + mark_node_pool: &mut SlowPool, ) -> MarkNodeId { let node = MarkupNode::Text { content: text, @@ -236,9 +294,51 @@ fn new_markup_node( syn_high_style: highlight_style, attributes: Attributes::new(), parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(node) + mark_node_pool.add(node) +} + +pub fn def2_to_markup<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + def2: &Def2, + def2_node_id: DefId, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> EdResult { + let ast_node_id = ASTNodeId::ADefId(def2_node_id); + + let mark_node_id = match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + let expr_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let tld_mn = tld_mark_node( + *identifier_id, + expr_mn_id, + ast_node_id, + mark_node_pool, + env, + interns, + )?; + + mark_node_pool.add(tld_mn) + } + Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), + }; + + Ok(mark_node_id) } // make Markup Nodes: generate String representation, assign Highlighting Style @@ -247,55 +347,44 @@ pub fn expr2_to_markup<'a, 'b>( env: &mut Env<'b>, expr2: &Expr2, expr2_node_id: ExprId, - markup_node_pool: &mut SlowPool, -) -> MarkNodeId { - match expr2 { + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> EdResult { + let ast_node_id = ASTNodeId::AExprId(expr2_node_id); + + let mark_node_id = match expr2 { Expr2::SmallInt { text, .. } | Expr2::I128 { text, .. } | Expr2::U128 { text, .. } | Expr2::Float { text, .. } => { let num_str = get_string(env, text); - new_markup_node( - num_str, - expr2_node_id, - HighlightStyle::Number, - markup_node_pool, - ) + new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) } Expr2::Str(text) => new_markup_node( "\"".to_owned() + text.as_str(env.pool) + "\"", - expr2_node_id, + ast_node_id, HighlightStyle::String, - markup_node_pool, + mark_node_pool, ), Expr2::GlobalTag { name, .. } => new_markup_node( get_string(env, name), - expr2_node_id, + ast_node_id, HighlightStyle::Type, - markup_node_pool, + mark_node_pool, ), Expr2::Call { expr: expr_id, .. } => { let expr = env.pool.get(*expr_id); - expr2_to_markup(arena, env, expr, *expr_id, markup_node_pool) + expr2_to_markup(arena, env, expr, *expr_id, mark_node_pool, interns)? } Expr2::Var(symbol) => { //TODO make bump_format with arena let text = format!("{:?}", symbol); - new_markup_node( - text, - expr2_node_id, - HighlightStyle::Variable, - markup_node_pool, - ) + new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) } Expr2::List { elems, .. } => { - let mut children_ids = vec![new_markup_node( - LEFT_SQUARE_BR.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )]; + let mut children_ids = + vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; let indexed_node_ids: Vec<(usize, ExprId)> = elems.iter(env.pool).copied().enumerate().collect(); @@ -308,64 +397,43 @@ pub fn expr2_to_markup<'a, 'b>( env, sub_expr2, *node_id, - markup_node_pool, - )); + mark_node_pool, + interns, + )?); if idx + 1 < elems.len() { - children_ids.push(new_markup_node( - ", ".to_string(), - expr2_node_id, - HighlightStyle::Operator, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); } } - children_ids.push(new_markup_node( - RIGHT_SQUARE_BR.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); let list_node = MarkupNode::Nested { - ast_node_id: expr2_node_id, + ast_node_id, children_ids, parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(list_node) + mark_node_pool.add(list_node) } Expr2::EmptyRecord => { let children_ids = vec![ - new_markup_node( - LEFT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - ), - new_markup_node( - RIGHT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - ), + mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), + mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), ]; let record_node = MarkupNode::Nested { - ast_node_id: expr2_node_id, + ast_node_id, children_ids, parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(record_node) + mark_node_pool.add(record_node) } Expr2::Record { fields, .. } => { - let mut children_ids = vec![new_markup_node( - LEFT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )]; + let mut children_ids = + vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; for (idx, field_node_id) in fields.iter_node_ids().enumerate() { let record_field = env.pool.get(field_node_id); @@ -374,21 +442,16 @@ pub fn expr2_to_markup<'a, 'b>( children_ids.push(new_markup_node( field_name.as_str(env.pool).to_owned(), - expr2_node_id, + ast_node_id, HighlightStyle::RecordField, - markup_node_pool, + mark_node_pool, )); match record_field { RecordField::InvalidLabelOnly(_, _) => (), RecordField::LabelOnly(_, _, _) => (), RecordField::LabeledValue(_, _, sub_expr2_node_id) => { - children_ids.push(new_markup_node( - COLON.to_string(), - expr2_node_id, - HighlightStyle::Operator, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); let sub_expr2 = env.pool.get(*sub_expr2_node_id); children_ids.push(expr2_to_markup( @@ -396,66 +459,117 @@ pub fn expr2_to_markup<'a, 'b>( env, sub_expr2, *sub_expr2_node_id, - markup_node_pool, - )); + mark_node_pool, + interns, + )?); } } if idx + 1 < fields.len() { - children_ids.push(new_markup_node( - ", ".to_string(), - expr2_node_id, - HighlightStyle::Operator, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); } } - children_ids.push(new_markup_node( - RIGHT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); let record_node = MarkupNode::Nested { - ast_node_id: expr2_node_id, + ast_node_id, children_ids, parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(record_node) + mark_node_pool.add(record_node) + } + Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), + Expr2::LetValue { + def_id, + body_id: _, + body_var: _, + } => { + let pattern_id = env.pool.get(*def_id).get_pattern_id(); + + let pattern2 = env.pool.get(pattern_id); + + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let value_def = env.pool.get(*def_id); + + match value_def { + ValueDef::NoAnnotation { + pattern_id: _, + expr_id, + expr_var: _, + } => { + let body_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let body_mn = mark_node_pool.get_mut(body_mn_id); + body_mn.add_newline_at_end(); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + mark_node_pool.add(full_let_node) + } + other => { + unimplemented!( + "I don't know how to convert {:?} into a MarkupNode yet.", + other + ) + } + } } - Expr2::Blank => markup_node_pool.add(MarkupNode::Blank { - ast_node_id: expr2_node_id, - attributes: Attributes::new(), - syn_high_style: HighlightStyle::Blank, - parent_id_opt: None, - }), Expr2::RuntimeError() => new_markup_node( "RunTimeError".to_string(), - expr2_node_id, + ast_node_id, HighlightStyle::Blank, - markup_node_pool, + mark_node_pool, ), rest => todo!("implement expr2_to_markup for {:?}", rest), - } + }; + + Ok(mark_node_id) } -pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut SlowPool) { - let node = markup_node_pool.get(markup_node_id); +pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { + let node = mark_node_pool.get(markup_node_id); if let MarkupNode::Nested { ast_node_id: _, children_ids, parent_id_opt: _, + newlines_at_end: _, } = node { // need to clone because of borrowing issues let children_ids_clone = children_ids.clone(); for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool); + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); } } } @@ -463,9 +577,9 @@ pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut Slo pub fn set_parent_for_all_helper( markup_node_id: MarkNodeId, parent_node_id: MarkNodeId, - markup_node_pool: &mut SlowPool, + mark_node_pool: &mut SlowPool, ) { - let node = markup_node_pool.get_mut(markup_node_id); + let node = mark_node_pool.get_mut(markup_node_id); match node { MarkupNode::Nested { @@ -479,7 +593,7 @@ pub fn set_parent_for_all_helper( let children_ids_clone = children_ids.clone(); for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool); + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); } } MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), @@ -487,23 +601,237 @@ pub fn set_parent_for_all_helper( } } +fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::PackageRelated, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +fn header_val_mn( + content: String, + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: highlight_style, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let expr_id = app_header.ast_node_id; + let ast_node_id = ASTNodeId::AExprId(expr_id); + + let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); + + let app_name_node_id = header_val_mn( + app_header.app_name.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let full_app_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![app_node_id, app_name_node_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); + + let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); + + let pack_base_node_id = header_val_mn( + "base: ".to_owned(), + expr_id, + HighlightStyle::RecordField, + mark_node_pool, + ); + + let pack_val_node_id = header_val_mn( + app_header.packages_base.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); + + let full_packages_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + packages_node_id, + pack_left_acc_node_id, + pack_base_node_id, + pack_val_node_id, + pack_right_acc_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); + + let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut import_child_ids: Vec = add_header_mn_list( + &app_header.imports, + expr_id, + HighlightStyle::Import, + mark_node_pool, + ); + + let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; + + full_import_children.append(&mut import_child_ids); + full_import_children.push(imports_right_square_node_id); + + let full_import_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_import_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); + + let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut provides_val_node_ids: Vec = add_header_mn_list( + &app_header.provides, + expr_id, + HighlightStyle::Provides, + mark_node_pool, + ); + + let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); + + let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; + + full_provides_children.append(&mut provides_val_node_ids); + full_provides_children.push(provides_right_square_node_id); + full_provides_children.push(provides_end_node_id); + + let full_provides_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_provides_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let full_app_node_id = mark_node_pool.add(full_app_node); + let full_packages_node = mark_node_pool.add(full_packages_node); + let full_import_node_id = mark_node_pool.add(full_import_node); + let full_provides_node_id = mark_node_pool.add(full_provides_node); + + let header_mark_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + full_app_node_id, + full_packages_node, + full_import_node_id, + full_provides_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let header_mn_id = mark_node_pool.add(header_mark_node); + + set_parent_for_all(header_mn_id, mark_node_pool); + + header_mn_id +} + +// Used for provides and imports +fn add_header_mn_list( + str_vec: &[String], + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> Vec { + let nr_of_elts = str_vec.len(); + + str_vec + .iter() + .enumerate() + .map(|(indx, provide_str)| { + let provide_str = header_val_mn( + provide_str.to_owned(), + expr_id, + highlight_style, + mark_node_pool, + ); + + if indx != nr_of_elts - 1 { + vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] + } else { + vec![provide_str] + } + }) + .flatten() + .collect() +} + +pub fn ast_to_mark_nodes<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + ast: &AST, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> EdResult> { + let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; + + for &def_id in ast.def_ids.iter() { + let def2 = env.pool.get(def_id); + + let expr2_markup_id = def2_to_markup(arena, env, def2, def_id, mark_node_pool, interns)?; + + set_parent_for_all(expr2_markup_id, mark_node_pool); + + all_mark_node_ids.push(expr2_markup_id); + } + + Ok(all_mark_node_ids) +} + impl fmt::Display for MarkupNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!( + write!( f, - "{} ({})", + "{} ({}, {})", self.node_type_as_string(), - self.get_content().unwrap_or_else(|_| "".to_string()) + self.get_content(), + self.get_newlines_at_end() ) } } pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { - let mut full_string = "\n\n(mark_node_tree)\n".to_owned(); + let mut full_string = "\n(mark_node_tree)\n".to_owned(); let node = mark_node_pool.get(root_node_id); - full_string.push_str(&format!("{}", node)); + full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id)); tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); @@ -524,11 +852,25 @@ fn tree_as_string_helper( .to_owned(); let child = mark_node_pool.get(child_id); + let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n"); - full_str.push_str(&format!("{}", child)); + full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id)); tree_string.push_str(&full_str); tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); } } + +// return to the the root parent_id of the current node +pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId { + let mut curr_mark_node_id = mark_node_id; + let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + + while let Some(curr_parent_id) = curr_parent_id_opt { + curr_mark_node_id = curr_parent_id; + curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + } + + curr_mark_node_id +} diff --git a/editor/src/editor/mod.rs b/editor/src/editor/mod.rs index 9a579fea50..89efb3a318 100644 --- a/editor/src/editor/mod.rs +++ b/editor/src/editor/mod.rs @@ -1,6 +1,6 @@ mod code_lines; mod config; -mod ed_error; +pub mod ed_error; mod grid_node_map; mod keyboard_input; pub mod main; diff --git a/editor/src/editor/mvc/app_update.rs b/editor/src/editor/mvc/app_update.rs index 5319577a15..1b6ef6bd60 100644 --- a/editor/src/editor/mvc/app_update.rs +++ b/editor/src/editor/mvc/app_update.rs @@ -1,8 +1,8 @@ use super::app_model::AppModel; use super::ed_update; -use crate::editor::ed_error::EdResult; use crate::window::keyboard_input::Modifiers; -use winit::event::VirtualKeyCode; +use crate::{editor::ed_error::EdResult, window::keyboard_input::from_winit}; +use winit::event::{ModifiersState, VirtualKeyCode}; pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> { if let Some(ref mut ed_model) = app_model.ed_model_opt { @@ -51,16 +51,26 @@ pub fn pass_keydown_to_focused( pub enum InputOutcome { Accepted, Ignored, + SilentIgnored, } -pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResult { +pub fn handle_new_char( + received_char: &char, + app_model: &mut AppModel, + modifiers_winit: ModifiersState, +) -> EdResult { if let Some(ref mut ed_model) = app_model.ed_model_opt { if ed_model.has_focus { - return ed_update::handle_new_char(received_char, ed_model); + let modifiers = from_winit(&modifiers_winit); + + if modifiers.new_char_modifiers() { + // shortcuts with modifiers are handled by ed_handle_key_down + return ed_update::handle_new_char(received_char, ed_model); + } } } - Ok(InputOutcome::Ignored) + Ok(InputOutcome::SilentIgnored) } /* diff --git a/editor/src/editor/mvc/break_line.rs b/editor/src/editor/mvc/break_line.rs new file mode 100644 index 0000000000..6fc8c7ca13 --- /dev/null +++ b/editor/src/editor/mvc/break_line.rs @@ -0,0 +1,80 @@ +use crate::editor::ed_error::EdResult; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::mvc::app_update::InputOutcome; +use crate::editor::mvc::ed_model::EdModel; +use crate::editor::util::index_of; +use crate::lang::ast::Def2; +use crate::lang::parse::ASTNodeId; +use crate::ui::text::text_pos::TextPos; + +// put everything after caret on new line, create a Def2::Blank if there was nothing after the caret. +pub fn break_line(ed_model: &mut EdModel) -> EdResult { + let carets = ed_model.get_carets(); + + for caret_pos in carets.iter() { + let caret_line_nr = caret_pos.line; + + // don't allow adding new lines on empty line + if caret_pos.column > 0 + && ed_model.grid_node_map.node_exists_at_pos(TextPos { + line: caret_line_nr, + column: caret_pos.column - 1, + }) + { + // one blank line between top level definitions + EdModel::insert_empty_line( + caret_line_nr + 1, + &mut ed_model.code_lines, + &mut ed_model.grid_node_map, + )?; + EdModel::insert_empty_line( + caret_line_nr + 1, + &mut ed_model.code_lines, + &mut ed_model.grid_node_map, + )?; + + insert_new_blank(ed_model, caret_pos, caret_pos.line + 2)?; + } + } + + ed_model.simple_move_carets_down(2); // one blank line between top level definitions + + Ok(InputOutcome::Accepted) +} + +pub fn insert_new_blank( + ed_model: &mut EdModel, + caret_pos: &TextPos, + insert_on_line_nr: usize, +) -> EdResult<()> { + let new_line_blank = Def2::Blank; + let new_line_blank_id = ed_model.module.env.pool.add(new_line_blank); + + let prev_def_mn_id = ed_model + .grid_node_map + .get_def_mark_node_id_before_line(caret_pos.line + 1, &ed_model.mark_node_pool)?; + let prev_def_mn_id_indx = index_of(prev_def_mn_id, &ed_model.markup_ids)?; + ed_model + .module + .ast + .def_ids + .insert(prev_def_mn_id_indx, new_line_blank_id); + + let blank_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls( + ASTNodeId::ADefId(new_line_blank_id), + None, + 2, + )); + + ed_model + .markup_ids + .insert(prev_def_mn_id_indx + 1, blank_mn_id); // + 1 because first markup node is header + + ed_model.insert_all_between_line( + insert_on_line_nr, // one blank line between top level definitions + 0, + &[blank_mn_id], + )?; + + Ok(()) +} diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 51f59ee993..a522bc6553 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -1,27 +1,22 @@ use crate::editor::code_lines::CodeLines; use crate::editor::grid_node_map::GridNodeMap; +use crate::editor::markup::nodes::ast_to_mark_nodes; use crate::editor::slow_pool::{MarkNodeId, SlowPool}; -use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::{ - ed_error::EdError::ParseError, - ed_error::{EdResult, MissingParent, NoNodeAtCaretPosition}, - markup::attribute::Attributes, - markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode}, + ed_error::SrcParseError, + ed_error::{EdResult, EmptyCodeString, MissingParent, NoNodeAtCaretPosition}, }; use crate::graphics::primitives::rect::Rect; -use crate::lang::ast::{Expr2, ExprId}; -use crate::lang::expr::{str_to_expr2, Env}; +use crate::lang::expr::Env; +use crate::lang::parse::{ASTNodeId, AST}; use crate::lang::pool::PoolStr; -use crate::lang::scope::Scope; -use crate::ui::text::caret_w_select::CaretWSelect; +use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect}; use crate::ui::text::lines::SelectableLines; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; -use bumpalo::collections::String as BumpString; use bumpalo::Bump; use nonempty::NonEmpty; -use roc_module::symbol::Interns; -use roc_region::all::Region; +use roc_load::file::LoadedModule; use std::path::Path; #[derive(Debug)] @@ -31,83 +26,98 @@ pub struct EdModel<'a> { pub code_lines: CodeLines, // allows us to map window coordinates to MarkNodeId's pub grid_node_map: GridNodeMap, - pub markup_root_id: MarkNodeId, - pub markup_node_pool: SlowPool, + pub markup_ids: Vec, // one root node for every expression + pub mark_node_pool: SlowPool, // contains single char dimensions, used to calculate line height, column width... pub glyph_dim_rect_opt: Option, pub has_focus: bool, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option)>, - pub selected_expr_opt: Option, - pub interns: &'a Interns, // this should eventually come from LoadedModule, see #1442 + pub selected_block_opt: Option, + pub loaded_module: LoadedModule, pub show_debug_view: bool, // EdModel is dirty if it has changed since the previous render. pub dirty: bool, } #[derive(Debug, Copy, Clone)] -pub struct SelectedExpression { - pub ast_node_id: ExprId, +pub struct SelectedBlock { + pub ast_node_id: ASTNodeId, pub mark_node_id: MarkNodeId, pub type_str: PoolStr, } pub fn init_model<'a>( - code_str: &'a BumpString, + code_str: &'a str, file_path: &'a Path, env: Env<'a>, - interns: &'a Interns, + loaded_module: LoadedModule, code_arena: &'a Bump, + caret_pos: CaretPos, // to set caret position ) -> EdResult> { let mut module = EdModule::new(code_str, env, code_arena)?; - let ast_root_id = module.ast_root_id; - let mut markup_node_pool = SlowPool::new(); + let mut mark_node_pool = SlowPool::new(); - let markup_root_id = if code_str.is_empty() { - let blank_root = MarkupNode::Blank { - ast_node_id: ast_root_id, - attributes: Attributes::new(), - syn_high_style: HighlightStyle::Blank, - parent_id_opt: None, - }; - - markup_node_pool.add(blank_root) + let markup_ids = if code_str.is_empty() { + EmptyCodeString {}.fail() } else { - let ast_root = &module.env.pool.get(ast_root_id); - - let temp_markup_root_id = expr2_to_markup( + ast_to_mark_nodes( code_arena, &mut module.env, - ast_root, - ast_root_id, - &mut markup_node_pool, - ); - set_parent_for_all(temp_markup_root_id, &mut markup_node_pool); + &module.ast, + &mut mark_node_pool, + &loaded_module.interns, + ) + }?; - temp_markup_root_id + let mut code_lines = CodeLines::default(); + let mut grid_node_map = GridNodeMap::default(); + + let mut line_nr = 0; + let mut col_nr = 0; + + for mark_node_id in &markup_ids { + EdModel::insert_mark_node_between_line( + &mut line_nr, + &mut col_nr, + *mark_node_id, + &mut grid_node_map, + &mut code_lines, + &mark_node_pool, + )? + } + + let caret = match caret_pos { + CaretPos::Start => CaretWSelect::default(), + CaretPos::Exact(txt_pos) => CaretWSelect::new(txt_pos, None), + CaretPos::End => CaretWSelect::new(code_lines.end_txt_pos(), None), }; - let code_lines = EdModel::build_code_lines_from_markup(markup_root_id, &markup_node_pool)?; - let grid_node_map = EdModel::build_node_map_from_markup(markup_root_id, &markup_node_pool)?; - Ok(EdModel { module, file_path, code_lines, grid_node_map, - markup_root_id, - markup_node_pool, + markup_ids, + mark_node_pool, glyph_dim_rect_opt: None, has_focus: true, - caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)), - selected_expr_opt: None, - interns, + caret_w_select_vec: NonEmpty::new((caret, None)), + selected_block_opt: None, + loaded_module, show_debug_view: false, dirty: true, }) } impl<'a> EdModel<'a> { + pub fn get_carets(&self) -> Vec { + self.caret_w_select_vec + .iter() + .map(|tup| tup.0.caret_pos) + .collect() + } + pub fn get_curr_mark_node_id(&self) -> UIResult { let caret_pos = self.get_caret(); self.grid_node_map.get_id_at_row_col(caret_pos) @@ -138,11 +148,11 @@ impl<'a> EdModel<'a> { pub fn get_curr_child_indices(&self) -> EdResult<(usize, usize)> { if self.node_exists_at_caret() { let curr_mark_node_id = self.get_curr_mark_node_id()?; - let curr_mark_node = self.markup_node_pool.get(curr_mark_node_id); + let curr_mark_node = self.mark_node_pool.get(curr_mark_node_id); if let Some(parent_id) = curr_mark_node.get_parent_id_opt() { - let parent = self.markup_node_pool.get(parent_id); - parent.get_child_indices(curr_mark_node_id, &self.markup_node_pool) + let parent = self.mark_node_pool.get(parent_id); + parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool) } else { MissingParent { node_id: curr_mark_node_id, @@ -161,38 +171,26 @@ impl<'a> EdModel<'a> { #[derive(Debug)] pub struct EdModule<'a> { pub env: Env<'a>, - pub ast_root_id: ExprId, + pub ast: AST, } // for debugging -// use crate::lang::ast::expr2_to_string; +//use crate::lang::ast::expr2_to_string; impl<'a> EdModule<'a> { pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult> { if !code_str.is_empty() { - let mut scope = Scope::new(env.home, env.pool, env.var_store); + let parse_res = AST::parse_from_string(code_str, &mut env, ast_arena); - let region = Region::new(0, 0, 0, 0); - - let expr2_result = str_to_expr2(ast_arena, code_str, &mut env, &mut scope, region); - - match expr2_result { - Ok((expr2, _output)) => { - let ast_root_id = env.pool.add(expr2); - - // for debugging - // dbg!(expr2_to_string(ast_root_id, env.pool)); - - Ok(EdModule { env, ast_root_id }) - } - Err(err) => Err(ParseError { + match parse_res { + Ok(ast) => Ok(EdModule { env, ast }), + Err(err) => SrcParseError { syntax_err: format!("{:?}", err), - }), + } + .fail(), } } else { - let ast_root_id = env.pool.add(Expr2::Blank); - - Ok(EdModule { env, ast_root_id }) + EmptyCodeString {}.fail() } } } @@ -200,40 +198,49 @@ impl<'a> EdModule<'a> { #[cfg(test)] pub mod test_ed_model { use crate::editor::ed_error::EdResult; + use crate::editor::main::load_module; use crate::editor::mvc::ed_model; + use crate::editor::resources::strings::HELLO_WORLD; use crate::lang::expr::Env; use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; + use crate::ui::text::caret_w_select::CaretPos; use crate::ui::text::lines::SelectableLines; + use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; - use bumpalo::collections::String as BumpString; use bumpalo::Bump; use ed_model::EdModel; - use roc_module::symbol::{IdentIds, Interns, ModuleIds}; + use roc_load::file::LoadedModule; + use roc_module::symbol::IdentIds; + use roc_module::symbol::ModuleIds; use roc_types::subs::VarStore; + use std::fs::File; + use std::io::Write; use std::path::Path; + use std::path::PathBuf; + use tempfile::tempdir; + use uuid::Uuid; pub fn init_dummy_model<'a>( - code_str: &'a BumpString, + code_str: &'a str, + loaded_module: LoadedModule, + module_ids: &'a ModuleIds, ed_model_refs: &'a mut EdModelRefs, + code_arena: &'a Bump, ) -> EdResult> { let file_path = Path::new(""); let dep_idents = IdentIds::exposed_builtins(8); let exposed_ident_ids = IdentIds::default(); - let mod_id = ed_model_refs - .interns - .module_ids - .get_or_insert(&"ModId123".into()); let env = Env::new( - mod_id, + loaded_module.module_id, &ed_model_refs.env_arena, &mut ed_model_refs.env_pool, &mut ed_model_refs.var_store, dep_idents, - &ed_model_refs.interns.module_ids, + module_ids, exposed_ident_ids, ); @@ -241,43 +248,69 @@ pub mod test_ed_model { code_str, file_path, env, - &ed_model_refs.interns, - &ed_model_refs.code_arena, + loaded_module, + code_arena, + CaretPos::End, ) } pub struct EdModelRefs { - code_arena: Bump, env_arena: Bump, env_pool: Pool, var_store: VarStore, - interns: Interns, } pub fn init_model_refs() -> EdModelRefs { EdModelRefs { - code_arena: Bump::new(), env_arena: Bump::new(), env_pool: Pool::with_capacity(1024), var_store: VarStore::default(), - interns: Interns { - module_ids: ModuleIds::default(), - all_ident_ids: IdentIds::exposed_builtins(8), - }, } } pub fn ed_model_from_dsl<'a>( - clean_code_str: &'a BumpString, - code_lines: &[&str], + clean_code_str: &'a mut String, + code_lines: Vec, ed_model_refs: &'a mut EdModelRefs, + module_ids: &'a ModuleIds, + code_arena: &'a Bump, ) -> Result, String> { - let code_lines_vec: Vec = (*code_lines).iter().map(|s| s.to_string()).collect(); - let caret_w_select = convert_dsl_to_selection(&code_lines_vec)?; + let full_code = vec![HELLO_WORLD, clean_code_str.as_str()]; + *clean_code_str = full_code.join("\n"); - let mut ed_model = init_dummy_model(clean_code_str, ed_model_refs)?; + let temp_dir = tempdir().expect("Failed to create temporary directory for test."); + let temp_file_path_buf = + PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); + let temp_file_full_path = temp_dir.path().join(temp_file_path_buf); - ed_model.set_caret(caret_w_select.caret_pos); + let mut file = File::create(temp_file_full_path.clone()).expect(&format!( + "Failed to create temporary file for path {:?}", + temp_file_full_path + )); + writeln!(file, "{}", clean_code_str).expect(&format!( + "Failed to write {:?} to file: {:?}", + clean_code_str, file + )); + + let loaded_module = load_module(&temp_file_full_path); + + let mut ed_model = init_dummy_model( + clean_code_str, + loaded_module, + module_ids, + ed_model_refs, + code_arena, + )?; + + // adjust for header and main function + let nr_hello_world_lines = HELLO_WORLD.matches('\n').count() - 2; + let caret_w_select = convert_dsl_to_selection(&code_lines)?; + let adjusted_caret_pos = TextPos { + line: caret_w_select.caret_pos.line + nr_hello_world_lines, + column: caret_w_select.caret_pos.column, + }; + + ed_model.set_caret(adjusted_caret_pos); Ok(ed_model) } diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 22f113bd10..96a9f61d33 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -1,5 +1,8 @@ #![allow(dead_code)] +use std::process::Command; +use std::process::Stdio; + use crate::editor::code_lines::CodeLines; use crate::editor::ed_error::from_ui_res; use crate::editor::ed_error::EdResult; @@ -8,9 +11,10 @@ use crate::editor::grid_node_map::GridNodeMap; use crate::editor::markup::attribute::Attributes; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; +use crate::editor::markup::nodes::EQUALS; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::mvc::ed_model::SelectedExpression; +use crate::editor::mvc::ed_model::SelectedBlock; use crate::editor::mvc::int_update::start_new_int; use crate::editor::mvc::int_update::update_int; use crate::editor::mvc::list_update::{add_blank_child, start_new_list}; @@ -22,11 +26,15 @@ use crate::editor::mvc::record_update::update_record_field; use crate::editor::mvc::string_update::start_new_string; use crate::editor::mvc::string_update::update_small_string; use crate::editor::mvc::string_update::update_string; +use crate::editor::mvc::tld_value_update::{start_new_tld_value, update_tld_val_name}; use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::Def2; +use crate::lang::ast::DefId; use crate::lang::ast::{Expr2, ExprId}; use crate::lang::constrain::constrain_expr; +use crate::lang::parse::ASTNodeId; use crate::lang::pool::Pool; use crate::lang::pool::PoolStr; use crate::lang::types::Type2; @@ -39,6 +47,8 @@ use crate::ui::text::selection::Selection; use crate::ui::text::text_pos::TextPos; use crate::ui::text::{lines, lines::Lines, lines::SelectableLines}; use crate::ui::ui_error::UIResult; +use crate::ui::util::path_to_string; +use crate::ui::util::write_to_file; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; use roc_can::expected::Expected; @@ -53,6 +63,10 @@ use snafu::OptionExt; use winit::event::VirtualKeyCode; use VirtualKeyCode::*; +use super::break_line::break_line; +use super::break_line::insert_new_blank; +use super::let_update::start_new_let_value; + impl<'a> EdModel<'a> { pub fn move_caret( &mut self, @@ -65,7 +79,7 @@ impl<'a> EdModel<'a> { caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?; caret_tup.1 = None; } - self.selected_expr_opt = None; + self.selected_block_opt = None; Ok(()) } @@ -79,6 +93,18 @@ impl<'a> EdModel<'a> { } } + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + // TODO error if no match was found for old_caret_pos + pub fn simple_move_caret_right(&mut self, old_caret_pos: TextPos, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + if caret_tup.0.caret_pos == old_caret_pos { + caret_tup.0.caret_pos.column += repeat; + caret_tup.1 = None; + } + } + } + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. // allows us to prevent multiple updates to EdModel.code_lines pub fn simple_move_carets_left(&mut self, repeat: usize) { @@ -88,94 +114,248 @@ impl<'a> EdModel<'a> { } } - pub fn build_node_map_from_markup( - markup_root_id: MarkNodeId, - markup_node_pool: &SlowPool, - ) -> EdResult { - let mut grid_node_map = GridNodeMap::new(); - - EdModel::build_grid_node_map(markup_root_id, &mut grid_node_map, markup_node_pool)?; - - Ok(grid_node_map) - } - - fn build_grid_node_map( - node_id: MarkNodeId, - grid_node_map: &mut GridNodeMap, - markup_node_pool: &SlowPool, - ) -> EdResult<()> { - let node = markup_node_pool.get(node_id); - - if node.is_nested() { - for child_id in node.get_children_ids() { - EdModel::build_grid_node_map(child_id, grid_node_map, markup_node_pool)?; - } - } else { - let node_content_str = node.get_content()?; - - grid_node_map.add_to_line(0, node_content_str.len(), node_id)?; + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + pub fn simple_move_carets_down(&mut self, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + caret_tup.0.caret_pos.column = 0; + caret_tup.0.caret_pos.line += repeat; + caret_tup.1 = None; } - - Ok(()) } - pub fn build_code_lines_from_markup( - markup_root_id: MarkNodeId, - markup_node_pool: &SlowPool, - ) -> EdResult { - let mut all_code_string = String::new(); + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + // TODO error if no match was found for old_caret_pos + pub fn simple_move_caret_down(&mut self, old_caret_pos: TextPos, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + if caret_tup.0.caret_pos == old_caret_pos { + caret_tup.0.caret_pos.column = 0; + caret_tup.0.caret_pos.line += repeat; + caret_tup.1 = None; + } + } + } - EdModel::build_markup_string(markup_root_id, &mut all_code_string, markup_node_pool)?; + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + pub fn simple_move_carets_up(&mut self, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + caret_tup.0.caret_pos.line -= repeat; + caret_tup.1 = None; + } + } - let code_lines = CodeLines::from_str(&all_code_string); - - Ok(code_lines) + pub fn add_mark_node(&mut self, node: MarkupNode) -> MarkNodeId { + self.mark_node_pool.add(node) } fn build_markup_string( node_id: MarkNodeId, all_code_string: &mut String, - markup_node_pool: &SlowPool, + mark_node_pool: &SlowPool, ) -> EdResult<()> { - let node = markup_node_pool.get(node_id); + let node = mark_node_pool.get(node_id); if node.is_nested() { for child_id in node.get_children_ids() { - EdModel::build_markup_string(child_id, all_code_string, markup_node_pool)?; + EdModel::build_markup_string(child_id, all_code_string, mark_node_pool)?; } } else { - let node_content_str = node.get_content()?; + let node_content_str = node.get_content(); all_code_string.push_str(&node_content_str); } + for _ in 0..node.get_newlines_at_end() { + all_code_string.push('\n'); + } + Ok(()) } // updates grid_node_map and code_lines but nothing else. pub fn insert_between_line( - &mut self, line_nr: usize, index: usize, new_str: &str, node_id: MarkNodeId, + grid_node_map: &mut GridNodeMap, + code_lines: &mut CodeLines, ) -> UIResult<()> { - self.grid_node_map - .insert_between_line(line_nr, index, new_str.len(), node_id)?; - self.code_lines.insert_between_line(line_nr, index, new_str) + grid_node_map.insert_between_line(line_nr, index, new_str.len(), node_id)?; + code_lines.insert_between_line(line_nr, index, new_str) + } + + pub fn insert_all_between_line( + &mut self, + line_nr: usize, + index: usize, + leaf_node_ids: &[MarkNodeId], + ) -> UIResult<()> { + let mut col_nr = index; + let mut curr_line_nr = line_nr; + + for &node_id in leaf_node_ids { + let mark_node = self.mark_node_pool.get(node_id); + let node_full_content = mark_node.get_full_content(); + + if node_full_content.contains('\n') { + //insert separate lines separately + let split_lines = node_full_content.split('\n'); + + for line in split_lines { + self.grid_node_map.insert_between_line( + curr_line_nr, + col_nr, + line.len(), + node_id, + )?; + + self.code_lines + .insert_between_line(curr_line_nr, col_nr, line)?; + + curr_line_nr += 1; + col_nr = 0; + } + } else { + let node_content = mark_node.get_content(); + + self.grid_node_map.insert_between_line( + line_nr, + col_nr, + node_content.len(), + node_id, + )?; + + self.code_lines + .insert_between_line(line_nr, col_nr, &node_content)?; + + col_nr += node_content.len(); + } + } + + Ok(()) + } + + pub fn insert_mark_node_between_line( + line_nr: &mut usize, + col_nr: &mut usize, + mark_node_id: MarkNodeId, + grid_node_map: &mut GridNodeMap, + code_lines: &mut CodeLines, + mark_node_pool: &SlowPool, + ) -> UIResult<()> { + let mark_node = mark_node_pool.get(mark_node_id); + let node_newlines = mark_node.get_newlines_at_end(); + + if mark_node.is_nested() { + let children_ids = mark_node.get_children_ids(); + + for child_id in children_ids { + EdModel::insert_mark_node_between_line( + line_nr, + col_nr, + child_id, + grid_node_map, + code_lines, + mark_node_pool, + )?; + } + } else { + let node_content = mark_node.get_content(); + + EdModel::insert_between_line( + *line_nr, + *col_nr, + &node_content, + mark_node_id, + grid_node_map, + code_lines, + )?; + + if node_newlines == 0 { + *col_nr += node_content.len(); + } + } + + if node_newlines > 0 { + EdModel::break_line(*line_nr, *col_nr, code_lines, grid_node_map)?; + + *line_nr += 1; + *col_nr = 0; + + for _ in 1..node_newlines { + EdModel::insert_empty_line(*line_nr, code_lines, grid_node_map)?; + + *line_nr += 1; + *col_nr = 0; + } + } + + Ok(()) + } + + // break(split) line at col_nr and move everything after col_nr to the next line + pub fn break_line( + line_nr: usize, + col_nr: usize, + code_lines: &mut CodeLines, + grid_node_map: &mut GridNodeMap, + ) -> UIResult<()> { + code_lines.break_line(line_nr, col_nr)?; + grid_node_map.break_line(line_nr, col_nr) + } + + pub fn insert_empty_line( + line_nr: usize, + code_lines: &mut CodeLines, + grid_node_map: &mut GridNodeMap, + ) -> UIResult<()> { + code_lines.insert_empty_line(line_nr)?; + grid_node_map.insert_empty_line(line_nr) + } + + pub fn push_empty_line(code_lines: &mut CodeLines, grid_node_map: &mut GridNodeMap) { + code_lines.push_empty_line(); + grid_node_map.push_empty_line(); + } + + pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> { + self.grid_node_map.clear_line(line_nr)?; + self.code_lines.clear_line(line_nr) + } + + pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> { + self.grid_node_map.del_line(line_nr); + self.code_lines.del_line(line_nr) } - // updates grid_node_map and code_lines but nothing else. pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { self.grid_node_map.del_at_line(line_nr, index)?; self.code_lines.del_at_line(line_nr, index) } + // updates grid_node_map and code_lines but nothing else. + pub fn del_range_at_line( + &mut self, + line_nr: usize, + col_range: std::ops::Range, + ) -> UIResult<()> { + self.grid_node_map + .del_range_at_line(line_nr, col_range.clone())?; + self.code_lines.del_range_at_line(line_nr, col_range) + } + + pub fn del_blank_expr_node(&mut self, txt_pos: TextPos) -> UIResult<()> { + self.del_at_line(txt_pos.line, txt_pos.column) + } + pub fn set_selected_expr( &mut self, expr_start_pos: TextPos, expr_end_pos: TextPos, - ast_node_id: ExprId, + ast_node_id: ASTNodeId, mark_node_id: MarkNodeId, ) -> EdResult<()> { self.set_raw_sel(RawSelection { @@ -185,9 +365,19 @@ impl<'a> EdModel<'a> { self.set_caret(expr_start_pos); - let type_str = self.expr2_to_type(ast_node_id); + let type_str = match ast_node_id { + ASTNodeId::ADefId(def_id) => { + if let Some(expr_id) = self.extract_expr_from_def(def_id) { + self.expr2_to_type(expr_id) + } else { + PoolStr::new(" * ", self.module.env.pool) + } + } - self.selected_expr_opt = Some(SelectedExpression { + ASTNodeId::AExprId(expr_id) => self.expr2_to_type(expr_id), + }; + + self.selected_block_opt = Some(SelectedBlock { ast_node_id, mark_node_id, type_str, @@ -201,11 +391,11 @@ impl<'a> EdModel<'a> { // select all MarkupNodes that refer to specific ast node and its children. pub fn select_expr(&mut self) -> EdResult<()> { // include parent in selection if an `Expr2` was already selected - if let Some(selected_expr) = &self.selected_expr_opt { - let expr2_level_mark_node = self.markup_node_pool.get(selected_expr.mark_node_id); + if let Some(selected_block) = &self.selected_block_opt { + let expr2_level_mark_node = self.mark_node_pool.get(selected_block.mark_node_id); if let Some(parent_id) = expr2_level_mark_node.get_parent_id_opt() { - let parent_mark_node = self.markup_node_pool.get(parent_id); + let parent_mark_node = self.mark_node_pool.get(parent_id); let ast_node_id = parent_mark_node.get_ast_node_id(); let (expr_start_pos, expr_end_pos) = self @@ -220,7 +410,7 @@ impl<'a> EdModel<'a> { if self.grid_node_map.node_exists_at_pos(caret_pos) { let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self .grid_node_map - .get_expr_start_end_pos(self.get_caret(), self)?; + .get_block_start_end_pos(self.get_caret(), self)?; self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; } else if self @@ -229,7 +419,7 @@ impl<'a> EdModel<'a> { { let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self .grid_node_map - .get_expr_start_end_pos(self.get_caret().decrement_col(), self)?; + .get_block_start_end_pos(self.get_caret().decrement_col(), self)?; self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; } @@ -238,6 +428,18 @@ impl<'a> EdModel<'a> { Ok(()) } + fn extract_expr_from_def(&self, def_id: DefId) -> Option { + let def = self.module.env.pool.get(def_id); + + match def { + Def2::ValueDef { + identifier_id: _, + expr_id, + } => Some(*expr_id), + Def2::Blank => None, + } + } + fn expr2_to_type(&mut self, expr2_id: ExprId) -> PoolStr { let var = self.module.env.var_store.fresh(); let expr = self.module.env.pool.get(expr2_id); @@ -274,7 +476,12 @@ impl<'a> EdModel<'a> { let content = subs.get_content_without_compacting(var); PoolStr::new( - &content_to_string(content, subs, self.module.env.home, self.interns), + &content_to_string( + content, + subs, + self.module.env.home, + &self.loaded_module.interns, + ), self.module.env.pool, ) } @@ -315,74 +522,121 @@ impl<'a> EdModel<'a> { virtual_keycode: VirtualKeyCode, ) -> EdResult<()> { match virtual_keycode { - Left => from_ui_res(self.move_caret_left(modifiers)), + Left => self.move_caret_left(modifiers)?, Up => { if modifiers.cmd_or_ctrl() && modifiers.shift { - self.select_expr() + self.select_expr()? } else { - from_ui_res(self.move_caret_up(modifiers)) + self.move_caret_up(modifiers)? } } - Right => from_ui_res(self.move_caret_right(modifiers)), - Down => from_ui_res(self.move_caret_down(modifiers)), + Right => self.move_caret_right(modifiers)?, + Down => self.move_caret_down(modifiers)?, A => { if modifiers.cmd_or_ctrl() { - from_ui_res(self.select_all()) - } else { - Ok(()) + self.select_all()? } } - Home => from_ui_res(self.move_caret_home(modifiers)), - End => from_ui_res(self.move_caret_end(modifiers)), + S => { + if modifiers.cmd_or_ctrl() { + self.save_file()? + } + } + R => { + if modifiers.cmd_or_ctrl() { + self.run_file()? + } + } + + Home => self.move_caret_home(modifiers)?, + End => self.move_caret_end(modifiers)?, + F11 => { self.show_debug_view = !self.show_debug_view; self.dirty = true; - Ok(()) } - _ => Ok(()), + _ => (), } + + Ok(()) } - fn replace_selected_expr_with_blank(&mut self) -> EdResult<()> { - let expr_mark_node_id_opt = if let Some(sel_expr) = &self.selected_expr_opt { - let expr2_level_mark_node = self.markup_node_pool.get(sel_expr.mark_node_id); + // Replaces selected expression with blank. + // If no expression is selected, this function will select one to guide the user to using backspace in a projectional editing way + fn backspace(&mut self) -> EdResult<()> { + if let Some(sel_block) = &self.selected_block_opt { + let expr2_level_mark_node = self.mark_node_pool.get(sel_block.mark_node_id); + let newlines_at_end = expr2_level_mark_node.get_newlines_at_end(); let blank_replacement = MarkupNode::Blank { - ast_node_id: sel_expr.ast_node_id, + ast_node_id: sel_block.ast_node_id, attributes: Attributes::new(), syn_high_style: HighlightStyle::Blank, parent_id_opt: expr2_level_mark_node.get_parent_id_opt(), + newlines_at_end, }; - self.markup_node_pool - .replace_node(sel_expr.mark_node_id, blank_replacement); + self.mark_node_pool + .replace_node(sel_block.mark_node_id, blank_replacement); let active_selection = self.get_selection().context(MissingSelection {})?; self.code_lines.del_selection(active_selection)?; self.grid_node_map.del_selection(active_selection)?; - self.module.env.pool.set(sel_expr.ast_node_id, Expr2::Blank); + match sel_block.ast_node_id { + ASTNodeId::ADefId(def_id) => { + self.module.env.pool.set(def_id, Def2::Blank); + } + ASTNodeId::AExprId(expr_id) => { + self.module.env.pool.set(expr_id, Expr2::Blank); + } + } - Some(sel_expr.mark_node_id) - } else { - None - }; + let expr_mark_node_id = sel_block.mark_node_id; - // have to split the previous `if` up to prevent borrowing issues - if let Some(expr_mark_node_id) = expr_mark_node_id_opt { let caret_pos = self.get_caret(); - self.insert_between_line( + EdModel::insert_between_line( caret_pos.line, caret_pos.column, nodes::BLANK_PLACEHOLDER, expr_mark_node_id, + &mut self.grid_node_map, + &mut self.code_lines, )?; - } - self.set_sel_none(); + self.set_sel_none(); + } else { + self.select_expr()?; + }; + + Ok(()) + } + + fn save_file(&mut self) -> UIResult<()> { + let all_lines_str = self.code_lines.all_lines_as_string(); + + write_to_file(self.file_path, &all_lines_str)?; + + println!("save successful!"); + + Ok(()) + } + + fn run_file(&mut self) -> UIResult<()> { + println!("Executing file..."); + + let roc_file_str = path_to_string(self.file_path); + + Command::new("cargo") + .arg("run") + .arg(roc_file_str) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .expect("Failed to run file"); Ok(()) } @@ -458,7 +712,7 @@ impl<'a> SelectableLines for EdModel<'a> { let end_col = selection.end_pos.column; if start_line_index == end_line_index { - let line_ref = self.code_lines.get_line(start_line_index)?; + let line_ref = self.code_lines.get_line_ref(start_line_index)?; Ok(Some(line_ref[start_col..end_col].to_string())) } else { @@ -480,7 +734,7 @@ impl<'a> SelectableLines for EdModel<'a> { fn set_sel_none(&mut self) { self.caret_w_select_vec.first_mut().0.selection_opt = None; - self.selected_expr_opt = None; + self.selected_block_opt = None; } fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) { @@ -505,7 +759,7 @@ impl<'a> SelectableLines for EdModel<'a> { fn last_text_pos(&self) -> UIResult { let nr_of_lines = self.code_lines.lines.len(); let last_line_index = nr_of_lines - 1; - let last_line = self.code_lines.get_line(last_line_index)?; + let last_line = self.code_lines.get_line_ref(last_line_index)?; Ok(TextPos { line: self.code_lines.lines.len() - 1, @@ -527,7 +781,7 @@ pub struct NodeContext<'a> { pub curr_mark_node_id: MarkNodeId, pub curr_mark_node: &'a MarkupNode, pub parent_id_opt: Option, - pub ast_node_id: ExprId, + pub ast_node_id: ASTNodeId, } pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> { @@ -535,7 +789,7 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> let curr_mark_node_id = ed_model .grid_node_map .get_id_at_row_col(ed_model.get_caret())?; - let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id); + let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); let parent_id_opt = curr_mark_node.get_parent_id_opt(); let ast_node_id = curr_mark_node.get_ast_node_id(); @@ -548,24 +802,353 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> }) } +fn if_modifiers(modifiers: &Modifiers, shortcut_result: UIResult<()>) -> EdResult<()> { + if modifiers.cmd_or_ctrl() { + from_ui_res(shortcut_result) + } else { + Ok(()) + } +} + +// current(=caret is here) MarkupNode corresponds to a Def2 in the AST +pub fn handle_new_char_def( + received_char: &char, + def_id: DefId, + ed_model: &mut EdModel, +) -> EdResult { + let def_ref = ed_model.module.env.pool.get(def_id); + let ch = received_char; + + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node, + parent_id_opt: _, + ast_node_id: _, + } = get_node_context(ed_model)?; + + let outcome = match def_ref { + Def2::Blank { .. } => match ch { + 'a'..='z' => start_new_tld_value(ed_model, ch)?, + _ => InputOutcome::Ignored, + }, + Def2::ValueDef { .. } => { + if curr_mark_node.get_content() == EQUALS { + let node_caret_offset = ed_model + .grid_node_map + .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; + + if node_caret_offset == 0 || node_caret_offset == EQUALS.len() { + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + + if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + update_tld_val_name( + prev_mark_node_id, + ed_model.get_caret(), // TODO update for multiple carets + ed_model, + ch, + )? + } else { + unreachable!() + } + } else { + InputOutcome::Ignored + } + } else { + update_tld_val_name( + curr_mark_node_id, + ed_model.get_caret(), // TODO update for multiple carets + ed_model, + ch, + )? + } + } + }; + + Ok(outcome) +} + +// current(=caret is here) MarkupNode corresponds to an Expr2 in the AST +pub fn handle_new_char_expr( + received_char: &char, + expr_id: ExprId, + ed_model: &mut EdModel, +) -> EdResult { + let expr_ref = ed_model.module.env.pool.get(expr_id); + let ch = received_char; + + let NodeContext { + old_caret_pos: _, + curr_mark_node_id, + curr_mark_node, + parent_id_opt: _, + ast_node_id: _, + } = get_node_context(ed_model)?; + + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + + let outcome = if let Expr2::Blank { .. } = expr_ref { + match ch { + 'a'..='z' => start_new_let_value(ed_model, ch)?, + '"' => start_new_string(ed_model)?, + '{' => start_new_record(ed_model)?, + '0'..='9' => start_new_int(ed_model, ch)?, + '[' => { + // this can also be a tag union or become a set, assuming list for now + start_new_list(ed_model)? + } + '\r' => { + println!("For convenience and consistency there is only one way to format Roc, you can't add extra blank lines."); + InputOutcome::Ignored + } + _ => InputOutcome::Ignored, + } + } else if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + if prev_mark_node_id == curr_mark_node_id { + match expr_ref { + Expr2::SmallInt { .. } => update_int(ed_model, curr_mark_node_id, ch)?, + Expr2::SmallStr(old_arr_str) => update_small_string(ch, old_arr_str, ed_model)?, + Expr2::Str(..) => update_string(*ch, ed_model)?, + Expr2::InvalidLookup(old_pool_str) => update_invalid_lookup( + &ch.to_string(), + old_pool_str, + curr_mark_node_id, + expr_id, + ed_model, + )?, + Expr2::EmptyRecord => { + // prev_mark_node_id and curr_mark_node_id should be different to allow creating field at current caret position + InputOutcome::Ignored + } + Expr2::Record { + record_var: _, + fields, + } => { + if curr_mark_node + .get_content() + .chars() + .all(|chr| chr.is_ascii_alphanumeric()) + { + update_record_field( + &ch.to_string(), + ed_model.get_caret(), + curr_mark_node_id, + fields, + ed_model, + )? + } else { + InputOutcome::Ignored + } + } + _ => InputOutcome::Ignored, + } + } else if ch.is_ascii_alphanumeric() { + // prev_mark_node_id != curr_mark_node_id + + match expr_ref { + Expr2::SmallInt { .. } => update_int(ed_model, curr_mark_node_id, ch)?, + _ => { + let prev_ast_node_id = ed_model + .mark_node_pool + .get(prev_mark_node_id) + .get_ast_node_id(); + + match prev_ast_node_id { + ASTNodeId::ADefId(_) => InputOutcome::Ignored, + ASTNodeId::AExprId(prev_expr_id) => { + handle_new_char_diff_mark_nodes_prev_is_expr( + ch, + prev_expr_id, + expr_id, + prev_mark_node_id, + curr_mark_node_id, + ed_model, + )? + } + } + } + } + } else if *ch == ':' { + let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); + + if let Some(mark_parent_id) = mark_parent_id_opt { + let parent_ast_id = ed_model + .mark_node_pool + .get(mark_parent_id) + .get_ast_node_id(); + + match parent_ast_id { + ASTNodeId::ADefId(_) => InputOutcome::Ignored, + ASTNodeId::AExprId(parent_expr_id) => { + update_record_colon(ed_model, parent_expr_id)? + } + } + } else { + InputOutcome::Ignored + } + } else if *ch == ',' { + if curr_mark_node.get_content() == nodes::LEFT_SQUARE_BR { + InputOutcome::Ignored + } else { + let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); + + if let Some(mark_parent_id) = mark_parent_id_opt { + let parent_ast_id = ed_model + .mark_node_pool + .get(mark_parent_id) + .get_ast_node_id(); + + match parent_ast_id { + ASTNodeId::ADefId(_) => InputOutcome::Ignored, + ASTNodeId::AExprId(parent_expr_id) => { + let parent_expr2 = ed_model.module.env.pool.get(parent_expr_id); + + match parent_expr2 { + Expr2::List { + elem_var: _, + elems: _, + } => { + let (new_child_index, new_ast_child_index) = + ed_model.get_curr_child_indices()?; + // insert a Blank first, this results in cleaner code + add_blank_child(new_child_index, new_ast_child_index, ed_model)? + } + Expr2::Record { + record_var: _, + fields: _, + } => { + todo!("multiple record fields") + } + _ => InputOutcome::Ignored, + } + } + } + } else { + InputOutcome::Ignored + } + } + } else if "\"{[".contains(*ch) { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); + + if prev_mark_node.get_content() == nodes::LEFT_SQUARE_BR + && curr_mark_node.get_content() == nodes::RIGHT_SQUARE_BR + { + let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?; + // insert a Blank first, this results in cleaner code + add_blank_child(new_child_index, new_ast_child_index, ed_model)?; + handle_new_char(received_char, ed_model)? + } else { + InputOutcome::Ignored + } + } else { + InputOutcome::Ignored + } + } else { + InputOutcome::Ignored + }; + + Ok(outcome) +} + +// handle new char when prev_mark_node != curr_mark_node and prev_mark_node's AST node is an Expr2 +pub fn handle_new_char_diff_mark_nodes_prev_is_expr( + received_char: &char, + prev_expr_id: ExprId, + curr_expr_id: ExprId, + prev_mark_node_id: MarkNodeId, + curr_mark_node_id: MarkNodeId, + ed_model: &mut EdModel, +) -> EdResult { + let prev_expr_ref = ed_model.module.env.pool.get(prev_expr_id); + let curr_expr_ref = ed_model.module.env.pool.get(curr_expr_id); + let ch = received_char; + let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); + + let outcome = match prev_expr_ref { + Expr2::SmallInt { .. } => update_int(ed_model, prev_mark_node_id, ch)?, + Expr2::InvalidLookup(old_pool_str) => update_invalid_lookup( + &ch.to_string(), + old_pool_str, + prev_mark_node_id, + prev_expr_id, + ed_model, + )?, + Expr2::Record { + record_var: _, + fields, + } => { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); + + if (curr_mark_node.get_content() == nodes::RIGHT_ACCOLADE + || curr_mark_node.get_content() == nodes::COLON) + && prev_mark_node.is_all_alphanumeric() + { + update_record_field( + &ch.to_string(), + ed_model.get_caret(), + prev_mark_node_id, + fields, + ed_model, + )? + } else if prev_mark_node.get_content() == nodes::LEFT_ACCOLADE + && curr_mark_node.is_all_alphanumeric() + { + update_record_field( + &ch.to_string(), + ed_model.get_caret(), + curr_mark_node_id, + fields, + ed_model, + )? + } else { + InputOutcome::Ignored + } + } + Expr2::List { + elem_var: _, + elems: _, + } => { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); + + if prev_mark_node.get_content() == nodes::LEFT_SQUARE_BR + && curr_mark_node.get_content() == nodes::RIGHT_SQUARE_BR + { + // based on if, we are at the start of the list + let new_child_index = 1; + let new_ast_child_index = 0; + // insert a Blank first, this results in cleaner code + add_blank_child(new_child_index, new_ast_child_index, ed_model)?; + handle_new_char(received_char, ed_model)? + } else { + InputOutcome::Ignored + } + } + _ => match curr_expr_ref { + Expr2::EmptyRecord => { + let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.mark_node_pool); + + update_empty_record(&ch.to_string(), prev_mark_node_id, sibling_ids, ed_model)? + } + _ => InputOutcome::Ignored, + }, + }; + + Ok(outcome) +} + pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult { let input_outcome = match received_char { - '\u{1}' // Ctrl + A - | '\u{3}' // Ctrl + C - | '\u{16}' // Ctrl + V - | '\u{18}' // Ctrl + X - | '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html + '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html | '\u{f0000}'..='\u{ffffd}' // ^ | '\u{100000}'..='\u{10fffd}' // ^ => { - // chars that can be ignored InputOutcome::Ignored } '\u{8}' | '\u{7f}' => { // On Linux, '\u{8}' is backspace, // on macOS '\u{7f}'. - ed_model.replace_selected_expr_with_blank()?; + ed_model.backspace()?; InputOutcome::Accepted } @@ -573,246 +1156,52 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult let outcome = if ed_model.node_exists_at_caret() { let curr_mark_node_id = ed_model.get_curr_mark_node_id()?; - let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id); - let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); + let ast_node_id = curr_mark_node.get_ast_node_id(); - let ast_node_id = curr_mark_node.get_ast_node_id(); - let ast_node_ref = ed_model.module.env.pool.get(ast_node_id); - - if let Expr2::Blank {..} = ast_node_ref { - match ch { - '"' => { - start_new_string(ed_model)? - }, - '{' => { - start_new_record(ed_model)? - } - '0'..='9' => { - start_new_int(ed_model, ch)? - } - '[' => { - // this can also be a tag union or become a set, assuming list for now - start_new_list(ed_model)? - } - _ => InputOutcome::Ignored - } - } else if let Some(prev_mark_node_id) = prev_mark_node_id_opt{ - if prev_mark_node_id == curr_mark_node_id { - match ast_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, curr_mark_node_id, ch)? - } - Expr2::SmallStr(old_arr_str) => { - update_small_string( - ch, old_arr_str, ed_model - )? - } - Expr2::Str(old_pool_str) => { - update_string( - &ch.to_string(), old_pool_str, ed_model - )? - } - Expr2::InvalidLookup(old_pool_str) => { - update_invalid_lookup( - &ch.to_string(), - old_pool_str, - curr_mark_node_id, - ast_node_id, - ed_model - )? - } - Expr2::EmptyRecord => { - // prev_mark_node_id and curr_mark_node_id should be different to allow creating field at current caret position - InputOutcome::Ignored - } - Expr2::Record{ record_var:_, fields } => { - if curr_mark_node.get_content()?.chars().all(|chr| chr.is_ascii_alphanumeric()){ - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - curr_mark_node_id, - fields, - ed_model, - )? - } else { - InputOutcome::Ignored - } - } - _ => InputOutcome::Ignored - } - } else if ch.is_ascii_alphanumeric() { // prev_mark_node_id != curr_mark_node_id - - match ast_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, curr_mark_node_id, ch)? - } - _ => { - let prev_ast_node_id = - ed_model - .markup_node_pool - .get(prev_mark_node_id) - .get_ast_node_id(); - - let prev_node_ref = ed_model.module.env.pool.get(prev_ast_node_id); - - match prev_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, prev_mark_node_id, ch)? - } - Expr2::InvalidLookup(old_pool_str) => { - update_invalid_lookup( - &ch.to_string(), - old_pool_str, - prev_mark_node_id, - prev_ast_node_id, - ed_model - )? - } - Expr2::Record{ record_var:_, fields } => { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - - if (curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE || curr_mark_node.get_content()? == nodes::COLON) && - prev_mark_node.is_all_alphanumeric()? { - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - prev_mark_node_id, - fields, - ed_model, - )? - } else if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE && curr_mark_node.is_all_alphanumeric()? { - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - curr_mark_node_id, - fields, - ed_model, - )? - } else { - InputOutcome::Ignored - } - } - Expr2::List{ elem_var: _, elems: _} => { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - - if prev_mark_node.get_content()? == nodes::LEFT_SQUARE_BR && curr_mark_node.get_content()? == nodes::RIGHT_SQUARE_BR { - // based on if, we are at the start of the list - let new_child_index = 1; - let new_ast_child_index = 0; - // insert a Blank first, this results in cleaner code - add_blank_child(new_child_index, new_ast_child_index, ed_model)?; - handle_new_char(received_char, ed_model)? - } else { - InputOutcome::Ignored - } - } - _ => { - match ast_node_ref { - Expr2::EmptyRecord => { - let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); - - update_empty_record( - &ch.to_string(), - prev_mark_node_id, - sibling_ids, - ed_model - )? - } - _ => InputOutcome::Ignored - } - } - } - } - } - } else if *ch == ':' { - let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); - - if let Some(mark_parent_id) = mark_parent_id_opt { - let parent_ast_id = ed_model.markup_node_pool.get(mark_parent_id).get_ast_node_id(); - - update_record_colon(ed_model, parent_ast_id)? - } else { - InputOutcome::Ignored - } - } else if *ch == ',' { - if curr_mark_node.get_content()? == nodes::LEFT_SQUARE_BR { - InputOutcome::Ignored - } else { - let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); - - if let Some(mark_parent_id) = mark_parent_id_opt { - let parent_ast_id = ed_model.markup_node_pool.get(mark_parent_id).get_ast_node_id(); - let parent_expr2 = ed_model.module.env.pool.get(parent_ast_id); - - match parent_expr2 { - Expr2::List { elem_var:_, elems:_} => { - - let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?; - // insert a Blank first, this results in cleaner code - add_blank_child( - new_child_index, - new_ast_child_index, - ed_model - )? - } - Expr2::Record { record_var:_, fields:_ } => { - todo!("multiple record fields") - } - _ => { - InputOutcome::Ignored - } - } - } else { - InputOutcome::Ignored - } - } - } else if "\"{[".contains(*ch) { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - - if prev_mark_node.get_content()? == nodes::LEFT_SQUARE_BR && curr_mark_node.get_content()? == nodes::RIGHT_SQUARE_BR { - let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?; - // insert a Blank first, this results in cleaner code - add_blank_child( - new_child_index, - new_ast_child_index, - ed_model - )?; - handle_new_char(received_char, ed_model)? - } else { - InputOutcome::Ignored - } - - } else { - InputOutcome::Ignored - } - - } else { - match ast_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, curr_mark_node_id, ch)? - }, - // only SmallInt currently allows prepending at the start - _ => InputOutcome::Ignored - } + match ast_node_id { + ASTNodeId::ADefId(def_id) => { + handle_new_char_def(received_char, def_id, ed_model)? + }, + ASTNodeId::AExprId(expr_id) => { + handle_new_char_expr(received_char, expr_id, ed_model)? } + } } else { //no MarkupNode at the current position - let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; - if let Some(prev_mark_node_id) = prev_mark_node_id_opt { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + if *received_char == '\r' { + break_line(ed_model)? + } else { + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); - let prev_ast_node = ed_model.module.env.pool.get(prev_mark_node.get_ast_node_id()); + let prev_ast_node = ed_model.module.env.pool.get(prev_mark_node.get_ast_node_id().to_expr_id()?); - match prev_ast_node { - Expr2::SmallInt{ .. } => { - update_int(ed_model, prev_mark_node_id, ch)? - }, - _ => { - InputOutcome::Ignored + match prev_ast_node { + Expr2::SmallInt{ .. } => { + update_int(ed_model, prev_mark_node_id, ch)? + }, + _ => { + InputOutcome::Ignored + } + } + } else { + match ch { + 'a'..='z' => { + for caret_pos in ed_model.get_carets() { + + if caret_pos.line > 0 { + insert_new_blank(ed_model, &caret_pos, caret_pos.line)?; + } + } + handle_new_char(received_char, ed_model)? + } + _ => { + InputOutcome::Ignored + } } } - } else { - InputOutcome::Ignored } }; @@ -840,13 +1229,14 @@ pub mod test_ed_update { use crate::editor::mvc::ed_update::handle_new_char; use crate::editor::mvc::ed_update::EdModel; use crate::editor::mvc::ed_update::EdResult; + use crate::editor::resources::strings::HELLO_WORLD; use crate::ui::text::lines::SelectableLines; use crate::ui::ui_error::UIResult; use crate::window::keyboard_input::no_mods; use crate::window::keyboard_input::test_modifiers::ctrl_cmd_shift; use crate::window::keyboard_input::Modifiers; - use bumpalo::collections::String as BumpString; use bumpalo::Bump; + use roc_module::symbol::ModuleIds; use winit::event::VirtualKeyCode::*; fn ed_res_to_res(ed_res: EdResult) -> Result { @@ -866,155 +1256,310 @@ pub mod test_ed_update { // Create ed_model from pre_lines DSL, do handle_new_char() with new_char, check if modified ed_model has expected // string representation of code, caret position and active selection. pub fn assert_insert( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, new_char: char, ) -> Result<(), String> { assert_insert_seq(pre_lines, expected_post_lines, &new_char.to_string()) } - pub fn assert_insert_ignore(lines: &[&str], new_char: char) -> Result<(), String> { - assert_insert_seq(lines, lines, &new_char.to_string()) + pub fn assert_insert_nls( + pre_lines: Vec, + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert(pre_lines, add_nls(expected_post_lines), new_char) + } + + pub fn assert_insert_no_pre( + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert_seq_no_pre(expected_post_lines, &new_char.to_string()) + } + + pub fn assert_insert_seq_no_pre( + expected_post_lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + assert_insert_seq(vec!["┃".to_owned()], expected_post_lines, new_char_seq) + } + + // pre-insert `val = ` + pub fn assert_insert_in_def( + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert_seq_in_def(expected_post_lines, &new_char.to_string()) + } + + // pre-insert `val = ` + pub fn assert_insert_seq_in_def( + expected_post_lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + let prefix = "val🡲🡲🡲"; + + let full_input = merge_strings(vec![prefix, new_char_seq]); + + let mut expected_post_lines_vec = expected_post_lines.to_vec(); + + let first_line_opt = expected_post_lines_vec.first(); + let val_str = "val = "; + + if let Some(first_line) = first_line_opt { + expected_post_lines_vec[0] = merge_strings(vec![val_str, first_line]); + } else { + expected_post_lines_vec = vec![val_str.to_owned()]; + } + + assert_insert_seq_no_pre(expected_post_lines_vec, &full_input) + } + + pub fn assert_insert_in_def_nls( + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert_seq_in_def(add_nls(expected_post_lines), &new_char.to_string()) } // Create ed_model from pre_lines DSL, do handle_new_char() for every char in new_char_seq, check if modified ed_model has expected // string representation of code, caret position and active selection. pub fn assert_insert_seq( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, new_char_seq: &str, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("\n").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for input_char in new_char_seq.chars() { if input_char == '🡲' { ed_model.simple_move_carets_right(1); } else if input_char == '🡰' { ed_model.simple_move_carets_left(1); + } else if input_char == '🡱' { + ed_model.simple_move_carets_up(1); } else { + //dbg!(input_char); ed_res_to_res(handle_new_char(&input_char, &mut ed_model))?; } } - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); // remove header for clean tests assert_eq!(post_lines, expected_post_lines); Ok(()) } - pub fn assert_insert_seq_ignore(lines: &[&str], new_char_seq: &str) -> Result<(), String> { - assert_insert_seq(lines, lines, new_char_seq) + fn strip_header(lines: &mut Vec) { + let nr_hello_world_lines = HELLO_WORLD.matches('\n').count() - 2; + lines.drain(0..nr_hello_world_lines); + } + + pub fn assert_insert_seq_nls( + pre_lines: Vec, + expected_post_lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + assert_insert_seq(pre_lines, add_nls(expected_post_lines), new_char_seq) + } + + pub fn assert_insert_seq_ignore(lines: Vec, new_char_seq: &str) -> Result<(), String> { + assert_insert_seq(lines.clone(), lines, new_char_seq) + } + + pub fn assert_insert_seq_ignore_nls( + lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + assert_insert_seq_ignore(add_nls(lines), new_char_seq) + } + + pub fn assert_insert_ignore(lines: Vec, new_char: char) -> Result<(), String> { + assert_insert_seq_ignore(lines, &new_char.to_string()) + } + + pub fn assert_insert_ignore_nls(lines: Vec, new_char: char) -> Result<(), String> { + assert_insert_seq_ignore(add_nls(lines), &new_char.to_string()) + } + + // to create Vec from list of &str + macro_rules! ovec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = Vec::new(); + $( + temp_vec.push($x.to_owned()); + )* + temp_vec + } + }; } #[test] fn test_ignore_basic() -> Result<(), String> { - // space is added because Blank is inserted - assert_insert(&["┃"], &["┃ "], 'a')?; - assert_insert(&["┃"], &["┃ "], ';')?; - assert_insert(&["┃"], &["┃ "], '-')?; - assert_insert(&["┃"], &["┃ "], '_')?; + assert_insert_no_pre(ovec!["┃"], ';')?; + assert_insert_no_pre(ovec!["┃"], '-')?; + assert_insert_no_pre(ovec!["┃"], '_')?; + // extra space because of Expr2::Blank placholder + assert_insert_in_def_nls(ovec!["┃ "], ';')?; + assert_insert_in_def_nls(ovec!["┃ "], '-')?; + assert_insert_in_def_nls(ovec!["┃ "], '_')?; Ok(()) } - #[test] - fn test_int() -> Result<(), String> { - assert_insert(&["┃"], &["0┃"], '0')?; - assert_insert(&["┃"], &["1┃"], '1')?; - assert_insert(&["┃"], &["2┃"], '2')?; - assert_insert(&["┃"], &["3┃"], '3')?; - assert_insert(&["┃"], &["4┃"], '4')?; - assert_insert(&["┃"], &["5┃"], '5')?; - assert_insert(&["┃"], &["6┃"], '6')?; - assert_insert(&["┃"], &["7┃"], '7')?; - assert_insert(&["┃"], &["8┃"], '8')?; - assert_insert(&["┃"], &["9┃"], '9')?; + // add newlines like the editor's formatting would add them + fn add_nls(lines: Vec) -> Vec { + let mut new_lines = lines.clone(); - assert_insert(&["1┃"], &["19┃"], '9')?; - assert_insert(&["9876┃"], &["98769┃"], '9')?; - assert_insert(&["10┃"], &["103┃"], '3')?; - assert_insert(&["┃0"], &["1┃0"], '1')?; - assert_insert(&["10000┃"], &["100000┃"], '0')?; + new_lines.append(&mut vec!["".to_owned(), "".to_owned()]); - assert_insert(&["┃1234"], &["5┃1234"], '5')?; - assert_insert(&["1┃234"], &["10┃234"], '0')?; - assert_insert(&["12┃34"], &["121┃34"], '1')?; - assert_insert(&["123┃4"], &["1232┃4"], '2')?; - - Ok(()) - } - - #[test] - fn test_ignore_int() -> Result<(), String> { - assert_insert_seq_ignore(&["┃0"], "{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["┃7"], "{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["0┃"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["8┃"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["20┃"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["83┃"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["1┃0"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["8┃4"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["┃10"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["┃84"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["129┃96"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["97┃684"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_ignore(&["0┃"], '0')?; - assert_insert_ignore(&["0┃"], '9')?; - assert_insert_ignore(&["┃0"], '0')?; - assert_insert_ignore(&["┃1234"], '0')?; - assert_insert_ignore(&["┃100"], '0')?; - - Ok(()) + new_lines } //TODO test_int arch bit limit + #[test] + fn test_int() -> Result<(), String> { + assert_insert_in_def_nls(ovec!["0┃"], '0')?; + assert_insert_in_def_nls(ovec!["1┃"], '1')?; + assert_insert_in_def_nls(ovec!["2┃"], '2')?; + assert_insert_in_def_nls(ovec!["3┃"], '3')?; + assert_insert_in_def_nls(ovec!["4┃"], '4')?; + assert_insert_in_def_nls(ovec!["5┃"], '5')?; + assert_insert_in_def_nls(ovec!["6┃"], '6')?; + assert_insert_in_def_nls(ovec!["7┃"], '7')?; + assert_insert_in_def_nls(ovec!["8┃"], '8')?; + assert_insert_in_def_nls(ovec!["9┃"], '9')?; + + assert_insert(ovec!["val = 1┃"], add_nls(ovec!["val = 19┃"]), '9')?; + assert_insert(ovec!["val = 9876┃"], add_nls(ovec!["val = 98769┃"]), '9')?; + assert_insert(ovec!["val = 10┃"], add_nls(ovec!["val = 103┃"]), '3')?; + assert_insert(ovec!["val = ┃0"], add_nls(ovec!["val = 1┃0"]), '1')?; + assert_insert(ovec!["val = 10000┃"], add_nls(ovec!["val = 100000┃"]), '0')?; + + assert_insert(ovec!["val = ┃1234"], add_nls(ovec!["val = 5┃1234"]), '5')?; + assert_insert(ovec!["val = 1┃234"], add_nls(ovec!["val = 10┃234"]), '0')?; + assert_insert(ovec!["val = 12┃34"], add_nls(ovec!["val = 121┃34"]), '1')?; + assert_insert(ovec!["val = 123┃4"], add_nls(ovec!["val = 1232┃4"]), '2')?; + + Ok(()) + } + + fn merge_strings(strings: Vec<&str>) -> String { + strings + .iter() + .map(|&some_str| some_str.to_owned()) + .collect::>() + .join("") + } + + const IGNORE_CHARS: &str = "{}()[]-><-_\"azAZ:@09"; + const IGNORE_CHARS_NO_NUM: &str = ",{}()[]-><-_\"azAZ:@"; + const IGNORE_NO_LTR: &str = "{\"5"; + const IGNORE_NO_NUM: &str = "a{\""; + + #[test] + fn test_ignore_int() -> Result<(), String> { + assert_insert_seq_ignore_nls(ovec!["vec = ┃0"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = ┃7"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = 0┃"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 8┃"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 20┃"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 83┃"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = 1┃0"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 8┃4"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = ┃10"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = ┃84"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = 129┃96"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 97┃684"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_ignore_nls(ovec!["vec = 0┃"], '0')?; + assert_insert_ignore_nls(ovec!["vec = 0┃"], '9')?; + assert_insert_ignore_nls(ovec!["vec = ┃0"], '0')?; + assert_insert_ignore_nls(ovec!["vec = ┃1234"], '0')?; + assert_insert_ignore_nls(ovec!["vec = ┃100"], '0')?; + + Ok(()) + } #[test] fn test_string() -> Result<(), String> { - assert_insert(&["┃"], &["\"┃\""], '"')?; - assert_insert(&["\"┃\""], &["\"a┃\""], 'a')?; - assert_insert(&["\"┃\""], &["\"{┃\""], '{')?; - assert_insert(&["\"┃\""], &["\"}┃\""], '}')?; - assert_insert(&["\"┃\""], &["\"[┃\""], '[')?; - assert_insert(&["\"┃\""], &["\"]┃\""], ']')?; - assert_insert(&["\"┃\""], &["\"-┃\""], '-')?; - assert_insert(&["\"┃-\""], &["\"<┃-\""], '<')?; - assert_insert(&["\"-┃\""], &["\"->┃\""], '>')?; + assert_insert_in_def_nls(ovec!["\"┃\""], '"')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"a┃\""]), 'a')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"{┃\""]), '{')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"}┃\""]), '}')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"[┃\""]), '[')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"]┃\""]), ']')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"-┃\""]), '-')?; + assert_insert(ovec!["val = \"┃-\""], add_nls(ovec!["val = \"<┃-\""]), '<')?; + assert_insert(ovec!["val = \"-┃\""], add_nls(ovec!["val = \"->┃\""]), '>')?; - assert_insert(&["\"a┃\""], &["\"ab┃\""], 'b')?; - assert_insert(&["\"ab┃\""], &["\"abc┃\""], 'c')?; - assert_insert(&["\"┃a\""], &["\"z┃a\""], 'z')?; - assert_insert(&["\"┃a\""], &["\" ┃a\""], ' ')?; - assert_insert(&["\"a┃b\""], &["\"az┃b\""], 'z')?; - assert_insert(&["\"a┃b\""], &["\"a ┃b\""], ' ')?; - - assert_insert(&["\"ab ┃\""], &["\"ab {┃\""], '{')?; - assert_insert(&["\"ab ┃\""], &["\"ab }┃\""], '}')?; - assert_insert(&["\"{ str: 4┃}\""], &["\"{ str: 44┃}\""], '4')?; + assert_insert(ovec!["val = \"a┃\""], add_nls(ovec!["val = \"ab┃\""]), 'b')?; assert_insert( - &["\"┃ello, hello, hello\""], - &["\"h┃ello, hello, hello\""], + ovec!["val = \"ab┃\""], + add_nls(ovec!["val = \"abc┃\""]), + 'c', + )?; + assert_insert(ovec!["val = \"┃a\""], add_nls(ovec!["val = \"z┃a\""]), 'z')?; + assert_insert(ovec!["val = \"┃a\""], add_nls(ovec!["val = \" ┃a\""]), ' ')?; + assert_insert( + ovec!["val = \"a┃b\""], + add_nls(ovec!["val = \"az┃b\""]), + 'z', + )?; + assert_insert( + ovec!["val = \"a┃b\""], + add_nls(ovec!["val = \"a ┃b\""]), + ' ', + )?; + + assert_insert( + ovec!["val = \"ab ┃\""], + add_nls(ovec!["val = \"ab {┃\""]), + '{', + )?; + assert_insert( + ovec!["val = \"ab ┃\""], + add_nls(ovec!["val = \"ab }┃\""]), + '}', + )?; + assert_insert( + ovec!["val = \"{ str: 4┃}\""], + add_nls(ovec!["val = \"{ str: 44┃}\""]), + '4', + )?; + assert_insert( + ovec!["val = \"┃ello, hello, hello\""], + add_nls(ovec!["val = \"h┃ello, hello, hello\""]), 'h', )?; assert_insert( - &["\"hello┃ hello, hello\""], - &["\"hello,┃ hello, hello\""], + ovec!["val = \"hello┃ hello, hello\""], + add_nls(ovec!["val = \"hello,┃ hello, hello\""]), ',', )?; assert_insert( - &["\"hello, hello, hello┃\""], - &["\"hello, hello, hello.┃\""], + ovec!["val = \"hello, hello, hello┃\""], + add_nls(ovec!["val = \"hello, hello, hello.┃\""]), '.', )?; @@ -1023,549 +1568,797 @@ pub mod test_ed_update { #[test] fn test_ignore_string() -> Result<(), String> { - assert_insert(&["┃\"\""], &["┃\"\""], 'a')?; - assert_insert(&["┃\"\""], &["┃\"\""], 'A')?; - assert_insert(&["┃\"\""], &["┃\"\""], '"')?; - assert_insert(&["┃\"\""], &["┃\"\""], '{')?; - assert_insert(&["┃\"\""], &["┃\"\""], '[')?; - assert_insert(&["┃\"\""], &["┃\"\""], '}')?; - assert_insert(&["┃\"\""], &["┃\"\""], ']')?; - assert_insert(&["┃\"\""], &["┃\"\""], '-')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '-')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], 'a')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], 'A')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '"')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '{')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '[')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '}')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], ']')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '-')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '-')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], 'a')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], 'A')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '"')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '{')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '[')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '}')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], ']')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '-')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '-')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], 'a')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], 'A')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '"')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '{')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '[')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '}')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], ']')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '-')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '-')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], 'a')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], 'A')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '"')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '{')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '[')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '}')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], ']')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '-')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '-')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], 'a')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], 'A')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '"')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '{')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '[')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '}')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], ']')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '-')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '-')?; - assert_insert(&["\"[ 1, 2, 3 ]\"┃"], &["\"[ 1, 2, 3 ]\"┃"], '{')?; - assert_insert(&["┃\"[ 1, 2, 3 ]\""], &["┃\"[ 1, 2, 3 ]\""], '{')?; - assert_insert( - &["\"hello, hello, hello\"┃"], - &["\"hello, hello, hello\"┃"], - '.', - )?; - assert_insert( - &["┃\"hello, hello, hello\""], - &["┃\"hello, hello, hello\""], - '.', - )?; + assert_insert_ignore(add_nls(ovec!["val = \"[ 1, 2, 3 ]\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"[ 1, 2, 3 ]\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"hello, hello, hello\"┃"]), '.')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"hello, hello, hello\""]), '.')?; Ok(()) } #[test] fn test_record() -> Result<(), String> { - assert_insert(&["┃"], &["{ ┃ }"], '{')?; - assert_insert(&["{ ┃ }"], &["{ a┃ }"], 'a')?; - assert_insert(&["{ a┃ }"], &["{ ab┃: RunTimeError }"], 'b')?; - assert_insert(&["{ a┃ }"], &["{ a1┃: RunTimeError }"], '1')?; - assert_insert(&["{ a1┃ }"], &["{ a1z┃: RunTimeError }"], 'z')?; - assert_insert(&["{ a1┃ }"], &["{ a15┃: RunTimeError }"], '5')?; - assert_insert(&["{ ab┃ }"], &["{ abc┃: RunTimeError }"], 'c')?; - assert_insert(&["{ ┃abc }"], &["{ z┃abc: RunTimeError }"], 'z')?; - assert_insert(&["{ a┃b }"], &["{ az┃b: RunTimeError }"], 'z')?; - assert_insert(&["{ a┃b }"], &["{ a9┃b: RunTimeError }"], '9')?; - - // extra space for Blank node - assert_insert(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ':')?; - assert_insert(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ':')?; - assert_insert(&["{ aBc┃ }"], &["{ aBc┃: RunTimeError }"], ':')?; - - assert_insert_seq(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ":\"")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ":\"")?; - - assert_insert_seq(&["{ a┃ }"], &["{ a0┃: RunTimeError }"], ":0")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc9┃: RunTimeError }"], ":9")?; - assert_insert_seq(&["{ a┃ }"], &["{ a1000┃: RunTimeError }"], ":1000")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc98761┃: RunTimeError }"], ":98761")?; - - assert_insert(&["{ a: \"┃\" }"], &["{ a: \"a┃\" }"], 'a')?; - assert_insert(&["{ a: \"a┃\" }"], &["{ a: \"ab┃\" }"], 'b')?; - assert_insert(&["{ a: \"a┃b\" }"], &["{ a: \"az┃b\" }"], 'z')?; - assert_insert(&["{ a: \"┃ab\" }"], &["{ a: \"z┃ab\" }"], 'z')?; - - assert_insert(&["{ a: 1┃ }"], &["{ a: 10┃ }"], '0')?; - assert_insert(&["{ a: 100┃ }"], &["{ a: 1004┃ }"], '4')?; - assert_insert(&["{ a: 9┃76 }"], &["{ a: 98┃76 }"], '8')?; - assert_insert(&["{ a: 4┃691 }"], &["{ a: 40┃691 }"], '0')?; - assert_insert(&["{ a: 469┃1 }"], &["{ a: 4699┃1 }"], '9')?; - - assert_insert(&["{ camelCase: \"┃\" }"], &["{ camelCase: \"a┃\" }"], 'a')?; - assert_insert(&["{ camelCase: \"a┃\" }"], &["{ camelCase: \"ab┃\" }"], 'b')?; - - assert_insert(&["{ camelCase: 3┃ }"], &["{ camelCase: 35┃ }"], '5')?; - assert_insert(&["{ camelCase: ┃2 }"], &["{ camelCase: 5┃2 }"], '5')?; - assert_insert(&["{ camelCase: 10┃2 }"], &["{ camelCase: 106┃2 }"], '6')?; - - assert_insert(&["{ a┃: \"\" }"], &["{ ab┃: \"\" }"], 'b')?; - assert_insert(&["{ ┃a: \"\" }"], &["{ z┃a: \"\" }"], 'z')?; - assert_insert(&["{ ab┃: \"\" }"], &["{ abc┃: \"\" }"], 'c')?; - assert_insert(&["{ ┃ab: \"\" }"], &["{ z┃ab: \"\" }"], 'z')?; - assert_insert( - &["{ camelCase┃: \"hello\" }"], - &["{ camelCaseB┃: \"hello\" }"], - 'B', + assert_insert_in_def_nls(ovec!["{ ┃ }"], '{')?; + assert_insert_nls(ovec!["val = { ┃ }"], ovec!["val = { a┃ }"], 'a')?; + assert_insert_nls( + ovec!["val = { a┃ }"], + ovec!["val = { ab┃: RunTimeError }"], + 'b', + )?; // TODO: remove RunTimeError, see isue #1649 + assert_insert_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a1┃: RunTimeError }"], + '1', )?; - assert_insert( - &["{ camel┃Case: \"hello\" }"], - &["{ camelZ┃Case: \"hello\" }"], - 'Z', + assert_insert_nls( + ovec!["val = { a1┃ }"], + ovec!["val = { a1z┃: RunTimeError }"], + 'z', )?; - assert_insert( - &["{ ┃camelCase: \"hello\" }"], - &["{ z┃camelCase: \"hello\" }"], + assert_insert_nls( + ovec!["val = { a1┃ }"], + ovec!["val = { a15┃: RunTimeError }"], + '5', + )?; + assert_insert_nls( + ovec!["val = { ab┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + 'c', + )?; + assert_insert_nls( + ovec!["val = { ┃abc }"], + ovec!["val = { z┃abc: RunTimeError }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { a┃b }"], + ovec!["val = { az┃b: RunTimeError }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { a┃b }"], + ovec!["val = { a9┃b: RunTimeError }"], + '9', + )?; + + assert_insert_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a┃: RunTimeError }"], + ':', + )?; + assert_insert_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + ':', + )?; + assert_insert_nls( + ovec!["val = { aBc┃ }"], + ovec!["val = { aBc┃: RunTimeError }"], + ':', + )?; + + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a┃: RunTimeError }"], + ":\"", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + ":\"", + )?; + + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a0┃: RunTimeError }"], + ":0", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc9┃: RunTimeError }"], + ":9", + )?; + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a1000┃: RunTimeError }"], + ":1000", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc98761┃: RunTimeError }"], + ":98761", + )?; + + assert_insert_nls( + ovec!["val = { a: \"┃\" }"], + ovec!["val = { a: \"a┃\" }"], + 'a', + )?; + assert_insert_nls( + ovec!["val = { a: \"a┃\" }"], + ovec!["val = { a: \"ab┃\" }"], + 'b', + )?; + assert_insert_nls( + ovec!["val = { a: \"a┃b\" }"], + ovec!["val = { a: \"az┃b\" }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { a: \"┃ab\" }"], + ovec!["val = { a: \"z┃ab\" }"], 'z', )?; - assert_insert(&["{ a┃: 0 }"], &["{ ab┃: 0 }"], 'b')?; - assert_insert(&["{ ┃a: 2100 }"], &["{ z┃a: 2100 }"], 'z')?; - assert_insert(&["{ ab┃: 9876 }"], &["{ abc┃: 9876 }"], 'c')?; - assert_insert(&["{ ┃ab: 102 }"], &["{ z┃ab: 102 }"], 'z')?; - assert_insert(&["{ camelCase┃: 99999 }"], &["{ camelCaseB┃: 99999 }"], 'B')?; - assert_insert(&["{ camel┃Case: 88156 }"], &["{ camelZ┃Case: 88156 }"], 'Z')?; - assert_insert(&["{ ┃camelCase: 1 }"], &["{ z┃camelCase: 1 }"], 'z')?; + assert_insert_nls(ovec!["val = { a: 1┃ }"], ovec!["val = { a: 10┃ }"], '0')?; + assert_insert_nls(ovec!["val = { a: 100┃ }"], ovec!["val = { a: 1004┃ }"], '4')?; + assert_insert_nls(ovec!["val = { a: 9┃76 }"], ovec!["val = { a: 98┃76 }"], '8')?; + assert_insert_nls( + ovec!["val = { a: 4┃691 }"], + ovec!["val = { a: 40┃691 }"], + '0', + )?; + assert_insert_nls( + ovec!["val = { a: 469┃1 }"], + ovec!["val = { a: 4699┃1 }"], + '9', + )?; - assert_insert_seq(&["┃"], &["{ camelCase: \"hello┃\" }"], "{camelCase:\"hello")?; - assert_insert_seq(&["┃"], &["{ camelCase: 10009┃ }"], "{camelCase:10009")?; + assert_insert_nls( + ovec!["val = { camelCase: \"┃\" }"], + ovec!["val = { camelCase: \"a┃\" }"], + 'a', + )?; + assert_insert_nls( + ovec!["val = { camelCase: \"a┃\" }"], + ovec!["val = { camelCase: \"ab┃\" }"], + 'b', + )?; + + assert_insert_nls( + ovec!["val = { camelCase: 3┃ }"], + ovec!["val = { camelCase: 35┃ }"], + '5', + )?; + assert_insert_nls( + ovec!["val = { camelCase: ┃2 }"], + ovec!["val = { camelCase: 5┃2 }"], + '5', + )?; + assert_insert_nls( + ovec!["val = { camelCase: 10┃2 }"], + ovec!["val = { camelCase: 106┃2 }"], + '6', + )?; + + assert_insert_nls( + ovec!["val = { a┃: \"\" }"], + ovec!["val = { ab┃: \"\" }"], + 'b', + )?; + assert_insert_nls( + ovec!["val = { ┃a: \"\" }"], + ovec!["val = { z┃a: \"\" }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { ab┃: \"\" }"], + ovec!["val = { abc┃: \"\" }"], + 'c', + )?; + assert_insert_nls( + ovec!["val = { ┃ab: \"\" }"], + ovec!["val = { z┃ab: \"\" }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { camelCase┃: \"hello\" }"], + ovec!["val = { camelCaseB┃: \"hello\" }"], + 'B', + )?; + assert_insert_nls( + ovec!["val = { camel┃Case: \"hello\" }"], + ovec!["val = { camelZ┃Case: \"hello\" }"], + 'Z', + )?; + assert_insert_nls( + ovec!["val = { ┃camelCase: \"hello\" }"], + ovec!["val = { z┃camelCase: \"hello\" }"], + 'z', + )?; + + assert_insert_nls(ovec!["val = { a┃: 0 }"], ovec!["val = { ab┃: 0 }"], 'b')?; + assert_insert_nls( + ovec!["val = { ┃a: 2100 }"], + ovec!["val = { z┃a: 2100 }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { ab┃: 9876 }"], + ovec!["val = { abc┃: 9876 }"], + 'c', + )?; + assert_insert_nls( + ovec!["val = { ┃ab: 102 }"], + ovec!["val = { z┃ab: 102 }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { camelCase┃: 99999 }"], + ovec!["val = { camelCaseB┃: 99999 }"], + 'B', + )?; + assert_insert_nls( + ovec!["val = { camel┃Case: 88156 }"], + ovec!["val = { camelZ┃Case: 88156 }"], + 'Z', + )?; + assert_insert_nls( + ovec!["val = { ┃camelCase: 1 }"], + ovec!["val = { z┃camelCase: 1 }"], + 'z', + )?; + + assert_insert_seq_nls( + ovec!["val = { ┃ }"], + ovec!["val = { camelCase: \"hello┃\" }"], + "camelCase:\"hello", + )?; + assert_insert_seq_nls( + ovec!["val = { ┃ }"], + ovec!["val = { camelCase: 10009┃ }"], + "camelCase:10009", + )?; Ok(()) } #[test] fn test_nested_record() -> Result<(), String> { - assert_insert_seq(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ":{")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ":{")?; - assert_insert_seq(&["{ camelCase┃ }"], &["{ camelCase┃: RunTimeError }"], ":{")?; + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a┃: RunTimeError }"], + ":{", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + ":{", + )?; + assert_insert_seq_nls( + ovec!["val = { camelCase┃ }"], + ovec!["val = { camelCase┃: RunTimeError }"], + ":{", + )?; - assert_insert_seq(&["{ a: { ┃ } }"], &["{ a: { zulu┃ } }"], "zulu")?; - assert_insert_seq( - &["{ abc: { ┃ } }"], - &["{ abc: { camelCase┃ } }"], + assert_insert_seq_nls( + ovec!["val = { a: { ┃ } }"], + ovec!["val = { a: { zulu┃ } }"], + "zulu", + )?; + assert_insert_seq_nls( + ovec!["val = { abc: { ┃ } }"], + ovec!["val = { abc: { camelCase┃ } }"], "camelCase", )?; - assert_insert_seq(&["{ camelCase: { ┃ } }"], &["{ camelCase: { z┃ } }"], "z")?; - - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu┃: RunTimeError } }"], - ":", - )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase┃: RunTimeError } }"], - ":", - )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z┃: RunTimeError } }"], - ":", - )?; - - assert_insert_seq( - &["{ a┃: { zulu } }"], - &["{ a0┃: { zulu: RunTimeError } }"], - "0", - )?; - assert_insert_seq( - &["{ ab┃c: { camelCase } }"], - &["{ abz┃c: { camelCase: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { ┃ } }"], + ovec!["val = { camelCase: { z┃ } }"], "z", )?; - assert_insert_seq( - &["{ ┃camelCase: { z } }"], - &["{ x┃camelCase: { z: RunTimeError } }"], + + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu┃: RunTimeError } }"], + ":", + )?; + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase┃: RunTimeError } }"], + ":", + )?; + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z┃: RunTimeError } }"], + ":", + )?; + + assert_insert_seq_nls( + ovec!["val = { a┃: { zulu } }"], + ovec!["val = { a0┃: { zulu: RunTimeError } }"], + "0", + )?; + assert_insert_seq_nls( + ovec!["val = { ab┃c: { camelCase } }"], + ovec!["val = { abz┃c: { camelCase: RunTimeError } }"], + "z", + )?; + assert_insert_seq_nls( + ovec!["val = { ┃camelCase: { z } }"], + ovec!["val = { x┃camelCase: { z: RunTimeError } }"], "x", )?; - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu┃: RunTimeError } }"], ":\"", )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase┃: RunTimeError } }"], ":\"", )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z┃: RunTimeError } }"], ":\"", )?; - assert_insert_seq( - &["{ a: { zulu: \"┃\" } }"], - &["{ a: { zulu: \"azula┃\" } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: \"┃\" } }"], + ovec!["val = { a: { zulu: \"azula┃\" } }"], "azula", )?; - assert_insert_seq( - &["{ a: { zulu: \"az┃a\" } }"], - &["{ a: { zulu: \"azul┃a\" } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: \"az┃a\" } }"], + ovec!["val = { a: { zulu: \"azul┃a\" } }"], "ul", )?; - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu1┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu1┃: RunTimeError } }"], ":1", )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase0┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase0┃: RunTimeError } }"], ":0", )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z45┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z45┃: RunTimeError } }"], ":45", )?; - assert_insert_seq(&["{ a: { zulu: ┃0 } }"], &["{ a: { zulu: 4┃0 } }"], "4")?; - assert_insert_seq( - &["{ a: { zulu: 10┃98 } }"], - &["{ a: { zulu: 1077┃98 } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: ┃0 } }"], + ovec!["val = { a: { zulu: 4┃0 } }"], + "4", + )?; + assert_insert_seq_nls( + ovec!["val = { a: { zulu: 10┃98 } }"], + ovec!["val = { a: { zulu: 1077┃98 } }"], "77", )?; - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu┃: RunTimeError } }"], ":{", )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase┃: RunTimeError } }"], ":{", )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z┃: RunTimeError } }"], ":{", )?; - assert_insert_seq( - &["{ a: { zulu: { ┃ } } }"], - &["{ a: { zulu: { he┃ } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: { ┃ } } }"], + ovec!["val = { a: { zulu: { he┃ } } }"], "he", )?; - assert_insert_seq( - &["{ a: { ┃zulu: { } } }"], - &["{ a: { x┃zulu: { } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { ┃zulu: { } } }"], + ovec!["val = { a: { x┃zulu: { } } }"], "x", )?; - assert_insert_seq( - &["{ a: { z┃ulu: { } } }"], - &["{ a: { z9┃ulu: { } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { z┃ulu: { } } }"], + ovec!["val = { a: { z9┃ulu: { } } }"], "9", )?; - assert_insert_seq( - &["{ a: { zulu┃: { } } }"], - &["{ a: { zulu7┃: { } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃: { } } }"], + ovec!["val = { a: { zulu7┃: { } } }"], "7", )?; - assert_insert_seq( - &["{ a┃: { bcD: { eFgHij: { k15 } } } }"], - &["{ a4┃: { bcD: { eFgHij: { k15: RunTimeError } } } }"], + assert_insert_seq_nls( + ovec!["val = { a┃: { bcD: { eFgHij: { k15 } } } }"], + ovec!["val = { a4┃: { bcD: { eFgHij: { k15: RunTimeError } } } }"], "4", )?; - assert_insert_seq( - &["{ ┃a: { bcD: { eFgHij: { k15 } } } }"], - &["{ y┃a: { bcD: { eFgHij: { k15: RunTimeError } } } }"], + assert_insert_seq_nls( + ovec!["val = { ┃a: { bcD: { eFgHij: { k15 } } } }"], + ovec!["val = { y┃a: { bcD: { eFgHij: { k15: RunTimeError } } } }"], "y", )?; - assert_insert_seq( - &["{ a: { bcD: { eF┃gHij: { k15 } } } }"], - &["{ a: { bcD: { eFxyz┃gHij: { k15: RunTimeError } } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { bcD: { eF┃gHij: { k15 } } } }"], + ovec!["val = { a: { bcD: { eFxyz┃gHij: { k15: RunTimeError } } } }"], "xyz", )?; - assert_insert_seq( - &["┃"], - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - "{g:{oi:{ng:{d:{e:{e:{p:{camelCase", + assert_insert_seq_nls( + ovec!["val = { ┃ }"], + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + "g:{oi:{ng:{d:{e:{e:{p:{camelCase", )?; Ok(()) } - const IGNORE_CHARS: &str = "{}()[]-><-_\"azAZ:@09"; - const IGNORE_NO_LTR: &str = "{\"5"; - const IGNORE_NO_NUM: &str = "a{\""; - #[test] fn test_ignore_record() -> Result<(), String> { - assert_insert_seq_ignore(&["┃{ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ ┃}"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃}"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ ┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃a: RunTimeError }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃abc: RunTimeError }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃a: RunTimeError }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃abc: RunTimeError }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ a: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a:┃ RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a15: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a15: ┃RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a15: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a15:┃ RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a15: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a15: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a15: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a15:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: ┃RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ camelCase: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase:┃ RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃\"\" }"], "0")?; - assert_insert_seq_ignore(&["{ a: ┃\"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"\"┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"\" }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃\"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"\" }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: 1 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: 2 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃6 }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 8┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 0 }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: 1 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: 2 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃6 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 8┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 0 }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase: 1 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ camelCase: 7 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: ┃2 }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCase: 4┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCase: 9 }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: 1 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: 7 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃2 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: 4┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: 9 }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ camelCase: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: ┃\"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: \"\"┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: \"\" }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃\"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: \"\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: \"\" }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: \"z\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: \"z\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃\"z\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"z\"┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"z\" }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: \"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: \"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃\"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"z\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"z\" }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore( - &["┃{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{┃ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + assert_insert_seq_ignore_nls( + ovec!["val = {┃ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{ a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" }"], + assert_insert_seq_ignore_nls( + ovec!["val = { a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{ a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ }"], + assert_insert_seq_ignore_nls( + ovec!["val = { a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃"], + assert_insert_seq_ignore_nls( + ovec!["val = { a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃"], IGNORE_CHARS, )?; - assert_insert_seq_ignore(&["┃{ a: 915480 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: 915480 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃915480 }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 915480┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 915480 }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: 915480 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: 915480 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃915480 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 915480┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 915480 }┃"], IGNORE_CHARS)?; Ok(()) } #[test] fn test_ignore_nested_record() -> Result<(), String> { - assert_insert_seq_ignore(&["{ a: { ┃ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: ┃{ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: {┃ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: { }┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: { } ┃}"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: { } }┃"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a:┃ { } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{┃ a: { } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ a: { } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃a: { } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { ┃ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃{ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: {┃ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { }┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { } ┃}"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { } }┃"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a:┃ { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃a: { } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: R┃unTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: Ru┃nTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: RunTimeError } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: RunTimeError } }"], "1")?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: ┃{ z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: R┃unTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: Ru┃nTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1:┃ { z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = {┃ camelCaseB1: { z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a: RunTimeError } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a: RunTimeError } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\"┃ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃\"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" ┃} }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" }┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } ┃}"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } }┃"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: \"\" } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: \"\" } }"], "1")?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\"┃ } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃\"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" ┃} }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: ┃{ z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" }┃ }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" } ┃}"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" } }┃"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1:┃ { z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = {┃ camelCaseB1: { z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a: \"\" } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a: \"\" } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 0┃ } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃123 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ 999 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 80 ┃} }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: 99000 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: 12 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 7 }┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 98 } ┃}"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 4582 } }┃"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: 0 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: 44 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: 100123 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: 5 } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: 6 } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 0┃ } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃123 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ 999 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 80 ┃} }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: 99000 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: ┃{ z15a: 12 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 7 }┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 98 } ┃}"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: 4582 } }┃"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1:┃ { z15a: 0 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCaseB1: { z15a: 44 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: 100123 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a: 5 } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a: 6 } }"], "1")?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a:┃ \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" ┃} }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" ┃} }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: {┃ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: ┃{ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: ┃{ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } ┃}"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } ┃}"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }┃"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }┃"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1:┃ { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1:┃ { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{┃ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = {┃ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["┃{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ ┃camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { ┃camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], "1", )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { ┃z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { ┃z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], "1", )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase:┃ RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase:┃ RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: R┃unTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: R┃unTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }┃"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }┃"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeEr┃ror } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeEr┃ror } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = {┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], "2", )?; Ok(()) @@ -1573,76 +2366,39 @@ pub mod test_ed_update { #[test] fn test_single_elt_list() -> Result<(), String> { - assert_insert(&["┃"], &["[ ┃ ]"], '[')?; + assert_insert_in_def_nls(ovec!["[ ┃ ]"], '[')?; - assert_insert_seq(&["┃"], &["[ 0┃ ]"], "[0")?; - assert_insert_seq(&["┃"], &["[ 1┃ ]"], "[1")?; - assert_insert_seq(&["┃"], &["[ 9┃ ]"], "[9")?; + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 0┃ ]"], '0')?; + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 1┃ ]"], '1')?; + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 9┃ ]"], '9')?; - assert_insert_seq(&["┃"], &["[ \"┃\" ]"], "[\"")?; - assert_insert_seq( - &["┃"], - &["[ \"hello, hello.0123456789ZXY{}[]-><-┃\" ]"], - "[\"hello, hello.0123456789ZXY{}[]-><-", + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ \"┃\" ]"], '\"')?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ \"hello, hello.0123456789ZXY{}[]-><-┃\" ]"], + "\"hello, hello.0123456789ZXY{}[]-><-", )?; - assert_insert_seq(&["┃"], &["[ { ┃ } ]"], "[{")?; - assert_insert_seq(&["┃"], &["[ { a┃ } ]"], "[{a")?; - assert_insert_seq( - &["┃"], - &["[ { camelCase: { zulu: \"nested┃\" } } ]"], - "[{camelCase:{zulu:\"nested", + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ { ┃ } ]"], '{')?; + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ { a┃ } ]"], "{a")?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ { camelCase: { zulu: \"nested┃\" } } ]"], + "{camelCase:{zulu:\"nested", )?; - assert_insert_seq(&["┃"], &["[ [ ┃ ] ]"], "[[")?; - assert_insert_seq(&["┃"], &["[ [ [ ┃ ] ] ]"], "[[[")?; - assert_insert_seq(&["┃"], &["[ [ 0┃ ] ]"], "[[0")?; - assert_insert_seq(&["┃"], &["[ [ \"abc┃\" ] ]"], "[[\"abc")?; - assert_insert_seq( - &["┃"], - &["[ [ { camelCase: { a: 79000┃ } } ] ]"], - "[[{camelCase:{a:79000", + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ [ ┃ ] ]"], '[')?; + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ [ [ ┃ ] ] ]"], "[[")?; + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ [ 0┃ ] ]"], "[0")?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ \"abc┃\" ] ]"], + "[\"abc", )?; - - Ok(()) - } - - #[test] - fn test_multi_elt_list() -> Result<(), String> { - assert_insert_seq(&["┃"], &["[ 0, 1┃ ]"], "[0,1")?; - assert_insert_seq(&["┃"], &["[ 987, 6543, 210┃ ]"], "[987,6543,210")?; - - assert_insert_seq( - &["┃"], - &["[ \"a\", \"bcd\", \"EFGH┃\" ]"], - "[\"a🡲,\"bcd🡲,\"EFGH", - )?; - - assert_insert_seq( - &["┃"], - &["[ { a: 1 }, { b: 23 }, { c: 456┃ } ]"], - "[{a:1🡲🡲,{b:23🡲🡲,{c:456", - )?; - - assert_insert_seq(&["┃"], &["[ [ 1 ], [ 23 ], [ 456┃ ] ]"], "[[1🡲🡲,[23🡲🡲,[456")?; - - // insert element in between - assert_insert_seq(&["┃"], &["[ 0, 2┃, 1 ]"], "[0,1🡰🡰🡰,2")?; - assert_insert_seq(&["┃"], &["[ 0, 2, 3┃, 1 ]"], "[0,1🡰🡰🡰,2,3")?; - assert_insert_seq(&["┃"], &["[ 0, 3┃, 2, 1 ]"], "[0,1🡰🡰🡰,2🡰🡰🡰,3")?; - - assert_insert_seq( - &["┃"], - &["[ \"abc\", \"f┃\", \"de\" ]"], - "[\"abc🡲,\"de🡰🡰🡰🡰🡰,\"f", - )?; - - assert_insert_seq(&["┃"], &["[ [ 0 ], [ 2┃ ], [ 1 ] ]"], "[[0🡲🡲,[1🡰🡰🡰🡰🡰,[2")?; - - assert_insert_seq( - &["┃"], - &["[ { a: 0 }, { a: 2┃ }, { a: 1 } ]"], - "[{a:0🡲🡲,{a:1🡰🡰🡰🡰🡰🡰🡰🡰,{a:2", + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ { camelCase: { a: 79000┃ } } ] ]"], + "[{camelCase:{a:79000", )?; Ok(()) @@ -1650,244 +2406,442 @@ pub mod test_ed_update { #[test] fn test_ignore_single_elt_list() -> Result<(), String> { - assert_insert_seq_ignore(&["┃[ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ 0 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 0 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 0 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 0 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ 137 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 137 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 137 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 137 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃137 ]"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["[ 137┃ ]"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 137 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 137 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 137 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 137 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃137 ]"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 137┃ ]"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["┃[ \"teststring\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"teststring\" ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ \"teststring\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"teststring\" ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃\"teststring\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"teststring\"┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ \"teststring\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"teststring\" ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ \"teststring\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"teststring\" ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃\"teststring\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"teststring\"┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 } ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 } ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃{ a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ {┃ a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a:┃ 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 ┃} ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 }┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 } ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 } ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃{ a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ {┃ a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a:┃ 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 ┃} ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 }┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ [ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ] ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ [ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ] ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃[ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ]┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [┃ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ┃] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ [ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ] ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ [ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ] ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃[ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ]┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [┃ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ┃] ]"], IGNORE_CHARS)?; + + Ok(()) + } + + #[test] + fn test_multi_elt_list() -> Result<(), String> { + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 0, 1┃ ]"], "0,1")?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 987, 6543, 210┃ ]"], + "987,6543,210", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ \"a\", \"bcd\", \"EFGH┃\" ]"], + "\"a🡲,\"bcd🡲,\"EFGH", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ { a: 1 }, { b: 23 }, { c: 456┃ } ]"], + "{a:1🡲🡲,{b:23🡲🡲,{c:456", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ 1 ], [ 23 ], [ 456┃ ] ]"], + "[1🡲🡲,[23🡲🡲,[456", + )?; + + // insert element in between + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 0, 2┃, 1 ]"], + "0,1🡰🡰🡰,2", + )?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 0, 2, 3┃, 1 ]"], + "0,1🡰🡰🡰,2,3", + )?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 0, 3┃, 2, 1 ]"], + "0,1🡰🡰🡰,2🡰🡰🡰,3", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ \"abc\", \"f┃\", \"de\" ]"], + "\"abc🡲,\"de🡰🡰🡰🡰🡰,\"f", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ 0 ], [ 2┃ ], [ 1 ] ]"], + "[0🡲🡲,[1🡰🡰🡰🡰🡰,[2", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ { a: 0 }, { a: 2┃ }, { a: 1 } ]"], + "{a:0🡲🡲,{a:1🡰🡰🡰🡰🡰🡰🡰🡰,{a:2", + )?; Ok(()) } #[test] fn test_ignore_multi_elt_list() -> Result<(), String> { - assert_insert_seq_ignore(&["┃[ 0, 1 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0, 1 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 0, 1 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0, 1 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0,┃ 1 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 0, 1 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0, 1 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 0, 1 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0, 1 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0,┃ 1 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ 123, 56, 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123, 56, 7 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 123, 56, 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123, 56, 7 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123,┃ 56, 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123, 56,┃ 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 123, 56, 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123, 56, 7 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 123, 56, 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123, 56, 7 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123,┃ 56, 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123, 56,┃ 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\", \"56\", \"7\" ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\", \"56\", \"7\" ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\",┃ \"56\", \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\", \"56\",┃ \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\", \"56\", \"7\" ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\", \"56\", \"7\" ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\",┃ \"56\", \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\", \"56\",┃ \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 0 }, { a: 1 } ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 0 }, { a: 1 } ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 0 },┃ { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 0 }, { a: 1 } ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 0 }, { a: 1 } ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 0 },┃ { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ] ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ] ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ],┃ [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃[ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ]┃, [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [┃ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ┃], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], ┃[ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [┃ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ]┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ┃] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ] ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ] ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ],┃ [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃[ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ]┃, [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [┃ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ┃], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], ┃[ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [┃ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ]┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ┃] ]"], IGNORE_CHARS)?; Ok(()) } - // Create ed_model from pre_lines DSL, do ctrl+shift+up as many times as repeat. + #[test] + fn test_tld_value() -> Result<(), String> { + assert_insert_nls(ovec!["┃"], ovec!["a┃ = "], 'a')?; + assert_insert_nls(ovec!["┃"], ovec!["m┃ = "], 'm')?; + assert_insert_nls(ovec!["┃"], ovec!["z┃ = "], 'z')?; + + assert_insert_seq_nls(ovec!["┃"], ovec!["ab┃ = "], "ab")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["mainVal┃ = "], "mainVal")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["camelCase123┃ = "], "camelCase123")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["c137┃ = "], "c137")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["c137Bb┃ = "], "c137Bb")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["bBbb┃ = "], "bBbb")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["cC0Z┃ = "], "cC0Z")?; + + Ok(()) + } + + #[test] + fn test_ignore_tld_value() -> Result<(), String> { + assert_insert_seq_ignore_nls(ovec!["a ┃= 0"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["a =┃ 0"], IGNORE_CHARS)?; + + assert_insert_seq_ignore_nls(ovec!["aBC ┃= 0"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["aBC =┃ 0"], IGNORE_CHARS)?; + + assert_insert_seq_ignore_nls(ovec!["camelCase123 ┃= 0"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["camelCase123 =┃ 0"], IGNORE_CHARS)?; + + Ok(()) + } + + #[test] + fn test_enter() -> Result<(), String> { + assert_insert_seq( + ovec!["┃"], + ovec!["ab = 5", "", "cd = \"good┃\"", "", ""], + "ab🡲🡲🡲5\rcd🡲🡲🡲\"good", + )?; + + assert_insert_seq( + ovec!["┃"], + ovec!["ab = 1", "", "cD = 2┃", "", "eF = 3", "", ""], + "ab🡲🡲🡲1\reF🡲🡲🡲3🡰🡰🡰🡰🡰🡰🡱🡱🡲🡲🡲🡲🡲🡲\rcD🡲🡲🡲2", + )?; + + Ok(()) + } + + // Create ed_model from pre_lines DSL, do handle_new_char for every char in input_seq, do ctrl+shift+up as many times as repeat. // check if modified ed_model has expected string representation of code, caret position and active selection. pub fn assert_ctrl_shift_up_repeat( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, + input_seq: &str, repeats: usize, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; + + for input_char in input_seq.chars() { + if input_char == '🡲' { + ed_model.simple_move_carets_right(1); + } else if input_char == '🡰' { + ed_model.simple_move_carets_left(1); + } else if input_char == '🡱' { + ed_model.simple_move_carets_up(1); + } else { + //dbg!(input_char); + ed_res_to_res(handle_new_char(&input_char, &mut ed_model))?; + } + } for _ in 0..repeats { ed_model.ed_handle_key_down(&ctrl_cmd_shift(), Up)?; } - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); // remove header for clean tests - assert_eq!(post_lines, expected_post_lines); + assert_eq!(post_lines, add_nls(expected_post_lines)); Ok(()) } - pub fn assert_ctrl_shift_up( - pre_lines: &[&str], - expected_post_lines: &[&str], + pub fn assert_ctrl_shift_up_no_inp( + pre_lines: Vec, + expected_post_lines: Vec, ) -> Result<(), String> { - assert_ctrl_shift_up_repeat(pre_lines, expected_post_lines, 1) + assert_ctrl_shift_up_repeat(pre_lines, expected_post_lines, "", 1) + } + + pub fn assert_ctrl_shift_up_repeat_no_inp( + pre_lines: Vec, + expected_post_lines: Vec, + repeats: usize, + ) -> Result<(), String> { + assert_ctrl_shift_up_repeat(pre_lines, expected_post_lines, "", repeats) } #[test] fn test_ctrl_shift_up_blank() -> Result<(), String> { - // Blank is auto-inserted - assert_ctrl_shift_up(&["┃"], &["┃❮ ❯"])?; - assert_ctrl_shift_up_repeat(&["┃"], &["┃❮ ❯"], 4)?; + // Blank is auto-inserted when creating top level def + assert_ctrl_shift_up_repeat(ovec!["┃"], ovec!["val = ┃❮ ❯"], "val=🡲🡲🡲", 1)?; + assert_ctrl_shift_up_repeat(ovec!["┃"], ovec!["┃❮val = ❯"], "val=🡲🡲🡲", 4)?; Ok(()) } #[test] fn test_ctrl_shift_up_int() -> Result<(), String> { - assert_ctrl_shift_up(&["5┃"], &["┃❮5❯"])?; - assert_ctrl_shift_up_repeat(&["0┃"], &["┃❮0❯"], 3)?; - assert_ctrl_shift_up(&["12345┃"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["┃12345"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["1┃2345"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["12┃345"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["123┃45"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["1234┃5"], &["┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 5┃"], ovec!["val = ┃❮5❯"])?; + assert_ctrl_shift_up_repeat_no_inp(ovec!["val = 0┃"], ovec!["┃❮val = 0❯"], 4)?; + assert_ctrl_shift_up_no_inp(ovec!["val = 12345┃"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃12345"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 1┃2345"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 12┃345"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 123┃45"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 1234┃5"], ovec!["val = ┃❮12345❯"])?; Ok(()) } #[test] fn test_ctrl_shift_up_string() -> Result<(), String> { - assert_ctrl_shift_up(&["\"┃\""], &["┃❮\"\"❯"])?; - assert_ctrl_shift_up(&["┃\"\""], &["┃❮\"\"❯"])?; - assert_ctrl_shift_up(&["\"┃0\""], &["┃❮\"0\"❯"])?; - assert_ctrl_shift_up(&["\"0┃\""], &["┃❮\"0\"❯"])?; - assert_ctrl_shift_up(&["\"abc┃\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up(&["\"ab┃c\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up(&["\"┃abc\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up(&["┃\"abc\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up_repeat(&["\"abc┃\""], &["┃❮\"abc\"❯"], 3)?; - assert_ctrl_shift_up( - &["\"hello, hello.0123456789ZXY{}[]-><-┃\""], - &["┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯"], + assert_ctrl_shift_up_no_inp(ovec!["val = \"┃\""], ovec!["val = ┃❮\"\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃\"\""], ovec!["val = ┃❮\"\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"┃0\""], ovec!["val = ┃❮\"0\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"0┃\""], ovec!["val = ┃❮\"0\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"abc┃\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"ab┃c\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"┃abc\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃\"abc\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_repeat_no_inp(ovec!["val = \"abc┃\""], ovec!["┃❮val = \"abc\"❯"], 4)?; + assert_ctrl_shift_up_no_inp( + ovec!["val = \"hello, hello.0123456789ZXY{}[]-><-┃\""], + ovec!["val = ┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯"], )?; - assert_ctrl_shift_up(&["\"\"┃"], &["┃❮\"\"❯"])?; - assert_ctrl_shift_up(&["\"abc\"┃"], &["┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"\"┃"], ovec!["val = ┃❮\"\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"abc\"┃"], ovec!["val = ┃❮\"abc\"❯"])?; Ok(()) } #[test] fn test_ctrl_shift_up_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - assert_ctrl_shift_up(&["{ ┃ }"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up(&["{┃ }"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up(&["┃{ }"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up(&["{ ┃}"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up_repeat(&["{ ┃ }"], &["┃❮{ }❯"], 4)?; - assert_ctrl_shift_up(&["{ }┃"], &["┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃ }"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ }"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ }"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃}"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_repeat_no_inp(ovec!["val = { ┃ }"], ovec!["┃❮val = { }❯"], 4)?; + assert_ctrl_shift_up_no_inp(ovec!["val = { }┃"], ovec!["val = ┃❮{ }❯"])?; + // TODO uncomment tests once #1649 is fixed + /*assert_ctrl_shift_up_no_inp(ovec!["val = { pear┃ }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { pea┃r }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { p┃ear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃pear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ pear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ pear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { pear ┃}"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_repeat(ovec!["val = { pear┃ }"], ovec!["val = ┃❮{ pear }❯"], 3)?; + assert_ctrl_shift_up_no_inp(ovec!["val = { pear }┃"], ovec!["val = ┃❮{ pear }❯"])?; - /*assert_ctrl_shift_up(&["{ pear┃ }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ pea┃r }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ p┃ear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ ┃pear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{┃ pear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["┃{ pear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ pear ┃}"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up_repeat(&["{ pear┃ }"], &["┃❮{ pear }❯"], 3)?; - assert_ctrl_shift_up(&["{ pear }┃"], &["┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { camelCase123┃ }"], ovec!["val = ┃❮{ camelCase123 }❯"])?;*/ - assert_ctrl_shift_up(&["{ camelCase123┃ }"], &["┃❮{ camelCase123 }❯"])?;*/ - - assert_ctrl_shift_up(&["{ a: \"┃\" }"], &["{ a: ┃❮\"\"❯ }"])?; - assert_ctrl_shift_up(&["{ a: ┃\"\" }"], &["{ a: ┃❮\"\"❯ }"])?; - assert_ctrl_shift_up(&["{ a: \"\"┃ }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ a: \"\" ┃}"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: \"\" ┃}"], &["┃❮{ a: \"\" }❯"], 3)?; - assert_ctrl_shift_up(&["{ a: \"\" }┃"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ a:┃ \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ a┃: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ ┃a: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{┃ a: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["┃{ a: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: \"┃\" }"], &["┃❮{ a: \"\" }❯"], 2)?; - assert_ctrl_shift_up_repeat(&["{ a: \"┃\" }"], &["┃❮{ a: \"\" }❯"], 4)?; - - assert_ctrl_shift_up(&["{ a: 1┃0 }"], &["{ a: ┃❮10❯ }"])?; - assert_ctrl_shift_up(&["{ a: ┃9 }"], &["{ a: ┃❮9❯ }"])?; - assert_ctrl_shift_up(&["{ a: 98┃89 }"], &["{ a: ┃❮9889❯ }"])?; - assert_ctrl_shift_up(&["{ a: 44┃ }"], &["┃❮{ a: 44 }❯"])?; - assert_ctrl_shift_up(&["{ a: 0 ┃}"], &["┃❮{ a: 0 }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: 123 ┃}"], &["┃❮{ a: 123 }❯"], 3)?; - assert_ctrl_shift_up(&["{ a: 96 }┃"], &["┃❮{ a: 96 }❯"])?; - assert_ctrl_shift_up(&["{ a:┃ 985600 }"], &["┃❮{ a: 985600 }❯"])?; - assert_ctrl_shift_up(&["{ a┃: 5648 }"], &["┃❮{ a: 5648 }❯"])?; - assert_ctrl_shift_up(&["{ ┃a: 1000000 }"], &["┃❮{ a: 1000000 }❯"])?; - assert_ctrl_shift_up(&["{┃ a: 1 }"], &["┃❮{ a: 1 }❯"])?; - assert_ctrl_shift_up(&["┃{ a: 900600 }"], &["┃❮{ a: 900600 }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: 10┃000 }"], &["┃❮{ a: 10000 }❯"], 2)?; - assert_ctrl_shift_up_repeat(&["{ a: ┃45 }"], &["┃❮{ a: 45 }❯"], 4)?; - - assert_ctrl_shift_up(&["{ abc: \"de┃\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: \"d┃e\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: \"┃de\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: ┃\"de\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: \"de\"┃ }"], &["┃❮{ abc: \"de\" }❯"])?; - assert_ctrl_shift_up_repeat(&["{ abc: \"d┃e\" }"], &["┃❮{ abc: \"de\" }❯"], 2)?; - assert_ctrl_shift_up_repeat(&["{ abc: \"d┃e\" }"], &["┃❮{ abc: \"de\" }❯"], 3)?; - - assert_ctrl_shift_up( - &["{ camelCase123: \"hello, hello.012┃3456789ZXY{}[]-><-\" }"], - &["{ camelCase123: ┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯ }"], + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"┃\" }"], ovec!["val = { a: ┃❮\"\"❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: ┃\"\" }"], ovec!["val = { a: ┃❮\"\"❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"\"┃ }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"\" ┃}"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: \"\" ┃}"], + ovec!["┃❮val = { a: \"\" }❯"], + 3, )?; - assert_ctrl_shift_up( - &["{ camel┃Case123: \"hello, hello.0123456789ZXY{}[]-><-\" }"], - &["┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"\" }┃"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a:┃ \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a┃: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃a: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ a: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ a: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: \"┃\" }"], + ovec!["val = ┃❮{ a: \"\" }❯"], + 2, )?; - assert_ctrl_shift_up_repeat( - &["{ camelCase123: \"hello, hello┃.0123456789ZXY{}[]-><-\" }"], - &["┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: \"┃\" }"], + ovec!["┃❮val = { a: \"\" }❯"], + 4, + )?; + + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 1┃0 }"], ovec!["val = { a: ┃❮10❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: ┃9 }"], ovec!["val = { a: ┃❮9❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 98┃89 }"], ovec!["val = { a: ┃❮9889❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 44┃ }"], ovec!["val = ┃❮{ a: 44 }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 0 ┃}"], ovec!["val = ┃❮{ a: 0 }❯"])?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: 123 ┃}"], + ovec!["┃❮val = { a: 123 }❯"], + 3, + )?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 96 }┃"], ovec!["val = ┃❮{ a: 96 }❯"])?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { a:┃ 985600 }"], + ovec!["val = ┃❮{ a: 985600 }❯"], + )?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a┃: 5648 }"], ovec!["val = ┃❮{ a: 5648 }❯"])?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { ┃a: 1000000 }"], + ovec!["val = ┃❮{ a: 1000000 }❯"], + )?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ a: 1 }"], ovec!["val = ┃❮{ a: 1 }❯"])?; + assert_ctrl_shift_up_no_inp( + ovec!["val = ┃{ a: 900600 }"], + ovec!["val = ┃❮{ a: 900600 }❯"], + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: 10┃000 }"], + ovec!["val = ┃❮{ a: 10000 }❯"], + 2, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: ┃45 }"], + ovec!["┃❮val = { a: 45 }❯"], + 4, + )?; + + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"de┃\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"d┃e\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"┃de\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: ┃\"de\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"de\"┃ }"], + ovec!["val = ┃❮{ abc: \"de\" }❯"], + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: \"d┃e\" }"], + ovec!["val = ┃❮{ abc: \"de\" }❯"], + 2, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: \"d┃e\" }"], + ovec!["┃❮val = { abc: \"de\" }❯"], + 3, + )?; + + assert_ctrl_shift_up_no_inp( + ovec!["val = { camelCase123: \"hello, hello.012┃3456789ZXY{}[]-><-\" }"], + ovec!["val = { camelCase123: ┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { camel┃Case123: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + ovec!["val = ┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { camelCase123: \"hello, hello┃.0123456789ZXY{}[]-><-\" }"], + ovec!["val = ┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], 2, )?; @@ -1896,103 +2850,173 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_nested_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - assert_ctrl_shift_up(&["{ abc: { ┃ } }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: {┃ } }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: ┃{ } }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { ┃} }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { }┃ }"], &["┃❮{ abc: { } }❯"])?; - - /*assert_ctrl_shift_up(&["{ abc: { ┃d } }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: {┃ d } }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: ┃{ d } }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { d ┃} }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { d┃e } }"], &["{ abc: ┃❮{ de }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { d }┃ }"], &["┃❮{ abc: { d } }❯"])?; - assert_ctrl_shift_up(&["┃{ abc: { d } }"], &["┃❮{ abc: { d } }❯"])?;*/ - - assert_ctrl_shift_up(&["{ abc: { de: { ┃ } } }"], &["{ abc: { de: ┃❮{ }❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: ┃{ } } }"], &["{ abc: { de: ┃❮{ }❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: { }┃ } }"], &["{ abc: ┃❮{ de: { } }❯ }"])?; - - assert_ctrl_shift_up(&["{ abc: { de: \"┃\" } }"], &["{ abc: { de: ┃❮\"\"❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: ┃\"\" } }"], &["{ abc: { de: ┃❮\"\"❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: \"\"┃ } }"], &["{ abc: ┃❮{ de: \"\" }❯ }"])?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g┃\" } }"], - &["{ abc: { de: ┃❮\"f g\"❯ } }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { ┃ } }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: { de┃: \"f g\" } }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: {┃ } }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: {┃ de: \"f g\" } }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: ┃{ } }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g\" ┃} }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { ┃} }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g\" }┃ }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], - )?; - assert_ctrl_shift_up( - &["┃{ abc: { de: \"f g\" } }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], - )?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g\" } }┃"], - &["┃❮{ abc: { de: \"f g\" } }❯"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { }┃ }"], + ovec!["val = ┃❮{ abc: { } }❯"], )?; - assert_ctrl_shift_up_repeat( - &["{ abc: { de: \"f g┃\" } }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + // TODO uncomment tests once #1649 is fixed + /*assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { ┃d } }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: {┃ d } }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: ┃{ d } }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { d ┃} }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { d┃e } }"], ovec!["val = { abc: ┃❮{ de }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { d }┃ }"], ovec!["val = ┃❮{ abc: { d } }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ abc: { d } }"], ovec!["val = ┃❮{ abc: { d } }❯"])?;*/ + + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: { ┃ } } }"], + ovec!["val = { abc: { de: ┃❮{ }❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: ┃{ } } }"], + ovec!["val = { abc: { de: ┃❮{ }❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: { }┃ } }"], + ovec!["val = { abc: ┃❮{ de: { } }❯ }"], + )?; + + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"┃\" } }"], + ovec!["val = { abc: { de: ┃❮\"\"❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: ┃\"\" } }"], + ovec!["val = { abc: { de: ┃❮\"\"❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"\"┃ } }"], + ovec!["val = { abc: ┃❮{ de: \"\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g┃\" } }"], + ovec!["val = { abc: { de: ┃❮\"f g\"❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de┃: \"f g\" } }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: {┃ de: \"f g\" } }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g\" ┃} }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g\" }┃ }"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = ┃{ abc: { de: \"f g\" } }"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g\" } }┃"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], + )?; + + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: \"f g┃\" } }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], 2, )?; - assert_ctrl_shift_up_repeat( - &["{ abc: { de: ┃\"f g\" } }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃\"f g\" } }"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], 3, )?; - assert_ctrl_shift_up_repeat( - &["{ abc: { de: ┃\"f g\" } }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃\"f g\" } }"], + ovec!["┃❮val = { abc: { de: \"f g\" } }❯"], 4, )?; - assert_ctrl_shift_up(&["{ abc: { de: ┃951 } }"], &["{ abc: { de: ┃❮951❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 11┃0 } }"], &["{ abc: { de: ┃❮110❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 444┃ } }"], &["{ abc: ┃❮{ de: 444 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { de┃: 99 } }"], &["{ abc: ┃❮{ de: 99 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: {┃ de: 0 } }"], &["{ abc: ┃❮{ de: 0 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 230 ┃} }"], &["{ abc: ┃❮{ de: 230 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 7 }┃ }"], &["┃❮{ abc: { de: 7 } }❯"])?; - assert_ctrl_shift_up(&["┃{ abc: { de: 1 } }"], &["┃❮{ abc: { de: 1 } }❯"])?; - assert_ctrl_shift_up( - &["{ abc: { de: 111111 } }┃"], - &["┃❮{ abc: { de: 111111 } }❯"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: ┃951 } }"], + ovec!["val = { abc: { de: ┃❮951❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 11┃0 } }"], + ovec!["val = { abc: { de: ┃❮110❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 444┃ } }"], + ovec!["val = { abc: ┃❮{ de: 444 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de┃: 99 } }"], + ovec!["val = { abc: ┃❮{ de: 99 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: {┃ de: 0 } }"], + ovec!["val = { abc: ┃❮{ de: 0 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 230 ┃} }"], + ovec!["val = { abc: ┃❮{ de: 230 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 7 }┃ }"], + ovec!["val = ┃❮{ abc: { de: 7 } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = ┃{ abc: { de: 1 } }"], + ovec!["val = ┃❮{ abc: { de: 1 } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 111111 } }┃"], + ovec!["val = ┃❮{ abc: { de: 111111 } }❯"], )?; - assert_ctrl_shift_up_repeat(&["{ abc: { de: 1┃5 } }"], &["{ abc: ┃❮{ de: 15 }❯ }"], 2)?; - assert_ctrl_shift_up_repeat(&["{ abc: { de: ┃55 } }"], &["┃❮{ abc: { de: 55 } }❯"], 3)?; - assert_ctrl_shift_up_repeat(&["{ abc: { de: ┃400 } }"], &["┃❮{ abc: { de: 400 } }❯"], 4)?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: 1┃5 } }"], + ovec!["val = { abc: ┃❮{ de: 15 }❯ }"], + 2, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃55 } }"], + ovec!["val = ┃❮{ abc: { de: 55 } }❯"], + 3, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃400 } }"], + ovec!["┃❮val = { abc: { de: 400 } }❯"], + 5, + )?; - /*assert_ctrl_shift_up_repeat( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - &["{ g: { oi: { ng: { d: ┃❮{ e: { e: { p: { camelCase } } } }❯ } } } }"], + // TODO uncomment tests once #1649 is fixed + /*assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + ovec!["val = { g: { oi: { ng: { d: ┃❮{ e: { e: { p: { camelCase } } } }❯ } } } }"], 4, )?; - assert_ctrl_shift_up_repeat( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - &["{ g: ┃❮{ oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } }❯ }"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + ovec!["val = { g: ┃❮{ oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } }❯ }"], 7, )?; - assert_ctrl_shift_up_repeat( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - &["┃❮{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + ovec!["val = ┃❮{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }❯"], 9, )?;*/ @@ -2002,16 +3026,23 @@ pub mod test_ed_update { // Create ed_model from pre_lines DSL, do handle_new_char() with new_char_seq, select current Expr2, // check if generated tooltips match expected_tooltips. pub fn assert_type_tooltips_seq( - pre_lines: &[&str], - expected_tooltips: &[&str], + pre_lines: Vec, + expected_tooltips: Vec, new_char_seq: &str, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for input_char in new_char_seq.chars() { if input_char == '🡲' { @@ -2024,7 +3055,7 @@ pub mod test_ed_update { for expected_tooltip in expected_tooltips.iter() { ed_model.select_expr()?; - let created_tooltip = ed_model.selected_expr_opt.unwrap().type_str; + let created_tooltip = ed_model.selected_block_opt.unwrap().type_str; assert_eq!( created_tooltip.as_str(ed_model.module.env.pool), @@ -2038,54 +3069,63 @@ pub mod test_ed_update { // Create ed_model from pre_lines DSL, do handle_new_char() with new_char, select current Expr2, // check if generated tooltip matches expected_tooltip. pub fn assert_type_tooltip( - pre_lines: &[&str], + pre_lines: Vec, expected_tooltip: &str, new_char: char, ) -> Result<(), String> { - assert_type_tooltips_seq(pre_lines, &[expected_tooltip], &new_char.to_string()) + assert_type_tooltips_seq(pre_lines, ovec![expected_tooltip], &new_char.to_string()) } - pub fn assert_type_tooltip_clean(lines: &[&str], expected_tooltip: &str) -> Result<(), String> { - assert_type_tooltips_seq(lines, &[expected_tooltip], "") + pub fn assert_type_tooltip_clean( + lines: Vec, + expected_tooltip: &str, + ) -> Result<(), String> { + assert_type_tooltips_seq(lines, ovec![expected_tooltip], "") } // When doing ctrl+shift+up multiple times we select the surrounding expression every time, // every new selection should have the correct tooltip pub fn assert_type_tooltips_clean( - lines: &[&str], - expected_tooltips: &[&str], + lines: Vec, + expected_tooltips: Vec, ) -> Result<(), String> { assert_type_tooltips_seq(lines, expected_tooltips, "") } #[test] fn test_type_tooltip() -> Result<(), String> { - assert_type_tooltip(&["┃"], "{}", '{')?; + assert_type_tooltip_clean(ovec!["val = ┃5"], "Num *")?; + assert_type_tooltip_clean(ovec!["val = 42┃"], "Num *")?; + assert_type_tooltip_clean(ovec!["val = 13┃7"], "Num *")?; - assert_type_tooltip_clean(&["┃5"], "Num *")?; - assert_type_tooltip_clean(&["42┃"], "Num *")?; - assert_type_tooltip_clean(&["13┃7"], "Num *")?; + assert_type_tooltip_clean(ovec!["val = \"┃abc\""], "Str")?; + assert_type_tooltip_clean(ovec!["val = ┃\"abc\""], "Str")?; + assert_type_tooltip_clean(ovec!["val = \"abc\"┃"], "Str")?; - assert_type_tooltip_clean(&["\"┃abc\""], "Str")?; - assert_type_tooltip_clean(&["┃\"abc\""], "Str")?; - assert_type_tooltip_clean(&["\"abc\"┃"], "Str")?; + assert_type_tooltip_clean(ovec!["val = { ┃ }"], "{}")?; + assert_type_tooltip_clean(ovec!["val = { a: \"abc\" }┃"], "{ a : Str }")?; + assert_type_tooltip_clean(ovec!["val = { ┃a: 0 }"], "{ a : Num * }")?; + assert_type_tooltip_clean(ovec!["val = { ┃z: { } }"], "{ z : {} }")?; + assert_type_tooltip_clean(ovec!["val = { camelCase: ┃0 }"], "Num *")?; - assert_type_tooltip_clean(&["{ a: \"abc\" }┃"], "{ a : Str }")?; - assert_type_tooltip_clean(&["{ ┃a: 0 }"], "{ a : Num * }")?; - assert_type_tooltip_clean(&["{ ┃z: { } }"], "{ z : {} }")?; - assert_type_tooltip_clean(&["{ camelCase: ┃0 }"], "Num *")?; + assert_type_tooltips_seq(ovec!["┃"], ovec!["*"], "val=🡲🡲🡲")?; + assert_type_tooltips_seq( + ovec!["┃"], + ovec!["*", "{ a : * }", "{ a : * }"], + "val=🡲🡲🡲{a:", + )?; - assert_type_tooltips_seq(&["┃"], &["*"], "")?; - assert_type_tooltips_seq(&["┃"], &["*", "{ a : * }"], "{a:")?; - - assert_type_tooltips_clean(&["{ camelCase: ┃0 }"], &["Num *", "{ camelCase : Num * }"])?; assert_type_tooltips_clean( - &["{ a: { b: { c: \"hello┃, hello.0123456789ZXY{}[]-><-\" } } }"], - &[ + ovec!["val = { camelCase: ┃0 }"], + ovec!["Num *", "{ camelCase : Num * }"], + )?; + assert_type_tooltips_clean( + ovec!["val = { a: { b: { c: \"hello┃, hello.0123456789ZXY{}[]-><-\" } } }"], + ovec![ "Str", "{ c : Str }", "{ b : { c : Str } }", - "{ a : { b : { c : Str } } }", + "{ a : { b : { c : Str } } }" ], )?; @@ -2094,56 +3134,64 @@ pub mod test_ed_update { #[test] fn test_type_tooltip_list() -> Result<(), String> { - assert_type_tooltip(&["┃"], "List *", '[')?; - assert_type_tooltips_seq(&["┃"], &["List (Num *)"], "[0")?; - assert_type_tooltips_seq(&["┃"], &["List (Num *)", "List (List (Num *))"], "[[0")?; - assert_type_tooltips_seq(&["┃"], &["Str", "List Str"], "[\"a")?; - assert_type_tooltips_seq( - &["┃"], - &[ + assert_type_tooltip_clean(ovec!["val = [ ┃ ]"], "List *")?; + assert_type_tooltips_clean(ovec!["val = [ ┃0 ]"], ovec!["Num *", "List (Num *)"])?; + assert_type_tooltips_clean( + ovec!["val = [ [ ┃0 ] ]"], + ovec!["Num *", "List (Num *)", "List (List (Num *))"], + )?; + + assert_type_tooltips_clean( + ovec!["val = [ [ [ \"ab┃c\" ] ] ]"], + ovec![ "Str", "List Str", "List (List Str)", - "List (List (List Str))", + "List (List (List Str))" ], - "[[[\"a", )?; - assert_type_tooltips_seq( - &["┃"], - &[ + assert_type_tooltips_clean( + ovec!["val = [ [ { a┃: 1 } ] ]"], + ovec![ "{ a : Num * }", "List { a : Num * }", - "List (List { a : Num * })", + "List (List { a : Num * })" ], - "[[{a:1", )?; // multi element lists - assert_type_tooltips_seq(&["┃"], &["List (Num *)"], "[1,2,3")?; - assert_type_tooltips_seq(&["┃"], &["Str", "List Str"], "[\"abc🡲,\"de🡲,\"f")?; - assert_type_tooltips_seq( - &["┃"], - &["{ a : Num * }", "List { a : Num * }"], - "[{a:0🡲🡲,{a:12🡲🡲,{a:444", + assert_type_tooltips_clean(ovec!["val = [ ┃1, 2, 3 ]"], ovec!["Num *", "List (Num *)"])?; + assert_type_tooltips_clean( + ovec!["val = [ \"┃abc\", \"de\", \"f\" ]"], + ovec!["Str", "List Str"], )?; + assert_type_tooltips_clean( + ovec!["val = [ { a:┃ 1 }, { a: 12 }, { a: 444 } ]"], + ovec!["{ a : Num * }", "List { a : Num * }"], + )?; + Ok(()) } #[test] fn test_type_tooltip_mismatch() -> Result<(), String> { - assert_type_tooltips_seq(&["┃"], &["Str", "List "], "[1,\"abc")?; - assert_type_tooltips_seq(&["┃"], &["List "], "[\"abc🡲,50")?; - - assert_type_tooltips_seq( - &["┃"], - &["Str", "{ a : Str }", "List "], - "[{a:0🡲🡲,{a:\"0", + assert_type_tooltips_clean( + ovec!["val = [ 1, \"ab┃c\" ]"], + ovec!["Str", "List "], + )?; + assert_type_tooltips_clean( + ovec!["val = [ \"abc\", 5┃0 ]"], + ovec!["Num *", "List "], )?; - assert_type_tooltips_seq( - &["┃"], - &["List (Num *)", "List (List )"], - "[[0,1,\"2🡲🡲🡲,[3, 4, 5", + assert_type_tooltips_clean( + ovec!["val = [ { a: 0 }, { a: \"0┃\" } ]"], + ovec!["Str", "{ a : Str }", "List "], + )?; + + assert_type_tooltips_clean( + ovec!["val = [ [ 0, 1, \"2\" ], [ 3, 4, 5 ┃] ]"], + ovec!["List (Num *)", "List (List )"], )?; Ok(()) @@ -2155,17 +3203,24 @@ pub mod test_ed_update { // move_caret_fun. Next check if modified ed_model has expected string representation of code, caret position and // active selection. fn assert_ctrl_shift_up_move( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, repeats: usize, move_caret_fun: ModelMoveCaretFun, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for _ in 0..repeats { ed_model.ed_handle_key_down(&ctrl_cmd_shift(), Up)?; @@ -2173,32 +3228,45 @@ pub mod test_ed_update { move_caret_fun(&mut ed_model, &no_mods())?; - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); // remove header for clean tests assert_eq!(post_lines, expected_post_lines); Ok(()) } + fn assert_ctrl_shift_up_move_nls( + pre_lines: Vec, + expected_post_lines: Vec, + repeats: usize, + move_caret_fun: ModelMoveCaretFun, + ) -> Result<(), String> { + assert_ctrl_shift_up_move( + pre_lines, + add_nls(expected_post_lines), + repeats, + move_caret_fun, + ) + } + fn assert_ctrl_shift_single_up_move( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, move_caret_fun: ModelMoveCaretFun, ) -> Result<(), String> { assert_ctrl_shift_up_move(pre_lines, expected_post_lines, 1, move_caret_fun) } + fn assert_ctrl_shift_single_up_move_nls( + pre_lines: Vec, + expected_post_lines: Vec, + move_caret_fun: ModelMoveCaretFun, + ) -> Result<(), String> { + assert_ctrl_shift_single_up_move(pre_lines, add_nls(expected_post_lines), move_caret_fun) + } + // because complex lifetime stuff - macro_rules! move_right { - () => { - |ed_model, modifiers| EdModel::move_caret_right(ed_model, modifiers) - }; - } - macro_rules! move_left { - () => { - |ed_model, modifiers| EdModel::move_caret_left(ed_model, modifiers) - }; - } macro_rules! move_up { () => { |ed_model, modifiers| EdModel::move_caret_up(ed_model, modifiers) @@ -2220,31 +3288,38 @@ pub mod test_ed_update { }; } - #[test] - fn test_ctrl_shift_up_move_blank() -> Result<(), String> { - // Blank is auto-inserted - assert_ctrl_shift_single_up_move(&["┃"], &[" ┃"], move_right!())?; - assert_ctrl_shift_up_move(&["┃"], &["┃ "], 3, move_left!())?; - - Ok(()) - } - #[test] fn test_ctrl_shift_up_move_int() -> Result<(), String> { - assert_ctrl_shift_single_up_move(&["┃0"], &["0┃"], move_down!())?; - assert_ctrl_shift_single_up_move(&["┃9654"], &["┃9654"], move_up!())?; - assert_ctrl_shift_single_up_move(&["┃100546"], &["100546┃"], move_end!())?; + assert_ctrl_shift_single_up_move_nls(ovec!["val = ┃0"], ovec!["val = 0┃"], move_down!())?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃9654"], + ovec!["val = ┃9654"], + move_up!(), + )?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃100546"], + ovec!["val = 100546┃"], + move_end!(), + )?; Ok(()) } #[test] fn test_ctrl_shift_up_move_string() -> Result<(), String> { - assert_ctrl_shift_single_up_move(&["┃\"\""], &["\"\"┃"], move_down!())?; - assert_ctrl_shift_single_up_move(&["┃\"abc\""], &["┃\"abc\""], move_up!())?; - assert_ctrl_shift_single_up_move( - &["┃\"hello, hello.0123456789ZXY{}[]-><-\""], - &["\"hello, hello.0123456789ZXY{}[]-><-\"┃"], + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃\"\""], + ovec!["val = \"\"┃"], + move_down!(), + )?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃\"abc\""], + ovec!["val = ┃\"abc\""], + move_up!(), + )?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃\"hello, hello.0123456789ZXY{}[]-><-\""], + ovec!["val = \"hello, hello.0123456789ZXY{}[]-><-\"┃"], move_end!(), )?; @@ -2253,27 +3328,35 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_move_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - assert_ctrl_shift_single_up_move(&["┃{ }"], &["┃{ }"], move_home!())?; - //assert_ctrl_shift_single_up_move(&["┃{ a }"], &["{ a }┃"], move_down!())?; - //assert_ctrl_shift_single_up_move(&["┃{ a: { b } }"], &["{ a: { b } }┃"], move_right!())?; - assert_ctrl_shift_single_up_move(&["{ a: { ┃ } }"], &["{ a: { } }┃"], move_end!())?; - assert_ctrl_shift_up_move( - &["{ a: { b: { ┃ } } }"], - &["{ a: ┃{ b: { } } }"], + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃{ }"], + ovec!["┃val = { }"], + move_home!(), + )?; + // TODO uncomment tests once #1649 is fixed. + //assert_ctrl_shift_single_up_move(ovec!["┃{ a }"], ovec!["{ a }┃"], move_down!())?; + //assert_ctrl_shift_single_up_move(ovec!["┃{ a: { b } }"], ovec!["{ a: { b } }┃"], move_right!())?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = { a: { ┃ } }"], + ovec!["val = { a: { } }┃"], + move_end!(), + )?; + assert_ctrl_shift_up_move_nls( + ovec!["val = { a: { b: { ┃ } } }"], + ovec!["val = { a: ┃{ b: { } } }"], 2, move_up!(), )?; - assert_ctrl_shift_up_move( - &["{ camelCase: { cC123: \"hello┃, hello.0123456789ZXY{}[]-><-\" } }"], - &["{ camelCase: { cC123: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], + assert_ctrl_shift_up_move_nls( + ovec!["val = { camelCase: { cC123: \"hello┃, hello.0123456789ZXY{}[]-><-\" } }"], + ovec!["val = { camelCase: { cC123: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], 2, move_down!(), )?; - assert_ctrl_shift_up_move( - &["{ camelCase: { cC123: 9┃5 } }"], - &["{ camelCase: { cC123: 95 }┃ }"], + assert_ctrl_shift_up_move_nls( + ovec!["val = { camelCase: { cC123: 9┃5 } }"], + ovec!["val = { camelCase: { cC123: 95 }┃ }"], 2, move_down!(), )?; @@ -2285,16 +3368,23 @@ pub mod test_ed_update { // Next check if modified ed_model has expected string representation of code, caret position and // active selection. fn assert_ctrl_shift_up_backspace( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, repeats: usize, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for _ in 0..repeats { ed_model.ed_handle_key_down(&ctrl_cmd_shift(), Up)?; @@ -2302,34 +3392,41 @@ pub mod test_ed_update { handle_new_char(&'\u{8}', &mut ed_model)?; // \u{8} is the char for backspace on linux - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); assert_eq!(post_lines, expected_post_lines); Ok(()) } + fn assert_ctrl_shift_up_backspace_nls( + pre_lines: Vec, + expected_post_lines: Vec, + repeats: usize, + ) -> Result<(), String> { + assert_ctrl_shift_up_backspace(pre_lines, add_nls(expected_post_lines), repeats) + } fn assert_ctrl_shift_single_up_backspace( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, ) -> Result<(), String> { assert_ctrl_shift_up_backspace(pre_lines, expected_post_lines, 1) } - #[test] - fn test_ctrl_shift_up_backspace_blank() -> Result<(), String> { - // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["┃"], &["┃ "])?; - - Ok(()) + fn assert_ctrl_shift_single_up_backspace_nls( + pre_lines: Vec, + expected_post_lines: Vec, + ) -> Result<(), String> { + assert_ctrl_shift_single_up_backspace(pre_lines, add_nls(expected_post_lines)) } #[test] fn test_ctrl_shift_up_backspace_int() -> Result<(), String> { // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["95┃21"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["0┃"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["┃10000"], &["┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = 95┃21"], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = 0┃"], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = ┃10000"], ovec!["val = ┃ "])?; Ok(()) } @@ -2337,12 +3434,12 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_backspace_string() -> Result<(), String> { // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["\"┃\""], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["\"\"┃"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["┃\"abc\""], &["┃ "])?; - assert_ctrl_shift_single_up_backspace( - &["\"hello┃, hello.0123456789ZXY{}[]-><-\""], - &["┃ "], + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = \"┃\""], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = \"\"┃"], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = ┃\"abc\""], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = \"hello┃, hello.0123456789ZXY{}[]-><-\""], + ovec!["val = ┃ "], )?; Ok(()) @@ -2350,35 +3447,49 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_backspace_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["{┃ }"], &["┃ "])?; - //assert_ctrl_shift_single_up_backspace(&["{ a┃ }"], &["┃ "])?; - //assert_ctrl_shift_single_up_backspace(&["{ a: { b }┃ }"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["{ a: \"b cd\"┃ }"], &["┃ "])?; + // Blank is inserted when root of Expr2 is deleted + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = {┃ }"], ovec!["val = ┃ "])?; - //assert_ctrl_shift_single_up_backspace(&["{ a: ┃{ b } }"], &["{ a: ┃ }"])?; - assert_ctrl_shift_single_up_backspace(&["{ a: \"┃b cd\" }"], &["{ a: ┃ }"])?; - assert_ctrl_shift_single_up_backspace(&["{ a: ┃12 }"], &["{ a: ┃ }"])?; + // TODO: uncomment tests, once isue #1649 is fixed + //assert_ctrl_shift_single_up_backspace(ovec!["{ a┃ }"], ovec!["┃ "])?; + //assert_ctrl_shift_single_up_backspace(ovec!["{ a: { b }┃ }"], ovec!["┃ "])?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = { a: \"b cd\"┃ }"], + ovec!["val = ┃ "], + )?; + + //assert_ctrl_shift_single_up_backspace(ovec!["{ a: ┃{ b } }"], ovec!["{ a: ┃ }"])?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = { a: \"┃b cd\" }"], + ovec!["val = { a: ┃ }"], + )?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = { a: ┃12 }"], + ovec!["val = { a: ┃ }"], + )?; /*assert_ctrl_shift_single_up_backspace( - &["{ g: { oi: { ng: { d: { ┃e: { e: { p: { camelCase } } } } } } } }"], - &["{ g: { oi: { ng: { d: ┃ } } } }"], + ovec!["{ g: { oi: { ng: { d: { ┃e: { e: { p: { camelCase } } } } } } } }"], + ovec!["{ g: { oi: { ng: { d: ┃ } } } }"], )?;*/ - assert_ctrl_shift_up_backspace( - &["{ a: { b: { c: \"abc┃ \" } } }"], - &["{ a: { b: ┃ } }"], + assert_ctrl_shift_up_backspace_nls( + ovec!["val = { a: { b: { c: \"abc┃ \" } } }"], + ovec!["val = { a: { b: ┃ } }"], 2, )?; - assert_ctrl_shift_up_backspace( - &["{ a: { b: { c: 100┃000 } } }"], - &["{ a: { b: ┃ } }"], + assert_ctrl_shift_up_backspace_nls( + ovec!["val = { a: { b: { c: 100┃000 } } }"], + ovec!["val = { a: { b: ┃ } }"], + 2, + )?; + assert_ctrl_shift_up_backspace_nls( + ovec!["val = { a: { b: { c: {┃ } } } }"], + ovec!["val = { a: { b: ┃ } }"], 2, )?; - assert_ctrl_shift_up_backspace(&["{ a: { b: { c: {┃ } } } }"], &["{ a: { b: ┃ } }"], 2)?; /*assert_ctrl_shift_up_backspace( - &["{ g: { oi: { ng: { d: { e: { e: { p┃: { camelCase } } } } } } } }"], - &["{ g: ┃ }"], + ovec!["{ g: { oi: { ng: { d: { e: { e: { p┃: { camelCase } } } } } } } }"], + ovec!["{ g: ┃ }"], 6, )?;*/ diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index 1181d110f2..6fee1301f5 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -1,7 +1,7 @@ use super::ed_model::EdModel; use crate::editor::config::Config; use crate::editor::ed_error::EdResult; -use crate::editor::mvc::ed_model::SelectedExpression; +use crate::editor::mvc::ed_model::SelectedBlock; use crate::editor::render_ast::build_code_graphics; use crate::editor::render_debug::build_debug_graphics; use crate::editor::resources::strings::START_TIP; @@ -20,33 +20,49 @@ use winit::dpi::PhysicalSize; #[derive(Debug)] pub struct RenderedWgpu { - pub text_sections: Vec, - pub rects: Vec, + pub text_sections_behind: Vec, // displayed in front of rect_behind, behind everything else + pub text_sections_front: Vec, // displayed in front of everything + pub rects_behind: Vec, // displayed at lowest depth + pub rects_front: Vec, // displayed in front of text_sections_behind, behind text_sections_front } impl RenderedWgpu { pub fn new() -> Self { Self { - text_sections: Vec::new(), - rects: Vec::new(), + text_sections_behind: Vec::new(), + text_sections_front: Vec::new(), + rects_behind: Vec::new(), + rects_front: Vec::new(), } } - pub fn add_text(&mut self, new_text_section: glyph_brush::OwnedSection) { - self.text_sections.push(new_text_section); + pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_behind.push(new_text_section); } - pub fn add_rect(&mut self, new_rect: Rect) { - self.rects.push(new_rect); + pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_front.push(new_text_section); } - pub fn add_rects(&mut self, new_rects: Vec) { - self.rects.extend(new_rects); + pub fn add_rect_behind(&mut self, new_rect: Rect) { + self.rects_behind.push(new_rect); + } + + pub fn add_rects_behind(&mut self, new_rects: Vec) { + self.rects_behind.extend(new_rects); + } + + pub fn add_rect_front(&mut self, new_rect: Rect) { + self.rects_front.push(new_rect); } pub fn extend(&mut self, rendered_wgpu: RenderedWgpu) { - self.text_sections.extend(rendered_wgpu.text_sections); - self.rects.extend(rendered_wgpu.rects); + self.text_sections_behind + .extend(rendered_wgpu.text_sections_behind); + self.text_sections_front + .extend(rendered_wgpu.text_sections_front); + self.rects_behind.extend(rendered_wgpu.rects_behind); + self.rects_front.extend(rendered_wgpu.rects_front); } } @@ -61,7 +77,10 @@ pub fn model_to_wgpu<'a>( let mut all_rendered = RenderedWgpu::new(); - let tip_txt_coords = (txt_coords.x, txt_coords.y - 4.0 * config.code_font_size); + let tip_txt_coords = ( + txt_coords.x, + txt_coords.y - (START_TIP.matches('\n').count() as f32 + 1.0) * config.code_font_size, + ); let start_tip_text = owned_section_from_text(&Text { position: tip_txt_coords.into(), @@ -72,15 +91,15 @@ pub fn model_to_wgpu<'a>( ..Default::default() }); - all_rendered.add_text(start_tip_text); + all_rendered.add_text_behind(start_tip_text); let rendered_code_graphics = build_code_graphics( - ed_model.markup_node_pool.get(ed_model.markup_root_id), + &ed_model.markup_ids, size, txt_coords, config, glyph_dim_rect, - &ed_model.markup_node_pool, + &ed_model.mark_node_pool, )?; all_rendered.extend(rendered_code_graphics); @@ -93,7 +112,7 @@ pub fn model_to_wgpu<'a>( let rendered_selection = build_selection_graphics( caret_w_sel_vec, - &ed_model.selected_expr_opt, + &ed_model.selected_block_opt, txt_coords, config, glyph_dim_rect, @@ -103,7 +122,7 @@ pub fn model_to_wgpu<'a>( all_rendered.extend(rendered_selection); if ed_model.show_debug_view { - all_rendered.add_text(build_debug_graphics(size, txt_coords, config, ed_model)?); + all_rendered.add_text_behind(build_debug_graphics(size, txt_coords, config, ed_model)?); } Ok(all_rendered) @@ -111,7 +130,7 @@ pub fn model_to_wgpu<'a>( pub fn build_selection_graphics( caret_w_select_vec: Vec, - selected_expr_opt: &Option, + selected_expr_opt: &Option, txt_coords: Vector2, config: &Config, glyph_dim_rect: Rect, @@ -139,7 +158,7 @@ pub fn build_selection_graphics( let width = ((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_width); - all_rendered.add_rect(make_selection_rect( + all_rendered.add_rect_behind(make_selection_rect( sel_rect_x, sel_rect_y, width, @@ -158,12 +177,12 @@ pub fn build_selection_graphics( let (tip_rect, tip_text) = tooltip.render_tooltip(&glyph_dim_rect, &config.ed_theme.ui_theme); - all_rendered.add_rect(tip_rect); - all_rendered.add_text(tip_text); + all_rendered.add_rect_front(tip_rect); + all_rendered.add_text_front(tip_text); } } - all_rendered.add_rect(make_caret_rect( + all_rendered.add_rect_front(make_caret_rect( top_left_x, top_left_y, &glyph_dim_rect, diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index af6eb80f84..237da57d86 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -25,6 +25,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult EdResult EdResult EdResult { update_small_int_num(number, &content_str)?; diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs new file mode 100644 index 0000000000..697531bb5a --- /dev/null +++ b/editor/src/editor/mvc/let_update.rs @@ -0,0 +1,185 @@ +use roc_module::symbol::Symbol; + +use crate::editor::ed_error::EdResult; +use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::markup::common_nodes::new_equals_mn; +use crate::editor::markup::nodes::MarkupNode; +use crate::editor::mvc::app_update::InputOutcome; +use crate::editor::mvc::ed_model::EdModel; +use crate::editor::mvc::ed_update::get_node_context; +use crate::editor::mvc::ed_update::NodeContext; +use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::{Expr2, ValueDef}; +use crate::lang::parse::ASTNodeId; +use crate::lang::pattern::Pattern2; + +pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node, + parent_id_opt, + ast_node_id, + } = get_node_context(ed_model)?; + + let is_blank_node = curr_mark_node.is_blank(); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); + + let val_name_string = new_char.to_string(); + // safe unwrap because our ArrString has a 30B capacity + let val_expr2_node = Expr2::Blank; + let val_expr_id = ed_model.module.env.pool.add(val_expr2_node); + + let ident_id = ed_model + .module + .env + .ident_ids + .add(val_name_string.clone().into()); + let var_symbol = Symbol::new(ed_model.module.env.home, ident_id); + let body = Expr2::Var(var_symbol); + let body_id = ed_model.module.env.pool.add(body); + + let pattern = Pattern2::Identifier(var_symbol); + let pattern_id = ed_model.module.env.pool.add(pattern); + + let value_def = ValueDef::NoAnnotation { + pattern_id, + expr_id: val_expr_id, + expr_var: ed_model.module.env.var_store.fresh(), + }; + let def_id = ed_model.module.env.pool.add(value_def); + + let expr2_node = Expr2::LetValue { + def_id, + body_id, + body_var: ed_model.module.env.var_store.fresh(), + }; + + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, expr2_node); + + let val_name_mark_node = MarkupNode::Text { + content: val_name_string, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: Some(curr_mark_node_id), + newlines_at_end: curr_mark_node_nls, + }; + + let val_name_mn_id = ed_model.add_mark_node(val_name_mark_node); + + let equals_mn_id = ed_model.add_mark_node(new_equals_mn(ast_node_id, Some(curr_mark_node_id))); + + let body_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls( + ASTNodeId::AExprId(val_expr_id), + Some(curr_mark_node_id), + 1, + )); + + let val_mark_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], + parent_id_opt, + newlines_at_end: 1, + }; + + if is_blank_node { + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, val_mark_node); + + // remove data corresponding to Blank node + ed_model.del_blank_expr_node(old_caret_pos)?; + + let char_len = 1; + ed_model.simple_move_carets_right(char_len); + + // update GridNodeMap and CodeLines + ed_model.insert_all_between_line( + old_caret_pos.line, + old_caret_pos.column, + &[val_name_mn_id, equals_mn_id, body_mn_id], + )?; + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } +} + +// TODO reenable this for updating non-top level value defs +/* +pub fn update_let_value( + val_name_mn_id: MarkNodeId, + def_id: NodeId, + body_id: NodeId, + ed_model: &mut EdModel, + new_char: &char, +) -> EdResult { + if new_char.is_ascii_alphanumeric() { + let old_caret_pos = ed_model.get_caret(); + + // update markup + let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id); + let content_str_mut = val_name_mn_mut.get_content_mut()?; + + let old_val_name = content_str_mut.clone(); + + let node_caret_offset = ed_model + .grid_node_map + .get_offset_to_node_id(old_caret_pos, val_name_mn_id)?; + + if node_caret_offset <= content_str_mut.len() { + content_str_mut.insert(node_caret_offset, *new_char); + + // update ast + let value_def = ed_model.module.env.pool.get(def_id); + let value_ident_pattern_id = value_def.get_pattern_id(); + + // TODO no unwrap + let ident_id = ed_model + .module + .env + .ident_ids + .update_key(&old_val_name, content_str_mut) + .unwrap(); + + let new_var_symbol = Symbol::new(ed_model.module.env.home, ident_id); + + ed_model + .module + .env + .pool + .set(value_ident_pattern_id, Pattern2::Identifier(new_var_symbol)); + + ed_model + .module + .env + .pool + .set(body_id, Expr2::Var(new_var_symbol)); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + &new_char.to_string(), + val_name_mn_id, + )?; + + // update caret + ed_model.simple_move_carets_right(1); + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } + } else { + Ok(InputOutcome::Ignored) + } +} +*/ diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 56aed8c6e9..65e6fce10b 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,6 +1,8 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; -use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::common_nodes::{ + new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, +}; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; @@ -8,9 +10,9 @@ use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::Expr2; -use crate::lang::ast::{expr2_to_string, ExprId}; +use crate::lang::ast::ExprId; +use crate::lang::ast::{ast_node_to_string, Expr2}; +use crate::lang::parse::ASTNodeId; use crate::lang::pool::PoolVec; use crate::ui::text::text_pos::TextPos; @@ -24,63 +26,62 @@ pub fn start_new_list(ed_model: &mut EdModel) -> EdResult { } = get_node_context(ed_model)?; let is_blank_node = curr_mark_node.is_blank(); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); let expr2_node = Expr2::List { elem_var: ed_model.module.env.var_store.fresh(), elems: PoolVec::empty(ed_model.module.env.pool), }; - let mark_node_pool = &mut ed_model.markup_node_pool; + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, expr2_node); - ed_model.module.env.pool.set(ast_node_id, expr2_node); + let left_bracket_node_id = ed_model.add_mark_node(new_left_square_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); - let left_bracket_node = MarkupNode::Text { - content: nodes::LEFT_SQUARE_BR.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let left_bracket_node_id = mark_node_pool.add(left_bracket_node); - - let right_bracket_node = MarkupNode::Text { - content: nodes::RIGHT_SQUARE_BR.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let right_bracket_node_id = mark_node_pool.add(right_bracket_node); + let right_bracket_node_id = ed_model.add_mark_node(new_right_square_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); let nested_node = MarkupNode::Nested { ast_node_id, children_ids: vec![left_bracket_node_id, right_bracket_node_id], parent_id_opt, + newlines_at_end: curr_mark_node_nls, }; if is_blank_node { - mark_node_pool.replace_node(curr_mark_node_id, nested_node); + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, nested_node); - // remove data corresponding to Blank node - ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; + ed_model.del_blank_expr_node(old_caret_pos)?; ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, nodes::LEFT_SQUARE_BR, left_bracket_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column + nodes::LEFT_SQUARE_BR.len(), nodes::RIGHT_SQUARE_BR, right_bracket_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(InputOutcome::Accepted) @@ -105,10 +106,10 @@ pub fn add_blank_child( let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt { - let parent = ed_model.markup_node_pool.get(parent_id); + let parent = ed_model.mark_node_pool.get(parent_id); let list_ast_node_id = parent.get_ast_node_id(); - let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id); + let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id.to_expr_id()?); match list_ast_node { Expr2::List { @@ -118,11 +119,11 @@ pub fn add_blank_child( let blank_elt = Expr2::Blank; let blank_elt_id = ed_model.module.env.pool.add(blank_elt); - Ok((blank_elt_id, list_ast_node_id, parent_id)) + Ok((blank_elt_id, list_ast_node_id.to_expr_id()?, parent_id)) } _ => UnexpectedASTNode { required_node_type: "List".to_string(), - encountered_node_type: expr2_to_string(ast_node_id, ed_model.module.env.pool), + encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool), } .fail(), } @@ -159,7 +160,7 @@ pub fn add_blank_child( } _ => UnexpectedASTNode { required_node_type: "List".to_string(), - encountered_node_type: expr2_to_string(ast_node_id, ed_model.module.env.pool), + encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool), } .fail(), }?; @@ -173,7 +174,7 @@ pub fn add_blank_child( ed_model, )?; - let parent = ed_model.markup_node_pool.get_mut(parent_id); + let parent = ed_model.mark_node_pool.get_mut(parent_id); for (indx, child) in new_mark_children.iter().enumerate() { parent.add_child_at_index(new_child_index + indx, *child)?; @@ -191,35 +192,26 @@ pub fn update_mark_children( parent_id_opt: Option, ed_model: &mut EdModel, ) -> EdResult> { - let blank_mark_node = MarkupNode::Blank { - ast_node_id: blank_elt_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + let blank_mark_node_id = ed_model.add_mark_node(new_blank_mn( + ASTNodeId::AExprId(blank_elt_id), parent_id_opt, - }; - - let blank_mark_node_id = ed_model.markup_node_pool.add(blank_mark_node); + )); let mut children: Vec = vec![]; if new_child_index > 1 { - let comma_mark_node = MarkupNode::Text { - content: nodes::COMMA.to_owned(), - ast_node_id: list_ast_node_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - }; - - let comma_mark_node_id = ed_model.markup_node_pool.add(comma_mark_node); + let comma_mark_node_id = + ed_model.add_mark_node(new_comma_mn(list_ast_node_id, parent_id_opt)); ed_model.simple_move_carets_right(nodes::COMMA.len()); - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, nodes::COMMA, comma_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; children.push(comma_mark_node_id); @@ -234,11 +226,13 @@ pub fn update_mark_children( }; // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column + comma_shift, nodes::BLANK_PLACEHOLDER, blank_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(children) diff --git a/editor/src/editor/mvc/lookup_update.rs b/editor/src/editor/mvc/lookup_update.rs index 972f6e3a7e..03a4905942 100644 --- a/editor/src/editor/mvc/lookup_update.rs +++ b/editor/src/editor/mvc/lookup_update.rs @@ -10,7 +10,7 @@ pub fn update_invalid_lookup( input_str: &str, old_pool_str: &PoolStr, curr_mark_node_id: MarkNodeId, - ast_node_id: ExprId, + expr_id: ExprId, ed_model: &mut EdModel, ) -> EdResult { if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) { @@ -32,10 +32,10 @@ pub fn update_invalid_lookup( .module .env .pool - .set(ast_node_id, Expr2::InvalidLookup(new_pool_str)); + .set(expr_id, Expr2::InvalidLookup(new_pool_str)); // update MarkupNode - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; content_str_mut.insert_str(caret_offset, input_str); @@ -43,11 +43,13 @@ pub fn update_invalid_lookup( ed_model.simple_move_carets_right(input_str.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, input_str, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(InputOutcome::Accepted) diff --git a/editor/src/editor/mvc/mod.rs b/editor/src/editor/mvc/mod.rs index 4088a4a1ad..5b4a11ad9a 100644 --- a/editor/src/editor/mvc/mod.rs +++ b/editor/src/editor/mvc/mod.rs @@ -1,10 +1,13 @@ pub mod app_model; pub mod app_update; +mod break_line; pub mod ed_model; pub mod ed_update; pub mod ed_view; mod int_update; +mod let_update; mod list_update; mod lookup_update; mod record_update; mod string_update; +pub mod tld_value_update; diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 628028953d..0b8c911ab6 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -2,6 +2,9 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::RecordWithoutFields; use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::common_nodes::new_blank_mn; +use crate::editor::markup::common_nodes::new_left_accolade_mn; +use crate::editor::markup::common_nodes::new_right_accolade_mn; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; @@ -12,6 +15,7 @@ use crate::editor::slow_pool::MarkNodeId; use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; use crate::lang::ast::{Expr2, ExprId, RecordField}; +use crate::lang::parse::ASTNodeId; use crate::lang::pool::{PoolStr, PoolVec}; use crate::ui::text::text_pos::TextPos; use snafu::OptionExt; @@ -26,61 +30,44 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult { } = get_node_context(ed_model)?; let is_blank_node = curr_mark_node.is_blank(); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); let ast_pool = &mut ed_model.module.env.pool; let expr2_node = Expr2::EmptyRecord; - let mark_node_pool = &mut ed_model.markup_node_pool; + ast_pool.set(ast_node_id.to_expr_id()?, expr2_node); - ast_pool.set(ast_node_id, expr2_node); + let left_bracket_node_id = ed_model.add_mark_node(new_left_accolade_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); - let left_bracket_node = MarkupNode::Text { - content: nodes::LEFT_ACCOLADE.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let left_bracket_node_id = mark_node_pool.add(left_bracket_node); - - let right_bracket_node = MarkupNode::Text { - content: nodes::RIGHT_ACCOLADE.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let right_bracket_node_id = mark_node_pool.add(right_bracket_node); + let right_bracket_node_id = ed_model.add_mark_node(new_right_accolade_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); let nested_node = MarkupNode::Nested { ast_node_id, children_ids: vec![left_bracket_node_id, right_bracket_node_id], parent_id_opt, + newlines_at_end: curr_mark_node_nls, }; if is_blank_node { - mark_node_pool.replace_node(curr_mark_node_id, nested_node); + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, nested_node); - // remove data corresponding to Blank node - ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; + ed_model.del_blank_expr_node(old_caret_pos)?; ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + ed_model.insert_all_between_line( old_caret_pos.line, old_caret_pos.column, - nodes::LEFT_ACCOLADE, - left_bracket_node_id, - )?; - - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column + nodes::LEFT_ACCOLADE.len(), - nodes::RIGHT_ACCOLADE, - right_bracket_node_id, + &[left_bracket_node_id, right_bracket_node_id], )?; Ok(InputOutcome::Accepted) @@ -100,7 +87,7 @@ pub fn update_empty_record( if input_chars.all(|ch| ch.is_ascii_alphabetic()) && input_chars.all(|ch| ch.is_ascii_lowercase()) { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); let NodeContext { old_caret_pos, @@ -110,8 +97,8 @@ pub fn update_empty_record( ast_node_id, } = get_node_context(ed_model)?; - if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE - && curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE + if prev_mark_node.get_content() == nodes::LEFT_ACCOLADE + && curr_mark_node.get_content() == nodes::RIGHT_ACCOLADE { // update AST let record_var = ed_model.module.env.var_store.fresh(); @@ -124,7 +111,11 @@ pub fn update_empty_record( let new_ast_node = Expr2::Record { record_var, fields }; - ed_model.module.env.pool.set(ast_node_id, new_ast_node); + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, new_ast_node); // update Markup @@ -134,12 +125,13 @@ pub fn update_empty_record( syn_high_style: HighlightStyle::RecordField, attributes: Attributes::new(), parent_id_opt, + newlines_at_end: 0, }; - let record_field_node_id = ed_model.markup_node_pool.add(record_field_node); + let record_field_node_id = ed_model.add_mark_node(record_field_node); if let Some(parent_id) = parent_id_opt { - let parent = ed_model.markup_node_pool.get_mut(parent_id); + let parent = ed_model.mark_node_pool.get_mut(parent_id); let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; @@ -155,11 +147,13 @@ pub fn update_empty_record( ed_model.simple_move_carets_right(1); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, new_input, record_field_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(InputOutcome::Accepted) @@ -183,118 +177,114 @@ pub fn update_record_colon( ast_node_id, } = get_node_context(ed_model)?; if let Some(parent_id) = parent_id_opt { - let curr_ast_node = ed_model.module.env.pool.get(ast_node_id); + let curr_ast_node = ed_model.module.env.pool.get(ast_node_id.to_expr_id()?); let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; if let Some(prev_mark_node_id) = prev_mark_node_id_opt { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); - let prev_ast_node = ed_model - .module - .env - .pool - .get(prev_mark_node.get_ast_node_id()); + match prev_mark_node.get_ast_node_id() { + ASTNodeId::ADefId(_) => Ok(InputOutcome::Ignored), + ASTNodeId::AExprId(prev_expr_id) => { + let prev_expr = ed_model.module.env.pool.get(prev_expr_id); - // current and prev node should always point to record when in valid position to add ':' - if matches!(prev_ast_node, Expr2::Record { .. }) - && matches!(curr_ast_node, Expr2::Record { .. }) - { - let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); + // current and prev node should always point to record when in valid position to add ':' + if matches!(prev_expr, Expr2::Record { .. }) + && matches!(curr_ast_node, Expr2::Record { .. }) + { + let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.mark_node_pool); - let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; + let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; - let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id); + let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id); - match ast_node_ref { - Expr2::Record { - record_var: _, - fields, - } => { - if ed_model.node_exists_at_caret() { - let next_mark_node_id = - ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; - let next_mark_node = ed_model.markup_node_pool.get(next_mark_node_id); - if next_mark_node.get_content()? == nodes::RIGHT_ACCOLADE { - // update AST node - let new_field_val = Expr2::Blank; - let new_field_val_id = ed_model.module.env.pool.add(new_field_val); + match ast_node_ref { + Expr2::Record { + record_var: _, + fields, + } => { + if ed_model.node_exists_at_caret() { + let next_mark_node_id = + ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; + let next_mark_node = + ed_model.mark_node_pool.get(next_mark_node_id); + if next_mark_node.get_content() == nodes::RIGHT_ACCOLADE { + // update AST node + let new_field_val = Expr2::Blank; + let new_field_val_id = + ed_model.module.env.pool.add(new_field_val); - let first_field_mut = fields - .iter_mut(ed_model.module.env.pool) - .next() - .with_context(|| RecordWithoutFields {})?; + let first_field_mut = fields + .iter_mut(ed_model.module.env.pool) + .next() + .with_context(|| RecordWithoutFields {})?; - *first_field_mut = RecordField::LabeledValue( - *first_field_mut.get_record_field_pool_str(), - *first_field_mut.get_record_field_var(), - new_field_val_id, - ); + *first_field_mut = RecordField::LabeledValue( + *first_field_mut.get_record_field_pool_str(), + *first_field_mut.get_record_field_var(), + new_field_val_id, + ); - // update Markup - let record_colon = nodes::COLON; + // update Markup + let record_colon = nodes::COLON; - let record_colon_node = MarkupNode::Text { - content: record_colon.to_owned(), - ast_node_id: record_ast_node_id, - syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), - parent_id_opt: Some(parent_id), - }; + let record_colon_node = MarkupNode::Text { + content: record_colon.to_owned(), + ast_node_id: ASTNodeId::AExprId(record_ast_node_id), + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt: Some(parent_id), + newlines_at_end: 0, + }; - let record_colon_node_id = - ed_model.markup_node_pool.add(record_colon_node); - ed_model - .markup_node_pool - .get_mut(parent_id) - .add_child_at_index(new_child_index, record_colon_node_id)?; + let record_colon_node_id = + ed_model.add_mark_node(record_colon_node); + ed_model + .mark_node_pool + .get_mut(parent_id) + .add_child_at_index( + new_child_index, + record_colon_node_id, + )?; - let record_blank_node = MarkupNode::Blank { - ast_node_id: new_field_val_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt: Some(parent_id), - }; + let record_blank_node_id = + ed_model.add_mark_node(new_blank_mn( + ASTNodeId::AExprId(new_field_val_id), + Some(parent_id), + )); - let record_blank_node_id = - ed_model.markup_node_pool.add(record_blank_node); - ed_model - .markup_node_pool - .get_mut(parent_id) - .add_child_at_index( - new_child_index + 1, - record_blank_node_id, - )?; + ed_model + .mark_node_pool + .get_mut(parent_id) + .add_child_at_index( + new_child_index + 1, + record_blank_node_id, + )?; - // update caret - ed_model.simple_move_carets_right(record_colon.len()); + // update caret + ed_model.simple_move_carets_right(record_colon.len()); - // update GridNodeMap and CodeLines - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column, - nodes::COLON, - record_colon_node_id, - )?; + // update GridNodeMap and CodeLines + ed_model.insert_all_between_line( + old_caret_pos.line, + old_caret_pos.column, + &[record_colon_node_id, record_blank_node_id], + )?; - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column + nodes::COLON.len(), - nodes::BLANK_PLACEHOLDER, - record_blank_node_id, - )?; - - Ok(InputOutcome::Accepted) - } else { - Ok(InputOutcome::Ignored) + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } + } else { + Ok(InputOutcome::Ignored) + } } - } else { - Ok(InputOutcome::Ignored) + _ => Ok(InputOutcome::Ignored), } + } else { + Ok(InputOutcome::Ignored) } - _ => Ok(InputOutcome::Ignored), } - } else { - Ok(InputOutcome::Ignored) } } else { Ok(InputOutcome::Ignored) @@ -315,7 +305,7 @@ pub fn update_record_field( ed_model: &mut EdModel, ) -> EdResult { // update MarkupNode - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; let node_caret_offset = ed_model .grid_node_map @@ -337,11 +327,13 @@ pub fn update_record_field( ed_model.simple_move_carets_right(new_input.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, new_input, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; // update AST Node diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index b0145804ac..2c35ca89fd 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -7,6 +7,7 @@ use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::update_str_expr; use crate::lang::ast::ArrString; use crate::lang::ast::Expr2; use crate::lang::pool::PoolStr; @@ -27,7 +28,7 @@ pub fn update_small_string( let new_input = &new_char.to_string(); // update markup - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; let node_caret_offset = ed_model .grid_node_map @@ -36,7 +37,7 @@ pub fn update_small_string( if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { if old_array_str.len() < ArrString::capacity() { if let Expr2::SmallStr(ref mut mut_array_str) = - ed_model.module.env.pool.get_mut(ast_node_id) + ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?) { // safe because we checked the length unsafe { @@ -51,17 +52,23 @@ pub fn update_small_string( let new_ast_node = Expr2::Str(PoolStr::new(&new_str, ed_model.module.env.pool)); - ed_model.module.env.pool.set(ast_node_id, new_ast_node); + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, new_ast_node); } content_str_mut.insert_str(node_caret_offset, new_input); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, new_input, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; // update caret @@ -73,11 +80,7 @@ pub fn update_small_string( } } -pub fn update_string( - new_input: &str, - old_pool_str: &PoolStr, - ed_model: &mut EdModel, -) -> EdResult { +pub fn update_string(new_char: char, ed_model: &mut EdModel) -> EdResult { let NodeContext { old_caret_pos, curr_mark_node_id, @@ -87,31 +90,32 @@ pub fn update_string( } = get_node_context(ed_model)?; // update markup - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; let node_caret_offset = ed_model .grid_node_map .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { - content_str_mut.insert_str(node_caret_offset, new_input); + content_str_mut.insert(node_caret_offset, new_char); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, - new_input, + &new_char.to_string(), curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; // update ast - let mut new_string = old_pool_str.as_str(ed_model.module.env.pool).to_owned(); - new_string.push_str(new_input); - - let new_pool_str = PoolStr::new(&new_string, &mut ed_model.module.env.pool); - let new_ast_node = Expr2::Str(new_pool_str); - - ed_model.module.env.pool.set(ast_node_id, new_ast_node); + update_str_expr( + ast_node_id.to_expr_id()?, + new_char, + node_caret_offset - 1, // -1 because offset was calculated with quotes + &mut ed_model.module.env.pool, + )?; // update caret ed_model.simple_move_carets_right(1); @@ -133,8 +137,13 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { if curr_mark_node.is_blank() { let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new()); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); - ed_model.module.env.pool.set(ast_node_id, new_expr2_node); + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, new_expr2_node); let new_string_node = MarkupNode::Text { content: nodes::STRING_QUOTES.to_owned(), @@ -142,21 +151,24 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { syn_high_style: HighlightStyle::String, attributes: Attributes::new(), parent_id_opt, + newlines_at_end: curr_mark_node_nls, }; ed_model - .markup_node_pool + .mark_node_pool .replace_node(curr_mark_node_id, new_string_node); // remove data corresponding to Blank node - ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; + ed_model.del_blank_expr_node(old_caret_pos)?; // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, nodes::STRING_QUOTES, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; ed_model.simple_move_carets_right(1); diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs new file mode 100644 index 0000000000..8cdb85feba --- /dev/null +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -0,0 +1,206 @@ +use roc_module::symbol::{Interns, Symbol}; + +use crate::{ + editor::{ + ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, + markup::{ + attribute::Attributes, + common_nodes::{new_blank_mn_w_nls, new_equals_mn}, + nodes::{set_parent_for_all, MarkupNode}, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, + }, + lang::{ + ast::{Def2, Expr2}, + expr::Env, + parse::ASTNodeId, + pattern::{get_identifier_string, Pattern2}, + pool::NodeId, + }, + ui::text::text_pos::TextPos, +}; + +use super::{ + app_update::InputOutcome, + ed_model::EdModel, + ed_update::{get_node_context, NodeContext}, +}; + +// Top Level Defined Value. example: `main = "Hello, World!"` + +pub fn tld_mark_node<'a>( + identifier_id: NodeId, + expr_mark_node_id: MarkNodeId, + ast_node_id: ASTNodeId, + mark_node_pool: &mut SlowPool, + env: &Env<'a>, + interns: &Interns, +) -> EdResult { + let pattern2 = env.pool.get(identifier_id); + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id], + parent_id_opt: None, + newlines_at_end: 2, + }; + + Ok(full_let_node) +} + +pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node: _, + parent_id_opt: _, + ast_node_id, + } = get_node_context(ed_model)?; + + let val_expr_node = Expr2::Blank; + let val_expr_id = ed_model.module.env.pool.add(val_expr_node); + + let val_expr_mn = new_blank_mn_w_nls(ASTNodeId::AExprId(val_expr_id), None, 0); + let val_expr_mn_id = ed_model.mark_node_pool.add(val_expr_mn); + + let val_name_string = new_char.to_string(); + + let ident_id = ed_model + .module + .env + .ident_ids + .add(val_name_string.clone().into()); + + let module_ident_ids_opt = ed_model + .loaded_module + .interns + .all_ident_ids + .get_mut(&ed_model.module.env.home); + + if let Some(module_ident_ids_ref) = module_ident_ids_opt { + // this might create different IdentId for interns and env.ident_ids which may be a problem + module_ident_ids_ref.add(val_name_string.into()); + } else { + KeyNotFound { + key_str: format!("{:?}", ed_model.module.env.home), + } + .fail()? + } + + let val_symbol = Symbol::new(ed_model.module.env.home, ident_id); + + let patt2 = Pattern2::Identifier(val_symbol); + let patt2_id = ed_model.module.env.pool.add(patt2); + + let tld_mark_node = tld_mark_node( + patt2_id, + val_expr_mn_id, + ast_node_id, + &mut ed_model.mark_node_pool, + &ed_model.module.env, + &ed_model.loaded_module.interns, + )?; + + let new_ast_node = Def2::ValueDef { + identifier_id: patt2_id, + expr_id: val_expr_id, + }; + + ed_model + .module + .env + .pool + .set(ast_node_id.to_def_id()?, new_ast_node); + + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, tld_mark_node); + + set_parent_for_all(curr_mark_node_id, &mut ed_model.mark_node_pool); + + // remove data corresponding to old Blank node + ed_model.del_line(old_caret_pos.line + 1)?; + ed_model.del_line(old_caret_pos.line)?; + + let char_len = 1; + ed_model.simple_move_carets_right(char_len); + + let mut curr_line = old_caret_pos.line; + let mut curr_column = old_caret_pos.column; + + EdModel::insert_mark_node_between_line( + &mut curr_line, + &mut curr_column, + curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, + &ed_model.mark_node_pool, + )?; + + Ok(InputOutcome::Accepted) +} + +pub fn update_tld_val_name( + val_name_mn_id: MarkNodeId, + old_caret_pos: TextPos, + ed_model: &mut EdModel, + new_char: &char, +) -> EdResult { + if new_char.is_ascii_alphanumeric() { + // update markup + let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id); + let content_str_mut = val_name_mn_mut.get_content_mut()?; + + let old_val_name = content_str_mut.clone(); + + let node_caret_offset = ed_model + .grid_node_map + .get_offset_to_node_id(old_caret_pos, val_name_mn_id)?; + + if node_caret_offset <= content_str_mut.len() { + content_str_mut.insert(node_caret_offset, *new_char); + + let update_val_name_res = ed_model + .module + .env + .ident_ids + .update_key(&old_val_name, content_str_mut); + + if let Err(err_str) = update_val_name_res { + FailedToUpdateIdentIdName { err_str }.fail()?; + } + + EdModel::insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + &new_char.to_string(), + val_name_mn_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, + )?; + + ed_model.simple_move_caret_right(old_caret_pos, 1); + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } + } else { + Ok(InputOutcome::Ignored) + } +} diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index e7b01ab55b..3fa42bc1de 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,4 +1,5 @@ use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; +use super::slow_pool::MarkNodeId; use crate::editor::mvc::ed_view::RenderedWgpu; use crate::editor::slow_pool::SlowPool; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; @@ -10,37 +11,49 @@ use winit::dpi::PhysicalSize; use crate::{editor::config::Config, graphics::colors}; pub fn build_code_graphics<'a>( - markup_node: &'a MarkupNode, + markup_ids: &[MarkNodeId], size: &PhysicalSize, txt_coords: Vector2, config: &Config, glyph_dim_rect: Rect, - markup_node_pool: &'a SlowPool, + mark_node_pool: &'a SlowPool, ) -> EdResult { let area_bounds = (size.width as f32, size.height as f32); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); let mut rendered_wgpu = RenderedWgpu::new(); - let (glyph_text_vec, rects) = markup_to_wgpu( - markup_node, - &CodeStyle { - ed_theme: &config.ed_theme, - font_size: config.code_font_size, - txt_coords, - glyph_dim_rect, - }, - markup_node_pool, - )?; + let mut all_glyph_text_vec = vec![]; + let mut all_rects = vec![]; + let mut txt_row_col = (0, 0); + + for markup_id in markup_ids.iter() { + let mark_node = mark_node_pool.get(*markup_id); + + let (mut glyph_text_vec, mut rects) = markup_to_wgpu( + mark_node, + &CodeStyle { + ed_theme: &config.ed_theme, + font_size: config.code_font_size, + txt_coords, + glyph_dim_rect, + }, + &mut txt_row_col, + mark_node_pool, + )?; + + all_glyph_text_vec.append(&mut glyph_text_vec); + all_rects.append(&mut rects) + } let section = gr_text::owned_section_from_glyph_texts( - glyph_text_vec, + all_glyph_text_vec, txt_coords.into(), area_bounds, layout, ); - rendered_wgpu.add_rects(rects); - rendered_wgpu.add_text(section); + rendered_wgpu.add_rects_behind(all_rects); // currently only rects for Blank + rendered_wgpu.add_text_behind(section); Ok(rendered_wgpu) } @@ -55,51 +68,57 @@ struct CodeStyle<'a> { fn markup_to_wgpu<'a>( markup_node: &'a MarkupNode, code_style: &CodeStyle, - markup_node_pool: &'a SlowPool, + txt_row_col: &mut (usize, usize), + mark_node_pool: &'a SlowPool, ) -> EdResult<(Vec, Vec)> { let mut wgpu_texts: Vec = Vec::new(); let mut rects: Vec = Vec::new(); - let mut txt_row_col = (0, 0); - markup_to_wgpu_helper( markup_node, &mut wgpu_texts, &mut rects, code_style, - &mut txt_row_col, - markup_node_pool, + txt_row_col, + mark_node_pool, )?; Ok((wgpu_texts, rects)) } -// TODO use text_row fn markup_to_wgpu_helper<'a>( markup_node: &'a MarkupNode, wgpu_texts: &mut Vec, rects: &mut Vec, code_style: &CodeStyle, txt_row_col: &mut (usize, usize), - markup_node_pool: &'a SlowPool, + mark_node_pool: &'a SlowPool, ) -> EdResult<()> { match markup_node { MarkupNode::Nested { ast_node_id: _, children_ids, parent_id_opt: _, + newlines_at_end, } => { for child_id in children_ids.iter() { - let child = markup_node_pool.get(*child_id); + let child = mark_node_pool.get(*child_id); markup_to_wgpu_helper( child, wgpu_texts, rects, code_style, txt_row_col, - markup_node_pool, + mark_node_pool, )?; } + + for _ in 0..*newlines_at_end { + wgpu_texts.push(newline(code_style.font_size)); + + txt_row_col.0 += 1; + txt_row_col.1 = 0; + } } MarkupNode::Text { content, @@ -107,14 +126,23 @@ fn markup_to_wgpu_helper<'a>( syn_high_style, attributes: _, parent_id_opt: _, + newlines_at_end, } => { let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; - let glyph_text = glyph_brush::OwnedText::new(content) + let full_content = markup_node.get_full_content(); + + let glyph_text = glyph_brush::OwnedText::new(full_content) .with_color(colors::to_slice(*highlight_color)) .with_scale(code_style.font_size); txt_row_col.1 += content.len(); + + for _ in 0..*newlines_at_end { + txt_row_col.0 += 1; + txt_row_col.1 = 0; + } + wgpu_texts.push(glyph_text); } MarkupNode::Blank { @@ -122,8 +150,11 @@ fn markup_to_wgpu_helper<'a>( attributes: _, syn_high_style, parent_id_opt: _, + newlines_at_end, } => { - let glyph_text = glyph_brush::OwnedText::new(BLANK_PLACEHOLDER) + let full_content = markup_node.get_full_content(); + + let glyph_text = glyph_brush::OwnedText::new(full_content) .with_color(colors::to_slice(colors::WHITE)) .with_scale(code_style.font_size); @@ -132,7 +163,7 @@ fn markup_to_wgpu_helper<'a>( let char_width = code_style.glyph_dim_rect.width; let char_height = code_style.glyph_dim_rect.height; - let hole_rect = Rect { + let blank_rect = Rect { top_left_coords: ( code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width, code_style.txt_coords.y @@ -144,12 +175,21 @@ fn markup_to_wgpu_helper<'a>( height: char_height, color: *highlight_color, }; - rects.push(hole_rect); + rects.push(blank_rect); txt_row_col.1 += BLANK_PLACEHOLDER.len(); wgpu_texts.push(glyph_text); + + for _ in 0..*newlines_at_end { + txt_row_col.0 += 1; + txt_row_col.1 = 0; + } } }; Ok(()) } + +fn newline(font_size: f32) -> glyph_brush::OwnedText { + glyph_brush::OwnedText::new("\n").with_scale(font_size) +} diff --git a/editor/src/editor/render_debug.rs b/editor/src/editor/render_debug.rs index ea3ca106b1..36e4377d80 100644 --- a/editor/src/editor/render_debug.rs +++ b/editor/src/editor/render_debug.rs @@ -4,7 +4,7 @@ use crate::editor::mvc::ed_model::EdModel; use crate::graphics::colors; use crate::graphics::colors::from_hsb; use crate::graphics::primitives::text as gr_text; -use crate::lang::ast::expr2_to_string; +use crate::lang::ast::def2_to_string; use cgmath::Vector2; use winit::dpi::PhysicalSize; @@ -19,36 +19,49 @@ pub fn build_debug_graphics( let area_bounds = (size.width as f32, size.height as f32); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); - let debug_txt_coords: Vector2 = (txt_coords.x, txt_coords.y * 3.0).into(); + let debug_txt_coords: Vector2 = (txt_coords.x * 20.0, txt_coords.y).into(); + + let carets_text = + glyph_brush::OwnedText::new(format!("carets: {:?}\n\n", ed_model.get_carets())) + .with_color(colors::to_slice(from_hsb(0, 0, 100))) + .with_scale(config.debug_font_size); let grid_node_map_text = glyph_brush::OwnedText::new(format!("{}", ed_model.grid_node_map)) .with_color(colors::to_slice(from_hsb(20, 41, 100))) - .with_scale(config.code_font_size); + .with_scale(config.debug_font_size); let code_lines_text = glyph_brush::OwnedText::new(format!("{}", ed_model.code_lines)) .with_color(colors::to_slice(from_hsb(0, 49, 96))) - .with_scale(config.code_font_size); + .with_scale(config.debug_font_size); - let mark_node_tree_text = glyph_brush::OwnedText::new(tree_as_string( - ed_model.markup_root_id, - &ed_model.markup_node_pool, - )) - .with_color(colors::to_slice(from_hsb(266, 31, 96))) - .with_scale(config.code_font_size); + let mut mark_node_trees_string = "\nmark node trees:".to_owned(); - let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.markup_node_pool)) + for mark_id in ed_model.markup_ids[1..].iter() { + // 1.. -> skip header + mark_node_trees_string.push_str(&tree_as_string(*mark_id, &ed_model.mark_node_pool)); + } + + let mark_node_tree_text = glyph_brush::OwnedText::new(mark_node_trees_string) + .with_color(colors::to_slice(from_hsb(266, 31, 96))) + .with_scale(config.debug_font_size); + + let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.mark_node_pool)) .with_color(colors::to_slice(from_hsb(110, 45, 82))) - .with_scale(config.code_font_size); + .with_scale(config.debug_font_size); - let ast_node_text = glyph_brush::OwnedText::new(format!( - "\n\n(ast_root)\n{}", - expr2_to_string(ed_model.module.ast_root_id, ed_model.module.env.pool) - )) - .with_color(colors::to_slice(from_hsb(211, 80, 100))) - .with_scale(config.code_font_size); + let mut ast_node_text_str = "AST:\n".to_owned(); + + for def_id in ed_model.module.ast.def_ids.iter() { + ast_node_text_str.push_str(&def2_to_string(*def_id, ed_model.module.env.pool)) + } + + let ast_node_text = glyph_brush::OwnedText::new(ast_node_text_str) + .with_color(colors::to_slice(from_hsb(211, 80, 100))) + .with_scale(config.debug_font_size); let section = gr_text::owned_section_from_glyph_texts( vec![ + carets_text, grid_node_map_text, code_lines_text, mark_node_tree_text, diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index 852749c680..44f9546811 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -1,3 +1,27 @@ -pub const NOTHING_OPENED: &str = "Opening files is not yet supported, execute `cargo run edit` from the root folder of the repo to try the editor."; -pub const START_TIP: &str = - "Start by typing '[', '{', '\"' or a number.\nInput chars that would create parse errors will be ignored."; +#![allow(dead_code)] + +pub const NOTHING_OPENED: &str = + "Execute `cargo run edit` from the root folder of the repo to try the editor."; + +pub const START_TIP: &str = r#"Currently supported: lists, records, string, numbers and value definitions. + +Use `Ctrl+Shift+Up` or `Cmd+Shift+Up` to select surrounding expression. +Use backspace after `Ctrl+Shift+Up` to delete the selected expression. + +`Ctrl+S` or `Cmd+S` to save. +`Ctrl+R` to run. + +Input chars that would create parse errors or change formatting will be ignored. +For convenience and consistency, there is only one way to format roc. +"#; + +pub const HELLO_WORLD: &str = r#" +app "test-app" + packages { base: "platform" } + imports [] + provides [ main ] to base + +main = "Hello, world!" + + +"#; diff --git a/editor/src/editor/slow_pool.rs b/editor/src/editor/slow_pool.rs index d6ee4c85b9..72dc43336c 100644 --- a/editor/src/editor/slow_pool.rs +++ b/editor/src/editor/slow_pool.rs @@ -63,7 +63,7 @@ impl fmt::Display for SlowPool { "{}: {} ({}) ast_id {:?} {}", index, node.node_type_as_string(), - node.get_content().unwrap_or_else(|_| "".to_string()), + node.get_content(), ast_node_id.parse::().unwrap(), child_str )?; diff --git a/editor/src/editor/style.rs b/editor/src/editor/style.rs index 6325b8fca4..2c4dc0530c 100644 --- a/editor/src/editor/style.rs +++ b/editor/src/editor/style.rs @@ -1 +1 @@ -pub const CODE_TXT_XY: (f32, f32) = (40.0, 130.0); +pub const CODE_TXT_XY: (f32, f32) = (40.0, 350.0); diff --git a/editor/src/editor/syntax_highlight.rs b/editor/src/editor/syntax_highlight.rs index c2fbb31c07..602a335b48 100644 --- a/editor/src/editor/syntax_highlight.rs +++ b/editor/src/editor/syntax_highlight.rs @@ -14,6 +14,8 @@ pub enum HighlightStyle { PackageRelated, // app, packages, imports, exposes, provides... Variable, RecordField, + Import, + Provides, Blank, } @@ -31,6 +33,8 @@ pub fn default_highlight_map() -> HashMap { (PackageRelated, gr_colors::WHITE), (Variable, gr_colors::WHITE), (RecordField, from_hsb(258, 50, 90)), + (Import, from_hsb(185, 50, 75)), + (Provides, from_hsb(185, 50, 75)), (Blank, from_hsb(258, 50, 90)), // comment from_hsb(285, 6, 47) or 186, 35, 40 ] diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs index 69fb7f4d56..3fffb3cdad 100644 --- a/editor/src/lang/ast.rs +++ b/editor/src/lang/ast.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::hash::BuildHasherDefault; +use crate::editor::ed_error::{EdResult, UnexpectedASTNode}; use crate::lang::pattern::{Pattern2, PatternId}; use crate::lang::pool::Pool; use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; @@ -222,6 +223,17 @@ pub enum Expr2 { RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), } +// A top level definition, not inside a function. For example: `main = "Hello, world!"` +#[derive(Debug)] +pub enum Def2 { + // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" + ValueDef { + identifier_id: NodeId, + expr_id: NodeId, + }, + Blank, +} + #[derive(Debug)] pub enum ValueDef { WithAnnotation { @@ -267,6 +279,48 @@ impl ShallowClone for ValueDef { } } +impl ValueDef { + pub fn get_expr_id(&self) -> ExprId { + match self { + ValueDef::WithAnnotation { expr_id, .. } => *expr_id, + ValueDef::NoAnnotation { expr_id, .. } => *expr_id, + } + } + + pub fn get_pattern_id(&self) -> NodeId { + match self { + ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, + ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, + } + } +} + +pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { + match val_def { + ValueDef::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => { + format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) + } + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + format!( + "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", + pool.get(*pattern_id), + expr2_to_string(*expr_id, pool), + expr_var + ) + } + } +} + #[derive(Debug)] pub enum FunctionDef { WithAnnotation { @@ -402,7 +456,11 @@ pub struct WhenBranch { // TODO make the inner types private? pub type ExprId = NodeId; +pub type DefId = NodeId; + use RecordField::*; + +use super::parse::ASTNodeId; impl RecordField { pub fn get_record_field_var(&self) -> &Variable { match self { @@ -437,6 +495,13 @@ impl RecordField { } } +pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { + match node_id { + ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), + ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), + } +} + pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { let mut full_string = String::new(); let expr2 = pool.get(node_id); @@ -550,16 +615,118 @@ fn expr2_to_string_helper( Expr2::SmallInt { text, .. } => { out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); } + Expr2::LetValue { + def_id, body_id, .. + } => { + out_string.push_str(&format!( + "LetValue(def_id: >>{:?}), body_id: >>{:?})", + value_def_to_string(pool.get(*def_id), pool), + pool.get(*body_id) + )); + } other => todo!("Implement for {:?}", other), } out_string.push('\n'); } +pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { + let mut full_string = String::new(); + let def2 = pool.get(node_id); + + match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + full_string.push_str(&format!( + "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", + pool.get(*identifier_id), + expr2_to_string(*expr_id, pool) + )); + } + Def2::Blank => { + full_string.push_str("Def2::Blank"); + } + } + + full_string +} + fn var_to_string(some_var: &Variable, indent_level: usize) -> String { format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) } +// get string from SmallStr or Str +pub fn get_string_from_expr2(node_id: ExprId, pool: &Pool) -> EdResult { + match pool.get(node_id) { + Expr2::SmallStr(arr_string) => Ok(arr_string.as_str().to_string()), + Expr2::Str(pool_str) => Ok(pool_str.as_str(pool).to_owned()), + other => UnexpectedASTNode { + required_node_type: "SmallStr or Str", + encountered_node_type: format!("{:?}", other), + } + .fail()?, + } +} + +pub fn update_str_expr( + node_id: ExprId, + new_char: char, + insert_index: usize, + pool: &mut Pool, +) -> EdResult<()> { + let str_expr = pool.get_mut(node_id); + + enum Either { + MyString(String), + MyPoolStr(PoolStr), + Done, + } + + let insert_either = match str_expr { + Expr2::SmallStr(arr_string) => { + let insert_res = arr_string.try_insert(insert_index as u8, new_char); + + match insert_res { + Ok(_) => Either::Done, + _ => { + let mut new_string = arr_string.as_str().to_string(); + new_string.insert(insert_index, new_char); + + Either::MyString(new_string) + } + } + } + Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), + other => UnexpectedASTNode { + required_node_type: "SmallStr or Str", + encountered_node_type: format!("{:?}", other), + } + .fail()?, + }; + + match insert_either { + Either::MyString(new_string) => { + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::MyPoolStr(old_pool_str) => { + let mut new_string = old_pool_str.as_str(pool).to_owned(); + + new_string.insert(insert_index, new_char); + + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::Done => (), + } + + Ok(()) +} + #[test] fn size_of_expr() { assert_eq!(std::mem::size_of::(), crate::lang::pool::NODE_BYTES); diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs index 2321870946..a50c315f94 100644 --- a/editor/src/lang/expr.rs +++ b/editor/src/lang/expr.rs @@ -3,10 +3,11 @@ #![allow(unused_imports)] use bumpalo::{collections::Vec as BumpVec, Bump}; use std::collections::HashMap; +use std::iter::FromIterator; use crate::lang::ast::{ - expr2_to_string, ClosureExtra, Expr2, ExprId, FloatVal, IntStyle, IntVal, RecordField, - WhenBranch, + expr2_to_string, value_def_to_string, ClosureExtra, Def2, Expr2, ExprId, FloatVal, IntStyle, + IntVal, RecordField, ValueDef, WhenBranch, }; use crate::lang::def::{ canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, @@ -288,6 +289,25 @@ pub fn to_expr_id<'a>( (env.add(expr, region), output) } +pub fn str_to_def2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> Result, SyntaxError<'a>> { + match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { + Ok(vec_loc_def) => Ok(defs_to_defs2( + arena, + env, + scope, + arena.alloc(vec_loc_def), + region, + )), + Err(fail) => Err(fail), + } +} + pub fn str_to_expr2<'a>( arena: &'a Bump, input: &'a str, @@ -296,20 +316,23 @@ pub fn str_to_expr2<'a>( region: Region, ) -> Result<(Expr2, self::Output), SyntaxError<'a>> { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => { - let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); - - Ok(to_expr2( - env, - scope, - arena.alloc(desugared_loc_expr.value), - region, - )) - } + Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), Err(fail) => Err(fail), } } +fn loc_expr_to_expr2<'a>( + arena: &'a Bump, + loc_expr: Located>, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> (Expr2, self::Output) { + let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); + + to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) +} + pub fn to_expr2<'a>( env: &mut Env<'a>, scope: &mut Scope, @@ -967,6 +990,74 @@ pub fn to_expr2<'a>( } } +pub fn defs_to_defs2<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_defs: &'a BumpVec>>, + region: Region, +) -> Vec { + use roc_parse::ast::Expr::*; + + parsed_defs + .iter() + .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) + .collect() +} + +pub fn to_def2_from_def<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_def: &'a roc_parse::ast::Def<'a>, + region: Region, +) -> Def2 { + use roc_parse::ast::Def::*; + + match parsed_def { + SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + Body(&loc_pattern, &loc_expr) => { + // TODO loc_pattern use identifier + let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; + let expr_id = env.pool.add(expr2); + + use roc_parse::ast::Pattern::*; + + match loc_pattern.value { + Identifier(_) => { + let (_, pattern2) = to_pattern2( + env, + scope, + PatternType::TopLevelDef, + &loc_pattern.value, + region, + ); + let pattern_id = env.pool.add(pattern2); + + // TODO support with annotation + Def2::ValueDef { + identifier_id: pattern_id, + expr_id, + } + } + other => { + unimplemented!( + "I don't yet know how to convert the pattern {:?} into an expr2", + other + ) + } + } + } + other => { + unimplemented!( + "I don't know how to make an expr2 from this def yet: {:?}", + other + ) + } + } +} + fn flatten_str_literal<'a>( env: &mut Env<'a>, scope: &mut Scope, @@ -1412,6 +1503,7 @@ fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret Def::AnnotationOnly { .. } => todo!(), Def::Value(value_def) => { let def_id = pool.add(value_def); + let body_id = pool.add(ret); Expr2::LetValue { diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs index dcb9c6dae1..fa747386e1 100644 --- a/editor/src/lang/mod.rs +++ b/editor/src/lang/mod.rs @@ -3,7 +3,8 @@ pub mod constrain; mod def; pub mod expr; mod module; -mod pattern; +pub mod parse; +pub mod pattern; pub mod pool; pub mod roc_file; pub mod scope; diff --git a/editor/src/lang/parse.rs b/editor/src/lang/parse.rs new file mode 100644 index 0000000000..9c7253bb75 --- /dev/null +++ b/editor/src/lang/parse.rs @@ -0,0 +1,98 @@ +use std::fmt::Debug; + +use crate::{ + editor::ed_error::ASTNodeIdWithoutExprId, editor::ed_error::EdResult, lang::scope::Scope, +}; +use bumpalo::Bump; +use roc_parse::parser::SyntaxError; +use roc_region::all::Region; + +use super::{ + ast::{DefId, Expr2, ExprId}, + expr::{str_to_def2, Env}, +}; + +#[derive(Debug)] +pub struct AST { + pub header: AppHeader, + pub def_ids: Vec, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ASTNodeId { + ADefId(DefId), + AExprId(ExprId), +} + +impl ASTNodeId { + pub fn to_expr_id(&self) -> EdResult { + match self { + ASTNodeId::AExprId(expr_id) => Ok(*expr_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } + + pub fn to_def_id(&self) -> EdResult { + match self { + ASTNodeId::ADefId(def_id) => Ok(*def_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } +} + +#[derive(Debug)] +pub struct AppHeader { + pub app_name: String, + pub packages_base: String, + pub imports: Vec, + pub provides: Vec, + pub ast_node_id: ExprId, // TODO probably want to use HeaderId +} + +impl AST { + pub fn parse_from_string<'a>( + code_str: &'a str, + env: &mut Env<'a>, + ast_arena: &'a Bump, + ) -> Result> { + let blank_line_indx = code_str + .find("\n\n") + .expect("I was expecting a double newline to split header and rest of code."); + + let header_str = &code_str[0..blank_line_indx]; + let tail_str = &code_str[blank_line_indx..]; + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + let region = Region::new(0, 0, 0, 0); + + let mut def_ids = Vec::::new(); + + let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; + + for def2 in def2_vec { + let def_id = env.pool.add(def2); + + def_ids.push(def_id); + } + + let ast_node_id = env.pool.add(Expr2::Blank); + + Ok(AST { + header: AppHeader::parse_from_string(header_str, ast_node_id), + def_ids, + }) + } +} + +impl AppHeader { + // TODO don't use mock struct and actually parse string + pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> Self { + AppHeader { + app_name: "\"untitled-app\"".to_owned(), + packages_base: "\"platform\"".to_owned(), + imports: vec![], + provides: vec!["main".to_owned()], + ast_node_id, + } + } +} diff --git a/editor/src/lang/pattern.rs b/editor/src/lang/pattern.rs index 03ed3c821f..aae7797e8a 100644 --- a/editor/src/lang/pattern.rs +++ b/editor/src/lang/pattern.rs @@ -1,6 +1,7 @@ #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] +use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant}; use crate::lang::ast::{ExprId, FloatVal, IntVal}; use crate::lang::expr::{to_expr_id, Env, Output}; use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; @@ -9,7 +10,7 @@ use bumpalo::collections::Vec as BumpVec; use roc_can::expr::unescape_char; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_collections::all::BumpMap; -use roc_module::symbol::Symbol; +use roc_module::symbol::{Interns, Symbol}; use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; @@ -482,6 +483,17 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { symbols } +pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult { + match pattern { + Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), + other => UnexpectedPattern2Variant { + required_pattern2: "Identifier".to_string(), + encountered_pattern2: format!("{:?}", other), + } + .fail()?, + } +} + pub fn symbols_and_variables_from_pattern( pool: &Pool, initial: &Pattern2, diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f94ac4ccc8..f6f3053840 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -17,6 +17,6 @@ mod window; use std::io; use std::path::Path; -pub fn launch(filepaths: &[&Path]) -> io::Result<()> { - editor::main::launch(filepaths) +pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> { + editor::main::launch(project_dir_path_opt) } diff --git a/editor/src/ui/text/big_text_area.rs b/editor/src/ui/text/big_text_area.rs index 0ebd964e33..c1189e84ae 100644 --- a/editor/src/ui/text/big_text_area.rs +++ b/editor/src/ui/text/big_text_area.rs @@ -9,23 +9,19 @@ use crate::ui::text::{ selection::{validate_raw_sel, RawSelection, Selection}, text_pos::TextPos, }; -use crate::ui::ui_error::{ - OutOfBounds, - UIError::{FileOpenFailed, TextBufReadFailed}, - UIResult, -}; +use crate::ui::ui_error::{OutOfBounds, UIResult}; use crate::ui::util::is_newline; use crate::window::keyboard_input::{no_mods, Modifiers}; -use bumpalo::collections::String as BumpString; use bumpalo::Bump; -use ropey::Rope; use snafu::ensure; -use std::{fmt, fs::File, io, path::Path}; +use std::{fmt, path::Path}; use winit::event::{VirtualKeyCode, VirtualKeyCode::*}; +use super::text_buffer::TextBuffer; + pub struct BigTextArea { pub caret_w_select: CaretWSelect, - text_rope: Rope, + text_buffer: TextBuffer, pub path_str: String, arena: Bump, } @@ -33,84 +29,37 @@ pub struct BigTextArea { impl BigTextArea { fn check_bounds(&self, char_indx: usize) -> UIResult<()> { ensure!( - char_indx <= self.text_rope.len_chars(), + char_indx <= self.text_buffer.nr_of_chars(), OutOfBounds { index: char_indx, - collection_name: "Rope", - len: self.text_rope.len_chars() + collection_name: "TextBuffer", + len: self.text_buffer.nr_of_chars() } ); Ok(()) } - - fn pos_to_char_indx(&self, pos: TextPos) -> usize { - self.text_rope.line_to_char(pos.line) + pos.column - } - - fn char_indx_to_pos(&self, char_indx: usize) -> TextPos { - let line = self.text_rope.char_to_line(char_indx); - - let char_idx_line_start = self.pos_to_char_indx(TextPos { line, column: 0 }); - - let column = char_indx - char_idx_line_start; - - TextPos { line, column } - } - - fn sel_to_tup(&self, val_sel: Selection) -> (usize, usize) { - let start_char_indx = self.pos_to_char_indx(val_sel.start_pos); - let end_char_indx = self.pos_to_char_indx(val_sel.end_pos); - - (start_char_indx, end_char_indx) - } } impl Lines for BigTextArea { - fn get_line(&self, line_nr: usize) -> UIResult<&str> { - ensure!( - line_nr < self.nr_of_lines(), - OutOfBounds { - index: line_nr, - collection_name: "BigTextArea", - len: self.nr_of_lines(), - } - ); - - let rope_slice = self.text_rope.line(line_nr); - - if let Some(line_str_ref) = rope_slice.as_str() { - Ok(line_str_ref) - } else { - // happens very rarely - let line_str = rope_slice.chunks().collect::(); - let arena_str_ref = self.arena.alloc(line_str); - Ok(arena_str_ref) - } + fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> { + self.text_buffer.get_line_ref(line_nr) } fn line_len(&self, line_nr: usize) -> UIResult { - self.get_line(line_nr).map(|line| line.len()) + self.get_line_ref(line_nr).map(|line| line.len()) } fn nr_of_lines(&self) -> usize { - self.text_rope.len_lines() + self.text_buffer.nr_of_lines() } fn nr_of_chars(&self) -> usize { - self.text_rope.len_chars() + self.text_buffer.nr_of_chars() } - // TODO use pool allocation here - // expensive function, don't use it if it can be done with a specialized, more efficient function - fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { - let mut lines = BumpString::with_capacity_in(self.text_rope.len_chars(), arena); - - for line in self.text_rope.lines() { - lines.extend(line.as_str()); - } - - lines + fn all_lines_as_string(&self) -> String { + self.text_buffer.all_lines_ref().join("\n") } fn is_last_line(&self, line_nr: usize) -> bool { @@ -118,7 +67,7 @@ impl Lines for BigTextArea { } fn last_char(&self, line_nr: usize) -> UIResult> { - Ok(self.get_line(line_nr)?.chars().last()) + Ok(self.get_line_ref(line_nr)?.chars().last()) } } @@ -177,17 +126,7 @@ impl SelectableLines for BigTextArea { fn get_selected_str(&self) -> UIResult> { if let Some(val_sel) = self.caret_w_select.selection_opt { - let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel); - - self.check_bounds(end_char_indx)?; - - let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx); - - if let Some(line_str_ref) = rope_slice.as_str() { - Ok(Some(line_str_ref.to_string())) - } else { - Ok(Some(rope_slice.chunks().collect::())) - } + Ok(Some(self.text_buffer.get_selected_str(val_sel)?)) } else { Ok(None) } @@ -223,7 +162,13 @@ impl SelectableLines for BigTextArea { } fn last_text_pos(&self) -> UIResult { - Ok(self.char_indx_to_pos(self.nr_of_chars())) + let line_nr = self.nr_of_lines() - 1; + let last_col = self.line_len(line_nr)?; + + Ok(TextPos { + line: line_nr, + column: last_col, + }) } fn handle_key_down( @@ -279,7 +224,7 @@ impl MutSelectableLines for BigTextArea { // On Linux, '\u{8}' is backspace, // on macOS '\u{7f}'. - self.pop_char()? + self.backspace()? } '\u{1}' // Ctrl + A @@ -303,27 +248,21 @@ impl MutSelectableLines for BigTextArea { fn insert_str(&mut self, new_str: &str) -> UIResult<()> { let caret_pos = self.caret_w_select.caret_pos; - let char_indx = self.pos_to_char_indx(caret_pos); - self.check_bounds(char_indx)?; - - self.text_rope.insert(char_indx, new_str); + self.text_buffer.insert_str(caret_pos, new_str)?; Ok(()) } - fn pop_char(&mut self) -> UIResult<()> { + fn backspace(&mut self) -> UIResult<()> { if self.is_selection_active() { self.del_selection()?; } else { let old_caret_pos = self.caret_w_select.caret_pos; - let char_indx = self.pos_to_char_indx(old_caret_pos); self.move_caret_left(&no_mods())?; - if (char_indx > 0) && char_indx <= self.text_rope.len_chars() { - self.text_rope.remove((char_indx - 1)..char_indx); - } + self.text_buffer.backspace_char(old_caret_pos)?; } Ok(()) @@ -331,11 +270,7 @@ impl MutSelectableLines for BigTextArea { fn del_selection(&mut self) -> UIResult<()> { if let Some(selection) = self.caret_w_select.selection_opt { - let (start_char_indx, end_char_indx) = self.sel_to_tup(selection); - - self.check_bounds(end_char_indx)?; - - self.text_rope.remove(start_char_indx..end_char_indx); + self.text_buffer.del_selection(selection)?; self.set_caret(selection.start_pos); @@ -349,13 +284,13 @@ impl MutSelectableLines for BigTextArea { impl Default for BigTextArea { fn default() -> Self { let caret_w_select = CaretWSelect::default(); - let text_rope = Rope::from_str(""); + let text_buffer = TextBuffer { lines: Vec::new() }; let path_str = "".to_owned(); let arena = Bump::new(); Self { caret_w_select, - text_rope, + text_buffer, path_str, arena, } @@ -363,11 +298,11 @@ impl Default for BigTextArea { } pub fn from_path(path: &Path) -> UIResult { - let text_rope = rope_from_path(path)?; + let text_buffer = TextBuffer::from_path(path)?; let path_str = path_to_string(path); Ok(BigTextArea { - text_rope, + text_buffer, path_str, ..Default::default() }) @@ -375,11 +310,9 @@ pub fn from_path(path: &Path) -> UIResult { #[allow(dead_code)] // used by tests but will also be used in the future -pub fn from_str(text: &str) -> BigTextArea { - let text_rope = Rope::from_str(text); - +pub fn from_str_vec(lines: Vec) -> BigTextArea { BigTextArea { - text_rope, + text_buffer: TextBuffer { lines }, ..Default::default() } } @@ -391,31 +324,12 @@ fn path_to_string(path: &Path) -> String { path_str } -fn rope_from_path(path: &Path) -> UIResult { - match File::open(path) { - Ok(file) => { - let buf_reader = &mut io::BufReader::new(file); - match Rope::from_reader(buf_reader) { - Ok(rope) => Ok(rope), - Err(e) => Err(TextBufReadFailed { - path_str: path_to_string(path), - err_msg: e.to_string(), - }), - } - } - Err(e) => Err(FileOpenFailed { - path_str: path_to_string(path), - err_msg: e.to_string(), - }), - } -} - // need to explicitly omit arena impl fmt::Debug for BigTextArea { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BigTextArea") .field("caret_w_select", &self.caret_w_select) - .field("text_rope", &self.text_rope) + .field("text_buffer", &self.text_buffer) .field("path_str", &self.path_str) .finish() } @@ -426,7 +340,6 @@ pub mod test_big_sel_text { use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::{ - big_text_area::from_str, big_text_area::BigTextArea, lines::{Lines, MutSelectableLines, SelectableLines}, text_pos::TextPos, @@ -436,6 +349,8 @@ pub mod test_big_sel_text { use snafu::OptionExt; use std::slice::SliceIndex; + use super::from_str_vec; + fn shift_pressed() -> Modifiers { Modifiers { shift: true, @@ -467,12 +382,11 @@ pub mod test_big_sel_text { } pub fn big_text_from_dsl_str(lines: &[String]) -> BigTextArea { - from_str( - &lines + from_str_vec( + lines .iter() .map(|line| line.replace(&['❮', '❯', '┃'][..], "")) - .collect::>() - .join(""), + .collect::>(), ) } @@ -480,7 +394,7 @@ pub mod test_big_sel_text { let mut lines: Vec = Vec::new(); for i in 0..big_sel_text.nr_of_lines() { - lines.push(big_sel_text.get_line(i).unwrap().to_string()); + lines.push(big_sel_text.get_line_ref(i).unwrap().to_string()); } lines @@ -520,10 +434,9 @@ pub mod test_big_sel_text { assert_insert(&["┃"], &[" ┃"], ' ')?; assert_insert(&["a┃"], &["aa┃"], 'a')?; assert_insert(&["a┃"], &["a ┃"], ' ')?; - assert_insert(&["a┃\n", ""], &["ab┃\n", ""], 'b')?; - assert_insert(&["a┃\n", ""], &["ab┃\n", ""], 'b')?; - assert_insert(&["a\n", "┃"], &["a\n", "b┃"], 'b')?; - assert_insert(&["a\n", "b\n", "c┃"], &["a\n", "b\n", "cd┃"], 'd')?; + assert_insert(&["a┃", ""], &["ab┃", ""], 'b')?; + assert_insert(&["a", "┃"], &["a", "b┃"], 'b')?; + assert_insert(&["a", "b", "c┃"], &["a", "b", "cd┃"], 'd')?; Ok(()) } @@ -532,9 +445,9 @@ pub mod test_big_sel_text { fn insert_new_char_mid() -> Result<(), String> { assert_insert(&["ab┃d"], &["abc┃d"], 'c')?; assert_insert(&["a┃cd"], &["ab┃cd"], 'b')?; - assert_insert(&["abc\n", "┃e"], &["abc\n", "d┃e"], 'd')?; - assert_insert(&["abc\n", "def\n", "┃ "], &["abc\n", "def\n", "g┃ "], 'g')?; - assert_insert(&["abc\n", "def\n", "┃ "], &["abc\n", "def\n", " ┃ "], ' ')?; + assert_insert(&["abc", "┃e"], &["abc", "d┃e"], 'd')?; + assert_insert(&["abc", "def", "┃ "], &["abc", "def", "g┃ "], 'g')?; + assert_insert(&["abc", "def", "┃ "], &["abc", "def", " ┃ "], ' ')?; Ok(()) } @@ -545,11 +458,11 @@ pub mod test_big_sel_text { assert_insert(&[" ┃"], &["┃"], '\u{8}')?; assert_insert(&["a┃"], &["┃"], '\u{8}')?; assert_insert(&["ab┃"], &["a┃"], '\u{8}')?; - assert_insert(&["a┃\n", ""], &["┃\n", ""], '\u{8}')?; - assert_insert(&["ab┃\n", ""], &["a┃\n", ""], '\u{8}')?; - assert_insert(&["a\n", "┃"], &["a┃"], '\u{8}')?; - assert_insert(&["a\n", "b\n", "c┃"], &["a\n", "b\n", "┃"], '\u{8}')?; - assert_insert(&["a\n", "b\n", "┃"], &["a\n", "b┃"], '\u{8}')?; + assert_insert(&["a┃", ""], &["┃", ""], '\u{8}')?; + assert_insert(&["ab┃", ""], &["a┃", ""], '\u{8}')?; + assert_insert(&["a", "┃"], &["a┃"], '\u{8}')?; + assert_insert(&["a", "b", "c┃"], &["a", "b", "┃"], '\u{8}')?; + assert_insert(&["a", "b", "┃"], &["a", "b┃"], '\u{8}')?; Ok(()) } @@ -560,32 +473,28 @@ pub mod test_big_sel_text { assert_insert(&["a❮a❯┃"], &["a┃"], '\u{8}')?; assert_insert(&["❮aa❯┃"], &["┃"], '\u{8}')?; assert_insert(&["a❮b c❯┃"], &["a┃"], '\u{8}')?; - assert_insert(&["❮abc❯┃\n", ""], &["┃\n", ""], '\u{8}')?; - assert_insert(&["a\n", "❮abc❯┃"], &["a\n", "┃"], '\u{8}')?; - assert_insert(&["❮a\n", "abc❯┃"], &["┃"], '\u{8}')?; - assert_insert(&["a❮b\n", "cdef ghij❯┃"], &["a┃"], '\u{8}')?; - assert_insert(&["❮a\n", "b\n", "c❯┃"], &["┃"], '\u{8}')?; - assert_insert(&["a\n", "❮b\n", "❯┃"], &["a\n", "┃"], '\u{8}')?; + assert_insert(&["❮abc❯┃", ""], &["┃", ""], '\u{8}')?; + assert_insert(&["a", "❮abc❯┃"], &["a", "┃"], '\u{8}')?; + assert_insert(&["❮a", "abc❯┃"], &["┃"], '\u{8}')?; + assert_insert(&["a❮b", "cdef ghij❯┃"], &["a┃"], '\u{8}')?; + assert_insert(&["❮a", "b", "c❯┃"], &["┃"], '\u{8}')?; + assert_insert(&["a", "❮b", "❯┃"], &["a", "┃"], '\u{8}')?; assert_insert( - &["abc\n", "d❮ef\n", "ghi❯┃\n", "jkl"], - &["abc\n", "d┃\n", "jkl"], + &["abc", "d❮ef", "ghi❯┃", "jkl"], + &["abc", "d┃", "jkl"], '\u{8}', )?; assert_insert( - &["abc\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "┃\n", "jkl"], + &["abc", "❮def", "ghi❯┃", "jkl"], + &["abc", "┃", "jkl"], '\u{8}', )?; assert_insert( - &["abc\n", "\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "\n", "┃\n", "jkl"], - '\u{8}', - )?; - assert_insert( - &["❮abc\n", "\n", "def\n", "ghi\n", "jkl❯┃"], - &["┃"], + &["abc", "", "❮def", "ghi❯┃", "jkl"], + &["abc", "", "┃", "jkl"], '\u{8}', )?; + assert_insert(&["❮abc", "", "def", "ghi", "jkl❯┃"], &["┃"], '\u{8}')?; Ok(()) } @@ -596,28 +505,24 @@ pub mod test_big_sel_text { assert_insert(&["a❮a❯┃"], &["az┃"], 'z')?; assert_insert(&["❮aa❯┃"], &["z┃"], 'z')?; assert_insert(&["a❮b c❯┃"], &["az┃"], 'z')?; - assert_insert(&["❮abc❯┃\n", ""], &["z┃\n", ""], 'z')?; - assert_insert(&["a\n", "❮abc❯┃"], &["a\n", "z┃"], 'z')?; - assert_insert(&["❮a\n", "abc❯┃"], &["z┃"], 'z')?; - assert_insert(&["a❮b\n", "cdef ghij❯┃"], &["az┃"], 'z')?; - assert_insert(&["❮a\n", "b\n", "c❯┃"], &["z┃"], 'z')?; - assert_insert(&["a\n", "❮b\n", "❯┃"], &["a\n", "z┃"], 'z')?; + assert_insert(&["❮abc❯┃", ""], &["z┃", ""], 'z')?; + assert_insert(&["a", "❮abc❯┃"], &["a", "z┃"], 'z')?; + assert_insert(&["❮a", "abc❯┃"], &["z┃"], 'z')?; + assert_insert(&["a❮b", "cdef ghij❯┃"], &["az┃"], 'z')?; + assert_insert(&["❮a", "b", "c❯┃"], &["z┃"], 'z')?; + assert_insert(&["a", "❮b", "❯┃"], &["a", "z┃"], 'z')?; assert_insert( - &["abc\n", "d❮ef\n", "ghi❯┃\n", "jkl"], - &["abc\n", "dz┃\n", "jkl"], + &["abc", "d❮ef", "ghi❯┃", "jkl"], + &["abc", "dz┃", "jkl"], 'z', )?; + assert_insert(&["abc", "❮def", "ghi❯┃", "jkl"], &["abc", "z┃", "jkl"], 'z')?; assert_insert( - &["abc\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "z┃\n", "jkl"], + &["abc", "", "❮def", "ghi❯┃", "jkl"], + &["abc", "", "z┃", "jkl"], 'z', )?; - assert_insert( - &["abc\n", "\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "\n", "z┃\n", "jkl"], - 'z', - )?; - assert_insert(&["❮abc\n", "\n", "def\n", "ghi\n", "jkl❯┃"], &["z┃"], 'z')?; + assert_insert(&["❮abc", "", "def", "ghi", "jkl❯┃"], &["z┃"], 'z')?; Ok(()) } @@ -647,16 +552,16 @@ pub mod test_big_sel_text { assert_select_all(&["❮a❯┃"], &["❮a❯┃"])?; assert_select_all(&["┃❮a❯"], &["❮a❯┃"])?; assert_select_all(&["┃❮abc def ghi❯"], &["❮abc def ghi❯┃"])?; - assert_select_all(&["a\n", "❮b\n", "❯┃"], &["❮a\n", "b\n", "❯┃"])?; - assert_select_all(&["a\n", "❮b❯┃\n", ""], &["❮a\n", "b\n", "❯┃"])?; - assert_select_all(&["a\n", "┃❮b\n", "❯"], &["❮a\n", "b\n", "❯┃"])?; + assert_select_all(&["a", "❮b", "❯┃"], &["❮a", "b", "❯┃"])?; + assert_select_all(&["a", "❮b❯┃", ""], &["❮a", "b", "❯┃"])?; + assert_select_all(&["a", "┃❮b", "❯"], &["❮a", "b", "❯┃"])?; assert_select_all( - &["abc\n", "def\n", "gh┃i\n", "jkl"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["abc", "def", "gh┃i", "jkl"], + &["❮abc", "def", "ghi", "jkl❯┃"], )?; assert_select_all( - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["┃❮abc", "def", "ghi", "jkl❯"], + &["❮abc", "def", "ghi", "jkl❯┃"], )?; Ok(()) @@ -680,7 +585,10 @@ pub mod test_big_sel_text { move_fun(&mut big_text, modifiers)?; - let lines_vec = all_lines_vec(&big_text); + let lines_vec = all_lines_vec(&big_text) + .iter() + .map(|l| l.replace("\n", "")) + .collect(); let post_lines_res = convert_selection_to_dsl(big_text.caret_w_select, lines_vec); match post_lines_res { @@ -704,57 +612,47 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["abc┃"], &no_mods(), move_caret_right)?; assert_move(&["┃ abc"], &[" ┃abc"], &no_mods(), move_caret_right)?; assert_move(&["abc┃ "], &["abc ┃"], &no_mods(), move_caret_right)?; + assert_move(&["abc┃", "d"], &["abc", "┃d"], &no_mods(), move_caret_right)?; + assert_move(&["abc┃", ""], &["abc", "┃"], &no_mods(), move_caret_right)?; assert_move( - &["abc┃\n", "d"], - &["abc\n", "┃d"], + &["abc", "┃def"], + &["abc", "d┃ef"], &no_mods(), move_caret_right, )?; assert_move( - &["abc┃\n", ""], - &["abc\n", "┃"], + &["abc", "def┃ "], + &["abc", "def ┃"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "d┃ef"], + &["abc", "def ┃", "ghi"], + &["abc", "def ", "┃ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def┃ "], - &["abc\n", "def ┃"], + &["abc", "def┃", ""], + &["abc", "def", "┃"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def \n", "┃ghi"], + &["abc", "def", "ghi┃", "jkl"], + &["abc", "def", "ghi", "┃jkl"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def┃\n", ""], - &["abc\n", "def\n", "┃"], + &["abc", "def", "┃ghi", "jkl"], + &["abc", "def", "g┃hi", "jkl"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &["abc\n", "def\n", "ghi\n", "┃jkl"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "gh┃i\n", "jkl"], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "gh┃i", "jkl"], &no_mods(), move_caret_right, )?; @@ -774,57 +672,47 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["ab┃c"], &no_mods(), move_caret_left)?; assert_move(&[" ┃abc"], &["┃ abc"], &no_mods(), move_caret_left)?; assert_move(&["abc ┃"], &["abc┃ "], &no_mods(), move_caret_left)?; + assert_move(&["abc", "┃d"], &["abc┃", "d"], &no_mods(), move_caret_left)?; + assert_move(&["abc", "┃"], &["abc┃", ""], &no_mods(), move_caret_left)?; assert_move( - &["abc\n", "┃d"], - &["abc┃\n", "d"], + &["abc", "d┃ef"], + &["abc", "┃def"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "┃"], - &["abc┃\n", ""], + &["abc", "def ┃"], + &["abc", "def┃ "], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "┃def"], + &["abc", "def ", "┃ghi"], + &["abc", "def ┃", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def ┃"], - &["abc\n", "def┃ "], + &["abc", "def", "┃"], + &["abc", "def┃", ""], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def \n", "┃ghi"], - &["abc\n", "def ┃\n", "ghi"], + &["abc", "def", "ghi", "┃jkl"], + &["abc", "def", "ghi┃", "jkl"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "┃"], - &["abc\n", "def┃\n", ""], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "┃ghi", "jkl"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi\n", "┃jkl"], - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &no_mods(), - move_caret_left, - )?; - assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &no_mods(), - move_caret_left, - )?; - assert_move( - &["abc\n", "def\n", "gh┃i\n", "jkl"], - &["abc\n", "def\n", "g┃hi\n", "jkl"], + &["abc", "def", "gh┃i", "jkl"], + &["abc", "def", "g┃hi", "jkl"], &no_mods(), move_caret_left, )?; @@ -843,158 +731,143 @@ pub mod test_big_sel_text { assert_move(&["ab┃c"], &["┃abc"], &no_mods(), move_caret_up)?; assert_move(&["abc┃"], &["┃abc"], &no_mods(), move_caret_up)?; assert_move( - &["┃abc\n", "def"], - &["┃abc\n", "def"], + &["┃abc", "def"], + &["┃abc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "┃def"], - &["┃abc\n", "def"], + &["abc", "┃def"], + &["┃abc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d┃ef"], - &["a┃bc\n", "def"], + &["abc", "d┃ef"], + &["a┃bc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃f"], - &["ab┃c\n", "def"], + &["abc", "de┃f"], + &["ab┃c", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def┃"], - &["abc┃\n", "def"], + &["abc", "def┃"], + &["abc┃", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "┃ghi"], - &["abc\n", "┃def \n", "ghi"], + &["abc", "def ", "┃ghi"], + &["abc", "┃def ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "g┃hi"], - &["abc\n", "d┃ef \n", "ghi"], + &["abc", "def ", "g┃hi"], + &["abc", "d┃ef ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "gh┃i"], - &["abc\n", "de┃f \n", "ghi"], + &["abc", "def ", "gh┃i"], + &["abc", "de┃f ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "ghi┃"], - &["abc\n", "def┃ \n", "ghi"], + &["abc", "def ", "ghi┃"], + &["abc", "def┃ ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de\n", "ghi┃"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "de", "ghi┃"], + &["abc", "de┃", "ghi"], + &no_mods(), + move_caret_up, + )?; + assert_move(&["abc", "de┃"], &["ab┃c", "de"], &no_mods(), move_caret_up)?; + assert_move(&["abc", "d┃e"], &["a┃bc", "de"], &no_mods(), move_caret_up)?; + assert_move(&["abc", "┃de"], &["┃abc", "de"], &no_mods(), move_caret_up)?; + assert_move( + &["ab", "cdef", "ghijkl", "mnopqrst┃"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃"], - &["ab┃c\n", "de"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d┃e"], - &["a┃bc\n", "de"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "┃de"], - &["┃abc\n", "de"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], + &["ab", "cde┃f", "ghijkl", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], + &["abcdefgh", "ijklmn", "op┃qr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijkl┃mn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdef┃gh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cde┃f\n", "ghijkl\n", "mnopqrst"], - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], - &["abcdefgh\n", "ijklmn\n", "op┃qr\n", "st"], + &["abcdefg┃h", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijkl┃mn\n", "opqr\n", "st"], + &["a┃bcdefgh", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdef┃gh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["abcdefg┃h\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["a┃bcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; @@ -1038,170 +911,170 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["abc┃"], &no_mods(), move_caret_down)?; assert_move(&["abc┃ "], &["abc ┃"], &no_mods(), move_caret_down)?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "def┃"], + &["abc", "┃def"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "def┃"], + &["abc", "d┃ef"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f"], - &["abc\n", "def┃"], + &["abc", "de┃f"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def┃"], - &["abc\n", "def┃"], + &["abc", "def┃"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["┃abc\n", "def"], - &["abc\n", "┃def"], + &["┃abc", "def"], + &["abc", "┃def"], &no_mods(), move_caret_down, )?; assert_move( - &["a┃bc\n", "def"], - &["abc\n", "d┃ef"], + &["a┃bc", "def"], + &["abc", "d┃ef"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃c\n", "def"], - &["abc\n", "de┃f"], + &["ab┃c", "def"], + &["abc", "de┃f"], &no_mods(), move_caret_down, )?; assert_move( - &["abc┃\n", "def"], - &["abc\n", "def┃"], + &["abc┃", "def"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "┃def \n", "ghi"], - &["abc\n", "def \n", "┃ghi"], + &["abc", "┃def ", "ghi"], + &["abc", "def ", "┃ghi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef \n", "ghi"], - &["abc\n", "def \n", "g┃hi"], + &["abc", "d┃ef ", "ghi"], + &["abc", "def ", "g┃hi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f \n", "ghi"], - &["abc\n", "def \n", "gh┃i"], + &["abc", "de┃f ", "ghi"], + &["abc", "def ", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def┃ \n", "ghi"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def┃ ", "ghi"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def ┃", "ghi"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃\n", "ghi"], - &["abc\n", "de\n", "gh┃i"], + &["abc", "de┃", "ghi"], + &["abc", "de", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc┃\n", "de"], - &["abc\n", "de┃"], + &["abc┃", "de"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃c\n", "de"], - &["abc\n", "de┃"], + &["ab┃c", "de"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["a┃bc\n", "de"], - &["abc\n", "d┃e"], + &["a┃bc", "de"], + &["abc", "d┃e"], &no_mods(), move_caret_down, )?; assert_move( - &["┃abc\n", "de"], - &["abc\n", "┃de"], + &["┃abc", "de"], + &["abc", "┃de"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cd┃ef\n", "ghijkl\n", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], + &["ab", "cd┃ef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghij┃kl\n", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], + &["ab", "cdef", "ghij┃kl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], &no_mods(), move_caret_down, )?; assert_move( - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "┃cdef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["ab", "┃cdef", "ghijkl", "mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "┃st"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr", "┃st"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; @@ -1272,80 +1145,80 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "de┃\n", "ghi"], - &["abc\n", "┃de\n", "ghi"], + &["abc", "de┃", "ghi"], + &["abc", "┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " d┃e\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["abc", " d┃e", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "┃ de\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["abc", "┃ de", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " ┃de\n", "ghi"], - &["abc\n", "┃ de\n", "ghi"], + &["abc", " ┃de", "ghi"], + &["abc", "┃ de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc┃\n", "de\n", "ghi"], - &["┃abc\n", "de\n", "ghi"], + &["abc┃", "de", "ghi"], + &["┃abc", "de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &[" ┃abc\n", "de\n", "ghi"], - &["┃ abc\n", "de\n", "ghi"], + &[" ┃abc", "de", "ghi"], + &["┃ abc", "de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["┃ abc\n", "de\n", "ghi"], - &[" ┃abc\n", "de\n", "ghi"], + &["┃ abc", "de", "ghi"], + &[" ┃abc", "de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "de\n", "ghi┃"], - &["abc\n", "de\n", "┃ghi"], + &["abc", "de", "ghi┃"], + &["abc", "de", "┃ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "de\n", " ┃ghi"], - &["abc\n", "de\n", "┃ ghi"], + &["abc", "de", " ┃ghi"], + &["abc", "de", "┃ ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "de\n", "┃ ghi"], - &["abc\n", "de\n", " ┃ghi"], + &["abc", "de", "┃ ghi"], + &["abc", "de", " ┃ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc \n", "de \n", "┃ghi "], - &["abc \n", "de \n", "┃ghi "], + &["abc ", "de ", "┃ghi "], + &["abc ", "de ", "┃ghi "], &no_mods(), move_caret_home, )?; assert_move( - &["abc \n", "┃de \n", "ghi "], - &["abc \n", "┃de \n", "ghi "], + &["abc ", "┃de ", "ghi "], + &["abc ", "┃de ", "ghi "], &no_mods(), move_caret_home, )?; assert_move( - &["┃abc \n", "de \n", "ghi "], - &["┃abc \n", "de \n", "ghi "], + &["┃abc ", "de ", "ghi "], + &["┃abc ", "de ", "ghi "], &no_mods(), move_caret_home, )?; @@ -1370,56 +1243,56 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "┃de\n", "ghi"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "┃de", "ghi"], + &["abc", "de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["abc\n", " d┃e\n", "ghi"], - &["abc\n", " de┃\n", "ghi"], + &["abc", " d┃e", "ghi"], + &["abc", " de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["┃abc\n", "de\n", "ghi"], - &["abc┃\n", "de\n", "ghi"], + &["┃abc", "de", "ghi"], + &["abc┃", "de", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["abc\n", "de\n", "g┃hi"], - &["abc\n", "de\n", "ghi┃"], + &["abc", "de", "g┃hi"], + &["abc", "de", "ghi┃"], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "de \n", "ghi┃ "], - &["abc \n", "de \n", "ghi ┃"], + &["abc ", "de ", "ghi┃ "], + &["abc ", "de ", "ghi ┃"], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "┃de \n", "ghi "], - &["abc \n", "de ┃\n", "ghi "], + &["abc ", "┃de ", "ghi "], + &["abc ", "de ┃", "ghi "], &no_mods(), move_caret_end, )?; assert_move( - &["abc ┃\n", "de \n", "ghi "], - &["abc ┃\n", "de \n", "ghi "], + &["abc ┃", "de ", "ghi "], + &["abc ┃", "de ", "ghi "], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "de ┃\n", "ghi "], - &["abc \n", "de ┃\n", "ghi "], + &["abc ", "de ┃", "ghi "], + &["abc ", "de ┃", "ghi "], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "de \n", "ghi ┃"], - &["abc \n", "de \n", "ghi ┃"], + &["abc ", "de ", "ghi ┃"], + &["abc ", "de ", "ghi ┃"], &no_mods(), move_caret_end, )?; @@ -1440,56 +1313,56 @@ pub mod test_big_sel_text { assert_move(&["┃ abc"], &["❮ ❯┃abc"], &shift_pressed(), move_caret_right)?; assert_move(&["abc┃ "], &["abc❮ ❯┃"], &shift_pressed(), move_caret_right)?; assert_move( - &["abc┃\n", "d"], - &["abc❮\n", "❯┃d"], + &["abc┃", "d"], + &["abc❮", "❯┃d"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc┃\n", ""], - &["abc❮\n", "❯┃"], + &["abc┃", ""], + &["abc❮", "❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "❮d❯┃ef"], + &["abc", "┃def"], + &["abc", "❮d❯┃ef"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def┃ "], - &["abc\n", "def❮ ❯┃"], + &["abc", "def┃ "], + &["abc", "def❮ ❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def ❮\n", "❯┃ghi"], + &["abc", "def ┃", "ghi"], + &["abc", "def ❮", "❯┃ghi"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def┃\n", ""], - &["abc\n", "def❮\n", "❯┃"], + &["abc", "def┃", ""], + &["abc", "def❮", "❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], + &["abc", "def", "ghi┃", "jkl"], + &["abc", "def", "ghi❮", "❯┃jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &["abc\n", "def\n", "❮g❯┃hi\n", "jkl"], + &["abc", "def", "┃ghi", "jkl"], + &["abc", "def", "❮g❯┃hi", "jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "g❮h❯┃i\n", "jkl"], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "g❮h❯┃i", "jkl"], &shift_pressed(), move_caret_right, )?; @@ -1510,68 +1383,68 @@ pub mod test_big_sel_text { assert_move(&[" ┃abc"], &["┃❮ ❯abc"], &shift_pressed(), move_caret_left)?; assert_move(&["abc ┃"], &["abc┃❮ ❯"], &shift_pressed(), move_caret_left)?; assert_move( - &["abc┃\n", "d"], - &["ab┃❮c❯\n", "d"], + &["abc┃", "d"], + &["ab┃❮c❯", "d"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "┃d"], - &["abc┃❮\n", "❯d"], + &["abc", "┃d"], + &["abc┃❮", "❯d"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "┃"], - &["abc┃❮\n", "❯"], + &["abc", "┃"], + &["abc┃❮", "❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", " ┃def"], - &["abc\n", "┃❮ ❯def"], + &["abc", " ┃def"], + &["abc", "┃❮ ❯def"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "┃❮d❯ef"], + &["abc", "d┃ef"], + &["abc", "┃❮d❯ef"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "de┃f "], - &["abc\n", "d┃❮e❯f "], + &["abc", "de┃f "], + &["abc", "d┃❮e❯f "], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "┃"], - &["abc\n", "def┃❮\n", "❯"], + &["abc", "def", "┃"], + &["abc", "def┃❮", "❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &["abc\n", "def┃❮\n", "❯ghi\n", "jkl"], + &["abc", "def", "┃ghi", "jkl"], + &["abc", "def┃❮", "❯ghi", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "┃❮g❯hi\n", "jkl"], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "┃❮g❯hi", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "gh┃i\n", "jkl"], - &["abc\n", "def\n", "g┃❮h❯i\n", "jkl"], + &["abc", "def", "gh┃i", "jkl"], + &["abc", "def", "g┃❮h❯i", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &["abc\n", "def\n", "gh┃❮i❯\n", "jkl"], + &["abc", "def", "ghi┃", "jkl"], + &["abc", "def", "gh┃❮i❯", "jkl"], &shift_pressed(), move_caret_left, )?; @@ -1591,170 +1464,170 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["abc┃"], &shift_pressed(), move_caret_down)?; assert_move(&["abc┃ "], &["abc❮ ❯┃"], &shift_pressed(), move_caret_down)?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "❮def❯┃"], + &["abc", "┃def"], + &["abc", "❮def❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "d❮ef❯┃"], + &["abc", "d┃ef"], + &["abc", "d❮ef❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f"], - &["abc\n", "de❮f❯┃"], + &["abc", "de┃f"], + &["abc", "de❮f❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "def┃"], - &["abc\n", "def┃"], + &["abc", "def┃"], + &["abc", "def┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["┃abc\n", "def"], - &["❮abc\n", "❯┃def"], + &["┃abc", "def"], + &["❮abc", "❯┃def"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a┃bc\n", "def"], - &["a❮bc\n", "d❯┃ef"], + &["a┃bc", "def"], + &["a❮bc", "d❯┃ef"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃c\n", "def"], - &["ab❮c\n", "de❯┃f"], + &["ab┃c", "def"], + &["ab❮c", "de❯┃f"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc┃\n", "def"], - &["abc❮\n", "def❯┃"], + &["abc┃", "def"], + &["abc❮", "def❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "┃def \n", "ghi"], - &["abc\n", "❮def \n", "❯┃ghi"], + &["abc", "┃def ", "ghi"], + &["abc", "❮def ", "❯┃ghi"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef \n", "ghi"], - &["abc\n", "d❮ef \n", "g❯┃hi"], + &["abc", "d┃ef ", "ghi"], + &["abc", "d❮ef ", "g❯┃hi"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f \n", "ghi"], - &["abc\n", "de❮f \n", "gh❯┃i"], + &["abc", "de┃f ", "ghi"], + &["abc", "de❮f ", "gh❯┃i"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "def┃ \n", "ghi"], - &["abc\n", "def❮ \n", "ghi❯┃"], + &["abc", "def┃ ", "ghi"], + &["abc", "def❮ ", "ghi❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def ❮\n", "ghi❯┃"], + &["abc", "def ┃", "ghi"], + &["abc", "def ❮", "ghi❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃\n", "ghi"], - &["abc\n", "de❮\n", "gh❯┃i"], + &["abc", "de┃", "ghi"], + &["abc", "de❮", "gh❯┃i"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc┃\n", "de"], - &["abc❮\n", "de❯┃"], + &["abc┃", "de"], + &["abc❮", "de❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃c\n", "de"], - &["ab❮c\n", "de❯┃"], + &["ab┃c", "de"], + &["ab❮c", "de❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a┃bc\n", "de"], - &["a❮bc\n", "d❯┃e"], + &["a┃bc", "de"], + &["a❮bc", "d❯┃e"], &shift_pressed(), move_caret_down, )?; assert_move( - &["┃abc\n", "de"], - &["❮abc\n", "❯┃de"], + &["┃abc", "de"], + &["❮abc", "❯┃de"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], - &["ab❮\n", "cd❯┃ef\n", "ghijkl\n", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], + &["ab❮", "cd❯┃ef", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cdef❮\n", "ghij❯┃kl\n", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], + &["ab", "cdef❮", "ghij❯┃kl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghijkl❮\n", "mnopqr❯┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef", "ghijkl❮", "mnopqr❯┃st"], &shift_pressed(), move_caret_down, )?; assert_move( - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], - &[" ❮ab\n", " ❯┃cdef\n", "ghijkl\n", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], + &[" ❮ab", " ❯┃cdef", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "┃cdef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "❮cdef\n", "❯┃ghijkl\n", "mnopqrst"], + &["ab", "┃cdef", "ghijkl", "mnopqrst"], + &["ab", "❮cdef", "❯┃ghijkl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "❮ghijkl\n", "❯┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], + &["ab", "cdef", "❮ghijkl", "❯┃mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["abcdefgh❮\n", "ijklmn❯┃\n", "opqr\n", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["abcdefgh❮", "ijklmn❯┃", "opqr", "st"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn❮\n", "opqr❯┃\n", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdefgh", "ijklmn❮", "opqr❯┃", "st"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr❮\n", "st❯┃"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijklmn", "opqr❮", "st❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "┃st"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "❮st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "┃st"], + &["abcdefgh", "ijklmn", "opqr", "❮st❯┃"], &shift_pressed(), move_caret_down, )?; @@ -1803,158 +1676,158 @@ pub mod test_big_sel_text { assert_move(&["ab┃c"], &["┃❮ab❯c"], &shift_pressed(), move_caret_up)?; assert_move(&["abc┃"], &["┃❮abc❯"], &shift_pressed(), move_caret_up)?; assert_move( - &["┃abc\n", "def"], - &["┃abc\n", "def"], + &["┃abc", "def"], + &["┃abc", "def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "┃def"], - &["┃❮abc\n", "❯def"], + &["abc", "┃def"], + &["┃❮abc", "❯def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "d┃ef"], - &["a┃❮bc\n", "d❯ef"], + &["abc", "d┃ef"], + &["a┃❮bc", "d❯ef"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de┃f"], - &["ab┃❮c\n", "de❯f"], + &["abc", "de┃f"], + &["ab┃❮c", "de❯f"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def┃"], - &["abc┃❮\n", "def❯"], + &["abc", "def┃"], + &["abc┃❮", "def❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "┃ghi"], - &["abc\n", "┃❮def \n", "❯ghi"], + &["abc", "def ", "┃ghi"], + &["abc", "┃❮def ", "❯ghi"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "g┃hi"], - &["abc\n", "d┃❮ef \n", "g❯hi"], + &["abc", "def ", "g┃hi"], + &["abc", "d┃❮ef ", "g❯hi"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "gh┃i"], - &["abc\n", "de┃❮f \n", "gh❯i"], + &["abc", "def ", "gh┃i"], + &["abc", "de┃❮f ", "gh❯i"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "ghi┃"], - &["abc\n", "def┃❮ \n", "ghi❯"], + &["abc", "def ", "ghi┃"], + &["abc", "def┃❮ ", "ghi❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de\n", "ghi┃"], - &["abc\n", "de┃❮\n", "ghi❯"], + &["abc", "de", "ghi┃"], + &["abc", "de┃❮", "ghi❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de┃"], - &["ab┃❮c\n", "de❯"], + &["abc", "de┃"], + &["ab┃❮c", "de❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "d┃e"], - &["a┃❮bc\n", "d❯e"], + &["abc", "d┃e"], + &["a┃❮bc", "d❯e"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "┃de"], - &["┃❮abc\n", "❯de"], + &["abc", "┃de"], + &["┃❮abc", "❯de"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "mnopqrst❯"], + &["ab", "cdef", "ghijkl", "mnopqrst┃"], + &["ab", "cdef", "ghijkl┃❮", "mnopqrst❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef┃❮\n", "ghijkl❯\n", "mnopqrst"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef┃❮", "ghijkl❯", "mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], - &["ab\n", "cdef\n", "┃❮ghijkl\n", "❯mnopqrst"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], + &["ab", "cdef", "┃❮ghijkl", "❯mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ┃❮ab\n", " ❯cdef\n", "ghijkl\n", "mnopqrst"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], + &[" ┃❮ab", " ❯cdef", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "mnopqr❯st"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], + &["ab", "cdef", "ghijkl┃❮", "mnopqr❯st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cde┃f\n", "ghijkl\n", "mnopqrst"], - &["ab┃❮\n", "cde❯f\n", "ghijkl\n", "mnopqrst"], + &["ab", "cde┃f", "ghijkl", "mnopqrst"], + &["ab┃❮", "cde❯f", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], - &["abcdefgh\n", "ijklmn\n", "op┃❮qr\n", "st❯"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], + &["abcdefgh", "ijklmn", "op┃❮qr", "st❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijkl┃❮mn\n", "opqr❯\n", "st"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijkl┃❮mn", "opqr❯", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdef┃❮gh\n", "ijklmn❯\n", "opqr\n", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdef┃❮gh", "ijklmn❯", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["┃❮abcdefgh❯\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["┃❮abcdefgh❯", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefg┃h\n", "ijklmn\n", "opqr\n", "st"], - &["┃❮abcdefg❯h\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefg┃h", "ijklmn", "opqr", "st"], + &["┃❮abcdefg❯h", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["a┃bcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃❮a❯bcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["a┃bcdefgh", "ijklmn", "opqr", "st"], + &["┃❮a❯bcdefgh", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; @@ -2031,74 +1904,74 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "def\n", "ghi┃"], - &["abc\n", "def\n", "┃❮ghi❯"], + &["abc", "def", "ghi┃"], + &["abc", "def", "┃❮ghi❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def┃\n", "ghi"], - &["abc\n", "┃❮def❯\n", "ghi"], + &["abc", "def┃", "ghi"], + &["abc", "┃❮def❯", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc┃\n", "def\n", "ghi"], - &["┃❮abc❯\n", "def\n", "ghi"], + &["abc┃", "def", "ghi"], + &["┃❮abc❯", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["┃abc\n", "def\n", "ghi"], - &["┃abc\n", "def\n", "ghi"], + &["┃abc", "def", "ghi"], + &["┃abc", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "┃def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["abc", "┃def", "ghi"], + &["abc", "┃def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", "┃ghi"], - &["abc\n", "def\n", "┃ghi"], + &["abc", "def", "┃ghi"], + &["abc", "def", "┃ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &[" ┃abc\n", "def\n", "ghi"], - &["┃❮ ❯abc\n", "def\n", "ghi"], + &[" ┃abc", "def", "ghi"], + &["┃❮ ❯abc", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", " ┃def\n", "ghi"], - &["abc\n", "┃❮ ❯def\n", "ghi"], + &["abc", " ┃def", "ghi"], + &["abc", "┃❮ ❯def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", " ┃ghi"], - &["abc\n", "def\n", "┃❮ ❯ghi"], + &["abc", "def", " ┃ghi"], + &["abc", "def", "┃❮ ❯ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["┃ abc\n", "def\n", "ghi"], - &["❮ ❯┃abc\n", "def\n", "ghi"], + &["┃ abc", "def", "ghi"], + &["❮ ❯┃abc", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "┃ def\n", "ghi"], - &["abc\n", "❮ ❯┃def\n", "ghi"], + &["abc", "┃ def", "ghi"], + &["abc", "❮ ❯┃def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", "┃ ghi"], - &["abc\n", "def\n", "❮ ❯┃ghi"], + &["abc", "def", "┃ ghi"], + &["abc", "def", "❮ ❯┃ghi"], &shift_pressed(), move_caret_home, )?; @@ -2121,38 +1994,38 @@ pub mod test_big_sel_text { assert_move(&[" abc┃ "], &[" abc❮ ❯┃"], &shift_pressed(), move_caret_end)?; assert_move(&[" ab┃c"], &[" ab❮c❯┃"], &shift_pressed(), move_caret_end)?; assert_move( - &["abc\n", "def\n", "┃ghi"], - &["abc\n", "def\n", "❮ghi❯┃"], + &["abc", "def", "┃ghi"], + &["abc", "def", "❮ghi❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "┃def\n", "ghi"], - &["abc\n", "❮def❯┃\n", "ghi"], + &["abc", "┃def", "ghi"], + &["abc", "❮def❯┃", "ghi"], &shift_pressed(), move_caret_end, )?; assert_move( - &["┃abc\n", "def\n", "ghi"], - &["❮abc❯┃\n", "def\n", "ghi"], + &["┃abc", "def", "ghi"], + &["❮abc❯┃", "def", "ghi"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "def\n", "┃ghi "], - &["abc\n", "def\n", "❮ghi ❯┃"], + &["abc", "def", "┃ghi "], + &["abc", "def", "❮ghi ❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "┃def \n", "ghi"], - &["abc\n", "❮def ❯┃\n", "ghi"], + &["abc", "┃def ", "ghi"], + &["abc", "❮def ❯┃", "ghi"], &shift_pressed(), move_caret_end, )?; assert_move( - &["┃abc \n", "def\n", "ghi"], - &["❮abc ❯┃\n", "def\n", "ghi"], + &["┃abc ", "def", "ghi"], + &["❮abc ❯┃", "def", "ghi"], &shift_pressed(), move_caret_end, )?; @@ -2172,94 +2045,89 @@ pub mod test_big_sel_text { assert_move(&["┃❮ ❯abc"], &[" ┃abc"], &no_mods(), move_caret_right)?; assert_move(&["a┃❮b❯c"], &["ab┃c"], &no_mods(), move_caret_right)?; assert_move( - &["abc❮\n", "❯┃d"], - &["abc\n", "┃d"], + &["abc❮", "❯┃d"], + &["abc", "┃d"], &no_mods(), move_caret_right, )?; assert_move( - &["abc┃❮\n", "❯d"], - &["abc\n", "┃d"], + &["abc┃❮", "❯d"], + &["abc", "┃d"], + &no_mods(), + move_caret_right, + )?; + assert_move(&["abc┃❮", "❯"], &["abc", "┃"], &no_mods(), move_caret_right)?; + assert_move( + &["abc", "❮d❯┃ef"], + &["abc", "d┃ef"], &no_mods(), move_caret_right, )?; assert_move( - &["abc┃❮\n", "❯"], - &["abc\n", "┃"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "❮d❯┃ef"], - &["abc\n", "d┃ef"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], - &["abc\n", "def\n", "ghi\n", "┃jkl"], + &["abc", "def", "ghi❮", "❯┃jkl"], + &["abc", "def", "ghi", "┃jkl"], &no_mods(), move_caret_right, )?; assert_move(&["❮ab❯┃c"], &["ab┃c"], &no_mods(), move_caret_right)?; assert_move(&["❮abc❯┃"], &["abc┃"], &no_mods(), move_caret_right)?; assert_move( - &["ab┃❮c\n", "❯def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["ab┃❮c", "❯def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["ab❮c\n", "❯┃def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["ab❮c", "❯┃def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["a┃❮bc\n", "❯def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["a┃❮bc", "❯def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["┃❮abc\n", "❯def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["┃❮abc", "❯def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["a┃❮bc\n", "d❯ef\n", "ghi"], - &["abc\n", "d┃ef\n", "ghi"], + &["a┃❮bc", "d❯ef", "ghi"], + &["abc", "d┃ef", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["┃❮abc\n", "def❯\n", "ghi"], - &["abc\n", "def┃\n", "ghi"], + &["┃❮abc", "def❯", "ghi"], + &["abc", "def┃", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["ab", "cdef", "ghijkl", "mnopqrst┃"], &no_mods(), move_caret_right, )?; assert_move( - &["┃❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], + &["┃❮ab", "cdef", "ghijkl", "mnopqrst❯"], + &["ab", "cdef", "ghijkl", "mnopqrst┃"], &no_mods(), move_caret_right, )?; assert_move( - &["ab\n", "c❮def\n", "ghijkl\n", "mno❯┃pqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "mno┃pqrst"], + &["ab", "c❮def", "ghijkl", "mno❯┃pqrst"], + &["ab", "cdef", "ghijkl", "mno┃pqrst"], &no_mods(), move_caret_right, )?; assert_move( - &["ab\n", "c┃❮def\n", "ghijkl\n", "mno❯pqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "mno┃pqrst"], + &["ab", "c┃❮def", "ghijkl", "mno❯pqrst"], + &["ab", "cdef", "ghijkl", "mno┃pqrst"], &no_mods(), move_caret_right, )?; @@ -2279,94 +2147,94 @@ pub mod test_big_sel_text { assert_move(&["┃❮ ❯abc"], &["┃ abc"], &no_mods(), move_caret_left)?; assert_move(&["a┃❮b❯c"], &["a┃bc"], &no_mods(), move_caret_left)?; assert_move( - &["abc❮\n", "❯┃d"], - &["abc┃\n", "d"], + &["abc❮", "❯┃d"], + &["abc┃", "d"], &no_mods(), move_caret_left, )?; assert_move( - &["abc┃❮\n", "❯d"], - &["abc┃\n", "d"], + &["abc┃❮", "❯d"], + &["abc┃", "d"], &no_mods(), move_caret_left, )?; assert_move( - &["abc┃❮\n", "❯"], - &["abc┃\n", ""], + &["abc┃❮", "❯"], + &["abc┃", ""], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "❮d❯┃ef"], - &["abc\n", "┃def"], + &["abc", "❮d❯┃ef"], + &["abc", "┃def"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], - &["abc\n", "def\n", "ghi┃\n", "jkl"], + &["abc", "def", "ghi❮", "❯┃jkl"], + &["abc", "def", "ghi┃", "jkl"], &no_mods(), move_caret_left, )?; assert_move(&["❮ab❯┃c"], &["┃abc"], &no_mods(), move_caret_left)?; assert_move(&["❮abc❯┃"], &["┃abc"], &no_mods(), move_caret_left)?; assert_move( - &["ab┃❮c\n", "❯def\n", "ghi"], - &["ab┃c\n", "def\n", "ghi"], + &["ab┃❮c", "❯def", "ghi"], + &["ab┃c", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["ab❮c\n", "❯┃def\n", "ghi"], - &["ab┃c\n", "def\n", "ghi"], + &["ab❮c", "❯┃def", "ghi"], + &["ab┃c", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["a┃❮bc\n", "❯def\n", "ghi"], - &["a┃bc\n", "def\n", "ghi"], + &["a┃❮bc", "❯def", "ghi"], + &["a┃bc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["┃❮abc\n", "❯def\n", "ghi"], - &["┃abc\n", "def\n", "ghi"], + &["┃❮abc", "❯def", "ghi"], + &["┃abc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["a┃❮bc\n", "d❯ef\n", "ghi"], - &["a┃bc\n", "def\n", "ghi"], + &["a┃❮bc", "d❯ef", "ghi"], + &["a┃bc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["┃❮abc\n", "def❯\n", "ghi"], - &["┃abc\n", "def\n", "ghi"], + &["┃❮abc", "def❯", "ghi"], + &["┃abc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["┃ab\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["┃ab", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?; assert_move( - &["┃❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯"], - &["┃ab\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["┃❮ab", "cdef", "ghijkl", "mnopqrst❯"], + &["┃ab", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?; assert_move( - &["ab\n", "c❮def\n", "ghijkl\n", "mno❯┃pqrst"], - &["ab\n", "c┃def\n", "ghijkl\n", "mnopqrst"], + &["ab", "c❮def", "ghijkl", "mno❯┃pqrst"], + &["ab", "c┃def", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?; assert_move( - &["ab\n", "c┃❮def\n", "ghijkl\n", "mno❯pqrst"], - &["ab\n", "c┃def\n", "ghijkl\n", "mnopqrst"], + &["ab", "c┃❮def", "ghijkl", "mno❯pqrst"], + &["ab", "c┃def", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?;*/ @@ -2384,164 +2252,164 @@ pub mod test_big_sel_text { assert_move(&["ab❮c❯┃"], &["abc┃"], &no_mods(), move_caret_down)?; assert_move(&["abc┃❮ ❯"], &["abc ┃"], &no_mods(), move_caret_down)?; assert_move( - &["abc\n", "┃❮def❯"], - &["abc\n", "def┃"], + &["abc", "┃❮def❯"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d┃❮ef❯"], - &["abc\n", "def┃"], + &["abc", "d┃❮ef❯"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["abc\n", "def┃"], + &["abc", "de┃❮f❯"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["❮abc\n", "❯┃def"], - &["abc\n", "┃def"], + &["❮abc", "❯┃def"], + &["abc", "┃def"], &no_mods(), move_caret_down, )?; assert_move( - &["a❮bc\n", "d❯┃ef"], - &["abc\n", "d┃ef"], + &["a❮bc", "d❯┃ef"], + &["abc", "d┃ef"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃❮c\n", "de❯f"], - &["abc\n", "de┃f"], + &["ab┃❮c", "de❯f"], + &["abc", "de┃f"], &no_mods(), move_caret_down, )?; assert_move( - &["abc❮\n", "def❯┃"], - &["abc\n", "def┃"], + &["abc❮", "def❯┃"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "┃❮def \n", "❯ghi"], - &["abc\n", "def \n", "┃ghi"], + &["abc", "┃❮def ", "❯ghi"], + &["abc", "def ", "┃ghi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d❮ef \n", "g❯┃hi"], - &["abc\n", "def \n", "g┃hi"], + &["abc", "d❮ef ", "g❯┃hi"], + &["abc", "def ", "g┃hi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de❮f \n", "gh❯┃i"], - &["abc\n", "def \n", "gh┃i"], + &["abc", "de❮f ", "gh❯┃i"], + &["abc", "def ", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def❮ \n", "ghi❯┃"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def❮ ", "ghi❯┃"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def ❮\n", "ghi❯┃"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def ❮", "ghi❯┃"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de❮\n", "gh❯┃i"], - &["abc\n", "de\n", "gh┃i"], + &["abc", "de❮", "gh❯┃i"], + &["abc", "de", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc┃❮\n", "de❯"], - &["abc\n", "de┃"], + &["abc┃❮", "de❯"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["ab❮c\n", "de❯┃"], - &["abc\n", "de┃"], + &["ab❮c", "de❯┃"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["a┃❮bc\n", "d❯e"], - &["abc\n", "d┃e"], + &["a┃❮bc", "d❯e"], + &["abc", "d┃e"], &no_mods(), move_caret_down, )?; assert_move( - &["❮abc\n", "❯┃de"], - &["abc\n", "┃de"], + &["❮abc", "❯┃de"], + &["abc", "┃de"], &no_mods(), move_caret_down, )?; assert_move( - &["ab❮\n", "cd❯┃ef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cd┃ef\n", "ghijkl\n", "mnopqrst"], + &["ab❮", "cd❯┃ef", "ghijkl", "mnopqrst"], + &["ab", "cd┃ef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef┃❮\n", "ghij❯kl\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghij┃kl\n", "mnopqrst"], + &["ab", "cdef┃❮", "ghij❯kl", "mnopqrst"], + &["ab", "cdef", "ghij┃kl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl❮\n", "mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], + &["ab", "cdef", "ghijkl❮", "mnopqr❯┃st"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], &no_mods(), move_caret_down, )?; assert_move( - &[" ❮ab\n", " ❯┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], + &[" ❮ab", " ❯┃cdef", "ghijkl", "mnopqrst"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "┃❮cdef\n", "❯ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["ab", "┃❮cdef", "❯ghijkl", "mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "❮ghijkl\n", "❯┃mnopqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], + &["ab", "cdef", "❮ghijkl", "❯┃mnopqrst"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh❮\n", "ijklmn❯┃\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], + &["abcdefgh❮", "ijklmn❯┃", "opqr", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn❮\n", "opqr❯┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], + &["abcdefgh", "ijklmn❮", "opqr❯┃", "st"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr❮\n", "st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr❮", "st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "❮st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr", "❮st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; @@ -2583,164 +2451,164 @@ pub mod test_big_sel_text { assert_move(&["ab❮c❯┃"], &["ab┃c"], &no_mods(), move_caret_up)?; assert_move(&["abc┃❮ ❯"], &["abc┃ "], &no_mods(), move_caret_up)?; assert_move( - &["abc\n", "┃❮def❯"], - &["abc\n", "┃def"], + &["abc", "┃❮def❯"], + &["abc", "┃def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d┃❮ef❯"], - &["abc\n", "d┃ef"], + &["abc", "d┃❮ef❯"], + &["abc", "d┃ef"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["abc\n", "de┃f"], + &["abc", "de┃❮f❯"], + &["abc", "de┃f"], &no_mods(), move_caret_up, )?; assert_move( - &["❮abc\n", "❯┃def"], - &["┃abc\n", "def"], + &["❮abc", "❯┃def"], + &["┃abc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["a❮bc\n", "d❯┃ef"], - &["a┃bc\n", "def"], + &["a❮bc", "d❯┃ef"], + &["a┃bc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["ab┃❮c\n", "de❯f"], - &["ab┃c\n", "def"], + &["ab┃❮c", "de❯f"], + &["ab┃c", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc❮\n", "def❯┃"], - &["abc┃\n", "def"], + &["abc❮", "def❯┃"], + &["abc┃", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "┃❮def \n", "❯ghi"], - &["abc\n", "┃def \n", "ghi"], + &["abc", "┃❮def ", "❯ghi"], + &["abc", "┃def ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d❮ef \n", "g❯┃hi"], - &["abc\n", "d┃ef \n", "ghi"], + &["abc", "d❮ef ", "g❯┃hi"], + &["abc", "d┃ef ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃❮f \n", "gh❯i"], - &["abc\n", "de┃f \n", "ghi"], + &["abc", "de┃❮f ", "gh❯i"], + &["abc", "de┃f ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def❮ \n", "ghi❯┃"], - &["abc\n", "def┃ \n", "ghi"], + &["abc", "def❮ ", "ghi❯┃"], + &["abc", "def┃ ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def ❮\n", "ghi❯┃"], - &["abc\n", "def ┃\n", "ghi"], + &["abc", "def ❮", "ghi❯┃"], + &["abc", "def ┃", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de❮\n", "gh❯┃i"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "de❮", "gh❯┃i"], + &["abc", "de┃", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc┃❮\n", "de❯"], - &["abc┃\n", "de"], + &["abc┃❮", "de❯"], + &["abc┃", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["ab❮c\n", "de❯┃"], - &["ab┃c\n", "de"], + &["ab❮c", "de❯┃"], + &["ab┃c", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["a┃❮bc\n", "d❯e"], - &["a┃bc\n", "de"], + &["a┃❮bc", "d❯e"], + &["a┃bc", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["❮abc\n", "❯┃de"], - &["┃abc\n", "de"], + &["❮abc", "❯┃de"], + &["┃abc", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["ab❮\n", "cd❯┃ef\n", "ghijkl\n", "mnopqrst"], - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["ab❮", "cd❯┃ef", "ghijkl", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef┃❮\n", "ghij❯kl\n", "mnopqrst"], - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], + &["ab", "cdef┃❮", "ghij❯kl", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl❮\n", "mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], + &["ab", "cdef", "ghijkl❮", "mnopqr❯┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &[" ❮ab\n", " ❯┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + &[" ❮ab", " ❯┃cdef", "ghijkl", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "┃❮cdef\n", "❯ghijkl\n", "mnopqrst"], - &["ab\n", "┃cdef\n", "ghijkl\n", "mnopqrst"], + &["ab", "┃❮cdef", "❯ghijkl", "mnopqrst"], + &["ab", "┃cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "❮ghijkl\n", "❯┃mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["ab", "cdef", "❮ghijkl", "❯┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh❮\n", "ijklmn❯┃\n", "opqr\n", "st"], - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefgh❮", "ijklmn❯┃", "opqr", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn❮\n", "opqr❯┃\n", "st"], - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], + &["abcdefgh", "ijklmn❮", "opqr❯┃", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr❮\n", "st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], + &["abcdefgh", "ijklmn", "opqr❮", "st❯┃"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "❮st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "┃st"], + &["abcdefgh", "ijklmn", "opqr", "❮st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "┃st"], &no_mods(), move_caret_up, )?; @@ -2804,32 +2672,32 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "d❮e❯┃\n", "ghi"], - &["abc\n", "┃de\n", "ghi"], + &["abc", "d❮e❯┃", "ghi"], + &["abc", "┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " ❮d❯┃e\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["abc", " ❮d❯┃e", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["❮abc\n", "❯┃ de\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["❮abc", "❯┃ de", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " ┃❮de\n❯", "ghi"], - &["abc\n", "┃ de\n", "ghi"], + &["abc", " ┃❮de❯", "ghi"], + &["abc", "┃ de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc┃❮\n", "de\n", "ghi❯"], - &["┃abc\n", "de\n", "ghi"], + &["abc┃❮", "de", "ghi❯"], + &["┃abc", "de", "ghi"], &no_mods(), move_caret_home, )?; @@ -2856,26 +2724,26 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "┃❮de\n", "ghi❯"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "┃❮de", "ghi❯"], + &["abc", "de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["❮abc\n", " d❯┃e\n", "ghi"], - &["abc\n", " de┃\n", "ghi"], + &["❮abc", " d❯┃e", "ghi"], + &["abc", " de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["┃❮abc\n", "de\n", "ghi❯"], - &["abc┃\n", "de\n", "ghi"], + &["┃❮abc", "de", "ghi❯"], + &["abc┃", "de", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["abc\n", "de\n", "g┃❮hi❯"], - &["abc\n", "de\n", "ghi┃"], + &["abc", "de", "g┃❮hi❯"], + &["abc", "de", "ghi┃"], &no_mods(), move_caret_end, )?; @@ -2900,50 +2768,50 @@ pub mod test_big_sel_text { assert_move(&["a❮bc❯┃"], &["a❮bc❯┃"], &shift_pressed(), move_caret_right)?; assert_move(&["ab❮c❯┃"], &["ab❮c❯┃"], &shift_pressed(), move_caret_right)?; assert_move( - &["abc❮\n", "❯┃d"], - &["abc❮\n", "d❯┃"], + &["abc❮", "❯┃d"], + &["abc❮", "d❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c❯┃\n", ""], - &["ab❮c\n", "❯┃"], + &["ab❮c❯┃", ""], + &["ab❮c", "❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c❯┃\n", "d"], - &["ab❮c\n", "❯┃d"], + &["ab❮c❯┃", "d"], + &["ab❮c", "❯┃d"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], - &["abc\n", "def\n", "ghi❮\n", "j❯┃kl"], + &["abc", "def", "ghi❮", "❯┃jkl"], + &["abc", "def", "ghi❮", "j❯┃kl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c\n", "def\n", "ghi\n", "❯┃jkl"], - &["ab❮c\n", "def\n", "ghi\n", "j❯┃kl"], + &["ab❮c", "def", "ghi", "❯┃jkl"], + &["ab❮c", "def", "ghi", "j❯┃kl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c\n", "def\n", "❯┃ghi\n", "jkl"], - &["ab❮c\n", "def\n", "g❯┃hi\n", "jkl"], + &["ab❮c", "def", "❯┃ghi", "jkl"], + &["ab❮c", "def", "g❯┃hi", "jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jk❯┃l"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["❮abc", "def", "ghi", "jk❯┃l"], + &["❮abc", "def", "ghi", "jkl❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi", "jkl❯┃"], &shift_pressed(), move_caret_right, )?; @@ -2967,38 +2835,38 @@ pub mod test_big_sel_text { move_caret_left, )?; assert_move( - &["abc┃❮\n", "❯d"], - &["ab┃❮c\n", "❯d"], + &["abc┃❮", "❯d"], + &["ab┃❮c", "❯d"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "┃❮d❯"], - &["abc┃❮\n", "d❯"], + &["abc", "┃❮d❯"], + &["abc┃❮", "d❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["ab┃❮c\n", "❯"], - &["a┃❮bc\n", "❯"], + &["ab┃❮c", "❯"], + &["a┃❮bc", "❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def┃❮\n", "ghi\n", "j❯kl"], - &["abc\n", "de┃❮f\n", "ghi\n", "j❯kl"], + &["abc", "def┃❮", "ghi", "j❯kl"], + &["abc", "de┃❮f", "ghi", "j❯kl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["a┃❮bc\n", "def\n", "ghi\n", "jkl❯"], - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], + &["a┃❮bc", "def", "ghi", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi\n", "┃❮jkl❯"], - &["abc\n", "def\n", "ghi┃❮\n", "jkl❯"], + &["abc", "def", "ghi", "┃❮jkl❯"], + &["abc", "def", "ghi┃❮", "jkl❯"], &shift_pressed(), move_caret_left, )?; @@ -3020,44 +2888,44 @@ pub mod test_big_sel_text { assert_move(&["❮a❯┃"], &["┃a"], &shift_pressed(), move_caret_up)?; assert_move(&["❮a❯┃bc"], &["┃abc"], &shift_pressed(), move_caret_up)?; assert_move( - &["❮a❯┃bc\n", "d"], - &["┃abc\n", "d"], + &["❮a❯┃bc", "d"], + &["┃abc", "d"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de❮f❯┃"], - &["abc┃❮\n", "de❯f"], + &["abc", "de❮f❯┃"], + &["abc┃❮", "de❯f"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["ab┃❮c\n", "def❯"], + &["abc", "de┃❮f❯"], + &["ab┃❮c", "def❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab┃❮c\n", "def❯"], - &["┃❮abc\n", "def❯"], + &["ab┃❮c", "def❯"], + &["┃❮abc", "def❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "❯mnopqrst"], + &["ab", "cdef", "ghijkl", "❮mnopqr❯┃st"], + &["ab", "cdef", "ghijkl┃❮", "❯mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqrs❯┃t"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "❯mnopqrst"], + &["ab", "cdef", "ghijkl", "❮mnopqrs❯┃t"], + &["ab", "cdef", "ghijkl┃❮", "❯mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "┃❮o❯pqr\n", "st"], - &["abcdefgh\n", "┃❮ijklmn\n", "o❯pqr\n", "st"], + &["abcdefgh", "ijklmn", "┃❮o❯pqr", "st"], + &["abcdefgh", "┃❮ijklmn", "o❯pqr", "st"], &shift_pressed(), move_caret_up, )?; @@ -3075,56 +2943,56 @@ pub mod test_big_sel_text { assert_move(&["┃❮ab❯c"], &["ab❮c❯┃"], &shift_pressed(), move_caret_down)?; assert_move(&["┃❮a❯bc"], &["a❮bc❯┃"], &shift_pressed(), move_caret_down)?; assert_move( - &["❮a❯┃bc\n", "d"], - &["❮abc\n", "d❯┃"], + &["❮a❯┃bc", "d"], + &["❮abc", "d❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮a❯┃bc\n", "de"], - &["❮abc\n", "d❯┃e"], + &["❮a❯┃bc", "de"], + &["❮abc", "d❯┃e"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮abc\n", "d❯┃e"], - &["❮abc\n", "de❯┃"], + &["❮abc", "d❯┃e"], + &["❮abc", "de❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮a❯┃bc\n", ""], - &["❮abc\n", "❯┃"], + &["❮a❯┃bc", ""], + &["❮abc", "❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqrst❯┃"], + &["ab", "cdef", "ghijkl", "❮mnopqr❯┃st"], + &["ab", "cdef", "ghijkl", "❮mnopqrst❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqr❯┃st"], - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["a❮b", "cdef", "ghijkl", "mnopqr❯┃st"], + &["a❮b", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcd❮efgh❯┃\n", "ijklmn\n", "opqr\n", "st"], - &["abcd❮efgh\n", "ijklmn❯┃\n", "opqr\n", "st"], + &["abcd❮efgh❯┃", "ijklmn", "opqr", "st"], + &["abcd❮efgh", "ijklmn❯┃", "opqr", "st"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcd❮e❯┃fgh\n", "ijklmn\n", "opqr\n", "st"], - &["abcd❮efgh\n", "ijklm❯┃n\n", "opqr\n", "st"], + &["abcd❮e❯┃fgh", "ijklmn", "opqr", "st"], + &["abcd❮efgh", "ijklm❯┃n", "opqr", "st"], &shift_pressed(), move_caret_down, )?; @@ -3149,50 +3017,50 @@ pub mod test_big_sel_text { )?; assert_move(&["ab❮c❯┃"], &["┃❮ab❯c"], &shift_pressed(), move_caret_home)?; assert_move( - &["abc\n", "de❮f❯┃"], - &["abc\n", "┃❮de❯f"], + &["abc", "de❮f❯┃"], + &["abc", "┃❮de❯f"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["abc\n", "┃❮def❯"], + &["abc", "de┃❮f❯"], + &["abc", "┃❮def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &["ab┃❮c\n", "def❯"], - &["┃❮abc\n", "def❯"], + &["ab┃❮c", "def❯"], + &["┃❮abc", "def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &[" ab┃❮c\n", "def❯"], - &[" ┃❮abc\n", "def❯"], + &[" ab┃❮c", "def❯"], + &[" ┃❮abc", "def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &[" ┃❮abc\n", "def❯"], - &["┃❮ abc\n", "def❯"], + &[" ┃❮abc", "def❯"], + &["┃❮ abc", "def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "┃❮mnopqr❯st"], - &["ab\n", "cdef\n", "ghijkl\n", "┃❮mnopqr❯st"], + &["ab", "cdef", "ghijkl", "┃❮mnopqr❯st"], + &["ab", "cdef", "ghijkl", "┃❮mnopqr❯st"], &shift_pressed(), move_caret_home, )?; assert_move( - &["ab\n", "cdef\n", "gh┃❮ijkl❯\n", "mnopqrst"], - &["ab\n", "cdef\n", "┃❮ghijkl❯\n", "mnopqrst"], + &["ab", "cdef", "gh┃❮ijkl❯", "mnopqrst"], + &["ab", "cdef", "┃❮ghijkl❯", "mnopqrst"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "op❮qr❯┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "┃❮op❯qr\n", "st"], + &["abcdefgh", "ijklmn", "op❮qr❯┃", "st"], + &["abcdefgh", "ijklmn", "┃❮op❯qr", "st"], &shift_pressed(), move_caret_home, )?; @@ -3210,44 +3078,44 @@ pub mod test_big_sel_text { assert_move(&["┃❮ab❯c"], &["ab❮c❯┃"], &shift_pressed(), move_caret_end)?; assert_move(&["┃❮a❯bc"], &["a❮bc❯┃"], &shift_pressed(), move_caret_end)?; assert_move( - &["❮a❯┃bc\n", "d"], - &["❮abc❯┃\n", "d"], + &["❮a❯┃bc", "d"], + &["❮abc❯┃", "d"], &shift_pressed(), move_caret_end, )?; assert_move( - &["❮a❯┃bc \n", "de"], - &["❮abc ❯┃\n", "de"], + &["❮a❯┃bc ", "de"], + &["❮abc ❯┃", "de"], &shift_pressed(), move_caret_end, )?; assert_move( - &["❮abc\n", "d❯┃e"], - &["❮abc\n", "de❯┃"], + &["❮abc", "d❯┃e"], + &["❮abc", "de❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqrst❯┃"], + &["ab", "cdef", "ghijkl", "❮mnopqr❯┃st"], + &["ab", "cdef", "ghijkl", "❮mnopqrst❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqr❯┃st"], - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["a❮b", "cdef", "ghijkl", "mnopqr❯┃st"], + &["a❮b", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abcd❮e❯┃fgh\n", "ijklmn\n", "opqr\n", "st"], - &["abcd❮efgh❯┃\n", "ijklmn\n", "opqr\n", "st"], + &["abcd❮e❯┃fgh", "ijklmn", "opqr", "st"], + &["abcd❮efgh❯┃", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_end, )?; @@ -3263,20 +3131,20 @@ pub mod test_big_sel_text { assert_move(&["a┃❮bc❯"], &["ab┃❮c❯"], &shift_pressed(), move_caret_right)?; assert_move(&["┃❮abc❯"], &["a┃❮bc❯"], &shift_pressed(), move_caret_right)?; assert_move( - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], - &["a┃❮bc\n", "def\n", "ghi\n", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], + &["a┃❮bc", "def", "ghi", "jkl❯"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "d┃❮ef\n", "❯ghi\n", "jkl"], - &["abc\n", "de┃❮f\n", "❯ghi\n", "jkl"], + &["abc", "d┃❮ef", "❯ghi", "jkl"], + &["abc", "de┃❮f", "❯ghi", "jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "de┃❮f❯\n", "ghi\n", "jkl"], - &["abc\n", "def┃\n", "ghi\n", "jkl"], + &["abc", "de┃❮f❯", "ghi", "jkl"], + &["abc", "def┃", "ghi", "jkl"], &shift_pressed(), move_caret_right, )?; @@ -3292,26 +3160,26 @@ pub mod test_big_sel_text { assert_move(&["a❮bc❯┃"], &["a❮b❯┃c"], &shift_pressed(), move_caret_left)?; assert_move(&["❮abc❯┃"], &["❮ab❯┃c"], &shift_pressed(), move_caret_left)?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi\n", "jk❯┃l"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi", "jk❯┃l"], &shift_pressed(), move_caret_left, )?; assert_move( - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def❮\n", "❯┃ghi\n", "jkl"], - &["abc\n", "def┃\n", "ghi\n", "jkl"], + &["abc", "def❮", "❯┃ghi", "jkl"], + &["abc", "def┃", "ghi", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "d❮ef\n", "gh❯┃i\n", "jkl"], - &["abc\n", "d❮ef\n", "g❯┃hi\n", "jkl"], + &["abc", "d❮ef", "gh❯┃i", "jkl"], + &["abc", "d❮ef", "g❯┃hi", "jkl"], &shift_pressed(), move_caret_left, )?; @@ -3328,38 +3196,38 @@ pub mod test_big_sel_text { assert_move(&["❮a❯┃bc"], &["┃abc"], &shift_pressed(), move_caret_up)?; assert_move(&["┃abc"], &["┃abc"], &shift_pressed(), move_caret_up)?; assert_move( - &["❮abc\n", "def❯┃"], - &["❮abc❯┃\n", "def"], + &["❮abc", "def❯┃"], + &["❮abc❯┃", "def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["❮abc\n", "de❯┃f"], - &["❮ab❯┃c\n", "def"], + &["❮abc", "de❯┃f"], + &["❮ab❯┃c", "def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi❯┃\n", "jkl"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi❯┃", "jkl"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "jkl❯┃"], - &["abc\n", "def\n", "ghi┃\n", "jkl"], + &["abc", "def", "ghi❮", "jkl❯┃"], + &["abc", "def", "ghi┃", "jkl"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "d❮ef\n", "ghi\n", "jk❯┃l"], - &["abc\n", "d❮ef\n", "gh❯┃i\n", "jkl"], + &["abc", "d❮ef", "ghi", "jk❯┃l"], + &["abc", "d❮ef", "gh❯┃i", "jkl"], &shift_pressed(), move_caret_up, )?; assert_move( - &["❮abc\n", "d❯┃ef\n", "ghi\n", "jkl"], - &["❮a❯┃bc\n", "def\n", "ghi\n", "jkl"], + &["❮abc", "d❯┃ef", "ghi", "jkl"], + &["❮a❯┃bc", "def", "ghi", "jkl"], &shift_pressed(), move_caret_up, )?; @@ -3373,50 +3241,50 @@ pub mod test_big_sel_text { assert_move(&["┃❮abc❯"], &["abc┃"], &shift_pressed(), move_caret_down)?; assert_move( - &["┃❮abc\n", "def❯"], - &["abc\n", "┃❮def❯"], + &["┃❮abc", "def❯"], + &["abc", "┃❮def❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a┃❮bc\n", "def❯"], - &["abc\n", "d┃❮ef❯"], + &["a┃❮bc", "def❯"], + &["abc", "d┃❮ef❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["┃❮abc\n", "def\n", "ghi❯"], - &["abc\n", "┃❮def\n", "ghi❯"], + &["┃❮abc", "def", "ghi❯"], + &["abc", "┃❮def", "ghi❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃❮c\n", "def\n", "ghi❯"], - &["abc\n", "de┃❮f\n", "ghi❯"], + &["ab┃❮c", "def", "ghi❯"], + &["abc", "de┃❮f", "ghi❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃❮f\n", "ghi❯"], - &["abc\n", "def\n", "gh┃❮i❯"], + &["abc", "de┃❮f", "ghi❯"], + &["abc", "def", "gh┃❮i❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdef┃❮\n", "ghij\n", "kl❯"], - &["abcdef\n", "ghij┃❮\n", "kl❯"], + &["abcdef┃❮", "ghij", "kl❯"], + &["abcdef", "ghij┃❮", "kl❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcde┃❮f\n", "ghij\n", "kl❯"], - &["abcdef\n", "ghij┃❮\n", "kl❯"], + &["abcde┃❮f", "ghij", "kl❯"], + &["abcdef", "ghij┃❮", "kl❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃❮cdef\n", "ghij\n", "kl❯"], - &["abcdef\n", "gh┃❮ij\n", "kl❯"], + &["ab┃❮cdef", "ghij", "kl❯"], + &["abcdef", "gh┃❮ij", "kl❯"], &shift_pressed(), move_caret_down, )?; @@ -3439,44 +3307,44 @@ pub mod test_big_sel_text { assert_move(&["a❮bc❯┃"], &["┃❮a❯bc"], &shift_pressed(), move_caret_home)?; assert_move( - &["❮abc\n", "def❯┃"], - &["❮abc\n", "❯┃def"], + &["❮abc", "def❯┃"], + &["❮abc", "❯┃def"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", " de❯┃f"], - &["❮abc\n", " ❯┃def"], + &["❮abc", " de❯┃f"], + &["❮abc", " ❯┃def"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi\n", "❯┃jkl"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi", "❯┃jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "jkl❯┃"], - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], + &["abc", "def", "ghi❮", "jkl❯┃"], + &["abc", "def", "ghi❮", "❯┃jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "d❮ef\n", " ghi\n", " jk❯┃l"], - &["abc\n", "d❮ef\n", " ghi\n", " ❯┃jkl"], + &["abc", "d❮ef", " ghi", " jk❯┃l"], + &["abc", "d❮ef", " ghi", " ❯┃jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", "d❯┃ef\n", "ghi\n", "jkl"], - &["❮abc\n", "❯┃def\n", "ghi\n", "jkl"], + &["❮abc", "d❯┃ef", "ghi", "jkl"], + &["❮abc", "❯┃def", "ghi", "jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", "d❯┃ef\n", "ghi\n", "jkl"], - &["❮abc\n", "❯┃def\n", "ghi\n", "jkl"], + &["❮abc", "d❯┃ef", "ghi", "jkl"], + &["❮abc", "❯┃def", "ghi", "jkl"], &shift_pressed(), move_caret_home, )?; @@ -3490,50 +3358,50 @@ pub mod test_big_sel_text { assert_move(&["┃❮abc❯"], &["abc┃"], &shift_pressed(), move_caret_end)?; assert_move( - &["┃❮abc\n", "def❯"], - &["abc┃❮\n", "def❯"], + &["┃❮abc", "def❯"], + &["abc┃❮", "def❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["a┃❮bc\n", "def❯"], - &["abc┃❮\n", "def❯"], + &["a┃❮bc", "def❯"], + &["abc┃❮", "def❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["a┃❮bc\n", "def\n", "ghi❯"], - &["abc┃❮\n", "def\n", "ghi❯"], + &["a┃❮bc", "def", "ghi❯"], + &["abc┃❮", "def", "ghi❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["ab┃❮c\n", "def\n", "ghi❯"], - &["abc┃❮\n", "def\n", "ghi❯"], + &["ab┃❮c", "def", "ghi❯"], + &["abc┃❮", "def", "ghi❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "de┃❮f\n", "ghi❯"], - &["abc\n", "def┃❮\n", "ghi❯"], + &["abc", "de┃❮f", "ghi❯"], + &["abc", "def┃❮", "ghi❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abcdef┃❮\n", "ghij\n", "kl❯"], - &["abcdef┃❮\n", "ghij\n", "kl❯"], + &["abcdef┃❮", "ghij", "kl❯"], + &["abcdef┃❮", "ghij", "kl❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["┃❮ abcdef\n", "ghij\n", "kl❯"], - &[" abcdef┃❮\n", "ghij\n", "kl❯"], + &["┃❮ abcdef", "ghij", "kl❯"], + &[" abcdef┃❮", "ghij", "kl❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abcdef\n", "ghij\n", "┃❮kl❯"], - &["abcdef\n", "ghij\n", "kl┃"], + &["abcdef", "ghij", "┃❮kl❯"], + &["abcdef", "ghij", "kl┃"], &shift_pressed(), move_caret_end, )?; diff --git a/editor/src/ui/text/caret_w_select.rs b/editor/src/ui/text/caret_w_select.rs index a9e5b9b2a7..b88acada7d 100644 --- a/editor/src/ui/text/caret_w_select.rs +++ b/editor/src/ui/text/caret_w_select.rs @@ -12,6 +12,12 @@ pub struct CaretWSelect { pub selection_opt: Option, } +pub enum CaretPos { + Start, + Exact(TextPos), + End, +} + fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult> { if start_pos == end_pos { Ok(None) @@ -146,7 +152,7 @@ pub mod test_caret_w_select { // Retrieve selection and position from formatted string pub fn convert_dsl_to_selection(lines: &[String]) -> Result { - let lines_str: String = lines.join(""); + let lines_str: String = lines.join("\n"); let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str) .expect("Selection test DSL parsing failed"); diff --git a/editor/src/ui/text/lines.rs b/editor/src/ui/text/lines.rs index 3fb16b5842..37e1d67452 100644 --- a/editor/src/ui/text/lines.rs +++ b/editor/src/ui/text/lines.rs @@ -13,14 +13,12 @@ use crate::ui::text::{ use crate::ui::ui_error::UIResult; use crate::ui::util::is_newline; use crate::window::keyboard_input::Modifiers; -use bumpalo::collections::String as BumpString; -use bumpalo::Bump; use std::cmp::max; use std::cmp::min; use winit::event::VirtualKeyCode; pub trait Lines { - fn get_line(&self, line_nr: usize) -> UIResult<&str>; + fn get_line_ref(&self, line_nr: usize) -> UIResult<&str>; fn line_len(&self, line_nr: usize) -> UIResult; @@ -28,7 +26,7 @@ pub trait Lines { fn nr_of_chars(&self) -> usize; - fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>; + fn all_lines_as_string(&self) -> String; fn is_last_line(&self, line_nr: usize) -> bool; @@ -83,7 +81,7 @@ pub trait MutSelectableLines { fn insert_str(&mut self, new_str: &str) -> UIResult<()>; - fn pop_char(&mut self) -> UIResult<()>; + fn backspace(&mut self) -> UIResult<()>; fn del_selection(&mut self) -> UIResult<()>; } @@ -114,7 +112,7 @@ pub fn move_caret_left( } else { let curr_line_len = lines.line_len(old_line_nr - 1)?; - (old_line_nr - 1, curr_line_len - 1) + (old_line_nr - 1, curr_line_len) } } else { (old_line_nr, old_col_nr - 1) @@ -185,7 +183,7 @@ pub fn move_caret_right( let is_last_line = lines.is_last_line(old_line_nr); if !is_last_line { - if old_col_nr + 1 > curr_line_len - 1 { + if old_col_nr + 1 > curr_line_len { (old_line_nr + 1, 0) } else { (old_line_nr, old_col_nr + 1) @@ -263,7 +261,9 @@ pub fn move_caret_up( let prev_line_len = lines.line_len(old_line_nr - 1)?; if prev_line_len <= old_col_nr { - (old_line_nr - 1, prev_line_len - 1) + let new_column = if prev_line_len > 0 { prev_line_len } else { 0 }; + + (old_line_nr - 1, new_column) } else { (old_line_nr - 1, old_col_nr) } @@ -331,7 +331,9 @@ pub fn move_caret_down( if next_line_len <= old_col_nr { if !is_last_line { - (old_line_nr + 1, next_line_len - 1) + let new_column = if next_line_len > 0 { next_line_len } else { 0 }; + + (old_line_nr + 1, new_column) } else { (old_line_nr + 1, next_line_len) } @@ -382,7 +384,7 @@ pub fn move_caret_home( let curr_line_nr = caret_w_select.caret_pos.line; let old_col_nr = caret_w_select.caret_pos.column; - let curr_line_str = lines.get_line(curr_line_nr)?; + let curr_line_str = lines.get_line_ref(curr_line_nr)?; let line_char_iter = curr_line_str.chars(); let mut first_no_space_char_col = 0; diff --git a/editor/src/ui/text/mod.rs b/editor/src/ui/text/mod.rs index 39616a47cd..feaa7946f8 100644 --- a/editor/src/ui/text/mod.rs +++ b/editor/src/ui/text/mod.rs @@ -2,4 +2,5 @@ pub mod big_text_area; pub mod caret_w_select; pub mod lines; pub mod selection; +mod text_buffer; pub mod text_pos; diff --git a/editor/src/ui/text/text_buffer.rs b/editor/src/ui/text/text_buffer.rs new file mode 100644 index 0000000000..b846acb391 --- /dev/null +++ b/editor/src/ui/text/text_buffer.rs @@ -0,0 +1,193 @@ +use std::path::Path; + +use crate::ui::{ + ui_error::{OutOfBounds, TextBufReadFailed, UIResult}, + util::{path_to_string, reader_from_path}, +}; +use snafu::ensure; + +use super::{selection::Selection, text_pos::TextPos}; +use std::io::BufRead; + +// Do not use for large amounts of text. +// This should become a trait in the future and be implemented by a SmallTextBuffer and Rope(for large amounts of text) +#[derive(Debug)] +pub struct TextBuffer { + pub lines: Vec, +} + +impl TextBuffer { + pub fn from_path(path: &Path) -> UIResult { + let buf_reader = reader_from_path(path)?; + let mut lines: Vec = Vec::new(); + + for line in buf_reader.lines() { + match line { + Ok(line_str) => lines.push(line_str), + Err(e) => { + TextBufReadFailed { + path_str: path_to_string(path), + err_msg: e.to_string(), + } + .fail()?; + } + } + } + + Ok(TextBuffer { lines }) + } + + pub fn nr_of_chars(&self) -> usize { + let mut nr_of_chars = 0; + + for line in self.lines.iter() { + nr_of_chars += line.len(); + } + + nr_of_chars + } + + pub fn nr_of_lines(&self) -> usize { + self.lines.len() + } + + pub fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> { + self.ensure_bounds(line_nr)?; + // safe unwrap because we checked the length + Ok(self.lines.get(line_nr).unwrap()) + } + + pub fn line_len(&self, line_nr: usize) -> UIResult { + Ok(self.get_line_ref(line_nr)?.len()) + } + + fn ensure_bounds(&self, line_nr: usize) -> UIResult<()> { + ensure!( + line_nr < self.nr_of_lines(), + OutOfBounds { + index: line_nr, + collection_name: "TextBuffer", + len: self.nr_of_lines(), + } + ); + + Ok(()) + } + + fn ensure_bounds_txt_pos(&self, txt_pos: TextPos) -> UIResult<()> { + ensure!( + txt_pos.line < self.nr_of_lines(), + OutOfBounds { + index: txt_pos.line, + collection_name: "TextBuffer", + len: self.nr_of_lines(), + } + ); + + let line_ref = self.get_line_ref(txt_pos.line)?; + let line_len = line_ref.len(); + + ensure!( + txt_pos.column <= line_len, + OutOfBounds { + index: txt_pos.column, + collection_name: format!("Line in TextBuffer: {}", line_ref), + len: line_len, + } + ); + + Ok(()) + } + + pub fn all_lines_ref(&self) -> &[String] { + &self.lines + } + + pub fn get_selected_str(&self, selection: Selection) -> UIResult { + let start_line_nr = selection.start_pos.line; + let start_col_nr = selection.start_pos.column; + + let end_line_nr = selection.end_pos.line; + let end_col_nr = selection.end_pos.column; + + let mut selected_str = String::new(); + + if end_line_nr > start_line_nr { + selected_str.push_str(&self.get_line_ref(start_line_nr)?[start_col_nr..]); + + for line_nr in start_line_nr + 1..end_line_nr - 1 { + selected_str.push_str(self.get_line_ref(line_nr)?); + } + + selected_str.push_str(&self.get_line_ref(end_line_nr)?[..end_col_nr]); + } else { + // start_line_nr == end_line_nr + selected_str.push_str(&self.get_line_ref(start_line_nr)?[start_col_nr..end_col_nr]); + } + + Ok(selected_str) + } + + pub fn insert_str(&mut self, txt_pos: TextPos, new_str: &str) -> UIResult<()> { + self.ensure_bounds_txt_pos(txt_pos)?; + + // safe unwrap because we checked the length + self.lines + .get_mut(txt_pos.line) + .unwrap() + .insert_str(txt_pos.column, new_str); + + Ok(()) + } + + pub fn backspace_char(&mut self, txt_pos: TextPos) -> UIResult<()> { + if txt_pos.column > 0 { + let prev_col_pos = TextPos { + line: txt_pos.line, + column: txt_pos.column - 1, + }; + + self.ensure_bounds_txt_pos(prev_col_pos)?; + + let line_ref = self.lines.get_mut(prev_col_pos.line).unwrap(); // safe because of earlier bounds check + + line_ref.remove(prev_col_pos.column); + } else if txt_pos.line > 0 { + self.lines.remove(txt_pos.line); + } + + Ok(()) + } + + pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> { + self.ensure_bounds_txt_pos(selection.start_pos)?; + self.ensure_bounds_txt_pos(selection.end_pos)?; + + let start_line_nr = selection.start_pos.line; + let start_col_nr = selection.start_pos.column; + let end_line_nr = selection.end_pos.line; + let end_col_nr = selection.end_pos.column; + + if end_line_nr > start_line_nr { + // remove in reverse to prevent shifting indices + if end_col_nr == self.line_len(end_line_nr)? { + self.lines.remove(end_line_nr); + } else { + let line_ref = self.lines.get_mut(end_line_nr).unwrap(); // safe because of earlier bounds check + line_ref.replace_range(..end_col_nr, ""); + } + + self.lines.drain(start_line_nr + 1..end_line_nr); + + let line_ref = self.lines.get_mut(start_line_nr).unwrap(); // safe because of earlier bounds check + line_ref.replace_range(start_col_nr.., "") + } else { + // selection.end_pos.line == selection.start_pos.line + let line_ref = self.lines.get_mut(selection.start_pos.line).unwrap(); // safe because of earlier bounds check + + line_ref.replace_range(selection.start_pos.column..selection.end_pos.column, "") + } + + Ok(()) + } +} diff --git a/editor/src/ui/ui_error.rs b/editor/src/ui/ui_error.rs index ec422d1663..9f4d1a96b0 100644 --- a/editor/src/ui/ui_error.rs +++ b/editor/src/ui/ui_error.rs @@ -1,3 +1,5 @@ +use std::io; + use snafu::{Backtrace, Snafu}; //import errors as follows: @@ -8,6 +10,16 @@ use snafu::{Backtrace, Snafu}; #[derive(Debug, Snafu)] #[snafu(visibility(pub))] pub enum UIError { + #[snafu(display( + "LineInsertionFailed: line_nr ({}) needs to be <= nr_of_lines ({}).", + line_nr, + nr_of_lines + ))] + LineInsertionFailed { + line_nr: usize, + nr_of_lines: usize, + backtrace: Backtrace, + }, #[snafu(display( "OutOfBounds: index {} was out of bounds for {} with length {}.", index, @@ -34,6 +46,13 @@ pub enum UIError { ))] FileOpenFailed { path_str: String, err_msg: String }, + #[snafu(display( + "FileWriteFailed: failed to write to file with path {}, I got this IO error: {}.", + path_str, + source + ))] + FileWriteFailed { source: io::Error, path_str: String }, + #[snafu(display("TextBufReadFailed: the file {} could be opened but we encountered the following error while trying to read it: {}.", path_str, err_msg))] TextBufReadFailed { path_str: String, err_msg: String }, diff --git a/editor/src/ui/util.rs b/editor/src/ui/util.rs index d33d8da38e..97c51a0dd8 100644 --- a/editor/src/ui/util.rs +++ b/editor/src/ui/util.rs @@ -1,6 +1,6 @@ -use super::ui_error::{OutOfBounds, UIResult}; -use snafu::OptionExt; -use std::slice::SliceIndex; +use super::ui_error::{FileOpenFailed, FileWriteFailed, OutOfBounds, UIResult}; +use snafu::{OptionExt, ResultExt}; +use std::{fs::File, io::BufReader, path::Path, slice::SliceIndex}; pub fn is_newline(char_ref: &char) -> bool { let newline_codes = vec!['\u{d}', '\n']; @@ -33,3 +33,27 @@ pub fn slice_get_mut( Ok(elt_ref) } + +pub fn reader_from_path(path: &Path) -> UIResult> { + match File::open(path) { + Ok(file) => Ok(BufReader::new(file)), + Err(e) => FileOpenFailed { + path_str: path_to_string(path), + err_msg: e.to_string(), + } + .fail()?, + } +} + +pub fn path_to_string(path: &Path) -> String { + let mut path_str = String::new(); + path_str.push_str(&path.to_string_lossy()); + + path_str +} + +pub fn write_to_file(path: &Path, content: &str) -> UIResult<()> { + std::fs::write(path, content).with_context(|| FileWriteFailed { + path_str: path_to_string(path), + }) +} diff --git a/editor/src/window/keyboard_input.rs b/editor/src/window/keyboard_input.rs index 9107e963e5..4848337e00 100644 --- a/editor/src/window/keyboard_input.rs +++ b/editor/src/window/keyboard_input.rs @@ -27,6 +27,17 @@ impl Modifiers { active } + + // returns true if modifiers are active that can be active when the user wants to insert a new char; e.g.: shift+a to make A + pub fn new_char_modifiers(&self) -> bool { + self.no_modifiers() + || (self.shift && !self.ctrl && !self.alt && !self.logo) // e.g.: shift+a to make A + || (self.cmd_or_ctrl() && self.alt) // e.g.: ctrl+alt+2 to make @ on azerty keyboard + } + + fn no_modifiers(&self) -> bool { + !self.shift && !self.ctrl && !self.alt && !self.logo + } } pub fn no_mods() -> Modifiers { diff --git a/editor/tests/README.md b/editor/tests/README.md new file mode 100644 index 0000000000..2e0697587d --- /dev/null +++ b/editor/tests/README.md @@ -0,0 +1,5 @@ + +# Where are the tests? + +We have a lot of tests at the end of source files, this allows us to test functions that are not exposed by the editor itself. +`editor/mvc/ed_update.rs` and `editor/ui/text/big_text_area.rs` have many important tests. \ No newline at end of file diff --git a/editor/tests/selection.pest b/editor/tests/selection.pest index d347767bff..6c70079a54 100644 --- a/editor/tests/selection.pest +++ b/editor/tests/selection.pest @@ -1,4 +1,4 @@ -text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" )* } +text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" | "=" )* } caret = {"┃"}