progress integrating text rope

This commit is contained in:
Anton-4 2021-01-11 19:46:15 +01:00
parent 2b91262073
commit bdf48d478f
16 changed files with 217 additions and 124 deletions

10
Cargo.lock generated
View File

@ -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"

View File

@ -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]

View File

@ -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<T, E = EdError> = std::result::Result<T, E>;

View File

@ -1,7 +1,7 @@
pub mod ast;
mod def;
mod expr;
pub mod file;
pub mod roc_file;
mod module;
mod pattern;
mod pool;

View File

@ -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<dyn Error>> {
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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<dyn Error>> {
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 {

View File

@ -0,0 +1 @@
pub const NOTHING_OPENED: &str = "Execute `cargo run edit <filename>` to open a file";

View File

@ -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<ValidSelection> {
pub fn validate_selection(selection: RawSelection) -> EdResult<ValidSelection> {
let RawSelection { start_pos, end_pos } = selection;
ensure!(

View File

@ -0,0 +1,7 @@
use crate::tea::ed_model::EdModel;
#[derive(Debug)]
pub struct AppModel {
pub ed_model_opt: Option<EdModel>
}

View File

@ -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<String>,
pub text_buf: TextBuffer,
pub caret_pos: Position,
pub selection_opt: Option<RawSelection>,
pub glyph_dim_rect_opt: Option<Rect>,
}
pub fn init_model() -> EdModel {
EdModel {
lines: vec![String::new()],
pub fn init_model(file_path: &Path) -> EdResult<EdModel> {
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?

View File

@ -1,2 +1,3 @@
pub mod ed_model;
pub mod app_model;
pub mod update;

View File

@ -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;
}
}
}
}
}

86
editor/src/text_buffer.rs Normal file
View File

@ -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<Range<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)
}
pub fn from_path(&self, path: &Path) -> EdResult<TextBuffer> {
// 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<Rope> {
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()
})
}
}
}

View File

@ -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)

View File

@ -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(),