From bdf48d478fc1362f6047d8b50ddc6c263a28d75a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 11 Jan 2021 19:46:15 +0100 Subject: [PATCH 01/12] progress integrating text rope --- Cargo.lock | 10 +++ editor/Cargo.toml | 1 + editor/src/error.rs | 12 ++- editor/src/lang/mod.rs | 2 +- editor/src/lang/{file.rs => roc_file.rs} | 0 editor/src/lib.rs | 52 +++++++++-- editor/src/resources/strings.rs | 1 + editor/src/selection.rs | 4 +- editor/src/tea/app_model.rs | 7 ++ editor/src/tea/ed_model.rs | 14 +-- editor/src/tea/mod.rs | 1 + editor/src/tea/update.rs | 77 +++++++++-------- editor/src/text_buffer.rs | 86 +++++++++++++++++++ .../{Simple.roc => SimpleUnformatted.roc} | 0 editor/tests/modules/Storage.roc | 68 --------------- editor/tests/test_file.rs | 6 +- 16 files changed, 217 insertions(+), 124 deletions(-) rename editor/src/lang/{file.rs => roc_file.rs} (100%) create mode 100644 editor/src/resources/strings.rs create mode 100644 editor/src/tea/app_model.rs create mode 100644 editor/src/text_buffer.rs rename editor/tests/modules/{Simple.roc => SimpleUnformatted.roc} (100%) delete mode 100644 editor/tests/modules/Storage.roc diff --git a/Cargo.lock b/Cargo.lock index 0af4e03703..90c3bdc7bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2694,6 +2694,7 @@ dependencies = [ "roc_types", "roc_unify", "roc_uniq", + "ropey", "snafu", "target-lexicon", "ven_graph", @@ -3023,6 +3024,15 @@ dependencies = [ "roc_types", ] +[[package]] +name = "ropey" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f3ef16589fdbb3e8fbce3dca944c08e61f39c7f16064b21a257d68ea911a83" +dependencies = [ + "smallvec", +] + [[package]] name = "rust-argon2" version = "0.8.3" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index fcfda19a8a..b0cb7578c2 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -68,6 +68,7 @@ snafu = { version = "0.6", features = ["backtraces"] } colored = "2" pest = "2.1" pest_derive = "2.1" +ropey = "1.2.0" [dependencies.bytemuck] diff --git a/editor/src/error.rs b/editor/src/error.rs index c3d2f53cbc..95f36bf6d0 100644 --- a/editor/src/error.rs +++ b/editor/src/error.rs @@ -19,13 +19,23 @@ pub enum EdError { vec_len: usize, backtrace: Backtrace, }, - #[snafu(display("InvalidSelection: {}", err_msg))] + #[snafu(display("InvalidSelection: {}.", err_msg))] InvalidSelection { err_msg: String, backtrace: Backtrace, }, #[snafu(display("MissingGlyphDims: glyph_dim_rect_opt was None for model. It needs to be set using the example_code_glyph_rect function."))] MissingGlyphDims {}, + #[snafu(display("FileOpenFailed: failed to open file with path {} with the following error: {}.", path_str, err_msg))] + FileOpenFailed { + path_str: String, + err_msg: 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 + } } pub type EdResult = std::result::Result; diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs index add841ec13..00a088b07f 100644 --- a/editor/src/lang/mod.rs +++ b/editor/src/lang/mod.rs @@ -1,7 +1,7 @@ pub mod ast; mod def; mod expr; -pub mod file; +pub mod roc_file; mod module; mod pattern; mod pool; diff --git a/editor/src/lang/file.rs b/editor/src/lang/roc_file.rs similarity index 100% rename from editor/src/lang/file.rs rename to editor/src/lang/roc_file.rs diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 0324eadcbc..5ba72b4f76 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -28,6 +28,8 @@ use crate::graphics::style::CODE_TXT_XY; use crate::selection::create_selection_rects; use crate::tea::ed_model::EdModel; use crate::tea::{ed_model, update}; +use crate::tea::app_model::AppModel; +use crate::vec_result::get_res; use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use cgmath::Vector2; @@ -51,24 +53,37 @@ mod selection; mod tea; mod util; mod vec_result; +mod text_buffer; /// 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 do any initialization here +pub fn launch(filepaths: &[&Path]) -> io::Result<()> { + //TODO support using multiple filepaths + let first_path_opt = if filepaths.len() > 0 { + match get_res(0, filepaths) { + Ok(path_ref_ref) => Some(*path_ref_ref), + Err(e) => { + eprintln!("{}", e); + None + } + } + } else { + None + }; - run_event_loop().expect("Error running event loop"); + run_event_loop(first_path_opt).expect("Error running event loop"); Ok(()) } -fn run_event_loop() -> Result<(), Box> { +fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { env_logger::init(); // Open window and create a surface let event_loop = winit::event_loop::EventLoop::new(); let window = winit::window::WindowBuilder::new() + .with_inner_size(PhysicalSize::new(1000.0, 800.0)) .build(&event_loop) .unwrap(); @@ -124,8 +139,31 @@ fn run_event_loop() -> Result<(), Box> { let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; let is_animating = true; - let mut ed_model = ed_model::init_model(); - ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush)); + let mut ed_model_opt = + if let Some(file_path) = file_path_opt { + let ed_model_res = + ed_model::init_model(file_path); + + match ed_model_res { + Ok(ed_model) => { + ed_model.glyph_dim_rect_opt = + Some(example_code_glyph_rect(&mut glyph_brush)); + + Some(ed_model) + }, + Err(e) => { + print_err(&e); + None + } + } + } else { + None + }; + + let mut app_model = AppModel { + ed_model_opt + }; + let mut keyboard_modifiers = ModifiersState::empty(); let arena = Bump::new(); @@ -180,7 +218,7 @@ fn run_event_loop() -> Result<(), Box> { event: event::WindowEvent::ReceivedCharacter(ch), .. } => { - update::update_text_state(&mut ed_model, &ch); + update::handle_new_char(&mut app_model, &ch); } //Keyboard Input Event::WindowEvent { diff --git a/editor/src/resources/strings.rs b/editor/src/resources/strings.rs new file mode 100644 index 0000000000..56866557ca --- /dev/null +++ b/editor/src/resources/strings.rs @@ -0,0 +1 @@ +pub const NOTHING_OPENED: &str = "Execute `cargo run edit ` to open a file"; \ No newline at end of file diff --git a/editor/src/selection.rs b/editor/src/selection.rs index 85f60ab3fd..9909941082 100644 --- a/editor/src/selection.rs +++ b/editor/src/selection.rs @@ -9,10 +9,10 @@ use snafu::ensure; //using the "parse don't validate" pattern struct ValidSelection { - selection: RawSelection, + pub selection: RawSelection, } -fn validate_selection(selection: RawSelection) -> EdResult { +pub fn validate_selection(selection: RawSelection) -> EdResult { let RawSelection { start_pos, end_pos } = selection; ensure!( diff --git a/editor/src/tea/app_model.rs b/editor/src/tea/app_model.rs new file mode 100644 index 0000000000..69f6e26aa6 --- /dev/null +++ b/editor/src/tea/app_model.rs @@ -0,0 +1,7 @@ + +use crate::tea::ed_model::EdModel; + +#[derive(Debug)] +pub struct AppModel { + pub ed_model_opt: Option +} \ No newline at end of file diff --git a/editor/src/tea/ed_model.rs b/editor/src/tea/ed_model.rs index a864a2f698..352a92b652 100644 --- a/editor/src/tea/ed_model.rs +++ b/editor/src/tea/ed_model.rs @@ -1,21 +1,25 @@ use crate::graphics::primitives::rect::Rect; +use crate::text_buffer; +use crate::text_buffer::TextBuffer; +use crate::error::EdResult; +use std::path::Path; use std::cmp::Ordering; #[derive(Debug)] pub struct EdModel { - pub lines: Vec, + pub text_buf: TextBuffer, pub caret_pos: Position, pub selection_opt: Option, pub glyph_dim_rect_opt: Option, } -pub fn init_model() -> EdModel { - EdModel { - lines: vec![String::new()], +pub fn init_model(file_path: &Path) -> EdResult { + Ok(EdModel { + text_buf: text_buffer::from_path(file_path)?, caret_pos: Position { line: 0, column: 0 }, selection_opt: None, glyph_dim_rect_opt: None, - } + }) } //Is model.rs the right place for these structs? diff --git a/editor/src/tea/mod.rs b/editor/src/tea/mod.rs index 33bcc304d1..589e859a6b 100644 --- a/editor/src/tea/mod.rs +++ b/editor/src/tea/mod.rs @@ -1,2 +1,3 @@ pub mod ed_model; +pub mod app_model; pub mod update; diff --git a/editor/src/tea/update.rs b/editor/src/tea/update.rs index f83dd677a5..2bf39ae8b5 100644 --- a/editor/src/tea/update.rs +++ b/editor/src/tea/update.rs @@ -1,6 +1,7 @@ use super::ed_model::EdModel; use super::ed_model::{Position, RawSelection}; use crate::util::is_newline; +use crate::tea::app_model::AppModel; use std::cmp::{max, min}; pub fn move_caret_left( @@ -293,52 +294,54 @@ pub fn move_caret_down( (new_caret_pos, new_selection_opt) } -pub fn update_text_state(ed_model: &mut EdModel, received_char: &char) { - ed_model.selection_opt = None; +pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) { + if let Some(ed_model) = app_model.ed_model_opt { + ed_model.selection_opt = None; - match received_char { - '\u{8}' | '\u{7f}' => { - // In Linux, we get a '\u{8}' when you press backspace, - // but in macOS we get '\u{7f}'. - if let Some(last_line) = ed_model.lines.last_mut() { - if !last_line.is_empty() { - last_line.pop(); - } else if ed_model.lines.len() > 1 { - ed_model.lines.pop(); + match received_char { + '\u{8}' | '\u{7f}' => { + // On Linux, '\u{8}' is backspace, + // on macOS '\u{7f}'. + if let Some(last_line) = ed_model.lines.last_mut() { + if !last_line.is_empty() { + last_line.pop(); + } else if ed_model.lines.len() > 1 { + ed_model.lines.pop(); + } + ed_model.caret_pos = + move_caret_left(ed_model.caret_pos, None, false, &ed_model.lines).0; } - ed_model.caret_pos = - move_caret_left(ed_model.caret_pos, None, false, &ed_model.lines).0; } - } - '\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => { - // These are private use characters; ignore them. - // See http://www.unicode.org/faq/private_use.html - } - ch if is_newline(ch) => { - if let Some(last_line) = ed_model.lines.last_mut() { - last_line.push(*received_char) + '\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => { + // These are private use characters; ignore them. + // See http://www.unicode.org/faq/private_use.html } - ed_model.lines.push(String::new()); - ed_model.caret_pos = Position { - line: ed_model.caret_pos.line + 1, - column: 0, - }; - - ed_model.selection_opt = None; - } - _ => { - let nr_lines = ed_model.lines.len(); - - if let Some(last_line) = ed_model.lines.last_mut() { - last_line.push(*received_char); - + ch if is_newline(ch) => { + if let Some(last_line) = ed_model.lines.last_mut() { + last_line.push(*received_char) + } + ed_model.lines.push(String::new()); ed_model.caret_pos = Position { - line: nr_lines - 1, - column: last_line.len(), + line: ed_model.caret_pos.line + 1, + column: 0, }; ed_model.selection_opt = None; } + _ => { + let nr_lines = ed_model.lines.len(); + + if let Some(last_line) = ed_model.lines.last_mut() { + last_line.push(*received_char); + + ed_model.caret_pos = Position { + line: nr_lines - 1, + column: last_line.len(), + }; + + ed_model.selection_opt = None; + } + } } } } diff --git a/editor/src/text_buffer.rs b/editor/src/text_buffer.rs new file mode 100644 index 0000000000..6b228d5e84 --- /dev/null +++ b/editor/src/text_buffer.rs @@ -0,0 +1,86 @@ + +// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license + +use crate::error::EdResult; +use crate::error::EdError::{TextBufReadFailed, FileOpenFailed}; +use crate::tea::ed_model::{Position, RawSelection}; +use crate::selection::{validate_selection}; +use std::fs::File; +use std::io; +use std::path::Path; +use std::ops::Range; +use ropey::{Rope}; + +#[derive(Debug)] +pub struct TextBuffer { + text_rope: Rope, + path_str: String, + dirty: bool // true if text has been changed, false if all actions following change have executed +} + +impl TextBuffer { + pub fn pop_char(&mut self, cursor_pos: Position) { + let char_indx = self.pos_to_char_indx(cursor_pos); + self.text_rope.remove(char_indx..char_indx); + } + + pub fn del_selection(&mut self, raw_sel: RawSelection) -> EdResult<()> { + let range = self.sel_to_range(raw_sel)?; + self.text_rope.remove(range); + Ok(()) + } + + fn pos_to_char_indx(&self, pos: Position) -> usize { + self.text_rope.line_to_char(pos.line) + pos.column + } + + fn sel_to_range(&self, raw_sel: RawSelection) -> EdResult> { + let valid_sel = validate_selection(raw_sel)?; + let start_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos); + let end_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos); + + Ok(start_char_indx..end_char_indx) + } + + pub fn from_path(&self, path: &Path) -> EdResult { + // TODO benchmark different file reading methods, see #886 + let text_rope = rope_from_path(path)?; + let path_str = path_to_string(path); + + Ok(TextBuffer { + text_rope, + path_str, + dirty: false + }) + } +} + +fn path_to_string(path: &Path) -> String { + let mut path_str = String::new(); + path_str.push_str(&path.to_string_lossy()); + + path_str +} + +fn rope_from_path(path: &Path) -> EdResult { + match File::open(path) { + Ok(file) => { + let mut 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() + }) + } + } +} \ No newline at end of file diff --git a/editor/tests/modules/Simple.roc b/editor/tests/modules/SimpleUnformatted.roc similarity index 100% rename from editor/tests/modules/Simple.roc rename to editor/tests/modules/SimpleUnformatted.roc diff --git a/editor/tests/modules/Storage.roc b/editor/tests/modules/Storage.roc deleted file mode 100644 index 950d1d01f1..0000000000 --- a/editor/tests/modules/Storage.roc +++ /dev/null @@ -1,68 +0,0 @@ -interface Storage - exposes [ - Storage, - decoder, - get, - listener, - set - ] - imports [ - Map.{ Map }, - Json.Decode.{ Decoder } as Decode - Json.Encode as Encode - Ports.FromJs as FromJs - Ports.ToJs as ToJs - ] - - -################################################################################ -## TYPES ## -################################################################################ - - -Storage : [ - @Storage (Map Str Decode.Value) -] - - -################################################################################ -## API ## -################################################################################ - - -get : Storage, Str, Decoder a -> [ Ok a, NotInStorage, DecodeError Decode.Error ]* -get = \key, decoder, @Storage map -> - when Map.get map key is - Ok json -> - Decode.decodeValue decoder json - - Err NotFound -> - NotInStorage - - -set : Encode.Value, Str -> Effect {} -set json str = - ToJs.type "setStorage" - |> ToJs.setFields [ - Field "key" (Encode.str str), - Field "value" json - ] - |> ToJs.send - - -decoder : Decoder Storage -decoder = - Decode.mapType Decode.value - |> Decode.map \map -> @Storage map - - -################################################################################ -## PORTS INCOMING ## -################################################################################ - - -listener : (Storage -> msg) -> FromJs.Listener msg -listener toMsg = - FromJs.listen "storageUpdated" - (Decode.map decoder toMsg) - diff --git a/editor/tests/test_file.rs b/editor/tests/test_file.rs index 6fa0b0f26c..32b850e17b 100644 --- a/editor/tests/test_file.rs +++ b/editor/tests/test_file.rs @@ -6,17 +6,17 @@ extern crate indoc; #[cfg(test)] mod test_file { use bumpalo::Bump; - use roc_editor::lang::file::File; + use roc_editor::lang::roc_file::File; use std::path::Path; #[test] fn read_and_fmt_simple_roc_module() { - let simple_module_path = Path::new("./tests/modules/Simple.roc"); + let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc"); let arena = Bump::new(); let file = File::read(simple_module_path, &arena) - .expect("Could not read Simple.roc in test_file test"); + .expect("Could not read SimpleUnformatted.roc in test_file test"); assert_eq!( file.fmt(), From 29e9cf3a6f0cbbc9ca8521a32ec5a1d51b9908c1 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 12 Jan 2021 20:10:20 +0100 Subject: [PATCH 02/12] integrated text rope, added open file, refactoring --- editor/Cargo.toml | 12 +- editor/src/error.rs | 14 ++- editor/src/graphics/colors.rs | 24 +++- editor/src/graphics/lowlevel/buffer.rs | 5 +- editor/src/graphics/lowlevel/vertex.rs | 4 +- editor/src/graphics/primitives/rect.rs | 2 +- editor/src/graphics/primitives/text.rs | 4 +- editor/src/graphics/shaders/rect.frag | 4 +- editor/src/graphics/shaders/rect.frag.spv | Bin 564 -> 444 bytes editor/src/graphics/shaders/rect.vert | 4 +- editor/src/graphics/shaders/rect.vert.spv | Bin 1360 -> 1328 bytes editor/src/graphics/style.rs | 2 +- editor/src/keyboard_input.rs | 53 +++------ editor/src/lib.rs | 138 +++++++++------------- editor/src/{tea => mvc}/app_model.rs | 2 +- editor/src/{tea => mvc}/ed_model.rs | 3 +- editor/src/mvc/ed_view.rs | 47 ++++++++ editor/src/{tea => mvc}/mod.rs | 1 + editor/src/{tea => mvc}/update.rs | 93 ++++++++------- editor/src/selection.rs | 68 ++++++++--- editor/src/text_buffer.rs | 112 ++++++++++++++---- editor/src/vec_result.rs | 7 +- 22 files changed, 357 insertions(+), 242 deletions(-) rename editor/src/{tea => mvc}/app_model.rs (69%) rename editor/src/{tea => mvc}/ed_model.rs (96%) create mode 100644 editor/src/mvc/ed_view.rs rename editor/src/{tea => mvc}/mod.rs (75%) rename editor/src/{tea => mvc}/update.rs (83%) diff --git a/editor/Cargo.toml b/editor/Cargo.toml index b0cb7578c2..61aec5ade5 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -84,9 +84,9 @@ quickcheck_macros = "0.8" # uncomment everything below if you have made changes to any shaders and # want to compile them to .spv -#[build-dependencies] -#rayon = "1.5.0" -#anyhow = "1.0" -#fs_extra = "1.1" -#glob = "0.3" -#shaderc = "0.6" +# [build-dependencies] +# rayon = "1.5.0" +# anyhow = "1.0" +# fs_extra = "1.1" +# glob = "0.3" +# shaderc = "0.6" diff --git a/editor/src/error.rs b/editor/src/error.rs index 95f36bf6d0..2e27b88378 100644 --- a/editor/src/error.rs +++ b/editor/src/error.rs @@ -10,13 +10,15 @@ use snafu::{Backtrace, ErrorCompat, Snafu}; #[snafu(visibility(pub))] pub enum EdError { #[snafu(display( - "OutOfBounds: index {} was out of bounds for Vec with length {}.", + "OutOfBounds: index {} was out of bounds for {} with length {}.", index, - vec_len + collection_name, + len ))] OutOfBounds { index: usize, - vec_len: usize, + collection_name: String, + len: usize, backtrace: Backtrace, }, #[snafu(display("InvalidSelection: {}.", err_msg))] @@ -25,11 +27,13 @@ pub enum EdError { backtrace: Backtrace, }, #[snafu(display("MissingGlyphDims: glyph_dim_rect_opt was None for model. It needs to be set using the example_code_glyph_rect function."))] - MissingGlyphDims {}, + MissingGlyphDims { + backtrace: Backtrace, + }, #[snafu(display("FileOpenFailed: failed to open file with path {} with the following error: {}.", path_str, err_msg))] FileOpenFailed { path_str: String, - err_msg: String + err_msg: 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 { diff --git a/editor/src/graphics/colors.rs b/editor/src/graphics/colors.rs index cce601fe75..89867b8e14 100644 --- a/editor/src/graphics/colors.rs +++ b/editor/src/graphics/colors.rs @@ -1,5 +1,19 @@ -pub const WHITE: [f32; 3] = [1.0, 1.0, 1.0]; -pub const TXT_COLOR: [f32; 4] = [0.4666, 0.2, 1.0, 1.0]; -pub const CODE_COLOR: [f32; 4] = [0.0, 0.05, 0.46, 1.0]; -pub const CARET_COLOR: [f32; 3] = WHITE; -pub const SELECT_COLOR: [f32; 3] = [0.45, 0.61, 1.0]; +pub const WHITE: (f32, f32, f32, f32) = (1.0, 1.0, 1.0, 1.0); +pub const TXT_COLOR: (f32, f32, f32, f32) = (1.0, 1.0, 1.0, 1.0); +pub const CODE_COLOR: (f32, f32, f32, f32) = (0.21, 0.55, 0.83, 1.0); +pub const CARET_COLOR: (f32, f32, f32, f32) = WHITE; +pub const SELECT_COLOR: (f32, f32, f32, f32) = (0.45, 0.61, 1.0, 1.0); +pub const BG_COLOR: (f32, f32, f32, f32) = (0.11, 0.11, 0.13, 1.0); + +pub fn to_wgpu_color((r, g, b, a): (f32, f32, f32, f32)) -> wgpu::Color { + wgpu::Color { + r: r as f64, + g: g as f64, + b: b as f64, + a: a as f64, + } +} + +pub fn to_slice((r, g, b, a): (f32, f32, f32, f32)) -> [f32; 4] { + [r, g, b, a] +} diff --git a/editor/src/graphics/lowlevel/buffer.rs b/editor/src/graphics/lowlevel/buffer.rs index 0be9cfeb8c..447a8d5a58 100644 --- a/editor/src/graphics/lowlevel/buffer.rs +++ b/editor/src/graphics/lowlevel/buffer.rs @@ -2,6 +2,7 @@ // by Benjamin Hansen, licensed under the MIT license use super::vertex::Vertex; use crate::graphics::primitives::rect::Rect; +use crate::graphics::colors::to_slice; use bumpalo::collections::Vec as BumpVec; use wgpu::util::{BufferInitDescriptor, DeviceExt}; @@ -25,7 +26,7 @@ impl QuadBufferBuilder { coords.y, coords.x + rect.width, coords.y + rect.height, - rect.color, + to_slice(rect.color), ) } @@ -35,7 +36,7 @@ impl QuadBufferBuilder { min_y: f32, max_x: f32, max_y: f32, - color: [f32; 3], + color: [f32; 4], ) -> Self { self.vertex_data.extend(&[ Vertex { diff --git a/editor/src/graphics/lowlevel/vertex.rs b/editor/src/graphics/lowlevel/vertex.rs index a77b6a2a29..f7b1776f5d 100644 --- a/editor/src/graphics/lowlevel/vertex.rs +++ b/editor/src/graphics/lowlevel/vertex.rs @@ -6,7 +6,7 @@ use cgmath::Vector2; pub struct Vertex { #[allow(dead_code)] pub position: Vector2, - pub color: [f32; 3], + pub color: [f32; 4], } unsafe impl bytemuck::Pod for Vertex {} @@ -28,7 +28,7 @@ impl Vertex { wgpu::VertexAttributeDescriptor { offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, shader_location: 1, - format: wgpu::VertexFormat::Float3, + format: wgpu::VertexFormat::Float4, }, ], }; diff --git a/editor/src/graphics/primitives/rect.rs b/editor/src/graphics/primitives/rect.rs index 4fdfdcf730..7560c0d10a 100644 --- a/editor/src/graphics/primitives/rect.rs +++ b/editor/src/graphics/primitives/rect.rs @@ -5,5 +5,5 @@ pub struct Rect { pub top_left_coords: Vector2, pub width: f32, pub height: f32, - pub color: [f32; 3], + pub color: (f32, f32, f32, f32), } diff --git a/editor/src/graphics/primitives/text.rs b/editor/src/graphics/primitives/text.rs index b996daa5f8..27479708a7 100644 --- a/editor/src/graphics/primitives/text.rs +++ b/editor/src/graphics/primitives/text.rs @@ -2,7 +2,7 @@ // by Benjamin Hansen, licensed under the MIT license use super::rect::Rect; -use crate::graphics::colors::CODE_COLOR; +use crate::graphics::colors::{CODE_COLOR, WHITE}; use crate::graphics::style::{CODE_FONT_SIZE, CODE_TXT_XY}; use ab_glyph::{FontArc, Glyph, InvalidFont}; use cgmath::{Vector2, Vector4}; @@ -102,7 +102,7 @@ fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect { top_left_coords: [position.x, top_y].into(), width, height, - color: [1.0, 1.0, 1.0], + color: WHITE, } } diff --git a/editor/src/graphics/shaders/rect.frag b/editor/src/graphics/shaders/rect.frag index 2246d723b6..c444a789ad 100644 --- a/editor/src/graphics/shaders/rect.frag +++ b/editor/src/graphics/shaders/rect.frag @@ -4,10 +4,10 @@ // Check build_shaders.rs on how to recompile shaders if you have made changes to this file -layout(location=0) in vec3 color; +layout(location=0) in vec4 color; layout(location=0) out vec4 fColor; void main() { - fColor = vec4(color, 1.0); + fColor = color; } \ No newline at end of file diff --git a/editor/src/graphics/shaders/rect.frag.spv b/editor/src/graphics/shaders/rect.frag.spv index ae9c49a4adb606ffe3801f3f3b56dec91d21682a..d1981bc7aefbd46acd0729d519e84fb9c0b45cf0 100644 GIT binary patch delta 107 zcmdnOvWJz3K4Wt=?7$j~DwSkM5nMs+Qfq{{MgMpVpeIl70OYFxX+|IhTEGC32k})W$Ff{j0Ewu9 TctC6iR1eYx;{OGzw*X=QNE;EV delta 187 zcmdnMb%BeQnMs+Qfq{{MgMpVpb0V)lt11HngZjoqXGTE~tIRn+C%=e+ft7)UL4ER8 z#`|DVIiL&(xPwGDFJ;PS^b`j&m4QNRK+Fup3P8RJlnoNMhVns%!sOL}Y(^l4$%FXn clWkcpD}Y2aKs+F}1F8q<0`dO>)ms2D05f?KxBvhE diff --git a/editor/src/graphics/style.rs b/editor/src/graphics/style.rs index a32deb6558..c166fd3808 100644 --- a/editor/src/graphics/style.rs +++ b/editor/src/graphics/style.rs @@ -1,2 +1,2 @@ -pub const CODE_FONT_SIZE: f32 = 40.0; +pub const CODE_FONT_SIZE: f32 = 30.0; pub const CODE_TXT_XY: (f32, f32) = (30.0, 90.0); diff --git a/editor/src/keyboard_input.rs b/editor/src/keyboard_input.rs index bbf3969886..7c8c378eb0 100644 --- a/editor/src/keyboard_input.rs +++ b/editor/src/keyboard_input.rs @@ -1,12 +1,12 @@ -use crate::tea::ed_model::EdModel; -use crate::tea::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up}; +use crate::mvc::ed_model::EdModel; +use crate::mvc::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun}; use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; pub fn handle_keydown( elem_state: ElementState, virtual_keycode: VirtualKeyCode, modifiers: ModifiersState, - model: &mut EdModel, + ed_model: &mut EdModel, ) { use winit::event::VirtualKeyCode::*; @@ -16,44 +16,16 @@ pub fn handle_keydown( match virtual_keycode { Left => { - let (new_caret_pos, new_selection_opt) = move_caret_left( - model.caret_pos, - model.selection_opt, - modifiers.shift(), - &model.lines, - ); - model.caret_pos = new_caret_pos; - model.selection_opt = new_selection_opt; + handle_arrow(move_caret_left, &modifiers, ed_model) } Up => { - let (new_caret_pos, new_selection_opt) = move_caret_up( - model.caret_pos, - model.selection_opt, - modifiers.shift(), - &model.lines, - ); - model.caret_pos = new_caret_pos; - model.selection_opt = new_selection_opt; + handle_arrow(move_caret_up, &modifiers, ed_model) } Right => { - let (new_caret_pos, new_selection_opt) = move_caret_right( - model.caret_pos, - model.selection_opt, - modifiers.shift(), - &model.lines, - ); - model.caret_pos = new_caret_pos; - model.selection_opt = new_selection_opt; + handle_arrow(move_caret_right, &modifiers, ed_model) } Down => { - let (new_caret_pos, new_selection_opt) = move_caret_down( - model.caret_pos, - model.selection_opt, - modifiers.shift(), - &model.lines, - ); - model.caret_pos = new_caret_pos; - model.selection_opt = new_selection_opt; + handle_arrow(move_caret_down, &modifiers, ed_model) } Copy => { todo!("copy"); @@ -68,6 +40,17 @@ pub fn handle_keydown( } } +fn handle_arrow(move_caret_fun: MoveCaretFun, modifiers: &ModifiersState, ed_model: &mut EdModel) { + let (new_caret_pos, new_selection_opt) = move_caret_fun( + ed_model.caret_pos, + ed_model.selection_opt, + modifiers.shift(), + &ed_model.text_buf, + ); + ed_model.caret_pos = new_caret_pos; + ed_model.selection_opt = new_selection_opt; +} + // pub fn handle_text_input( // text_state: &mut String, // elem_state: ElementState, diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 5ba72b4f76..9df30fe881 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -13,24 +13,20 @@ extern crate pest; #[macro_use] extern crate pest_derive; -use crate::error::EdError::MissingGlyphDims; use crate::error::{print_err, EdResult}; -use crate::graphics::colors::{CARET_COLOR, CODE_COLOR, TXT_COLOR}; +use crate::graphics::colors::{CODE_COLOR, TXT_COLOR}; use crate::graphics::lowlevel::buffer::create_rect_buffers; use crate::graphics::lowlevel::ortho::update_ortho_buffer; use crate::graphics::lowlevel::pipelines; -use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text::{ build_glyph_brush, example_code_glyph_rect, queue_text_draw, Text, }; use crate::graphics::style::CODE_FONT_SIZE; use crate::graphics::style::CODE_TXT_XY; -use crate::selection::create_selection_rects; -use crate::tea::ed_model::EdModel; -use crate::tea::{ed_model, update}; -use crate::tea::app_model::AppModel; +use crate::mvc::ed_model::EdModel; +use crate::mvc::{ed_model, update, ed_view}; +use crate::mvc::app_model::AppModel; use crate::vec_result::get_res; -use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use cgmath::Vector2; use ed_model::Position; @@ -50,7 +46,7 @@ pub mod graphics; mod keyboard_input; pub mod lang; mod selection; -mod tea; +mod mvc; mod util; mod vec_result; mod text_buffer; @@ -59,7 +55,7 @@ mod text_buffer; /// 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.len() > 0 { + let first_path_opt = if !filepaths.is_empty(){ match get_res(0, filepaths) { Ok(path_ref_ref) => Some(*path_ref_ref), Err(e) => { @@ -120,7 +116,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let local_spawner = local_pool.spawner(); // Prepare swap chain - let render_format = wgpu::TextureFormat::Bgra8UnormSrgb; + let render_format = wgpu::TextureFormat::Bgra8Unorm; let mut size = window.inner_size(); let swap_chain_descr = wgpu::SwapChainDescriptor { @@ -139,13 +135,13 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; let is_animating = true; - let mut ed_model_opt = + let ed_model_opt = if let Some(file_path) = file_path_opt { let ed_model_res = ed_model::init_model(file_path); match ed_model_res { - Ok(ed_model) => { + Ok(mut ed_model) => { ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush)); @@ -218,7 +214,9 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { event: event::WindowEvent::ReceivedCharacter(ch), .. } => { - update::handle_new_char(&mut app_model, &ch); + if let Err(e) = update::handle_new_char(&mut app_model, &ch) { + print_err(&e) + } } //Keyboard Input Event::WindowEvent { @@ -226,12 +224,16 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { .. } => { if let Some(virtual_keycode) = input.virtual_keycode { - keyboard_input::handle_keydown( - input.state, - virtual_keycode, - keyboard_modifiers, - &mut ed_model, - ); + if let Some(ref mut ed_model) = app_model.ed_model_opt { + if ed_model.has_focus { + keyboard_input::handle_keydown( + input.state, + virtual_keycode, + keyboard_modifiers, + ed_model, + ); + } + } } } //Modifiers Changed @@ -254,17 +256,20 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { .expect("Failed to acquire next SwapChainFrame") .output; - queue_all_text( - &size, - &ed_model.lines, - ed_model.caret_pos, - CODE_TXT_XY.into(), - &mut glyph_brush, - ); + if let Some(ed_model) = &app_model.ed_model_opt { + //TODO don't pass invisible lines + //TODO show text if no file was opened + queue_all_text( + &size, + &ed_model.text_buf.all_lines(), + ed_model.caret_pos, + CODE_TXT_XY.into(), + &mut glyph_brush, + ); + } match draw_all_rects( - &ed_model, - &ed_model.glyph_dim_rect_opt, + &app_model.ed_model_opt, &arena, &mut encoder, &frame.view, @@ -310,41 +315,30 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { } fn draw_all_rects( - ed_model: &EdModel, - glyph_dim_rect_opt: &Option, + ed_model_opt: &Option, arena: &Bump, encoder: &mut CommandEncoder, texture_view: &TextureView, gpu_device: &wgpu::Device, rect_resources: &RectResources, ) -> EdResult<()> { - let mut all_rects: BumpVec = BumpVec::new_in(arena); + if let Some(ed_model) = ed_model_opt { + let all_rects = ed_view::create_ed_rects(ed_model, arena)?; - let glyph_rect = if let Some(glyph_dim_rect) = glyph_dim_rect_opt { - glyph_dim_rect + let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects); + + let mut render_pass = begin_render_pass(encoder, texture_view); + + render_pass.set_pipeline(&rect_resources.pipeline); + render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); + render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); + render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..)); + render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1); } else { - return Err(MissingGlyphDims {}); - }; - - if let Some(selection) = ed_model.selection_opt { - let mut selection_rects = - create_selection_rects(selection, &ed_model.lines, glyph_rect, &arena)?; - - all_rects.append(&mut selection_rects); + // need to begin render pass to clear screen + begin_render_pass(encoder, texture_view); } - all_rects.push(make_caret_rect(ed_model.caret_pos, glyph_rect)?); - - let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects); - - let mut render_pass = begin_render_pass(encoder, texture_view); - - render_pass.set_pipeline(&rect_resources.pipeline); - render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); - render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); - render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..)); - render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1); - Ok(()) } @@ -358,9 +352,9 @@ fn begin_render_pass<'a>( resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, + r: 0.1, + g: 0.2, + b: 0.3, a: 1.0, }), store: true, @@ -370,45 +364,21 @@ fn begin_render_pass<'a>( }) } -fn make_caret_rect(caret_pos: Position, glyph_dim_rect: &Rect) -> EdResult { - let caret_y = - glyph_dim_rect.top_left_coords.y + (caret_pos.line as f32) * glyph_dim_rect.height; - - let caret_x = - glyph_dim_rect.top_left_coords.x + glyph_dim_rect.width * (caret_pos.column as f32); - - Ok(Rect { - top_left_coords: (caret_x, caret_y).into(), - height: glyph_dim_rect.height, - width: 2.0, - color: CARET_COLOR, - }) -} - // returns bounding boxes for every glyph fn queue_all_text( size: &PhysicalSize, - lines: &[String], + editor_lines: &str, caret_pos: Position, code_coords: Vector2, glyph_brush: &mut GlyphBrush<()>, ) { let area_bounds = (size.width as f32, size.height as f32).into(); - let main_label = Text { - position: (30.0, 30.0).into(), - area_bounds, - color: TXT_COLOR.into(), - text: String::from("Enter some text:"), - size: CODE_FONT_SIZE, - ..Default::default() - }; - let code_text = Text { position: code_coords, area_bounds, color: CODE_COLOR.into(), - text: lines.join(""), + text: editor_lines.to_owned(), size: CODE_FONT_SIZE, ..Default::default() }; @@ -422,8 +392,6 @@ fn queue_all_text( ..Default::default() }; - queue_text_draw(&main_label, glyph_brush); - queue_text_draw(&caret_pos_label, glyph_brush); queue_text_draw(&code_text, glyph_brush); diff --git a/editor/src/tea/app_model.rs b/editor/src/mvc/app_model.rs similarity index 69% rename from editor/src/tea/app_model.rs rename to editor/src/mvc/app_model.rs index 69f6e26aa6..2ae860df78 100644 --- a/editor/src/tea/app_model.rs +++ b/editor/src/mvc/app_model.rs @@ -1,5 +1,5 @@ -use crate::tea::ed_model::EdModel; +use super::ed_model::EdModel; #[derive(Debug)] pub struct AppModel { diff --git a/editor/src/tea/ed_model.rs b/editor/src/mvc/ed_model.rs similarity index 96% rename from editor/src/tea/ed_model.rs rename to editor/src/mvc/ed_model.rs index 352a92b652..e40461b700 100644 --- a/editor/src/tea/ed_model.rs +++ b/editor/src/mvc/ed_model.rs @@ -11,6 +11,7 @@ pub struct EdModel { pub caret_pos: Position, pub selection_opt: Option, pub glyph_dim_rect_opt: Option, + pub has_focus: bool } pub fn init_model(file_path: &Path) -> EdResult { @@ -19,10 +20,10 @@ pub fn init_model(file_path: &Path) -> EdResult { caret_pos: Position { line: 0, column: 0 }, selection_opt: None, glyph_dim_rect_opt: None, + has_focus: true }) } -//Is model.rs the right place for these structs? #[derive(Debug, Copy, Clone)] pub struct Position { pub line: usize, diff --git a/editor/src/mvc/ed_view.rs b/editor/src/mvc/ed_view.rs new file mode 100644 index 0000000000..398fe087c8 --- /dev/null +++ b/editor/src/mvc/ed_view.rs @@ -0,0 +1,47 @@ + +use super::ed_model::{EdModel, Position}; +use crate::graphics::primitives::rect::Rect; +use crate::error::{EdResult, MissingGlyphDims}; +use crate::selection::create_selection_rects; +use crate::graphics::colors::{CARET_COLOR}; +use bumpalo::collections::Vec as BumpVec; +use bumpalo::Bump; +use snafu::{ensure}; + +//TODO add editor text here as well + +pub fn create_ed_rects<'a>(ed_model: &EdModel, arena: &'a Bump) -> EdResult> { + ensure!(ed_model.glyph_dim_rect_opt.is_some(), + MissingGlyphDims {} + ); + + let glyph_rect = ed_model.glyph_dim_rect_opt.unwrap(); + + let mut all_rects: BumpVec = BumpVec::new_in(arena); + + if let Some(selection) = ed_model.selection_opt { + let mut selection_rects = + create_selection_rects(selection, &ed_model.text_buf, &glyph_rect, &arena)?; + + all_rects.append(&mut selection_rects); + } + + all_rects.push(make_caret_rect(ed_model.caret_pos, &glyph_rect)); + + Ok(all_rects) +} + +fn make_caret_rect(caret_pos: Position, glyph_dim_rect: &Rect) -> Rect { + let caret_y = + glyph_dim_rect.top_left_coords.y + (caret_pos.line as f32) * glyph_dim_rect.height; + + let caret_x = + glyph_dim_rect.top_left_coords.x + glyph_dim_rect.width * (caret_pos.column as f32); + + Rect { + top_left_coords: (caret_x, caret_y).into(), + height: glyph_dim_rect.height, + width: 2.0, + color: CARET_COLOR, + } +} diff --git a/editor/src/tea/mod.rs b/editor/src/mvc/mod.rs similarity index 75% rename from editor/src/tea/mod.rs rename to editor/src/mvc/mod.rs index 589e859a6b..47d65c22a4 100644 --- a/editor/src/tea/mod.rs +++ b/editor/src/mvc/mod.rs @@ -1,3 +1,4 @@ pub mod ed_model; +pub mod ed_view; pub mod app_model; pub mod update; diff --git a/editor/src/tea/update.rs b/editor/src/mvc/update.rs similarity index 83% rename from editor/src/tea/update.rs rename to editor/src/mvc/update.rs index 2bf39ae8b5..8340985d66 100644 --- a/editor/src/tea/update.rs +++ b/editor/src/mvc/update.rs @@ -1,14 +1,18 @@ -use super::ed_model::EdModel; use super::ed_model::{Position, RawSelection}; +use crate::text_buffer::TextBuffer; use crate::util::is_newline; -use crate::tea::app_model::AppModel; +use super::app_model::AppModel; +use crate::error::EdResult; use std::cmp::{max, min}; +pub type MoveCaretFun = + fn(Position, Option, bool, &TextBuffer) -> (Position, Option); + pub fn move_caret_left( old_caret_pos: Position, old_selection_opt: Option, shift_pressed: bool, - lines: &[String], + text_buf: &TextBuffer, ) -> (Position, Option) { let old_line_nr = old_caret_pos.line; let old_col_nr = old_caret_pos.column; @@ -21,8 +25,8 @@ pub fn move_caret_left( } else if old_col_nr == 0 { if old_line_nr == 0 { (0, 0) - } else if let Some(curr_line) = lines.get(old_line_nr - 1) { - (old_line_nr - 1, curr_line.len() - 1) + } else if let Some(curr_line_len) = text_buf.line_len(old_line_nr - 1) { + (old_line_nr - 1, curr_line_len - 1) } else { unreachable!() } @@ -80,7 +84,7 @@ pub fn move_caret_right( old_caret_pos: Position, old_selection_opt: Option, shift_pressed: bool, - lines: &[String], + text_buf: &TextBuffer, ) -> (Position, Option) { let old_line_nr = old_caret_pos.line; let old_col_nr = old_caret_pos.column; @@ -90,7 +94,7 @@ pub fn move_caret_right( Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column), None => unreachable!(), } - } else if let Some(curr_line) = lines.get(old_line_nr) { + } else if let Some(curr_line) = text_buf.line(old_line_nr) { if let Some(last_char) = curr_line.chars().last() { if is_newline(&last_char) { if old_col_nr + 1 > curr_line.len() - 1 { @@ -160,7 +164,7 @@ pub fn move_caret_up( old_caret_pos: Position, old_selection_opt: Option, shift_pressed: bool, - lines: &[String], + text_buf: &TextBuffer, ) -> (Position, Option) { let old_line_nr = old_caret_pos.line; let old_col_nr = old_caret_pos.column; @@ -172,9 +176,9 @@ pub fn move_caret_up( } } else if old_line_nr == 0 { (old_line_nr, 0) - } else if let Some(prev_line) = lines.get(old_line_nr - 1) { - if prev_line.len() <= old_col_nr { - (old_line_nr - 1, prev_line.len() - 1) + } else if let Some(prev_line_len) = text_buf.line_len(old_line_nr - 1) { + if prev_line_len <= old_col_nr { + (old_line_nr - 1, prev_line_len - 1) } else { (old_line_nr - 1, old_col_nr) } @@ -223,7 +227,7 @@ pub fn move_caret_down( old_caret_pos: Position, old_selection_opt: Option, shift_pressed: bool, - lines: &[String], + text_buf: &TextBuffer, ) -> (Position, Option) { let old_line_nr = old_caret_pos.line; let old_col_nr = old_caret_pos.column; @@ -233,13 +237,13 @@ pub fn move_caret_down( Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column), None => unreachable!(), } - } else if old_line_nr + 1 >= lines.len() { - if let Some(curr_line) = lines.get(old_line_nr) { - (old_line_nr, curr_line.len()) + } else if old_line_nr + 1 >= text_buf.nr_of_lines() { + if let Some(curr_line_len) = text_buf.line_len(old_line_nr) { + (old_line_nr, curr_line_len) } else { unreachable!() } - } else if let Some(next_line) = lines.get(old_line_nr + 1) { + } else if let Some(next_line) = text_buf.line(old_line_nr + 1) { if next_line.len() <= old_col_nr { if let Some(last_char) = next_line.chars().last() { if is_newline(&last_char) { @@ -294,54 +298,49 @@ pub fn move_caret_down( (new_caret_pos, new_selection_opt) } -pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) { - if let Some(ed_model) = app_model.ed_model_opt { +pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResult<()> { + if let Some(ref mut ed_model) = app_model.ed_model_opt { ed_model.selection_opt = None; + let old_caret_pos = ed_model.caret_pos; match received_char { '\u{8}' | '\u{7f}' => { // On Linux, '\u{8}' is backspace, // on macOS '\u{7f}'. - if let Some(last_line) = ed_model.lines.last_mut() { - if !last_line.is_empty() { - last_line.pop(); - } else if ed_model.lines.len() > 1 { - ed_model.lines.pop(); - } + if let Some(selection) = ed_model.selection_opt { + ed_model.text_buf.del_selection(selection)?; + ed_model.caret_pos = selection.start_pos; + } else { + ed_model.text_buf.pop_char(old_caret_pos); + ed_model.caret_pos = - move_caret_left(ed_model.caret_pos, None, false, &ed_model.lines).0; + move_caret_left(old_caret_pos, None, false, &ed_model.text_buf).0; } } + ch if is_newline(ch) => { + ed_model.text_buf.insert_char(old_caret_pos, ch)?; + ed_model.caret_pos = Position { + line: old_caret_pos.line + 1, + column: 0, + }; + } '\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => { // These are private use characters; ignore them. // See http://www.unicode.org/faq/private_use.html } - ch if is_newline(ch) => { - if let Some(last_line) = ed_model.lines.last_mut() { - last_line.push(*received_char) - } - ed_model.lines.push(String::new()); + _ => { + ed_model.text_buf.insert_char(old_caret_pos, received_char)?; + ed_model.caret_pos = Position { - line: ed_model.caret_pos.line + 1, - column: 0, + line: old_caret_pos.line, + column: old_caret_pos.column + 1, }; - ed_model.selection_opt = None; - } - _ => { - let nr_lines = ed_model.lines.len(); - - if let Some(last_line) = ed_model.lines.last_mut() { - last_line.push(*received_char); - - ed_model.caret_pos = Position { - line: nr_lines - 1, - column: last_line.len(), - }; - - ed_model.selection_opt = None; - } } } + + ed_model.selection_opt = None; } + + Ok(()) } diff --git a/editor/src/selection.rs b/editor/src/selection.rs index 9909941082..7d86dd1efd 100644 --- a/editor/src/selection.rs +++ b/editor/src/selection.rs @@ -1,14 +1,14 @@ use crate::error::{EdResult, InvalidSelection}; use crate::graphics::colors; use crate::graphics::primitives::rect::Rect; -use crate::tea::ed_model::RawSelection; -use crate::vec_result::get_res; +use crate::mvc::ed_model::RawSelection; +use crate::text_buffer::TextBuffer; use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use snafu::ensure; //using the "parse don't validate" pattern -struct ValidSelection { +pub struct ValidSelection { pub selection: RawSelection, } @@ -43,7 +43,7 @@ pub fn validate_selection(selection: RawSelection) -> EdResult { pub fn create_selection_rects<'a>( raw_sel: RawSelection, - lines: &[String], + text_buf: &TextBuffer, glyph_dim_rect: &Rect, arena: &'a Bump, ) -> EdResult> { @@ -71,7 +71,7 @@ pub fn create_selection_rects<'a>( Ok(all_rects) } else { // first line - let end_col = get_res(start_pos.line, lines)?.len(); + let end_col = text_buf.line_len_res(start_pos.line)?; let width = ((end_col as f32) * glyph_dim_rect.width) - ((start_pos.column as f32) * glyph_dim_rect.width); @@ -89,7 +89,7 @@ pub fn create_selection_rects<'a>( let first_mid_line = start_pos.line + 1; for i in first_mid_line..(first_mid_line + nr_mid_lines) { - let mid_line_len = get_res(i, lines)?.len(); + let mid_line_len = text_buf.line_len_res(i)?; let width = (mid_line_len as f32) * glyph_dim_rect.width; @@ -125,12 +125,14 @@ pub fn create_selection_rects<'a>( #[cfg(test)] mod test_parse { use crate::error::{EdResult, OutOfBounds}; - use crate::tea::ed_model::{Position, RawSelection}; - use crate::tea::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up}; + use crate::mvc::ed_model::{Position, RawSelection}; + use crate::mvc::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun}; use crate::vec_result::get_res; + use crate::text_buffer::TextBuffer; use core::cmp::Ordering; use pest::Parser; use snafu::OptionExt; + use ropey::Rope; use std::collections::HashMap; use std::slice::SliceIndex; @@ -199,11 +201,39 @@ mod test_parse { ) -> EdResult<&mut >::Output> { let vec_len = vec.len(); - let elt_ref = vec.get_mut(index).context(OutOfBounds { index, vec_len })?; + let elt_ref = vec.get_mut(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: vec_len + })?; Ok(elt_ref) } + fn text_buffer_from_str(lines_str: &str) -> TextBuffer { + TextBuffer { + text_rope: Rope::from_str(lines_str), + path_str: "".to_owned(), + } + } + + pub fn all_lines_vec(text_buf: &TextBuffer) -> Vec { + let mut lines: Vec = Vec::new(); + + for line in text_buf.text_rope.lines() { + lines.push( + line + .as_str() + .expect( + "Failed to get str from RopeSlice. See https://docs.rs/ropey/1.2.0/ropey/struct.RopeSlice.html#method.as_str" + ) + .to_owned() + ); + } + + lines + } + // Retrieve selection and position from formatted string fn convert_dsl_to_selection( lines: &[String], @@ -303,9 +333,6 @@ mod test_parse { } } - pub type MoveCaretFun = - fn(Position, Option, bool, &[String]) -> (Position, Option); - // Convert nice string representations and compare results fn assert_move( pre_lines_str: &[&str], @@ -321,15 +348,20 @@ mod test_parse { let (sel_opt, caret_pos) = convert_dsl_to_selection(&pre_lines)?; - let mut clean_lines = pre_lines - .into_iter() - .map(|line| line.replace(&['[', ']', '|'][..], "")) - .collect::>(); + let clean_text_buf = + text_buffer_from_str( + &pre_lines + .into_iter() + .map(|line| line.replace(&['[', ']', '|'][..], "")) + .collect::>() + .join("") + ); let (new_caret_pos, new_sel_opt) = - move_fun(caret_pos, sel_opt, shift_pressed, &clean_lines); + move_fun(caret_pos, sel_opt, shift_pressed, &clean_text_buf); - let post_lines_res = convert_selection_to_dsl(new_sel_opt, new_caret_pos, &mut clean_lines); + let mut lines_vec = all_lines_vec(&clean_text_buf); + let post_lines_res = convert_selection_to_dsl(new_sel_opt, new_caret_pos, &mut lines_vec); match post_lines_res { Ok(post_lines) => { diff --git a/editor/src/text_buffer.rs b/editor/src/text_buffer.rs index 6b228d5e84..5bb34902e6 100644 --- a/editor/src/text_buffer.rs +++ b/editor/src/text_buffer.rs @@ -2,57 +2,121 @@ // Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license use crate::error::EdResult; +use crate::error::OutOfBounds; use crate::error::EdError::{TextBufReadFailed, FileOpenFailed}; -use crate::tea::ed_model::{Position, RawSelection}; +use crate::mvc::ed_model::{Position, RawSelection}; use crate::selection::{validate_selection}; use std::fs::File; use std::io; use std::path::Path; -use std::ops::Range; use ropey::{Rope}; +use snafu::{ensure, OptionExt}; + #[derive(Debug)] pub struct TextBuffer { - text_rope: Rope, - path_str: String, - dirty: bool // true if text has been changed, false if all actions following change have executed + pub text_rope: Rope, + pub path_str: String, } impl TextBuffer { - pub fn pop_char(&mut self, cursor_pos: Position) { - let char_indx = self.pos_to_char_indx(cursor_pos); - self.text_rope.remove(char_indx..char_indx); + pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> { + let char_indx = self.pos_to_char_indx(caret_pos); + + ensure!(char_indx > self.text_rope.len_chars(), OutOfBounds{ + index: char_indx, + collection_name: "Rope", + len: self.text_rope.len_chars() + }); + + self.text_rope.insert(char_indx, &new_char.to_string()); + + Ok(()) + } + + pub fn pop_char(&mut self, caret_pos: Position) { + let char_indx = self.pos_to_char_indx(caret_pos) - 1; + if char_indx < self.text_rope.len_chars() { + self.text_rope.remove(char_indx..char_indx); + } } pub fn del_selection(&mut self, raw_sel: RawSelection) -> EdResult<()> { - let range = self.sel_to_range(raw_sel)?; - self.text_rope.remove(range); + let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?; + + ensure!(end_char_indx < self.text_rope.len_chars(), OutOfBounds{ + index: end_char_indx, + collection_name: "Rope", + len: self.text_rope.len_chars() + }); + + self.text_rope.remove(start_char_indx..end_char_indx); + Ok(()) } + pub fn line(&self, line_nr: usize) -> Option<&str> { + if line_nr < self.text_rope.len_lines() { + self.text_rope.line(line_nr).as_str() + } else { + None + } + } + + pub fn line_len(&self, line_nr: usize) -> Option { + if line_nr < self.text_rope.len_lines() { + Some(self.text_rope.line(line_nr).len_chars()) + } else { + None + } + } + + pub fn line_len_res(&self, line_nr: usize) -> EdResult { + self.line_len(line_nr).context(OutOfBounds { + index: line_nr, + collection_name: "Rope", + len: self.text_rope.len_lines() + }) + } + + pub fn nr_of_lines(&self) -> usize { + self.text_rope.len_lines() + } + + // expensive function, don't use it if it can be done with a specialized, more efficient function + // TODO use bump allocation here + pub fn all_lines(&self) -> String { + let mut lines = String::new(); + + for line in self.text_rope.lines() { + lines.extend(line.as_str()); + } + + lines + } + fn pos_to_char_indx(&self, pos: Position) -> usize { self.text_rope.line_to_char(pos.line) + pos.column } - fn sel_to_range(&self, raw_sel: RawSelection) -> EdResult> { + fn sel_to_tup(&self, raw_sel: RawSelection) -> EdResult<(usize, usize)> { let valid_sel = validate_selection(raw_sel)?; let start_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos); let end_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos); - Ok(start_char_indx..end_char_indx) + Ok((start_char_indx, end_char_indx)) } +} - pub fn from_path(&self, path: &Path) -> EdResult { - // TODO benchmark different file reading methods, see #886 - let text_rope = rope_from_path(path)?; - let path_str = path_to_string(path); - - Ok(TextBuffer { - text_rope, - path_str, - dirty: false - }) - } +pub fn from_path(path: &Path) -> EdResult { + // TODO benchmark different file reading methods, see #886 + let text_rope = rope_from_path(path)?; + let path_str = path_to_string(path); + + Ok(TextBuffer { + text_rope, + path_str, + }) } fn path_to_string(path: &Path) -> String { @@ -65,7 +129,7 @@ fn path_to_string(path: &Path) -> String { fn rope_from_path(path: &Path) -> EdResult { match File::open(path) { Ok(file) => { - let mut buf_reader = &mut io::BufReader::new(file); + let buf_reader = &mut io::BufReader::new(file); match Rope::from_reader(buf_reader) { Ok(rope) => Ok(rope), diff --git a/editor/src/vec_result.rs b/editor/src/vec_result.rs index 202af28a96..dddb2c0aaf 100644 --- a/editor/src/vec_result.rs +++ b/editor/src/vec_result.rs @@ -5,10 +5,11 @@ use std::slice::SliceIndex; // replace vec methods that return Option with ones that return Result and proper Error -pub fn get_res(index: usize, vec: &[T]) -> EdResult<&>::Output> { - let elt_ref = vec.get(index).context(OutOfBounds { +pub fn get_res(index: usize, slice: &[T]) -> EdResult<&>::Output> { + let elt_ref = slice.get(index).context(OutOfBounds { index, - vec_len: vec.len(), + collection_name: "Slice", + len: slice.len(), })?; Ok(elt_ref) From d6dc6dbf8b4521349edb9673791bdc28d255f19a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 13 Jan 2021 16:14:11 +0100 Subject: [PATCH 03/12] handle_new_char tests, bug fixes for #869 --- editor/src/lib.rs | 6 +- editor/src/mvc/update.rs | 286 ++++++++++++++++++++++++++++++++++++-- editor/src/selection.rs | 24 ++-- editor/src/text_buffer.rs | 13 +- 4 files changed, 295 insertions(+), 34 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 9df30fe881..ea4d15fdb0 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -79,7 +79,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let event_loop = winit::event_loop::EventLoop::new(); let window = winit::window::WindowBuilder::new() - .with_inner_size(PhysicalSize::new(1000.0, 800.0)) + .with_inner_size(PhysicalSize::new(1200.0, 1000.0)) .build(&event_loop) .unwrap(); @@ -384,11 +384,11 @@ fn queue_all_text( }; let caret_pos_label = Text { - position: (30.0, (size.height as f32) - 45.0).into(), + position: ((size.width as f32) - 150.0, (size.height as f32) - 40.0).into(), area_bounds, color: TXT_COLOR.into(), text: format!("Ln {}, Col {}", caret_pos.line, caret_pos.column), - size: 30.0, + size: 25.0, ..Default::default() }; diff --git a/editor/src/mvc/update.rs b/editor/src/mvc/update.rs index 8340985d66..4187cfe4db 100644 --- a/editor/src/mvc/update.rs +++ b/editor/src/mvc/update.rs @@ -2,6 +2,7 @@ use super::ed_model::{Position, RawSelection}; use crate::text_buffer::TextBuffer; use crate::util::is_newline; use super::app_model::AppModel; +use super::ed_model::EdModel; use crate::error::EdResult; use std::cmp::{max, min}; @@ -298,9 +299,15 @@ pub fn move_caret_down( (new_caret_pos, new_selection_opt) } +fn del_selection(selection: RawSelection, ed_model: &mut EdModel) -> EdResult<()> { + ed_model.text_buf.del_selection(selection)?; + ed_model.caret_pos = selection.start_pos; + + Ok(()) +} + pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResult<()> { if let Some(ref mut ed_model) = app_model.ed_model_opt { - ed_model.selection_opt = None; let old_caret_pos = ed_model.caret_pos; match received_char { @@ -308,34 +315,46 @@ pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResu // On Linux, '\u{8}' is backspace, // on macOS '\u{7f}'. if let Some(selection) = ed_model.selection_opt { - ed_model.text_buf.del_selection(selection)?; - ed_model.caret_pos = selection.start_pos; + del_selection(selection, ed_model)?; } else { - ed_model.text_buf.pop_char(old_caret_pos); - ed_model.caret_pos = move_caret_left(old_caret_pos, None, false, &ed_model.text_buf).0; + + ed_model.text_buf.pop_char(old_caret_pos); } } ch if is_newline(ch) => { - ed_model.text_buf.insert_char(old_caret_pos, ch)?; - ed_model.caret_pos = Position { - line: old_caret_pos.line + 1, - column: 0, - }; + if let Some(selection) = ed_model.selection_opt { + del_selection(selection, ed_model)?; + ed_model.text_buf.insert_char(ed_model.caret_pos, &'\n')?; + } else { + ed_model.text_buf.insert_char(old_caret_pos, &'\n')?; + + ed_model.caret_pos = Position { + line: old_caret_pos.line + 1, + column: 0, + }; + } } '\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => { // These are private use characters; ignore them. // See http://www.unicode.org/faq/private_use.html } _ => { - ed_model.text_buf.insert_char(old_caret_pos, received_char)?; + if let Some(selection) = ed_model.selection_opt { + del_selection(selection, ed_model)?; + ed_model.text_buf.insert_char(ed_model.caret_pos, received_char)?; - ed_model.caret_pos = Position { - line: old_caret_pos.line, - column: old_caret_pos.column + 1, - }; + ed_model.caret_pos = + move_caret_right(ed_model.caret_pos, None, false, &ed_model.text_buf).0; + } else { + ed_model.text_buf.insert_char(old_caret_pos, received_char)?; + ed_model.caret_pos = Position { + line: old_caret_pos.line, + column: old_caret_pos.column + 1, + }; + } } } @@ -344,3 +363,240 @@ pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResu Ok(()) } + + +#[cfg(test)] +mod test_update { + use crate::selection::test_selection::{convert_dsl_to_selection, convert_selection_to_dsl, text_buffer_from_dsl_str, all_lines_vec}; + use crate::mvc::ed_model::{Position, EdModel, RawSelection}; + use crate::mvc::app_model::AppModel; + use crate::mvc::update::{handle_new_char}; + use crate::text_buffer::TextBuffer; + + fn gen_caret_text_buf(lines: &[&str]) -> Result<(Position, Option, TextBuffer), String> { + let lines_string_slice: Vec = lines.iter().map(|l| l.to_string()).collect(); + let (selection_opt, caret_pos) = convert_dsl_to_selection(&lines_string_slice)?; + let text_buf = text_buffer_from_dsl_str(&lines_string_slice); + + Ok((caret_pos, selection_opt, text_buf)) + } + + fn mock_app_model(text_buf: TextBuffer, caret_pos: Position, selection_opt: Option) -> AppModel { + AppModel { + ed_model_opt: Some( + EdModel{ + text_buf, + caret_pos, + selection_opt, + glyph_dim_rect_opt: None, + has_focus: true + } + ) + } + } + + fn assert_insert( + pre_lines_str: &[&str], + expected_post_lines_str: &[&str], + new_char: char, + ) -> Result<(), String> { + let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?; + + let mut app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt); + + if let Err(e) = handle_new_char(&mut app_model, &new_char) { + return Err(e.to_string()); + } + + if let Some(ed_model) = app_model.ed_model_opt { + let mut actual_lines = all_lines_vec(&ed_model.text_buf); + let dsl_slice = convert_selection_to_dsl(ed_model.selection_opt, ed_model.caret_pos, &mut actual_lines).unwrap(); + assert_eq!(dsl_slice, expected_post_lines_str); + } else { + panic!("Mock AppModel did not have an EdModel."); + } + + Ok(()) + } + + #[test] + fn insert_new_char_simple() -> Result<(), String> { + assert_insert( + &["|"], &["a|"], 'a' + )?; + 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' + )?; + + Ok(()) + } + + #[test] + 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", " | "], ' ' + )?; + + + Ok(()) + } + + #[test] + fn simple_backspace() -> Result<(), String> { + assert_insert( + &["|"], &["|"], '\u{8}' + )?; + 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}' + )?; + + Ok(()) + } + + #[test] + fn selection_backspace() -> Result<(), String> { + assert_insert( + &["[a]|"], &["|"], '\u{8}' + )?; + 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\n", "d[ef\n", "ghi]|\n", "jkl"], &["abc\n", "d|\n", "jkl"], '\u{8}' + )?; + assert_insert( + &["abc\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "|\n", "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]|"], &["|"], '\u{8}' + )?; + + Ok(()) + } + + #[test] + fn insert_with_selection() -> Result<(), String> { + assert_insert( + &["[a]|"], &["z|"], 'z' + )?; + 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\n", "d[ef\n", "ghi]|\n", "jkl"], &["abc\n", "dz|\n", "jkl"], 'z' + )?; + assert_insert( + &["abc\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "z|\n", "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' + )?; + + Ok(()) + } +} diff --git a/editor/src/selection.rs b/editor/src/selection.rs index 7d86dd1efd..1ff8f4c653 100644 --- a/editor/src/selection.rs +++ b/editor/src/selection.rs @@ -123,7 +123,7 @@ pub fn create_selection_rects<'a>( } #[cfg(test)] -mod test_parse { +pub mod test_selection { use crate::error::{EdResult, OutOfBounds}; use crate::mvc::ed_model::{Position, RawSelection}; use crate::mvc::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun}; @@ -141,7 +141,7 @@ mod test_parse { pub struct LineParser; // show selection and caret position as symbols in lines for easy testing - fn convert_selection_to_dsl( + pub fn convert_selection_to_dsl( raw_sel_opt: Option, caret_pos: Position, lines: &mut [String], @@ -217,6 +217,16 @@ mod test_parse { } } + pub fn text_buffer_from_dsl_str(lines: &[String]) -> TextBuffer { + text_buffer_from_str( + &lines + .iter() + .map(|line| line.replace(&['[', ']', '|'][..], "")) + .collect::>() + .join("") + ) + } + pub fn all_lines_vec(text_buf: &TextBuffer) -> Vec { let mut lines: Vec = Vec::new(); @@ -235,7 +245,7 @@ mod test_parse { } // Retrieve selection and position from formatted string - fn convert_dsl_to_selection( + pub fn convert_dsl_to_selection( lines: &[String], ) -> Result<(Option, Position), String> { let lines_str: String = lines.join(""); @@ -349,13 +359,7 @@ mod test_parse { let (sel_opt, caret_pos) = convert_dsl_to_selection(&pre_lines)?; let clean_text_buf = - text_buffer_from_str( - &pre_lines - .into_iter() - .map(|line| line.replace(&['[', ']', '|'][..], "")) - .collect::>() - .join("") - ); + text_buffer_from_dsl_str(&pre_lines); let (new_caret_pos, new_sel_opt) = move_fun(caret_pos, sel_opt, shift_pressed, &clean_text_buf); diff --git a/editor/src/text_buffer.rs b/editor/src/text_buffer.rs index 5bb34902e6..bc7c52cc14 100644 --- a/editor/src/text_buffer.rs +++ b/editor/src/text_buffer.rs @@ -23,7 +23,7 @@ impl TextBuffer { pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> { let char_indx = self.pos_to_char_indx(caret_pos); - ensure!(char_indx > self.text_rope.len_chars(), OutOfBounds{ + ensure!(char_indx <= self.text_rope.len_chars(), OutOfBounds{ index: char_indx, collection_name: "Rope", len: self.text_rope.len_chars() @@ -35,16 +35,17 @@ impl TextBuffer { } pub fn pop_char(&mut self, caret_pos: Position) { - let char_indx = self.pos_to_char_indx(caret_pos) - 1; - if char_indx < self.text_rope.len_chars() { - self.text_rope.remove(char_indx..char_indx); + let char_indx = self.pos_to_char_indx(caret_pos); + + if (char_indx > 0) && char_indx <= self.text_rope.len_chars() { + self.text_rope.remove((char_indx - 1)..char_indx); } } pub fn del_selection(&mut self, raw_sel: RawSelection) -> EdResult<()> { let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?; - ensure!(end_char_indx < self.text_rope.len_chars(), OutOfBounds{ + ensure!(end_char_indx <= self.text_rope.len_chars(), OutOfBounds{ index: end_char_indx, collection_name: "Rope", len: self.text_rope.len_chars() @@ -102,7 +103,7 @@ impl TextBuffer { fn sel_to_tup(&self, raw_sel: RawSelection) -> EdResult<(usize, usize)> { let valid_sel = validate_selection(raw_sel)?; let start_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos); - let end_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos); + let end_char_indx = self.pos_to_char_indx(valid_sel.selection.end_pos); Ok((start_char_indx, end_char_indx)) } From 76e2edff97015a6edd798c8e7508684218bb9bbc Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 13 Jan 2021 16:48:45 +0100 Subject: [PATCH 04/12] show instructions if no file was opened, fmt --- editor/README.md | 2 +- editor/src/error.rs | 20 +- editor/src/graphics/lowlevel/buffer.rs | 2 +- editor/src/keyboard_input.rs | 20 +- editor/src/lang/mod.rs | 2 +- editor/src/lib.rs | 76 ++++--- editor/src/mvc/app_model.rs | 5 +- editor/src/mvc/ed_model.rs | 8 +- editor/src/mvc/ed_view.rs | 11 +- editor/src/mvc/mod.rs | 2 +- editor/src/mvc/update.rs | 275 ++++++++++--------------- editor/src/resources/mod.rs | 1 + editor/src/resources/strings.rs | 2 +- editor/src/selection.rs | 17 +- editor/src/text_buffer.rs | 68 +++--- 15 files changed, 229 insertions(+), 282 deletions(-) create mode 100644 editor/src/resources/mod.rs diff --git a/editor/README.md b/editor/README.md index e92385db90..7009c372f8 100644 --- a/editor/README.md +++ b/editor/README.md @@ -3,7 +3,7 @@ Run the following from the roc folder: ``` -cargo run edit +cargo run edit examples/hello-world/Hello.roc ``` ## Troubleshooting diff --git a/editor/src/error.rs b/editor/src/error.rs index 2e27b88378..42f428f6fc 100644 --- a/editor/src/error.rs +++ b/editor/src/error.rs @@ -27,19 +27,15 @@ pub enum EdError { backtrace: Backtrace, }, #[snafu(display("MissingGlyphDims: glyph_dim_rect_opt was None for model. It needs to be set using the example_code_glyph_rect function."))] - MissingGlyphDims { - backtrace: Backtrace, - }, - #[snafu(display("FileOpenFailed: failed to open file with path {} with the following error: {}.", path_str, err_msg))] - FileOpenFailed { - path_str: String, - err_msg: String, - }, + MissingGlyphDims { backtrace: Backtrace }, + #[snafu(display( + "FileOpenFailed: failed to open file with path {} with the following error: {}.", + path_str, + err_msg + ))] + FileOpenFailed { path_str: String, err_msg: 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 - } + TextBufReadFailed { path_str: String, err_msg: String }, } pub type EdResult = std::result::Result; diff --git a/editor/src/graphics/lowlevel/buffer.rs b/editor/src/graphics/lowlevel/buffer.rs index 447a8d5a58..a3e8fc0a42 100644 --- a/editor/src/graphics/lowlevel/buffer.rs +++ b/editor/src/graphics/lowlevel/buffer.rs @@ -1,8 +1,8 @@ // Adapted from https://github.com/sotrh/learn-wgpu // by Benjamin Hansen, licensed under the MIT license use super::vertex::Vertex; -use crate::graphics::primitives::rect::Rect; use crate::graphics::colors::to_slice; +use crate::graphics::primitives::rect::Rect; use bumpalo::collections::Vec as BumpVec; use wgpu::util::{BufferInitDescriptor, DeviceExt}; diff --git a/editor/src/keyboard_input.rs b/editor/src/keyboard_input.rs index 7c8c378eb0..40fd9e4a4c 100644 --- a/editor/src/keyboard_input.rs +++ b/editor/src/keyboard_input.rs @@ -1,5 +1,7 @@ use crate::mvc::ed_model::EdModel; -use crate::mvc::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun}; +use crate::mvc::update::{ + move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun, +}; use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; pub fn handle_keydown( @@ -15,18 +17,10 @@ pub fn handle_keydown( } match virtual_keycode { - Left => { - handle_arrow(move_caret_left, &modifiers, ed_model) - } - Up => { - handle_arrow(move_caret_up, &modifiers, ed_model) - } - Right => { - handle_arrow(move_caret_right, &modifiers, ed_model) - } - Down => { - handle_arrow(move_caret_down, &modifiers, ed_model) - } + Left => handle_arrow(move_caret_left, &modifiers, ed_model), + Up => handle_arrow(move_caret_up, &modifiers, ed_model), + Right => handle_arrow(move_caret_right, &modifiers, ed_model), + Down => handle_arrow(move_caret_down, &modifiers, ed_model), Copy => { todo!("copy"); } diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs index 00a088b07f..a4025ace23 100644 --- a/editor/src/lang/mod.rs +++ b/editor/src/lang/mod.rs @@ -1,9 +1,9 @@ pub mod ast; mod def; mod expr; -pub mod roc_file; mod module; mod pattern; mod pool; +pub mod roc_file; mod scope; mod types; diff --git a/editor/src/lib.rs b/editor/src/lib.rs index ea4d15fdb0..6bd2304555 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -23,9 +23,10 @@ use crate::graphics::primitives::text::{ }; use crate::graphics::style::CODE_FONT_SIZE; use crate::graphics::style::CODE_TXT_XY; -use crate::mvc::ed_model::EdModel; -use crate::mvc::{ed_model, update, ed_view}; use crate::mvc::app_model::AppModel; +use crate::mvc::ed_model::EdModel; +use crate::mvc::{ed_model, ed_view, update}; +use crate::resources::strings::NOTHING_OPENED; use crate::vec_result::get_res; use bumpalo::Bump; use cgmath::Vector2; @@ -45,17 +46,18 @@ pub mod error; pub mod graphics; mod keyboard_input; pub mod lang; -mod selection; mod mvc; +mod resources; +mod selection; +mod text_buffer; mod util; mod vec_result; -mod text_buffer; /// 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(){ + let first_path_opt = if !filepaths.is_empty() { match get_res(0, filepaths) { Ok(path_ref_ref) => Some(*path_ref_ref), Err(e) => { @@ -135,31 +137,26 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; let is_animating = true; - let ed_model_opt = - if let Some(file_path) = file_path_opt { - let ed_model_res = - ed_model::init_model(file_path); + let ed_model_opt = if let Some(file_path) = file_path_opt { + let ed_model_res = ed_model::init_model(file_path); - match ed_model_res { - Ok(mut ed_model) => { - ed_model.glyph_dim_rect_opt = - Some(example_code_glyph_rect(&mut glyph_brush)); + match ed_model_res { + Ok(mut ed_model) => { + ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush)); - Some(ed_model) - }, - Err(e) => { - print_err(&e); - None - } + Some(ed_model) } - } else { - None - }; - - let mut app_model = AppModel { - ed_model_opt + Err(e) => { + print_err(&e); + None + } + } + } else { + None }; + let mut app_model = AppModel { ed_model_opt }; + let mut keyboard_modifiers = ModifiersState::empty(); let arena = Bump::new(); @@ -258,15 +255,16 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { if let Some(ed_model) = &app_model.ed_model_opt { //TODO don't pass invisible lines - //TODO show text if no file was opened - queue_all_text( + queue_editor_text( &size, &ed_model.text_buf.all_lines(), ed_model.caret_pos, CODE_TXT_XY.into(), &mut glyph_brush, ); - } + } else { + queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush); + } match draw_all_rects( &app_model.ed_model_opt, @@ -365,7 +363,7 @@ fn begin_render_pass<'a>( } // returns bounding boxes for every glyph -fn queue_all_text( +fn queue_editor_text( size: &PhysicalSize, editor_lines: &str, caret_pos: Position, @@ -396,3 +394,23 @@ fn queue_all_text( queue_text_draw(&code_text, glyph_brush); } + +fn queue_no_file_text( + size: &PhysicalSize, + text: &str, + text_coords: Vector2, + glyph_brush: &mut GlyphBrush<()>, +) { + let area_bounds = (size.width as f32, size.height as f32).into(); + + let code_text = Text { + position: text_coords, + area_bounds, + color: CODE_COLOR.into(), + text: text.to_owned(), + size: CODE_FONT_SIZE, + ..Default::default() + }; + + queue_text_draw(&code_text, glyph_brush); +} diff --git a/editor/src/mvc/app_model.rs b/editor/src/mvc/app_model.rs index 2ae860df78..b8f7fcc55d 100644 --- a/editor/src/mvc/app_model.rs +++ b/editor/src/mvc/app_model.rs @@ -1,7 +1,6 @@ - use super::ed_model::EdModel; #[derive(Debug)] pub struct AppModel { - pub ed_model_opt: Option -} \ No newline at end of file + pub ed_model_opt: Option, +} diff --git a/editor/src/mvc/ed_model.rs b/editor/src/mvc/ed_model.rs index e40461b700..fc2cad09dd 100644 --- a/editor/src/mvc/ed_model.rs +++ b/editor/src/mvc/ed_model.rs @@ -1,9 +1,9 @@ +use crate::error::EdResult; use crate::graphics::primitives::rect::Rect; use crate::text_buffer; use crate::text_buffer::TextBuffer; -use crate::error::EdResult; -use std::path::Path; use std::cmp::Ordering; +use std::path::Path; #[derive(Debug)] pub struct EdModel { @@ -11,7 +11,7 @@ pub struct EdModel { pub caret_pos: Position, pub selection_opt: Option, pub glyph_dim_rect_opt: Option, - pub has_focus: bool + pub has_focus: bool, } pub fn init_model(file_path: &Path) -> EdResult { @@ -20,7 +20,7 @@ pub fn init_model(file_path: &Path) -> EdResult { caret_pos: Position { line: 0, column: 0 }, selection_opt: None, glyph_dim_rect_opt: None, - has_focus: true + has_focus: true, }) } diff --git a/editor/src/mvc/ed_view.rs b/editor/src/mvc/ed_view.rs index 398fe087c8..b62c040940 100644 --- a/editor/src/mvc/ed_view.rs +++ b/editor/src/mvc/ed_view.rs @@ -1,19 +1,16 @@ - use super::ed_model::{EdModel, Position}; -use crate::graphics::primitives::rect::Rect; use crate::error::{EdResult, MissingGlyphDims}; +use crate::graphics::colors::CARET_COLOR; +use crate::graphics::primitives::rect::Rect; use crate::selection::create_selection_rects; -use crate::graphics::colors::{CARET_COLOR}; use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; -use snafu::{ensure}; +use snafu::ensure; //TODO add editor text here as well pub fn create_ed_rects<'a>(ed_model: &EdModel, arena: &'a Bump) -> EdResult> { - ensure!(ed_model.glyph_dim_rect_opt.is_some(), - MissingGlyphDims {} - ); + ensure!(ed_model.glyph_dim_rect_opt.is_some(), MissingGlyphDims {}); let glyph_rect = ed_model.glyph_dim_rect_opt.unwrap(); diff --git a/editor/src/mvc/mod.rs b/editor/src/mvc/mod.rs index 47d65c22a4..e401205005 100644 --- a/editor/src/mvc/mod.rs +++ b/editor/src/mvc/mod.rs @@ -1,4 +1,4 @@ +pub mod app_model; pub mod ed_model; pub mod ed_view; -pub mod app_model; pub mod update; diff --git a/editor/src/mvc/update.rs b/editor/src/mvc/update.rs index 4187cfe4db..cb20ffc144 100644 --- a/editor/src/mvc/update.rs +++ b/editor/src/mvc/update.rs @@ -1,9 +1,9 @@ -use super::ed_model::{Position, RawSelection}; -use crate::text_buffer::TextBuffer; -use crate::util::is_newline; use super::app_model::AppModel; use super::ed_model::EdModel; +use super::ed_model::{Position, RawSelection}; use crate::error::EdResult; +use crate::text_buffer::TextBuffer; +use crate::util::is_newline; use std::cmp::{max, min}; pub type MoveCaretFun = @@ -299,7 +299,7 @@ pub fn move_caret_down( (new_caret_pos, new_selection_opt) } -fn del_selection(selection: RawSelection, ed_model: &mut EdModel) -> EdResult<()> { +fn del_selection(selection: RawSelection, ed_model: &mut EdModel) -> EdResult<()> { ed_model.text_buf.del_selection(selection)?; ed_model.caret_pos = selection.start_pos; @@ -343,12 +343,16 @@ pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResu _ => { if let Some(selection) = ed_model.selection_opt { del_selection(selection, ed_model)?; - ed_model.text_buf.insert_char(ed_model.caret_pos, received_char)?; + ed_model + .text_buf + .insert_char(ed_model.caret_pos, received_char)?; ed_model.caret_pos = move_caret_right(ed_model.caret_pos, None, false, &ed_model.text_buf).0; } else { - ed_model.text_buf.insert_char(old_caret_pos, received_char)?; + ed_model + .text_buf + .insert_char(old_caret_pos, received_char)?; ed_model.caret_pos = Position { line: old_caret_pos.line, @@ -364,16 +368,19 @@ pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResu Ok(()) } - #[cfg(test)] mod test_update { - use crate::selection::test_selection::{convert_dsl_to_selection, convert_selection_to_dsl, text_buffer_from_dsl_str, all_lines_vec}; - use crate::mvc::ed_model::{Position, EdModel, RawSelection}; use crate::mvc::app_model::AppModel; - use crate::mvc::update::{handle_new_char}; + use crate::mvc::ed_model::{EdModel, Position, RawSelection}; + use crate::mvc::update::handle_new_char; + use crate::selection::test_selection::{ + all_lines_vec, convert_dsl_to_selection, convert_selection_to_dsl, text_buffer_from_dsl_str, + }; use crate::text_buffer::TextBuffer; - fn gen_caret_text_buf(lines: &[&str]) -> Result<(Position, Option, TextBuffer), String> { + fn gen_caret_text_buf( + lines: &[&str], + ) -> Result<(Position, Option, TextBuffer), String> { let lines_string_slice: Vec = lines.iter().map(|l| l.to_string()).collect(); let (selection_opt, caret_pos) = convert_dsl_to_selection(&lines_string_slice)?; let text_buf = text_buffer_from_dsl_str(&lines_string_slice); @@ -381,17 +388,19 @@ mod test_update { Ok((caret_pos, selection_opt, text_buf)) } - fn mock_app_model(text_buf: TextBuffer, caret_pos: Position, selection_opt: Option) -> AppModel { + fn mock_app_model( + text_buf: TextBuffer, + caret_pos: Position, + selection_opt: Option, + ) -> AppModel { AppModel { - ed_model_opt: Some( - EdModel{ - text_buf, - caret_pos, - selection_opt, - glyph_dim_rect_opt: None, - has_focus: true - } - ) + ed_model_opt: Some(EdModel { + text_buf, + caret_pos, + selection_opt, + glyph_dim_rect_opt: None, + has_focus: true, + }), } } @@ -401,7 +410,7 @@ mod test_update { new_char: char, ) -> Result<(), String> { let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?; - + let mut app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt); if let Err(e) = handle_new_char(&mut app_model, &new_char) { @@ -410,7 +419,12 @@ mod test_update { if let Some(ed_model) = app_model.ed_model_opt { let mut actual_lines = all_lines_vec(&ed_model.text_buf); - let dsl_slice = convert_selection_to_dsl(ed_model.selection_opt, ed_model.caret_pos, &mut actual_lines).unwrap(); + let dsl_slice = convert_selection_to_dsl( + ed_model.selection_opt, + ed_model.caret_pos, + &mut actual_lines, + ) + .unwrap(); assert_eq!(dsl_slice, expected_post_lines_str); } else { panic!("Mock AppModel did not have an EdModel."); @@ -421,182 +435,109 @@ mod test_update { #[test] fn insert_new_char_simple() -> Result<(), String> { - assert_insert( - &["|"], &["a|"], 'a' - )?; - 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' - )?; - - Ok(()) + assert_insert(&["|"], &["a|"], 'a')?; + 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')?; + + Ok(()) } #[test] 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(&["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", " | "], ' ')?; - - Ok(()) + Ok(()) } #[test] fn simple_backspace() -> Result<(), String> { - assert_insert( - &["|"], &["|"], '\u{8}' - )?; - 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}' - )?; - - Ok(()) + assert_insert(&["|"], &["|"], '\u{8}')?; + 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}')?; + + Ok(()) } #[test] fn selection_backspace() -> Result<(), String> { + assert_insert(&["[a]|"], &["|"], '\u{8}')?; + 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( - &["[a]|"], &["|"], '\u{8}' + &["abc\n", "d[ef\n", "ghi]|\n", "jkl"], + &["abc\n", "d|\n", "jkl"], + '\u{8}', )?; assert_insert( - &["a[a]|"], &["a|"], '\u{8}' + &["abc\n", "[def\n", "ghi]|\n", "jkl"], + &["abc\n", "|\n", "jkl"], + '\u{8}', )?; assert_insert( - &["[aa]|"], &["|"], '\u{8}' + &["abc\n", "\n", "[def\n", "ghi]|\n", "jkl"], + &["abc\n", "\n", "|\n", "jkl"], + '\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\n", "d[ef\n", "ghi]|\n", "jkl"], &["abc\n", "d|\n", "jkl"], '\u{8}' - )?; - assert_insert( - &["abc\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "|\n", "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]|"], &["|"], '\u{8}' + &["[abc\n", "\n", "def\n", "ghi\n", "jkl]|"], + &["|"], + '\u{8}', )?; - Ok(()) + Ok(()) } #[test] fn insert_with_selection() -> Result<(), String> { + assert_insert(&["[a]|"], &["z|"], 'z')?; + 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( - &["[a]|"], &["z|"], 'z' + &["abc\n", "d[ef\n", "ghi]|\n", "jkl"], + &["abc\n", "dz|\n", "jkl"], + 'z', )?; assert_insert( - &["a[a]|"], &["az|"], 'z' + &["abc\n", "[def\n", "ghi]|\n", "jkl"], + &["abc\n", "z|\n", "jkl"], + '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\n", "d[ef\n", "ghi]|\n", "jkl"], &["abc\n", "dz|\n", "jkl"], 'z' - )?; - assert_insert( - &["abc\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "z|\n", "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' + &["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')?; - Ok(()) + Ok(()) } } diff --git a/editor/src/resources/mod.rs b/editor/src/resources/mod.rs new file mode 100644 index 0000000000..e8dfd788ff --- /dev/null +++ b/editor/src/resources/mod.rs @@ -0,0 +1 @@ +pub mod strings; diff --git a/editor/src/resources/strings.rs b/editor/src/resources/strings.rs index 56866557ca..720fb4c89b 100644 --- a/editor/src/resources/strings.rs +++ b/editor/src/resources/strings.rs @@ -1 +1 @@ -pub const NOTHING_OPENED: &str = "Execute `cargo run edit ` to open a file"; \ No newline at end of file +pub const NOTHING_OPENED: &str = "Execute `cargo run edit ` to open a file."; diff --git a/editor/src/selection.rs b/editor/src/selection.rs index 1ff8f4c653..b15eab2691 100644 --- a/editor/src/selection.rs +++ b/editor/src/selection.rs @@ -126,13 +126,15 @@ pub fn create_selection_rects<'a>( pub mod test_selection { use crate::error::{EdResult, OutOfBounds}; use crate::mvc::ed_model::{Position, RawSelection}; - use crate::mvc::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun}; - use crate::vec_result::get_res; + use crate::mvc::update::{ + move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun, + }; use crate::text_buffer::TextBuffer; + use crate::vec_result::get_res; use core::cmp::Ordering; use pest::Parser; - use snafu::OptionExt; use ropey::Rope; + use snafu::OptionExt; use std::collections::HashMap; use std::slice::SliceIndex; @@ -201,10 +203,10 @@ pub mod test_selection { ) -> EdResult<&mut >::Output> { let vec_len = vec.len(); - let elt_ref = vec.get_mut(index).context(OutOfBounds { + let elt_ref = vec.get_mut(index).context(OutOfBounds { index, collection_name: "Slice", - len: vec_len + len: vec_len, })?; Ok(elt_ref) @@ -223,7 +225,7 @@ pub mod test_selection { .iter() .map(|line| line.replace(&['[', ']', '|'][..], "")) .collect::>() - .join("") + .join(""), ) } @@ -358,8 +360,7 @@ pub mod test_selection { let (sel_opt, caret_pos) = convert_dsl_to_selection(&pre_lines)?; - let clean_text_buf = - text_buffer_from_dsl_str(&pre_lines); + let clean_text_buf = text_buffer_from_dsl_str(&pre_lines); let (new_caret_pos, new_sel_opt) = move_fun(caret_pos, sel_opt, shift_pressed, &clean_text_buf); diff --git a/editor/src/text_buffer.rs b/editor/src/text_buffer.rs index bc7c52cc14..97f7baf838 100644 --- a/editor/src/text_buffer.rs +++ b/editor/src/text_buffer.rs @@ -1,17 +1,15 @@ - // Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license +use crate::error::EdError::{FileOpenFailed, TextBufReadFailed}; use crate::error::EdResult; use crate::error::OutOfBounds; -use crate::error::EdError::{TextBufReadFailed, FileOpenFailed}; use crate::mvc::ed_model::{Position, RawSelection}; -use crate::selection::{validate_selection}; +use crate::selection::validate_selection; +use ropey::Rope; +use snafu::{ensure, OptionExt}; use std::fs::File; use std::io; use std::path::Path; -use ropey::{Rope}; -use snafu::{ensure, OptionExt}; - #[derive(Debug)] pub struct TextBuffer { @@ -20,14 +18,17 @@ pub struct TextBuffer { } impl TextBuffer { - pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> { + pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> { let char_indx = self.pos_to_char_indx(caret_pos); - ensure!(char_indx <= self.text_rope.len_chars(), OutOfBounds{ - index: char_indx, - collection_name: "Rope", - len: self.text_rope.len_chars() - }); + ensure!( + char_indx <= self.text_rope.len_chars(), + OutOfBounds { + index: char_indx, + collection_name: "Rope", + len: self.text_rope.len_chars() + } + ); self.text_rope.insert(char_indx, &new_char.to_string()); @@ -41,18 +42,21 @@ impl TextBuffer { self.text_rope.remove((char_indx - 1)..char_indx); } } - + pub fn del_selection(&mut self, raw_sel: RawSelection) -> EdResult<()> { let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?; - ensure!(end_char_indx <= self.text_rope.len_chars(), OutOfBounds{ - index: end_char_indx, - collection_name: "Rope", - len: self.text_rope.len_chars() - }); + ensure!( + end_char_indx <= self.text_rope.len_chars(), + OutOfBounds { + index: end_char_indx, + collection_name: "Rope", + len: self.text_rope.len_chars() + } + ); self.text_rope.remove(start_char_indx..end_char_indx); - + Ok(()) } @@ -76,7 +80,7 @@ impl TextBuffer { self.line_len(line_nr).context(OutOfBounds { index: line_nr, collection_name: "Rope", - len: self.text_rope.len_lines() + len: self.text_rope.len_lines(), }) } @@ -132,20 +136,16 @@ fn rope_from_path(path: &Path) -> EdResult { 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() - }) + 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() - }) - } + Err(e) => Err(FileOpenFailed { + path_str: path_to_string(path), + err_msg: e.to_string(), + }), } -} \ No newline at end of file +} From 1f90c6f47b76d48ff7957b30243d289f8b481e26 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 13 Jan 2021 17:02:41 +0100 Subject: [PATCH 05/12] Used bump allocation for TextBuffer::all_lines --- editor/src/lib.rs | 2 +- editor/src/text_buffer.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 6bd2304555..fbfffd29bb 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -257,7 +257,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { //TODO don't pass invisible lines queue_editor_text( &size, - &ed_model.text_buf.all_lines(), + &ed_model.text_buf.all_lines(&arena), ed_model.caret_pos, CODE_TXT_XY.into(), &mut glyph_brush, diff --git a/editor/src/text_buffer.rs b/editor/src/text_buffer.rs index 97f7baf838..dc4fbdea1a 100644 --- a/editor/src/text_buffer.rs +++ b/editor/src/text_buffer.rs @@ -5,6 +5,8 @@ use crate::error::EdResult; use crate::error::OutOfBounds; use crate::mvc::ed_model::{Position, RawSelection}; use crate::selection::validate_selection; +use bumpalo::collections::String as BumpString; +use bumpalo::Bump; use ropey::Rope; use snafu::{ensure, OptionExt}; use std::fs::File; @@ -90,8 +92,8 @@ impl TextBuffer { // expensive function, don't use it if it can be done with a specialized, more efficient function // TODO use bump allocation here - pub fn all_lines(&self) -> String { - let mut lines = String::new(); + pub 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()); From fdc2b6ad860c1c242fc147e45e7a8949b14b2c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Besnier?= Date: Thu, 14 Jan 2021 16:12:46 +0100 Subject: [PATCH 06/12] add bitwise xor --- cli/tests/repl_eval.rs | 11 +++++++++++ compiler/builtins/src/std.rs | 9 +++++++++ compiler/builtins/src/unique.rs | 9 +++++++++ compiler/can/src/builtins.rs | 8 +++++++- compiler/gen/src/llvm/build.rs | 3 ++- compiler/gen/tests/gen_num.rs | 8 ++++++++ compiler/module/src/low_level.rs | 1 + compiler/module/src/symbol.rs | 19 ++++++++++--------- compiler/mono/src/borrow.rs | 5 ++--- 9 files changed, 59 insertions(+), 14 deletions(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index b0c953a428..57a1dfc4d4 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -260,6 +260,17 @@ mod repl_eval { expect_success("Num.bitwiseAnd 200 0", "0 : Int *") } + #[test] + fn num_bitwise_xor() { + expect_success("Num.bitwiseXor 20 20", "0 : Int *"); + + expect_success("Num.bitwiseXor 15 14", "1 : Int *"); + + expect_success("Num.bitwiseXor 7 15", "8 : Int *"); + + expect_success("Num.bitwiseXor 200 0", "200 : Int *") + } + #[test] fn num_add_wrap() { expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : Int *"); diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 2e211d7b18..cbd3a01fc1 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -291,6 +291,15 @@ pub fn types() -> MutMap { ), ); + // bitwiseXor : Int a, Int a -> Int a + add_type( + Symbol::NUM_BITWISE_XOR, + top_level_function( + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(int_type(flex(TVAR1))), + ), + ); + // rem : Int a, Int a -> Result (Int a) [ DivByZero ]* add_type( Symbol::NUM_REM, diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 317715cf03..23fd052e13 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -294,6 +294,15 @@ pub fn types() -> MutMap { ) }); + // bitwiseAnd : Attr * Int, Attr * Int -> Attr * Int + add_type(Symbol::NUM_BITWISE_XOR, { + let_tvars! { star1, star2, star3, int }; + unique_function( + vec![int_type(star1, int), int_type(star2, int)], + int_type(star3, int), + ) + }); + // divFloat : Float, Float -> Float add_type(Symbol::NUM_DIV_FLOAT, { let_tvars! { star1, star2, star3, star4, star5}; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 007c48bee8..96b2e1c21d 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -117,7 +117,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ASIN => num_asin, NUM_MAX_INT => num_max_int, NUM_MIN_INT => num_min_int, - NUM_BITWISE_AND => num_bitwise_and + NUM_BITWISE_AND => num_bitwise_and, + NUM_BITWISE_XOR => num_bitwise_xor } } @@ -1153,6 +1154,11 @@ fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumBitwiseAnd) } +/// Num.bitwiseXor : Int, Int -> Int +fn num_bitwise_xor(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumBitwiseXor) +} + /// List.isEmpty : List * -> Bool fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 84cd50b019..f45b11121f 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -3346,7 +3346,7 @@ fn run_low_level<'a, 'ctx, 'env>( build_num_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op) } - NumBitwiseAnd => { + NumBitwiseAnd | NumBitwiseXor => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); @@ -3633,6 +3633,7 @@ fn build_int_binop<'a, 'ctx, 'env>( NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), + NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } diff --git a/compiler/gen/tests/gen_num.rs b/compiler/gen/tests/gen_num.rs index d041b17833..cdab29699e 100644 --- a/compiler/gen/tests/gen_num.rs +++ b/compiler/gen/tests/gen_num.rs @@ -757,6 +757,14 @@ mod gen_num { assert_evals_to!("Num.bitwiseAnd 200 0", 0, i64); } + #[test] + fn bitwise_xor() { + assert_evals_to!("Num.bitwiseXor 20 20", 0, i64); + assert_evals_to!("Num.bitwiseXor 15 14", 1, i64); + assert_evals_to!("Num.bitwiseXor 7 15", 8, i64); + assert_evals_to!("Num.bitwiseXor 200 0", 200, i64); + } + #[test] fn lt_i64() { assert_evals_to!("1 < 2", true, bool); diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 989c113da8..17c5dc62b0 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -59,6 +59,7 @@ pub enum LowLevel { NumAcos, NumAsin, NumBitwiseAnd, + NumBitwiseXor, Eq, NotEq, And, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index c749068f1a..f7b87b9fa1 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -823,15 +823,16 @@ define_builtins! { 79 NUM_AT_BINARY32: "@Binary32" 80 NUM_BINARY32: "Binary32" imported 81 NUM_BITWISE_AND: "bitwiseAnd" - 82 NUM_SUB_WRAP: "subWrap" - 83 NUM_SUB_CHECKED: "subChecked" - 84 NUM_MUL_WRAP: "mulWrap" - 85 NUM_MUL_CHECKED: "mulChecked" - 86 NUM_INT: "Int" imported - 87 NUM_FLOAT: "Float" imported - 88 NUM_AT_NATURAL: "@Natural" - 89 NUM_NATURAL: "Natural" imported - 90 NUM_NAT: "Nat" imported + 82 NUM_BITWISE_XOR: "bitwiseXor" + 83 NUM_SUB_WRAP: "subWrap" + 84 NUM_SUB_CHECKED: "subChecked" + 85 NUM_MUL_WRAP: "mulWrap" + 86 NUM_MUL_CHECKED: "mulChecked" + 87 NUM_INT: "Int" imported + 88 NUM_FLOAT: "Float" imported + 89 NUM_AT_NATURAL: "@Natural" + 90 NUM_NATURAL: "Natural" imported + 91 NUM_NAT: "Nat" imported } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 8fe05e3e7e..7d5312aca1 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -578,9 +578,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte - | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd => { - arena.alloc_slice_copy(&[irrelevant, irrelevant]) - } + | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd + | NumBitwiseXor => arena.alloc_slice_copy(&[irrelevant, irrelevant]), NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => { From ab6cb7ac93c811390ffda28b37a05179534b6c5a Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 14 Jan 2021 16:49:55 +0100 Subject: [PATCH 07/12] pass stdlib by reference --- cli/src/build.rs | 2 +- cli/src/repl/gen.rs | 2 +- compiler/gen/tests/helpers/eval.rs | 44 ++++---------------------- compiler/gen_dev/tests/helpers/eval.rs | 2 +- compiler/load/src/file.rs | 10 +++--- compiler/load/tests/test_load.rs | 6 ++-- compiler/mono/tests/test_mono.rs | 2 +- compiler/solve/tests/solve_expr.rs | 2 +- docs/src/main.rs | 2 +- 9 files changed, 20 insertions(+), 52 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index 41dc30b0fb..16a7bbed87 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -42,7 +42,7 @@ pub fn build_file( let loaded = roc_load::file::load_and_monomorphize( &arena, roc_file_path.clone(), - stdlib, + &stdlib, src_dir.as_path(), subs_by_module, ptr_bytes, diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 3a674e87ba..f3642d5ca7 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -42,7 +42,7 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result String { pub fn helper<'a>( arena: &'a bumpalo::Bump, src: &str, - stdlib: roc_builtins::std::StdLib, + stdlib: &'a roc_builtins::std::StdLib, leak: bool, context: &'a inkwell::context::Context, ) -> (&'static str, String, Library) { @@ -295,40 +295,6 @@ pub fn helper<'a>( (main_fn_name, delayed_errors.join("\n"), lib) } -// TODO this is almost all code duplication with assert_llvm_evals_to -// the only difference is that this calls uniq_expr instead of can_expr. -// Should extract the common logic into test helpers. -#[macro_export] -macro_rules! assert_opt_evals_to { - ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { - use bumpalo::Bump; - use inkwell::context::Context; - use roc_gen::run_jit_function; - - let arena = Bump::new(); - - let context = Context::create(); - - // don't use uniqueness types any more - // let stdlib = roc_builtins::unique::uniq_stdlib(); - let stdlib = roc_builtins::std::standard_stdlib(); - - let (main_fn_name, errors, lib) = - $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context); - - let transform = |success| { - let expected = $expected; - let given = $transform(success); - assert_eq!(&given, &expected); - }; - run_jit_function!(lib, main_fn_name, $ty, transform, errors) - }; - - ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { - assert_opt_evals_to!($src, $expected, $ty, $transform, true) - }; -} - #[macro_export] macro_rules! assert_llvm_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { @@ -337,9 +303,10 @@ macro_rules! assert_llvm_evals_to { use roc_gen::run_jit_function; let arena = Bump::new(); - let context = Context::create(); - let stdlib = roc_builtins::std::standard_stdlib(); + + // NOTE the stdlib must be in the arena; just taking a reference will segfault + let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let (main_fn_name, errors, lib) = $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context); @@ -377,7 +344,8 @@ macro_rules! assert_evals_to { assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak); } { - assert_opt_evals_to!($src, $expected, $ty, $transform, $leak); + // NOTE at the moment, the optimized tests do the same thing + // assert_opt_evals_to!($src, $expected, $ty, $transform, $leak); } }; } diff --git a/compiler/gen_dev/tests/helpers/eval.rs b/compiler/gen_dev/tests/helpers/eval.rs index b464a0c10c..80c994569e 100644 --- a/compiler/gen_dev/tests/helpers/eval.rs +++ b/compiler/gen_dev/tests/helpers/eval.rs @@ -47,7 +47,7 @@ pub fn helper<'a>( arena, filename, &module_src, - stdlib, + &stdlib, src_dir, exposed_types, 8, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 4e28a4db4a..a114ade91b 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -699,7 +699,7 @@ struct State<'a> { pub root_id: ModuleId, pub platform_id: Option, pub goal_phase: Phase, - pub stdlib: StdLib, + pub stdlib: &'a StdLib, pub exposed_types: SubsByModule, pub output_path: Option<&'a str>, pub platform_path: Option>, @@ -944,7 +944,7 @@ fn enqueue_task<'a>( pub fn load_and_typecheck( arena: &Bump, filename: PathBuf, - stdlib: StdLib, + stdlib: &StdLib, src_dir: &Path, exposed_types: SubsByModule, ptr_bytes: u32, @@ -970,7 +970,7 @@ pub fn load_and_typecheck( pub fn load_and_monomorphize<'a>( arena: &'a Bump, filename: PathBuf, - stdlib: StdLib, + stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, ptr_bytes: u32, @@ -997,7 +997,7 @@ pub fn load_and_monomorphize_from_str<'a>( arena: &'a Bump, filename: PathBuf, src: &'a str, - stdlib: StdLib, + stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, ptr_bytes: u32, @@ -1146,7 +1146,7 @@ fn load<'a>( arena: &'a Bump, //filename: PathBuf, load_start: LoadStart<'a>, - stdlib: StdLib, + stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, goal_phase: Phase, diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 966cf31c52..6e87ccdda3 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -82,7 +82,7 @@ mod test_load { roc_load::file::load_and_typecheck( arena, full_file_path, - stdlib, + &stdlib, dir.path(), exposed_types, 8, @@ -124,7 +124,7 @@ mod test_load { let loaded = roc_load::file::load_and_typecheck( &arena, filename, - roc_builtins::std::standard_stdlib(), + &roc_builtins::std::standard_stdlib(), src_dir.as_path(), subs_by_module, 8, @@ -287,7 +287,7 @@ mod test_load { let loaded = roc_load::file::load_and_typecheck( &arena, filename, - roc_builtins::std::standard_stdlib(), + &roc_builtins::std::standard_stdlib(), src_dir.as_path(), subs_by_module, 8, diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 0b14ca7e13..f7763da5ee 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -58,7 +58,7 @@ mod test_mono { arena, filename, &module_src, - stdlib, + &stdlib, src_dir, exposed_types, 8, diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 792d6948f8..07d801402a 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -59,7 +59,7 @@ mod solve_expr { let result = roc_load::file::load_and_typecheck( arena, full_file_path, - stdlib, + &stdlib, dir.path(), exposed_types, 8, diff --git a/docs/src/main.rs b/docs/src/main.rs index 1089b54686..cb27432fc4 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -129,7 +129,7 @@ fn files_to_documentations( let mut loaded = roc_load::file::load_and_typecheck( &arena, filename, - std_lib.clone(), + &std_lib, src_dir, MutMap::default(), 8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system) From 30023ac86b1ccaba45b098db05ed56be0bc2e581 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 12 Jan 2021 22:16:33 +0100 Subject: [PATCH 08/12] turn count_targets_help into a loop --- compiler/mono/src/decision_tree.rs | 49 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index f0107b78ce..d11e6994a6 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -1631,32 +1631,37 @@ fn count_targets(decision_tree: &Decider) -> MutMap { result } -fn count_targets_help(decision_tree: &Decider, targets: &mut MutMap) { +fn count_targets_help(initial: &Decider, targets: &mut MutMap) { use Decider::*; - match decision_tree { - Leaf(target) => match targets.get_mut(target) { - None => { - targets.insert(*target, 1); + + let mut stack = vec![initial]; + + while let Some(decision_tree) = stack.pop() { + match decision_tree { + Leaf(target) => match targets.get_mut(target) { + None => { + targets.insert(*target, 1); + } + Some(current) => { + *current += 1; + } + }, + + Chain { + success, failure, .. + } => { + stack.push(success); + stack.push(failure); } - Some(current) => { - *current += 1; - } - }, - Chain { - success, failure, .. - } => { - count_targets_help(success, targets); - count_targets_help(failure, targets); - } + FanOut { + tests, fallback, .. + } => { + stack.push(fallback); - FanOut { - tests, fallback, .. - } => { - count_targets_help(fallback, targets); - - for (_, decider) in tests { - count_targets_help(decider, targets); + for (_, decider) in tests { + stack.push(decider); + } } } } From e1b5076a87da156085ead49c7c06bbb92f8b9c87 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 13 Jan 2021 21:23:09 +0100 Subject: [PATCH 09/12] be a bit more efficient in generating jumps --- compiler/mono/src/decision_tree.rs | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index d11e6994a6..95b2e772f8 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -902,7 +902,10 @@ pub fn optimize_when<'a>( let decision_tree = compile(patterns); let decider = tree_to_decider(decision_tree); - let target_counts = count_targets(&decider); + + // for each target (branch body), count in how many ways it can be reached + let mut target_counts = bumpalo::vec![in env.arena; 0; indexed_branches.len()]; + count_targets(&mut target_counts, &decider); let mut choices = MutMap::default(); let mut jumps = Vec::new(); @@ -1342,7 +1345,6 @@ fn decide_to_branching<'a>( match decider { Leaf(Jump(label)) => { - // we currently inline the jumps: does fewer jumps but produces a larger artifact let (_, expr) = jumps .iter() .find(|(l, _)| l == &label) @@ -1624,28 +1626,16 @@ fn to_chain<'a>( /// If a target appears exactly once in a Decider, the corresponding expression /// can be inlined. Whether things are inlined or jumps is called a "choice". -fn count_targets(decision_tree: &Decider) -> MutMap { - let mut result = MutMap::default(); - count_targets_help(decision_tree, &mut result); - - result -} - -fn count_targets_help(initial: &Decider, targets: &mut MutMap) { +fn count_targets(targets: &mut bumpalo::collections::Vec, initial: &Decider) { use Decider::*; let mut stack = vec![initial]; while let Some(decision_tree) = stack.pop() { match decision_tree { - Leaf(target) => match targets.get_mut(target) { - None => { - targets.insert(*target, 1); - } - Some(current) => { - *current += 1; - } - }, + Leaf(target) => { + targets[*target as usize] += 1; + } Chain { success, failure, .. @@ -1669,11 +1659,11 @@ fn count_targets_help(initial: &Decider, targets: &mut MutMap) { #[allow(clippy::type_complexity)] fn create_choices<'a>( - target_counts: &MutMap, + target_counts: &bumpalo::collections::Vec<'a, u64>, target: u64, branch: Stmt<'a>, ) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) { - match target_counts.get(&target) { + match target_counts.get(target as usize) { None => unreachable!( "this should never happen: {:?} not in {:?}", target, target_counts From d4e7ba552ac742a70beed73deb0f61a9569a0822 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 13 Jan 2021 21:25:14 +0100 Subject: [PATCH 10/12] add jp id --- compiler/mono/src/decision_tree.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 95b2e772f8..c943ef6596 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -913,8 +913,9 @@ pub fn optimize_when<'a>( for (index, branch) in indexed_branches.into_iter() { let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch); - if let Some(jump) = opt_jump { - jumps.push(jump); + if let Some((index, body)) = opt_jump { + let id = JoinPointId(env.unique_symbol()); + jumps.push((index, id, body)); } choices.insert(branch_index, choice); @@ -1336,7 +1337,7 @@ fn decide_to_branching<'a>( cond_layout: Layout<'a>, ret_layout: Layout<'a>, decider: Decider<'a, Choice<'a>>, - jumps: &Vec<(u64, Stmt<'a>)>, + jumps: &Vec<(u64, JoinPointId, Stmt<'a>)>, ) -> Stmt<'a> { use Choice::*; use Decider::*; @@ -1345,9 +1346,9 @@ fn decide_to_branching<'a>( match decider { Leaf(Jump(label)) => { - let (_, expr) = jumps + let (_, _, expr) = jumps .iter() - .find(|(l, _)| l == &label) + .find(|(l, _, _)| l == &label) .expect("jump not in list of jumps"); expr.clone() } From c1b5a422732afc32227c114bf321fa0b447775bb Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 13 Jan 2021 21:49:16 +0100 Subject: [PATCH 11/12] add jumps to decision tree creation --- compiler/gen/tests/gen_primitives.rs | 45 ++++++++++++++++++++++++++++ compiler/mono/src/decision_tree.rs | 23 ++++++++++---- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index b5b0f1ec8f..479a22202a 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -1859,4 +1859,49 @@ mod gen_primitives { i64 ); } + + #[test] + fn case_or_pattern() { + // the `0` branch body should only be generated once in the future + // it is currently duplicated + assert_evals_to!( + indoc!( + r#" + x : [ Red, Green, Blue ] + x = Red + + when x is + Red | Green -> 0 + Blue -> 1 + "# + ), + 0, + i64 + ); + } + + #[test] + fn case_jump() { + // the decision tree will generate a jump to the `1` branch here + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + ConsList a : [ Cons a (ConsList a), Nil ] + + x : ConsList I64 + x = Nil + + main = + when Pair x x is + Pair Nil _ -> 1 + Pair _ Nil -> 2 + Pair (Cons a _) (Cons b _) -> a + b + 3 + "# + ), + 1, + i64 + ); + } } diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index c943ef6596..4a7ba31eb2 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -923,7 +923,7 @@ pub fn optimize_when<'a>( let choice_decider = insert_choices(&choices, decider); - decide_to_branching( + let mut stmt = decide_to_branching( env, procs, layout_cache, @@ -932,7 +932,18 @@ pub fn optimize_when<'a>( ret_layout, choice_decider, &jumps, - ) + ); + + for (_, id, body) in jumps.into_iter() { + stmt = Stmt::Join { + id, + parameters: &[], + continuation: env.arena.alloc(body), + remainder: env.arena.alloc(stmt), + }; + } + + stmt } #[derive(Debug)] @@ -1346,11 +1357,11 @@ fn decide_to_branching<'a>( match decider { Leaf(Jump(label)) => { - let (_, _, expr) = jumps - .iter() - .find(|(l, _, _)| l == &label) + let index = jumps + .binary_search_by_key(&label, |ref r| r.0) .expect("jump not in list of jumps"); - expr.clone() + + Stmt::Jump(jumps[index].1, &[]) } Leaf(Inline(expr)) => expr, Chain { From b3d0c0194d62a73eb9b0653c65650aaff04c140c Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 14 Jan 2021 16:56:15 +0100 Subject: [PATCH 12/12] use const --- compiler/builtins/src/bitcode.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 606c03b3f5..c104342bdb 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -2,14 +2,15 @@ use std::fs::File; use std::io::prelude::Read; use std::vec::Vec; +const PATH: &str = env!( + "BUILTINS_BC", + "Env var BUILTINS_BC not found. Is there a problem with the build script?" +); + pub fn get_bytes() -> Vec { // In the build script for the builtins module, we compile the builtins bitcode and set // BUILTINS_BC to the path to the compiled output. - let path: &'static str = env!( - "BUILTINS_BC", - "Env var BUILTINS_BC not found. Is there a problem with the build script?" - ); - let mut builtins_bitcode = File::open(path).expect("Unable to find builtins bitcode source"); + let mut builtins_bitcode = File::open(PATH).expect("Unable to find builtins bitcode source"); let mut buffer = Vec::new(); builtins_bitcode .read_to_end(&mut buffer)