From 33184b60f86b7bdde840b2f08042124ba7543feb Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 12 Apr 2021 17:33:22 +0200 Subject: [PATCH] finished hadnle_new_char tests, bugfixes --- editor/src/editor/code_lines.rs | 5 +- editor/src/editor/markup/nodes.rs | 43 ++- editor/src/editor/mvc/ed_model.rs | 48 +-- editor/src/editor/mvc/ed_update.rs | 443 +++++++++++++++++++++---- editor/src/editor/mvc/record_update.rs | 45 ++- editor/src/lang/ast.rs | 21 +- editor/src/lang/expr.rs | 97 ++++-- editor/src/ui/text/big_text_area.rs | 10 +- editor/src/ui/text/caret_w_select.rs | 12 +- 9 files changed, 557 insertions(+), 167 deletions(-) diff --git a/editor/src/editor/code_lines.rs b/editor/src/editor/code_lines.rs index 365f1ee3ac..4ad97f6c44 100644 --- a/editor/src/editor/code_lines.rs +++ b/editor/src/editor/code_lines.rs @@ -15,7 +15,10 @@ 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(), + lines: code_str + .split_inclusive('\n') + .map(|s| s.to_owned()) + .collect(), nr_of_chars: code_str.len(), } } diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs index 06f62cddf7..0cc2ecef2e 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/editor/src/editor/markup/nodes.rs @@ -1,4 +1,3 @@ -use crate::lang::ast::{RecordField}; use super::attribute::Attributes; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::ExpectedTextNode; @@ -7,6 +6,7 @@ use crate::editor::ed_error::NestedNodeRequired; use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::RecordField; use crate::lang::{ ast::Expr2, expr::Env, @@ -200,7 +200,12 @@ pub fn expr2_to_markup<'a, 'b>( 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, + expr2_node_id, + HighlightStyle::Variable, + markup_node_pool, + ) } Expr2::List { elems, .. } => { let mut children_ids = vec![new_markup_node( @@ -213,7 +218,13 @@ pub fn expr2_to_markup<'a, 'b>( for (idx, node_id) in elems.iter_node_ids().enumerate() { let sub_expr2 = env.pool.get(node_id); - children_ids.push(expr2_to_markup(arena, env, sub_expr2, node_id, markup_node_pool)); + children_ids.push(expr2_to_markup( + arena, + env, + sub_expr2, + node_id, + markup_node_pool, + )); if idx + 1 < elems.len() { children_ids.push(new_markup_node( @@ -275,7 +286,7 @@ pub fn expr2_to_markup<'a, 'b>( // let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(field_node_id); let record_field = env.pool.get(field_node_id); - let field_name = record_field.get_record_field_pool_str(); + let field_name = record_field.get_record_field_pool_str(); children_ids.push(new_markup_node( field_name.as_str(env.pool).to_owned(), @@ -284,20 +295,26 @@ pub fn expr2_to_markup<'a, 'b>( markup_node_pool, )); - children_ids.push(new_markup_node( - COLON.to_string(), - expr2_node_id, - HighlightStyle::Operator, - markup_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, + )); + let sub_expr2 = env.pool.get(*sub_expr2_node_id); - children_ids.push(expr2_to_markup(arena, env, sub_expr2, *sub_expr2_node_id, markup_node_pool)); - }, + children_ids.push(expr2_to_markup( + arena, + env, + sub_expr2, + *sub_expr2_node_id, + markup_node_pool, + )); + } } if idx + 1 < fields.len() { diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 6bc05bad88..2c04a412c0 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -23,7 +23,6 @@ use nonempty::NonEmpty; use roc_region::all::Region; use std::path::Path; - #[derive(Debug)] pub struct EdModel<'a> { pub module: EdModule<'a>, @@ -68,8 +67,13 @@ pub fn init_model<'a>( } else { let ast_root = &module.env.pool.get(ast_root_id); - let temp_markup_root_id = - expr2_to_markup(code_arena, &mut module.env, ast_root, ast_root_id, &mut markup_node_pool); + let temp_markup_root_id = expr2_to_markup( + 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); temp_markup_root_id @@ -159,23 +163,25 @@ impl<'a> EdModule<'a> { #[cfg(test)] pub mod test_ed_model { - use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; -use crate::ui::ui_error::UIResult; -use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; -use std::{path::Path}; + use crate::editor::ed_error::EdResult; + use crate::editor::mvc::ed_model; use crate::lang::expr::Env; use crate::lang::pool::Pool; - use crate::editor::mvc::ed_model; - use ed_model::EdModel; + 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::lines::SelectableLines; + use crate::ui::ui_error::UIResult; + use bumpalo::collections::String as BumpString; use bumpalo::Bump; + use ed_model::EdModel; use roc_module::symbol::{IdentIds, ModuleIds}; use roc_types::subs::VarStore; - use bumpalo::collections::String as BumpString; - use crate::editor::ed_error::EdResult; - use crate::ui::text::lines::SelectableLines; + use std::path::Path; - - pub fn init_dummy_model<'a>(code_str: &'a BumpString, ed_model_refs: &'a mut EdModelRefs) -> EdResult> { + pub fn init_dummy_model<'a>( + code_str: &'a BumpString, + ed_model_refs: &'a mut EdModelRefs, + ) -> EdResult> { let file_path = Path::new(""); let dep_idents = IdentIds::exposed_builtins(8); @@ -189,14 +195,14 @@ use std::{path::Path}; &mut ed_model_refs.env_pool, &mut ed_model_refs.var_store, dep_idents, - & ed_model_refs.module_ids, + &ed_model_refs.module_ids, exposed_ident_ids, ); ed_model::init_model(&code_str, file_path, env, &ed_model_refs.code_arena) } - pub struct EdModelRefs{ + pub struct EdModelRefs { code_arena: Bump, env_arena: Bump, env_pool: Pool, @@ -214,11 +220,13 @@ use std::{path::Path}; } } - pub fn ed_model_from_dsl<'a>(clean_code_str: &'a BumpString, code_lines: &[&str], ed_model_refs: &'a mut EdModelRefs) -> Result, String> { + pub fn ed_model_from_dsl<'a>( + clean_code_str: &'a BumpString, + code_lines: &[&str], + ed_model_refs: &'a mut EdModelRefs, + ) -> 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 caret_w_select = convert_dsl_to_selection(&code_lines_vec)?; let mut ed_model = init_dummy_model(clean_code_str, ed_model_refs)?; diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index dfee28c143..fa72044ac2 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -443,12 +443,16 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult 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 - )? + if ch.is_ascii_alphabetic() && ch.is_ascii_lowercase() { + update_empty_record( + &ch.to_string(), + prev_mark_node_id, + sibling_ids, + ed_model + )? + } else { + InputOutcome::Ignored + } } _ => InputOutcome::Ignored } @@ -488,26 +492,26 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult #[cfg(test)] pub mod test_ed_update { - use crate::ui::ui_error::UIResult; - use crate::editor::mvc::ed_model::test_ed_model::ed_model_to_dsl; - use crate::editor::mvc::ed_update::EdResult; - use crate::editor::mvc::ed_update::handle_new_char; use crate::editor::mvc::ed_model::test_ed_model::ed_model_from_dsl; - use bumpalo::Bump; - use bumpalo::collections::String as BumpString; + use crate::editor::mvc::ed_model::test_ed_model::ed_model_to_dsl; use crate::editor::mvc::ed_model::test_ed_model::init_model_refs; + use crate::editor::mvc::ed_update::handle_new_char; + use crate::editor::mvc::ed_update::EdResult; + use crate::ui::ui_error::UIResult; + use bumpalo::collections::String as BumpString; + use bumpalo::Bump; fn ed_res_to_res(ed_res: EdResult) -> Result { match ed_res { Ok(t) => Ok(t), - Err(e) => Err(e.to_string()) + Err(e) => Err(e.to_string()), } } fn ui_res_to_res(ed_res: UIResult) -> Result { match ed_res { Ok(t) => Ok(t), - Err(e) => Err(e.to_string()) + Err(e) => Err(e.to_string()), } } @@ -516,25 +520,16 @@ pub mod test_ed_update { expected_post_lines: &[&str], new_char: char, ) -> Result<(), String> { - - assert_insert_char_seq( - pre_lines, - expected_post_lines, - &new_char.to_string() - ) + assert_insert_seq(pre_lines, expected_post_lines, &new_char.to_string()) } - pub fn assert_insert_char_seq( + pub fn assert_insert_seq( pre_lines: &[&str], expected_post_lines: &[&str], 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 code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); let mut model_refs = init_model_refs(); @@ -551,6 +546,10 @@ pub mod test_ed_update { Ok(()) } + pub fn assert_insert_seq_ignore(lines: &[&str], new_char_seq: &str) -> Result<(), String> { + assert_insert_seq(lines, lines, new_char_seq) + } + #[test] fn test_ignore_basic() -> Result<(), String> { // space is added because Blank is inserted @@ -585,9 +584,21 @@ pub mod test_ed_update { assert_insert(&["\"ab ┃\""], &["\"ab {┃\""], '{')?; assert_insert(&["\"ab ┃\""], &["\"ab }┃\""], '}')?; assert_insert(&["\"{ str: 4┃}\""], &["\"{ str: 44┃}\""], '4')?; - assert_insert(&["\"┃ello, hello, hello\""], &["\"h┃ello, hello, hello\""], 'h')?; - assert_insert(&["\"hello┃ hello, hello\""], &["\"hello,┃ hello, hello\""], ',')?; - assert_insert(&["\"hello, hello, hello┃\""], &["\"hello, hello, hello.┃\""], '.')?; + assert_insert( + &["\"┃ello, hello, hello\""], + &["\"h┃ello, hello, hello\""], + 'h', + )?; + assert_insert( + &["\"hello┃ hello, hello\""], + &["\"hello,┃ hello, hello\""], + ',', + )?; + assert_insert( + &["\"hello, hello, hello┃\""], + &["\"hello, hello, hello.┃\""], + '.', + )?; Ok(()) } @@ -650,31 +661,41 @@ pub mod test_ed_update { 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( + &["\"hello, hello, hello\"┃"], + &["\"hello, hello, hello\"┃"], + '.', + )?; + assert_insert( + &["┃\"hello, hello, hello\""], + &["┃\"hello, hello, hello\""], + '.', + )?; + // TODO char_seq Ok(()) } #[test] fn test_record() -> Result<(), String> { - // assert_insert(&["┃"], &["{ ┃ }"], '{')?; - // assert_insert(&["{ ┃ }"], &["{ a┃ }"], 'a')?; - // assert_insert(&["{ a┃ }"], &["{ ab┃ }"], 'b')?; - // assert_insert(&["{ ab┃ }"], &["{ abc┃ }"], 'c')?; - // assert_insert(&["{ ┃ab }"], &["{ z┃abc }"], 'z')?; - // assert_insert(&["{ a┃b }"], &["{ az┃b }"], 'z')?; + assert_insert(&["┃"], &["{ ┃ }"], '{')?; + assert_insert(&["{ ┃ }"], &["{ a┃ }"], 'a')?; + assert_insert(&["{ a┃ }"], &["{ ab┃ }"], 'b')?; + assert_insert(&["{ a┃ }"], &["{ a1┃ }"], '1')?; + assert_insert(&["{ a1┃ }"], &["{ a1z┃ }"], 'z')?; + assert_insert(&["{ a1┃ }"], &["{ a15┃ }"], '5')?; + assert_insert(&["{ ab┃ }"], &["{ abc┃ }"], 'c')?; + assert_insert(&["{ ┃abc }"], &["{ z┃abc }"], 'z')?; + assert_insert(&["{ a┃b }"], &["{ az┃b }"], 'z')?; + assert_insert(&["{ a┃b }"], &["{ a9┃b }"], '9')?; - // assert_insert(&["{ a┃ }"], &["{ a: ┃ }"], ':')?; - // assert_insert(&["{ abc┃ }"], &["{ abc: ┃ }"], ':')?; - // assert_insert(&["{ aBc┃ }"], &["{ aBc: ┃ }"], ':')?; + // extra space for Blank node + assert_insert(&["{ a┃ }"], &["{ a: ┃ }"], ':')?; + assert_insert(&["{ abc┃ }"], &["{ abc: ┃ }"], ':')?; + assert_insert(&["{ aBc┃ }"], &["{ aBc: ┃ }"], ':')?; - // TODO use assert_insert_char_seq here - // assert_insert(&["{ a: ┃ }"], &["{ a: \"┃\" }"], '"')?; - // assert_insert(&["{ abc: ┃ }"], &["{ abc: \"┃\" }"], '"')?; - - // assert_insert(&["{ a: ┃ }"], &["{ a: { ┃ }"], '{')?; - // assert_insert(&["{ abc: ┃ }"], &["{ abc: { ┃ }"], '{')?; + assert_insert_seq(&["{ a┃ }"], &["{ a: \"┃\" }"], ":\"")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc: \"┃\" }"], ":\"")?; assert_insert(&["{ a: \"┃\" }"], &["{ a: \"a┃\" }"], 'a')?; assert_insert(&["{ a: \"a┃\" }"], &["{ a: \"ab┃\" }"], 'b')?; @@ -688,50 +709,328 @@ pub mod test_ed_update { 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(&["{ camel┃Case: \"hello\" }"], &["{ camelZ┃Case: \"hello\" }"], 'Z')?; - assert_insert(&["{ ┃camelCase: \"hello\" }"], &["{ z┃camelCase: \"hello\" }"], 'z')?; + assert_insert( + &["{ camelCase┃: \"hello\" }"], + &["{ camelCaseB┃: \"hello\" }"], + 'B', + )?; + assert_insert( + &["{ camel┃Case: \"hello\" }"], + &["{ camelZ┃Case: \"hello\" }"], + 'Z', + )?; + assert_insert( + &["{ ┃camelCase: \"hello\" }"], + &["{ z┃camelCase: \"hello\" }"], + 'z', + )?; + + assert_insert_seq(&["┃"], &["{ camelCase: \"hello┃\" }"], "{camelCase:\"hello")?; Ok(()) } #[test] fn test_nested_record() -> Result<(), String> { - // TODO construct nested record + assert_insert_seq(&["{ a┃ }"], &["{ a: { ┃ } }"], ":{")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc: { ┃ } }"], ":{")?; + assert_insert_seq(&["{ camelCase┃ }"], &["{ camelCase: { ┃ } }"], ":{")?; - assert_insert_char_seq(&["{ ┃ }"], &["{ a: { ┃ } }"], "a:{")?; - assert_insert_char_seq(&["{ ┃ }"], &["{ abc: { ┃ } }"], "abc:{")?; - assert_insert_char_seq(&["{ ┃ }"], &["{ camelCase: { ┃ } }"], "camelCase:{")?; + assert_insert_seq(&["{ a: { ┃ } }"], &["{ a: { zulu┃ } }"], "zulu")?; + assert_insert_seq( + &["{ abc: { ┃ } }"], + &["{ abc: { camelCase┃ } }"], + "camelCase", + )?; + assert_insert_seq(&["{ camelCase: { ┃ } }"], &["{ camelCase: { z┃ } }"], "z")?; + + assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: ┃ } }"], ":")?; + assert_insert_seq( + &["{ abc: { camelCase┃ } }"], + &["{ abc: { camelCase: ┃ } }"], + ":", + )?; + assert_insert_seq( + &["{ camelCase: { z┃ } }"], + &["{ camelCase: { z: ┃ } }"], + ":", + )?; + + assert_insert_seq(&["{ a┃: { zulu } }"], &["{ a0┃: { zulu } }"], "0")?; + assert_insert_seq( + &["{ ab┃c: { camelCase } }"], + &["{ abz┃c: { camelCase } }"], + "z", + )?; + assert_insert_seq(&["{ ┃camelCase: { z } }"], &["{ x┃camelCase: { z } }"], "x")?; + + assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: \"┃\" } }"], ":\"")?; + assert_insert_seq( + &["{ abc: { camelCase┃ } }"], + &["{ abc: { camelCase: \"┃\" } }"], + ":\"", + )?; + assert_insert_seq( + &["{ camelCase: { z┃ } }"], + &["{ camelCase: { z: \"┃\" } }"], + ":\"", + )?; + + assert_insert_seq( + &["{ a: { zulu: \"┃\" } }"], + &["{ a: { zulu: \"azula┃\" } }"], + "azula", + )?; + assert_insert_seq( + &["{ a: { zulu: \"az┃a\" } }"], + &["{ a: { zulu: \"azul┃a\" } }"], + "ul", + )?; + + assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: { ┃ } } }"], ":{")?; + assert_insert_seq( + &["{ abc: { camelCase┃ } }"], + &["{ abc: { camelCase: { ┃ } } }"], + ":{", + )?; + assert_insert_seq( + &["{ camelCase: { z┃ } }"], + &["{ camelCase: { z: { ┃ } } }"], + ":{", + )?; + + assert_insert_seq( + &["{ a: { zulu: { ┃ } } }"], + &["{ a: { zulu: { he┃ } } }"], + "he", + )?; + assert_insert_seq( + &["{ a: { ┃zulu: { } } }"], + &["{ a: { x┃zulu: { } } }"], + "x", + )?; + assert_insert_seq( + &["{ a: { z┃ulu: { } } }"], + &["{ a: { z9┃ulu: { } } }"], + "9", + )?; + assert_insert_seq( + &["{ a: { zulu┃: { } } }"], + &["{ a: { zulu7┃: { } } }"], + "7", + )?; + + assert_insert_seq( + &["{ a┃: { bcD: { eFgHij: { k15 } } } }"], + &["{ a4┃: { bcD: { eFgHij: { k15 } } } }"], + "4", + )?; + assert_insert_seq( + &["{ ┃a: { bcD: { eFgHij: { k15 } } } }"], + &["{ y┃a: { bcD: { eFgHij: { k15 } } } }"], + "y", + )?; + assert_insert_seq( + &["{ a: { bcD: { eF┃gHij: { k15 } } } }"], + &["{ a: { bcD: { eFxyz┃gHij: { k15 } } } }"], + "xyz", + )?; + + assert_insert_seq( + &["┃"], + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + "{g:{oi:{ng:{d:{e:{e:{p:{camelCase", + )?; Ok(()) } #[test] fn test_ignore_record() -> Result<(), String> { - assert_insert(&["┃{ }"], &["┃{ }"], 'a')?; - assert_insert(&["┃{ }"], &["┃{ }"], '{')?; - assert_insert(&["┃{ }"], &["┃{ }"], '"')?; - assert_insert(&["┃{ }"], &["┃{ }"], '5')?; + assert_insert_seq_ignore(&["┃{ }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ ┃}"], "a{\"5")?; - assert_insert(&["{ }┃"], &["{ }┃"], 'a')?; - assert_insert(&["{ }┃"], &["{ }┃"], '{')?; - assert_insert(&["{ }┃"], &["{ }┃"], '"')?; - assert_insert(&["{ }┃"], &["{ }┃"], '5')?; + assert_insert_seq_ignore(&["{ ┃ }"], "{\"5")?; + assert_insert_seq_ignore(&["{ ┃a }"], "{\"5")?; + assert_insert_seq_ignore(&["{ ┃abc }"], "{\"5")?; - assert_insert(&["{┃ }"], &["{┃ }"], 'a')?; - assert_insert(&["{┃ }"], &["{┃ }"], '{')?; - assert_insert(&["{┃ }"], &["{┃ }"], '"')?; - assert_insert(&["{┃ }"], &["{┃ }"], '5')?; + assert_insert_seq_ignore(&["┃{ a }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ a }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a ┃}"], "a{\"5")?; - assert_insert(&["{ ┃}"], &["{ ┃}"], 'a')?; - assert_insert(&["{ ┃}"], &["{ ┃}"], '{')?; - assert_insert(&["{ ┃}"], &["{ ┃}"], '"')?; - assert_insert(&["{ ┃}"], &["{ ┃}"], '5')?; + assert_insert_seq_ignore(&["┃{ a15 }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a15 }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ a15 }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a15 ┃}"], "a{\"5")?; - // TODO non-empty records + assert_insert_seq_ignore(&["┃{ camelCase }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ camelCase }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ camelCase }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ camelCase ┃}"], "a{\"5")?; + + assert_insert_seq_ignore(&["┃{ a: \"\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ a: \"\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: ┃\"\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: \"\"┃ }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: \"\" }┃"], "a{\"5")?; + + assert_insert_seq_ignore(&["┃{ camelCase: \"\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ camelCase: \"\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ camelCase: ┃\"\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ camelCase: \"\"┃ }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ camelCase: \"\" }┃"], "a{\"5")?; + + assert_insert_seq_ignore(&["┃{ a: \"z\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ a: \"z\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: ┃\"z\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: \"z\"┃ }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: \"z\" }┃"], "a{\"5")?; + + assert_insert_seq_ignore(&["┃{ a: \"hello, hello.12345ZXY{}[]-><-\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{┃ a: \"hello, hello.12345ZXY{}[]-><-\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: ┃\"hello, hello.12345ZXY{}[]-><-\" }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: \"hello, hello.12345ZXY{}[]-><-\"┃ }"], "a{\"5")?; + assert_insert_seq_ignore(&["{ a: \"hello, hello.12345ZXY{}[]-><-\" }┃"], "a{\"5")?; Ok(()) } + + #[test] + fn test_ignore_nested_record() -> Result<(), String> { + assert_insert_seq_ignore(&["{ a: { ┃ } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ a: ┃{ } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ a: {┃ } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ a: { }┃ }"], "{\"5")?; + assert_insert_seq_ignore(&["{ a: { } ┃}"], "{\"5")?; + assert_insert_seq_ignore(&["{ a: { } }┃"], "{\"5")?; + assert_insert_seq_ignore(&["{ a:┃ { } }"], "{\"5")?; + assert_insert_seq_ignore(&["{┃ a: { } }"], "{\"5")?; + assert_insert_seq_ignore(&["┃{ a: { } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ ┃a: { } }"], "1")?; + + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a ┃} }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a }┃ }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } ┃}"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } }┃"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a } }"], "{\"5")?; + assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a } }"], "{\"5")?; + assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a } }"], "1")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a } }"], "1")?; + + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\"┃ } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃\"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ \"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" ┃} }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: \"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: \"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" }┃ }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } ┃}"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } }┃"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: \"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: \"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: \"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: \"\" } }"], "1")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: \"\" } }"], "1")?; + + assert_insert_seq_ignore( + &["{ camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\"┃ } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: { z15a: ┃\"hello, hello.12345ZXY{}[]-><-\" } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: { z15a:┃ \"hello, hello.12345ZXY{}[]-><-\" } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\" ┃} }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: {┃ z15a: \"hello, hello.12345ZXY{}[]-><-\" } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: ┃{ z15a: \"hello, hello.12345ZXY{}[]-><-\" } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\" }┃ }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\" } ┃}"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\" } }┃"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1:┃ { z15a: \"hello, hello.12345ZXY{}[]-><-\" } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{┃ camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\" } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["┃{ camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\" } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ ┃camelCaseB1: { z15a: \"hello, hello.12345ZXY{}[]-><-\" } }"], + "1", + )?; + assert_insert_seq_ignore( + &["{ camelCaseB1: { ┃z15a: \"hello, hello.12345ZXY{}[]-><-\" } }"], + "1", + )?; + + assert_insert_seq_ignore( + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase ┃} } } } } } } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } ┃} } } } } } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }┃"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } ┃} } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase } } } } } } } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase } } } } } } } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], + "{\"5", + )?; + assert_insert_seq_ignore( + &["{ ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], + "2", + )?; + Ok(()) + } } - - diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 0fd31ddc31..7c002031a0 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -1,4 +1,3 @@ -use crate::lang::ast::RecordField; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::RecordWithoutFields; @@ -13,6 +12,7 @@ use crate::editor::slow_pool::MarkNodeId; use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; use crate::lang::ast::Expr2; +use crate::lang::ast::RecordField; use crate::lang::pool::{NodeId, PoolStr, PoolVec}; use crate::ui::text::text_pos::TextPos; use snafu::OptionExt; @@ -197,12 +197,11 @@ pub fn update_record_colon( .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; @@ -277,6 +276,17 @@ pub fn update_record_field( 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 { + let first_char_opt = new_input.chars().next(); + let first_char_is_num = first_char_opt.unwrap_or('0').is_ascii_digit(); + + // variable name can't start with number + if first_char_is_num { + return Ok(InputOutcome::Ignored); + } + } + content_str_mut.insert_str(node_caret_offset, new_input); // update caret @@ -296,7 +306,9 @@ pub fn update_record_field( .next() .with_context(|| RecordWithoutFields {})?; - let field_pool_str = first_field.get_record_field_pool_str().as_str(ed_model.module.env.pool); + let field_pool_str = first_field + .get_record_field_pool_str() + .as_str(ed_model.module.env.pool); let mut new_field_name = String::new(); @@ -321,23 +333,22 @@ pub fn update_record_field( match first_field_b { RecordField::InvalidLabelOnly(_, _) => { - // TODO check if label is now valid - }, + // TODO check if label is now valid. If it is, return LabelOnly + } RecordField::LabelOnly(_, _, _symbol) => { - // TODO check if symbol is still valid - }, + // TODO check if symbol is still valid. If not, return InvalidLabelOnly + } RecordField::LabeledValue(_, _, field_val_id_ref) => { let field_val_id = *field_val_id_ref; let sub_expr2 = ed_model.module.env.pool.get(field_val_id); if let Expr2::InvalidLookup(_) = sub_expr2 { ed_model - .module - .env - .pool - .set(field_val_id, Expr2::InvalidLookup(new_field_pool_str)); + .module + .env + .pool + .set(field_val_id, Expr2::InvalidLookup(new_field_pool_str)); } - } } diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs index ba5a71f4f7..42222aa4a6 100644 --- a/editor/src/lang/ast.rs +++ b/editor/src/lang/ast.rs @@ -58,7 +58,7 @@ pub enum FloatVal { pub enum RecordField { InvalidLabelOnly(PoolStr, Variable), LabelOnly(PoolStr, Variable, Symbol), - LabeledValue(PoolStr, Variable, NodeId) + LabeledValue(PoolStr, Variable, NodeId), } #[test] @@ -166,7 +166,7 @@ pub enum Expr2 { }, // Product Types Record { - record_var: Variable, // 4B + record_var: Variable, // 4B fields: PoolVec, // TODO ??B }, /// Empty record constant @@ -190,10 +190,10 @@ pub enum Expr2 { field_var: Variable, // 4B }, Update { - symbol: Symbol, // 8B + symbol: Symbol, // 8B updates: PoolVec, // 8B - record_var: Variable, // 4B - ext_var: Variable, // 4B + record_var: Variable, // 4B + ext_var: Variable, // 4B }, // Sum Types @@ -311,7 +311,6 @@ pub type ExprId = NodeId; use RecordField::*; impl RecordField { - pub fn get_record_field_var(&self) -> &Variable { match self { InvalidLabelOnly(_, var) => var, @@ -319,7 +318,7 @@ impl RecordField { LabeledValue(_, var, _) => var, } } - + pub fn get_record_field_pool_str(&self) -> &PoolStr { match self { InvalidLabelOnly(pool_str, _) => pool_str, @@ -327,7 +326,7 @@ impl RecordField { LabeledValue(pool_str, _, _) => pool_str, } } - + pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr { match self { InvalidLabelOnly(pool_str, _) => pool_str, @@ -335,7 +334,7 @@ impl RecordField { LabeledValue(pool_str, _, _) => pool_str, } } - + pub fn get_record_field_val_node_id(&self) -> Option> { match self { InvalidLabelOnly(_, _) => None, @@ -409,7 +408,7 @@ fn expr2_to_string_helper( pool_str.as_str(pool), var, )); - }, + } RecordField::LabelOnly(pool_str, var, symbol) => { out_string.push_str(&format!( "{}({}, Var({:?}), Symbol({:?})", @@ -418,7 +417,7 @@ fn expr2_to_string_helper( var, symbol )); - }, + } RecordField::LabeledValue(pool_str, var, val_node_id) => { out_string.push_str(&format!( "{}({}, Var({:?}), Expr2(\n", diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs index 2e69f83357..5e0b583bf7 100644 --- a/editor/src/lang/expr.rs +++ b/editor/src/lang/expr.rs @@ -1,6 +1,7 @@ #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] +use crate::lang::ast::expr2_to_string; use crate::lang::ast::RecordField; use crate::lang::ast::{ClosureExtra, Expr2, ExprId, FloatVal, IntStyle, IntVal, WhenBranch}; use crate::lang::def::References; @@ -1014,32 +1015,57 @@ enum CanonicalizeRecordProblem { record_region: Region, }, } + +enum FieldVar { + VarAndExprId(Variable, ExprId), + OnlyVar(Variable), +} + fn canonicalize_fields<'a>( env: &mut Env<'a>, scope: &mut Scope, fields: &'a [Located>>], ) -> Result<(PoolVec, Output), CanonicalizeRecordProblem> { - let mut can_fields: MutMap<&'a str, (Variable, ExprId)> = MutMap::default(); + let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); let mut output = Output::default(); for loc_field in fields.iter() { match canonicalize_field(env, scope, &loc_field.value) { - Ok((label, field_expr, field_out, field_var)) => { - let expr_id = env.pool.add(field_expr); - let replaced = can_fields.insert(label, (field_var, expr_id)); + Ok(can_field) => { + match can_field { + CanonicalField::LabelAndValue { + label, + value_expr, + value_output, + var, + } => { + let expr_id = env.pool.add(value_expr); - if let Some(_old) = replaced { - // env.problems.push(Problem::DuplicateRecordFieldValue { - // field_name: label, - // field_region: loc_field.region, - // record_region: region, - // replaced_region: old.region, - // }); - todo!() + let replaced = + can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id)); + + if let Some(_old) = replaced { + // env.problems.push(Problem::DuplicateRecordFieldValue { + // field_name: label, + // field_region: loc_field.region, + // record_region: region, + // replaced_region: old.region, + // }); + todo!() + } + + output.references.union_mut(value_output.references); + } + CanonicalField::InvalidLabelOnly { label, var } => { + let replaced = can_fields.insert(label, FieldVar::OnlyVar(var)); + + if let Some(_old) = replaced { + todo!() + } + } } - - output.references.union_mut(field_out.references); } + Err(CanonicalizeFieldProblem::InvalidOptionalValue { field_name: _, field_region: _, @@ -1061,10 +1087,17 @@ fn canonicalize_fields<'a>( let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool); - for (node_id, (string, (var, expr_id))) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) - { + for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) { let name = PoolStr::new(string, env.pool); - env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); + + match field_var { + FieldVar::VarAndExprId(var, expr_id) => { + env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); + } + FieldVar::OnlyVar(var) => { + env.pool[node_id] = RecordField::InvalidLabelOnly(name, var); + } // TODO RecordField::LabelOnly + } } Ok((pool_vec, output)) @@ -1076,11 +1109,23 @@ enum CanonicalizeFieldProblem { field_region: Region, }, } +enum CanonicalField<'a> { + LabelAndValue { + label: &'a str, + value_expr: Expr2, + value_output: Output, + var: Variable, + }, + InvalidLabelOnly { + label: &'a str, + var: Variable, + }, // TODO make ValidLabelOnly +} fn canonicalize_field<'a>( env: &mut Env<'a>, scope: &mut Scope, field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>, -) -> Result<(&'a str, Expr2, Output, Variable), CanonicalizeFieldProblem> { +) -> Result, CanonicalizeFieldProblem> { use roc_parse::ast::AssignedField::*; match field { @@ -1089,7 +1134,12 @@ fn canonicalize_field<'a>( let field_var = env.var_store.fresh(); let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); - Ok((label.value, loc_can_expr, output, field_var)) + Ok(CanonicalField::LabelAndValue { + label: label.value, + value_expr: loc_can_expr, + value_output: output, + var: field_var, + }) } OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { @@ -1098,8 +1148,13 @@ fn canonicalize_field<'a>( }), // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) - LabelOnly(_) => { - panic!("Somehow a LabelOnly record field was not desugared!"); + LabelOnly(label) => { + let field_var = env.var_store.fresh(); + // TODO return ValidLabel if label points to in scope variable + Ok(CanonicalField::InvalidLabelOnly { + label: label.value, + var: field_var, + }) } SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => { diff --git a/editor/src/ui/text/big_text_area.rs b/editor/src/ui/text/big_text_area.rs index 292c7b265c..8c0d738547 100644 --- a/editor/src/ui/text/big_text_area.rs +++ b/editor/src/ui/text/big_text_area.rs @@ -423,8 +423,8 @@ impl fmt::Debug for BigTextArea { #[cfg(test)] pub mod test_big_sel_text { - use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; 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, @@ -434,7 +434,7 @@ pub mod test_big_sel_text { use crate::ui::ui_error::{OutOfBounds, UIResult}; use crate::window::keyboard_input::{no_mods, Modifiers}; use snafu::OptionExt; - use std::{slice::SliceIndex}; + use std::slice::SliceIndex; fn shift_pressed() -> Modifiers { Modifiers { @@ -508,8 +508,7 @@ pub mod test_big_sel_text { } let actual_lines = all_lines_vec(&big_text); - let dsl_slice = - convert_selection_to_dsl(big_text.caret_w_select, actual_lines).unwrap(); + let dsl_slice = convert_selection_to_dsl(big_text.caret_w_select, actual_lines).unwrap(); assert_eq!(dsl_slice, expected_post_lines_str); Ok(()) @@ -632,8 +631,7 @@ pub mod test_big_sel_text { big_text.select_all().unwrap(); let big_text_lines = all_lines_vec(&big_text); - let post_lines_str = - convert_selection_to_dsl(big_text.caret_w_select, big_text_lines)?; + let post_lines_str = convert_selection_to_dsl(big_text.caret_w_select, big_text_lines)?; assert_eq!(post_lines_str, expected_post_lines_str); diff --git a/editor/src/ui/text/caret_w_select.rs b/editor/src/ui/text/caret_w_select.rs index bd30028254..ba5874ea24 100644 --- a/editor/src/ui/text/caret_w_select.rs +++ b/editor/src/ui/text/caret_w_select.rs @@ -114,16 +114,16 @@ pub fn make_caret_rect( #[cfg(test)] pub mod test_caret_w_select { - use crate::ui::ui_error::OutOfBounds; - use crate::ui::util::slice_get; - use crate::ui::ui_error::UIResult; + use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::selection::validate_selection; use crate::ui::text::text_pos::TextPos; - use crate::ui::text::caret_w_select::CaretWSelect; + use crate::ui::ui_error::OutOfBounds; + use crate::ui::ui_error::UIResult; + use crate::ui::util::slice_get; use core::cmp::Ordering; use pest::Parser; - use std::{collections::HashMap, slice::SliceIndex}; use snafu::OptionExt; + use std::{collections::HashMap, slice::SliceIndex}; #[derive(Parser)] #[grammar = "../tests/selection.pest"] @@ -277,7 +277,7 @@ pub mod test_caret_w_select { fn insert_at_pos(lines: &mut [String], pos: TextPos, insert_char: char) -> UIResult<()> { let line = get_mut_res(pos.line, lines)?; - + let mut chars: Vec = line.chars().collect(); chars.insert(pos.column, insert_char);