diff --git a/editor/src/editor/code_lines.rs b/editor/src/editor/code_lines.rs index 65ec9efdc8..d281c81cdd 100644 --- a/editor/src/editor/code_lines.rs +++ b/editor/src/editor/code_lines.rs @@ -19,13 +19,6 @@ impl CodeLines { } } - pub fn add_to_line(&mut self, line_nr: usize, new_str: &str) -> UIResult<()> { - let line_ref = slice_get_mut(line_nr, &mut self.lines)?; - line_ref.push_str(new_str); - - Ok(()) - } - pub fn insert_between_line( &mut self, line_nr: usize, @@ -36,6 +29,8 @@ impl CodeLines { line_ref.insert_str(index, new_str); + self.nr_of_chars += new_str.len(); + Ok(()) } @@ -44,6 +39,8 @@ impl CodeLines { line_ref.remove(index); + self.nr_of_chars -= 1; + Ok(()) } } diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index 145622f091..9dda436ec4 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -1,4 +1,3 @@ -use crate::editor::code_lines::CodeLines; use crate::editor::ed_error::EdResult; use crate::editor::slow_pool::MarkNodeId; use crate::editor::util::index_of; @@ -73,37 +72,3 @@ impl GridNodeMap { Ok(caret_pos.column - first_node_index) } } - -// perform updates for both GridNodeMap and CodeLines -pub fn add_to_line_both( - grid_node_map: &mut GridNodeMap, - code_lines: &mut CodeLines, - line_nr: usize, - new_str: &str, - node_id: MarkNodeId, -) -> UIResult<()> { - grid_node_map.add_to_line(line_nr, new_str.len(), node_id)?; - code_lines.add_to_line(line_nr, new_str) -} - -pub fn insert_between_line_both( - grid_node_map: &mut GridNodeMap, - code_lines: &mut CodeLines, - line_nr: usize, - index: usize, - new_str: &str, - node_id: MarkNodeId, -) -> UIResult<()> { - 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 del_at_line_both( - grid_node_map: &mut GridNodeMap, - code_lines: &mut CodeLines, - line_nr: usize, - index: usize, -) -> UIResult<()> { - grid_node_map.del_at_line(line_nr, index)?; - code_lines.del_at_line(line_nr, index) -} diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs index 6bcd46bba8..e0b0a19cdd 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/editor/src/editor/markup/nodes.rs @@ -135,6 +135,7 @@ pub const BLANK_PLACEHOLDER: &str = " "; pub const LEFT_ACCOLADE: &str = "{ "; pub const RIGHT_ACCOLADE: &str = " }"; pub const COLON: &str = ": "; +pub const STRING_QUOTES: &str = "\"\""; fn new_markup_node( text: String, diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 8d2ca470ad..bf29c0a2c2 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -1,20 +1,20 @@ use crate::editor::code_lines::CodeLines; use crate::editor::ed_error::EdResult; -use crate::editor::grid_node_map::insert_between_line_both; 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::mvc::ed_model::EdModel; use crate::editor::mvc::record_update::start_new_record; +use crate::editor::mvc::record_update::update_new_record; use crate::editor::mvc::record_update::update_record_colon; 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::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; -use crate::lang::pool::{NodeId, PoolStr, PoolVec}; +use crate::lang::pool::NodeId; use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::lines::MoveCaretFun; use crate::ui::text::selection::validate_raw_sel; @@ -116,6 +116,25 @@ impl<'a> EdModel<'a> { 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, + ) -> 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) + } + + // 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) + } } impl<'a> SelectableLines for EdModel<'a> { @@ -308,6 +327,10 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult update_record_colon(ed_model)?; } + '"' => { + + start_new_string(ed_model)?; + } '\u{8}' | '\u{7f}' => { // On Linux, '\u{8}' is backspace, // On macOS '\u{7f}'. @@ -347,105 +370,52 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult None }; - let prev_mark_node_opt = prev_mark_node_id_opt.map( - |prev_mark_node_id| ed_model.markup_node_pool.get(prev_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(); + + let ast_node_ref = ed_model.module.env.pool.get(ast_node_id); - if let Some(parent_id) = parent_id_opt { - let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); + match ast_node_ref { + Expr2::EmptyRecord => { - let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; + let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); - match ast_node_ref { - Expr2::EmptyRecord => { - if let Some(prev_mark_node) = prev_mark_node_opt { - if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE { - // update Markup - let record_field_str = &ch.to_string(); + update_new_record(&ch.to_string(), prev_mark_node_id_opt, sibling_ids, ed_model)?; + }, + Expr2::Record { record_var:_, fields } => { + 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_node_content = prev_mark_node.get_content()?; - let record_field_node = MarkupNode::Text { - content: record_field_str.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::RecordField, - attributes: Attributes::new(), - parent_id_opt: Some(parent_id), - }; + let node_to_update_id = + if prev_node_content == nodes::LEFT_ACCOLADE { + curr_mark_node_id + } else { + // caret is already past field so we need the previous MarkupNode + prev_mark_node_id + }; - let record_field_node_id = ed_model.markup_node_pool.add(record_field_node); + update_record_field( + &ch.to_string(), + old_caret_pos, + node_to_update_id, + fields, + ed_model, + )?; + } + }, + Expr2::SmallStr(array_str) => { - let parent = ed_model.markup_node_pool.get_mut(parent_id); - parent.add_child_at_index(new_child_index, record_field_node_id)?; + update_small_string(ch, array_str, ed_model)?; + }, + Expr2::Str(old_pool_str) => { - // update caret - ed_model.simple_move_carets_right(); - - // update GridNodeMap and CodeLines - insert_between_line_both( - &mut ed_model.grid_node_map, - &mut ed_model.code_lines, - old_caret_pos.line, - old_caret_pos.column, - record_field_str, - record_field_node_id, - )?; - - // update AST - let record_var = ed_model.module.env.var_store.fresh(); - let field_name = PoolStr::new(record_field_str, &mut ed_model.module.env.pool); - let field_var = ed_model.module.env.var_store.fresh(); - //TODO actually check if field_str belongs to a previously defined variable - let field_val = Expr2::InvalidLookup( - PoolStr::new(record_field_str, ed_model.module.env.pool) - ); - let field_val_id = ed_model.module.env.pool.add(field_val); - let first_field = (field_name, field_var, field_val_id); - - let fields = PoolVec::new( - vec![first_field].into_iter(), - &mut ed_model.module.env.pool, - ); - - let new_ast_node = - Expr2::Record { - record_var, - fields, - }; - - ed_model.module.env.pool.set(ast_node_id, new_ast_node); - } - } - }, - Expr2::Record { record_var:_, fields } => { - 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_node_content = prev_mark_node.get_content()?; - - let node_to_update_id = - if prev_node_content == nodes::LEFT_ACCOLADE { - curr_mark_node_id - } else { - // caret is already past field so we need the previous MarkupNode - prev_mark_node_id - }; - - update_record_field( - &ch.to_string(), - old_caret_pos, - node_to_update_id, - fields, - ed_model, - )?; - } - }, - other => { - unimplemented!("TODO implement updating of Expr2 {:?}.", other) - }, - } + update_string(&ch.to_string(), old_pool_str, ed_model)?; + }, + other => { + unimplemented!("TODO implement updating of Expr2 {:?}.", other) + }, } } } diff --git a/editor/src/editor/mvc/mod.rs b/editor/src/editor/mvc/mod.rs index 672fcdb6cf..69ebe9974e 100644 --- a/editor/src/editor/mvc/mod.rs +++ b/editor/src/editor/mvc/mod.rs @@ -4,3 +4,4 @@ pub mod ed_model; pub mod ed_update; pub mod ed_view; mod record_update; +mod string_update; diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 9c5398b740..00f240544b 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -1,9 +1,6 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::RecordWithoutFields; -use crate::editor::grid_node_map::add_to_line_both; -use crate::editor::grid_node_map::del_at_line_both; -use crate::editor::grid_node_map::insert_between_line_both; use crate::editor::markup::attribute::Attributes; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; @@ -44,7 +41,7 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<()> { ast_node_id, syn_high_style: HighlightStyle::Bracket, attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), + parent_id_opt: Some(curr_mark_node_id), // current node will be replace with nested one }; let left_bracket_node_id = mark_node_pool.add(left_bracket_node); @@ -54,7 +51,7 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<()> { ast_node_id, syn_high_style: HighlightStyle::Bracket, attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), + parent_id_opt: Some(curr_mark_node_id), // current node will be replace with nested one }; let right_bracket_node_id = mark_node_pool.add(right_bracket_node); @@ -69,30 +66,23 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<()> { mark_node_pool.replace_node(curr_mark_node_id, nested_node); // remove data corresponding to Blank node - del_at_line_both( - &mut ed_model.grid_node_map, - &mut ed_model.code_lines, - old_caret_pos.line, - old_caret_pos.column, - )?; + ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; for _ in 0..nodes::LEFT_ACCOLADE.len() { ed_model.simple_move_carets_right(); } // update GridNodeMap and CodeLines - add_to_line_both( - &mut ed_model.grid_node_map, - &mut ed_model.code_lines, + ed_model.insert_between_line( old_caret_pos.line, + old_caret_pos.column, nodes::LEFT_ACCOLADE, left_bracket_node_id, )?; - add_to_line_both( - &mut ed_model.grid_node_map, - &mut ed_model.code_lines, + ed_model.insert_between_line( old_caret_pos.line, + old_caret_pos.column + nodes::LEFT_ACCOLADE.len(), nodes::RIGHT_ACCOLADE, right_bracket_node_id, )?; @@ -101,6 +91,81 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<()> { Ok(()) } +pub fn update_new_record( + new_input: &str, + prev_mark_node_id_opt: Option, + sibling_ids: Vec, + ed_model: &mut EdModel, +) -> EdResult<()> { + let prev_mark_node_opt = prev_mark_node_id_opt + .map(|prev_mark_node_id| ed_model.markup_node_pool.get(prev_mark_node_id)); + + if let Some(prev_mark_node) = prev_mark_node_opt { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node: _, + parent_id_opt, + ast_node_id, + } = get_node_context(&ed_model)?; + + if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE { + // update Markup + + let record_field_node = MarkupNode::Text { + content: new_input.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::RecordField, + attributes: Attributes::new(), + parent_id_opt, + }; + + let record_field_node_id = ed_model.markup_node_pool.add(record_field_node); + + if let Some(parent_id) = parent_id_opt { + let parent = ed_model.markup_node_pool.get_mut(parent_id); + + let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; + + parent.add_child_at_index(new_child_index, record_field_node_id)?; + } else { + MissingParent { + node_id: curr_mark_node_id, + } + .fail()? + } + + // update caret + ed_model.simple_move_carets_right(); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + new_input, + record_field_node_id, + )?; + + // update AST + let record_var = ed_model.module.env.var_store.fresh(); + let field_name = PoolStr::new(new_input, &mut ed_model.module.env.pool); + let field_var = ed_model.module.env.var_store.fresh(); + //TODO actually check if field_str belongs to a previously defined variable + let field_val = Expr2::InvalidLookup(PoolStr::new(new_input, ed_model.module.env.pool)); + let field_val_id = ed_model.module.env.pool.add(field_val); + let first_field = (field_name, field_var, field_val_id); + + let fields = PoolVec::new(vec![first_field].into_iter(), &mut ed_model.module.env.pool); + + let new_ast_node = Expr2::Record { record_var, fields }; + + ed_model.module.env.pool.set(ast_node_id, new_ast_node); + } + } + + Ok(()) +} + pub fn update_record_field( new_input: &str, old_caret_pos: TextPos, @@ -122,9 +187,7 @@ pub fn update_record_field( } // update GridNodeMap and CodeLines - insert_between_line_both( - &mut ed_model.grid_node_map, - &mut ed_model.code_lines, + ed_model.insert_between_line( old_caret_pos.line, old_caret_pos.column, new_input, @@ -212,18 +275,16 @@ pub fn update_record_colon(ed_model: &mut EdModel) -> EdResult<()> { } // update GridNodeMap and CodeLines - add_to_line_both( - &mut ed_model.grid_node_map, - &mut ed_model.code_lines, + ed_model.insert_between_line( old_caret_pos.line, + old_caret_pos.column, nodes::COLON, record_colon_node_id, )?; - add_to_line_both( - &mut ed_model.grid_node_map, - &mut ed_model.code_lines, + ed_model.insert_between_line( old_caret_pos.line, + old_caret_pos.column + nodes::COLON.len(), nodes::BLANK_PLACEHOLDER, record_blank_node_id, )?; diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs new file mode 100644 index 0000000000..817afff51d --- /dev/null +++ b/editor/src/editor/mvc/string_update.rs @@ -0,0 +1,157 @@ +use crate::editor::ed_error::EdResult; +use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::nodes; +use crate::editor::markup::nodes::MarkupNode; +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::ArrString; +use crate::lang::ast::Expr2; +use crate::lang::pool::PoolStr; + +pub fn update_small_string( + new_char: &char, + old_array_str: &ArrString, + ed_model: &mut EdModel, +) -> 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 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 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)?; + content_str_mut.insert_str(node_caret_offset, new_input); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + new_input, + curr_mark_node_id, + )?; + + 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) + { + // safe because we checked the length + unsafe { + mut_array_str.push_unchecked(*new_char); + } + } else { + unreachable!() + } + } else { + let mut new_str = old_array_str.as_str().to_owned(); + new_str.push(*new_char); + + 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); + } + + // update caret + for _ in 0..new_input.len() { + ed_model.simple_move_carets_right(); + } + + Ok(()) +} + +pub fn update_string( + new_input: &str, + old_pool_str: &PoolStr, + ed_model: &mut EdModel, +) -> EdResult<()> { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node: _, + parent_id_opt: _, + ast_node_id, + } = get_node_context(&ed_model)?; + + // update markup + let curr_mark_node_mut = ed_model.markup_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)?; + content_str_mut.insert_str(node_caret_offset, new_input); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + new_input, + curr_mark_node_id, + )?; + + // 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 caret + ed_model.simple_move_carets_right(); + + Ok(()) +} + +pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<()> { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node, + parent_id_opt, + ast_node_id, + } = get_node_context(&ed_model)?; + + if curr_mark_node.is_blank() { + let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new()); + + ed_model.module.env.pool.set(ast_node_id, new_expr2_node); + + let new_string_node = MarkupNode::Text { + content: nodes::STRING_QUOTES.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::String, + attributes: Attributes::new(), + parent_id_opt, + }; + + ed_model + .markup_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)?; + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + nodes::STRING_QUOTES, + curr_mark_node_id, + )?; + + ed_model.simple_move_carets_right(); + } + + Ok(()) +} diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs index b415d1bf9d..dc3844da9b 100644 --- a/editor/src/lang/ast.rs +++ b/editor/src/lang/ast.rs @@ -58,6 +58,8 @@ fn size_of_intval() { assert_eq!(std::mem::size_of::(), 16); } +pub type ArrString = ArrayString; + /// An Expr that fits in 32B. /// It has a 1B discriminant and variants which hold payloads of at most 31B. #[derive(Debug)] @@ -94,7 +96,7 @@ pub enum Expr2 { text: PoolStr, // 8B }, /// string literals of length up to 30B - SmallStr(ArrayString), // 31B + SmallStr(ArrString), // 31B /// string literals of length 31B or more Str(PoolStr), // 8B // Lookups