Merge pull request #1607 from rtfeldman/editor-let-value

file loading/saving/running, headers, top level defs
This commit is contained in:
Richard Feldman 2021-10-03 15:45:08 -05:00 committed by GitHub
commit e3a8d436cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 6193 additions and 2959 deletions

3
.gitignore vendored
View file

@ -26,6 +26,9 @@ editor/benches/resources/25000000_lines.roc
editor/benches/resources/50000_lines.roc editor/benches/resources/50000_lines.roc
editor/benches/resources/500_lines.roc editor/benches/resources/500_lines.roc
# file editor creates when no arg is passed
new-roc-project
# rust cache (sccache folder) # rust cache (sccache folder)
sccache_dir sccache_dir

20
Cargo.lock generated
View file

@ -1215,6 +1215,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]] [[package]]
name = "fuchsia-cprng" name = "fuchsia-cprng"
version = "0.1.1" version = "0.1.1"
@ -3567,6 +3573,7 @@ dependencies = [
"confy", "confy",
"copypasta", "copypasta",
"env_logger 0.8.4", "env_logger 0.8.4",
"fs_extra",
"futures", "futures",
"glyph_brush", "glyph_brush",
"im 15.0.0", "im 15.0.0",
@ -3584,9 +3591,11 @@ dependencies = [
"quickcheck 1.0.3", "quickcheck 1.0.3",
"quickcheck_macros 1.0.0", "quickcheck_macros 1.0.0",
"rand 0.8.4", "rand 0.8.4",
"roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_fmt", "roc_fmt",
"roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
@ -3598,6 +3607,8 @@ dependencies = [
"ropey", "ropey",
"serde", "serde",
"snafu", "snafu",
"tempfile",
"uuid",
"ven_graph", "ven_graph",
"wgpu", "wgpu",
"wgpu_glyph", "wgpu_glyph",
@ -4758,6 +4769,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.3",
]
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"

View file

@ -65,7 +65,7 @@ check-rustfmt:
RUN cargo fmt --all -- --check RUN cargo fmt --all -- --check
check-typos: check-typos:
RUN cargo install --version 1.0.11 typos-cli RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
RUN typos RUN typos
@ -97,7 +97,6 @@ test-all:
BUILD +test-zig BUILD +test-zig
BUILD +check-rustfmt BUILD +check-rustfmt
BUILD +check-clippy BUILD +check-clippy
BUILD +check-typos
BUILD +test-rust BUILD +test-rust
BUILD +verify-no-git-changes BUILD +verify-no-git-changes

View file

@ -34,7 +34,7 @@ fn main() -> io::Result<()> {
} }
None => { None => {
launch_editor(&[])?; launch_editor(None)?;
Ok(0) Ok(0)
} }
@ -91,16 +91,13 @@ If you're building the compiler from source you'll want to do `cargo run [FILE]`
.subcommand_matches(CMD_EDIT) .subcommand_matches(CMD_EDIT)
.unwrap() .unwrap()
.values_of_os(DIRECTORY_OR_FILES) .values_of_os(DIRECTORY_OR_FILES)
.map(|mut values| values.next())
{ {
None => { Some(Some(os_str)) => {
launch_editor(&[])?; launch_editor(Some(Path::new(os_str)))?;
} }
Some(values) => { _ => {
let paths = values launch_editor(None)?;
.map(|os_str| Path::new(os_str))
.collect::<Vec<&Path>>();
launch_editor(&paths)?;
} }
} }
@ -187,8 +184,8 @@ fn roc_files_recursive<P: AsRef<Path>>(
} }
#[cfg(feature = "editor")] #[cfg(feature = "editor")]
fn launch_editor(filepaths: &[&Path]) -> io::Result<()> { fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> {
roc_editor::launch(filepaths) roc_editor::launch(project_dir_path)
} }
#[cfg(not(feature = "editor"))] #[cfg(not(feature = "editor"))]

View file

@ -1967,7 +1967,7 @@ fn update<'a>(
); );
} }
if module_id == state.root_id && state.goal_phase == Phase::SolveTypes { if is_host_exposed && state.goal_phase == Phase::SolveTypes {
debug_assert!(work.is_empty()); debug_assert!(work.is_empty());
debug_assert!(state.dependencies.solved_all()); debug_assert!(state.dependencies.solved_all());

View file

@ -551,7 +551,53 @@ impl IdentIds {
} }
} }
/// Generates a unique, new name that's just a stringified integer // necessary when the name of a value is changed in the editor
pub fn update_key(
&mut self,
old_ident_name: &str,
new_ident_name: &str,
) -> Result<IdentId, String> {
let old_ident: Ident = old_ident_name.into();
let ident_id_ref_opt = self.by_ident.get(&old_ident);
match ident_id_ref_opt {
Some(ident_id_ref) => {
let ident_id = *ident_id_ref;
self.by_ident.remove(&old_ident);
self.by_ident.insert(new_ident_name.into(), ident_id);
let by_id = &mut self.by_id;
let key_index_opt = by_id.iter().position(|x| *x == old_ident);
if let Some(key_index) = key_index_opt {
if let Some(vec_elt) = by_id.get_mut(key_index) {
*vec_elt = new_ident_name.into();
} else {
// we get the index from by_id
unreachable!()
}
Ok(ident_id)
} else {
Err(
format!(
"Tried to find position of key {:?} in IdentIds.by_id but I could not find the key. IdentIds.by_id: {:?}",
old_ident_name,
self.by_id
)
)
}
}
None => Err(format!(
"Tried to update key in IdentIds ({:?}) but I could not find the key ({}).",
self.by_ident, old_ident_name
)),
}
}
/// Generates a unique, new name that's just a strigified integer
/// (e.g. "1" or "5"), using an internal counter. Since valid Roc variable /// (e.g. "1" or "5"), using an internal counter. Since valid Roc variable
/// names cannot begin with a number, this has no chance of colliding /// names cannot begin with a number, this has no chance of colliding
/// with actual user-defined variables. /// with actual user-defined variables.

View file

@ -32,7 +32,7 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
#[inline(always)] #[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, SyntaxError<'a>> { pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, SyntaxError<'a>> {
// force that we pare until the end of the input // force that we parse until the end of the input
let min_indent = 0; let min_indent = 0;
skip_second!( skip_second!(
specialize( specialize(

View file

@ -1,6 +1,9 @@
use crate::ast; use crate::ast;
use crate::module::module_defs;
// use crate::module::module_defs; // use crate::module::module_defs;
use crate::parser::Parser;
use crate::parser::{State, SyntaxError}; use crate::parser::{State, SyntaxError};
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::Located; use roc_region::all::Located;
@ -23,3 +26,15 @@ pub fn parse_loc_with<'a>(
Err(fail) => Err(SyntaxError::Expr(fail)), Err(fail) => Err(SyntaxError::Expr(fail)),
} }
} }
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<BumpVec<'a, Located<ast::Def<'a>>>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes());
match module_defs().parse(arena, state) {
Ok(tuple) => Ok(tuple.1),
Err(tuple) => Err(tuple.1),
}
}

View file

@ -9,6 +9,8 @@ exclude = ["src/shaders/*.spv"]
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_load = { path = "../compiler/load" }
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" } roc_can = { path = "../compiler/can" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
@ -49,6 +51,9 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [
], default-features = false } ], default-features = false }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
nonempty = "0.6.0" nonempty = "0.6.0"
tempfile = "3.2.0"
uuid = { version = "0.8", features = ["v4"] }
fs_extra = "1.2.0"
[dependencies.bytemuck] [dependencies.bytemuck]
version = "1.4" version = "1.4"

View file

@ -1,10 +1,10 @@
use crate::ui::text::lines::Lines; use crate::ui::text::lines::Lines;
use crate::ui::text::selection::Selection; use crate::ui::text::selection::Selection;
use crate::ui::ui_error::UIResult; use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult};
use crate::ui::util::slice_get; use crate::ui::util::slice_get;
use crate::ui::util::slice_get_mut; use crate::ui::util::slice_get_mut;
use bumpalo::collections::String as BumpString; use std::cmp::Ordering;
use bumpalo::Bump;
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
@ -14,31 +14,102 @@ pub struct CodeLines {
} }
impl CodeLines { impl CodeLines {
pub fn from_str(code_str: &str) -> CodeLines {
CodeLines {
lines: code_str
.split_inclusive('\n')
.map(|s| s.to_owned())
.collect(),
nr_of_chars: code_str.len(),
}
}
pub fn insert_between_line( pub fn insert_between_line(
&mut self, &mut self,
line_nr: usize, line_nr: usize,
index: usize, index: usize,
new_str: &str, new_str: &str,
) -> UIResult<()> { ) -> UIResult<()> {
let nr_of_lines = self.lines.len();
if line_nr < nr_of_lines {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?; let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.insert_str(index, new_str); line_ref.insert_str(index, new_str);
} else if line_nr >= self.lines.len() {
for _ in 0..((line_nr - nr_of_lines) + 1) {
self.push_empty_line();
}
self.insert_between_line(line_nr, index, new_str)?;
} else {
LineInsertionFailed {
line_nr,
nr_of_lines,
}
.fail()?;
}
self.nr_of_chars += new_str.len(); self.nr_of_chars += new_str.len();
Ok(()) Ok(())
} }
pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> {
if line_nr <= self.lines.len() {
self.lines.insert(line_nr, String::new());
Ok(())
} else {
OutOfBounds {
index: line_nr,
collection_name: "code_lines.lines".to_owned(),
len: self.lines.len(),
}
.fail()
}
}
pub fn push_empty_line(&mut self) {
self.lines.push(String::new())
}
pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> {
// clippy prefers this over if-else
match line_nr.cmp(&self.lines.len()) {
Ordering::Less => {
self.insert_empty_line(line_nr + 1)?;
let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr
if col_nr < line_ref.len() {
let next_line_str: String = line_ref.drain(col_nr..).collect();
let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line
*next_line_ref = next_line_str;
}
Ok(())
}
Ordering::Equal => self.insert_empty_line(line_nr + 1),
Ordering::Greater => OutOfBounds {
index: line_nr,
collection_name: "code_lines.lines".to_owned(),
len: self.lines.len(),
}
.fail(),
}
}
pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
*line_ref = String::new();
Ok(())
}
pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> {
let line_len = self.line_len(line_nr)?;
self.lines.remove(line_nr);
self.nr_of_chars -= line_len;
Ok(())
}
pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?; let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
@ -49,6 +120,18 @@ impl CodeLines {
Ok(()) Ok(())
} }
pub fn del_range_at_line(
&mut self,
line_nr: usize,
col_range: std::ops::Range<usize>,
) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.drain(col_range);
Ok(())
}
pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> { pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> {
if selection.is_on_same_line() { if selection.is_on_same_line() {
let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?; let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?;
@ -60,17 +143,36 @@ impl CodeLines {
Ok(()) Ok(())
} }
// last column of last line
pub fn end_txt_pos(&self) -> TextPos {
let last_line_nr = self.nr_of_lines() - 1;
TextPos {
line: last_line_nr,
column: self.line_len(last_line_nr).unwrap(), // safe because we just calculated last_line
}
}
}
impl Default for CodeLines {
fn default() -> Self {
CodeLines {
lines: Vec::new(),
nr_of_chars: 0,
}
}
} }
impl Lines for CodeLines { impl Lines for CodeLines {
fn get_line(&self, line_nr: usize) -> UIResult<&str> { fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> {
let line_string = slice_get(line_nr, &self.lines)?; let line_string = slice_get(line_nr, &self.lines)?;
Ok(line_string) Ok(line_string)
} }
fn line_len(&self, line_nr: usize) -> UIResult<usize> { fn line_len(&self, line_nr: usize) -> UIResult<usize> {
self.get_line(line_nr).map(|line| line.len()) self.get_line_ref(line_nr).map(|line| line.len())
} }
fn nr_of_lines(&self) -> usize { fn nr_of_lines(&self) -> usize {
@ -81,14 +183,8 @@ impl Lines for CodeLines {
self.nr_of_chars self.nr_of_chars
} }
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { fn all_lines_as_string(&self) -> String {
let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena); self.lines.join("\n")
for line in &self.lines {
lines.push_str(line);
}
lines
} }
fn is_last_line(&self, line_nr: usize) -> bool { fn is_last_line(&self, line_nr: usize) -> bool {
@ -96,7 +192,7 @@ impl Lines for CodeLines {
} }
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> { fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last()) Ok(self.get_line_ref(line_nr)?.chars().last())
} }
} }
@ -105,14 +201,16 @@ impl fmt::Display for CodeLines {
for row in &self.lines { for row in &self.lines {
let row_str = row let row_str = row
.chars() .chars()
.map(|code_char| format!("'{}'", code_char)) .map(|code_char| format!("{}", code_char))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(" ");
write!(f, "\n{}", row_str)?; let escaped_row_str = row_str.replace("\n", "\\n");
write!(f, "\n{}", escaped_row_str)?;
} }
write!(f, " (code_lines)")?; writeln!(f, " (code_lines, {:?} lines)", self.lines.len())?;
Ok(()) Ok(())
} }

View file

@ -5,6 +5,7 @@ use crate::editor::theme::EdTheme;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Config { pub struct Config {
pub code_font_size: f32, pub code_font_size: f32,
pub debug_font_size: f32,
pub ed_theme: EdTheme, pub ed_theme: EdTheme,
} }
@ -12,6 +13,7 @@ impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
code_font_size: 30.0, code_font_size: 30.0,
debug_font_size: 20.0,
ed_theme: EdTheme::default(), ed_theme: EdTheme::default(),
} }
} }

View file

@ -1,3 +1,4 @@
use crate::lang::parse::ASTNodeId;
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos};
use colored::*; use colored::*;
@ -11,6 +12,24 @@ use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))] #[snafu(visibility(pub))]
pub enum EdError { pub enum EdError {
#[snafu(display(
"ASTNodeIdWithoutDefId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(DefId)` .",
ast_node_id
))]
ASTNodeIdWithoutDefId {
ast_node_id: ASTNodeId,
backtrace: Backtrace,
},
#[snafu(display(
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .",
ast_node_id
))]
ASTNodeIdWithoutExprId {
ast_node_id: ASTNodeId,
backtrace: Backtrace,
},
#[snafu(display( #[snafu(display(
"CaretNotFound: No carets were found in the expected node with id {}", "CaretNotFound: No carets were found in the expected node with id {}",
node_id node_id
@ -43,6 +62,17 @@ pub enum EdError {
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display(
"EmptyCodeString: I need to have a code string (code_str) that contains either an app, interface or Package-Config header. The code string was empty.",
))]
EmptyCodeString { backtrace: Backtrace },
#[snafu(display("FailedToUpdateIdentIdName: {}", err_str))]
FailedToUpdateIdentIdName {
err_str: String,
backtrace: Backtrace,
},
#[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))] #[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))]
GetContentOnNestedNode { backtrace: Backtrace }, GetContentOnNestedNode { backtrace: Backtrace },
@ -99,6 +129,12 @@ pub enum EdError {
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display("NoDefMarkNodeBeforeLineNr: I could not find a MarkupNode whose root parent points to a DefId located before the given line number: {}.", line_nr))]
NoDefMarkNodeBeforeLineNr {
line_nr: usize,
backtrace: Backtrace,
},
#[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))] #[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))]
NodeWithoutAttributes { backtrace: Backtrace }, NodeWithoutAttributes { backtrace: Backtrace },
@ -131,6 +167,17 @@ pub enum EdError {
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display(
"UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.",
required_pattern2,
encountered_pattern2,
))]
UnexpectedPattern2Variant {
required_pattern2: String,
encountered_pattern2: String,
backtrace: Backtrace,
},
#[snafu(display( #[snafu(display(
"UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.", "UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.",
descriptive_vec_name descriptive_vec_name
@ -154,7 +201,10 @@ pub enum EdError {
}, },
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))] #[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))]
ParseError { syntax_err: String }, SrcParseError {
syntax_err: String,
backtrace: Backtrace,
},
#[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))] #[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))]
RecordWithoutFields { backtrace: Backtrace }, RecordWithoutFields { backtrace: Backtrace },
@ -176,14 +226,6 @@ pub fn print_err(err: &EdError) {
} }
} }
pub fn print_ui_err(err: &UIError) {
eprintln!("{}", format!("{}", err).truecolor(255, 0, 0));
if let Some(backtrace) = ErrorCompat::backtrace(err) {
eprintln!("{}", color_backtrace(backtrace));
}
}
fn color_backtrace(backtrace: &snafu::Backtrace) -> String { fn color_backtrace(backtrace: &snafu::Backtrace) -> String {
let backtrace_str = format!("{}", backtrace); let backtrace_str = format!("{}", backtrace);
let backtrace_split = backtrace_str.split('\n'); let backtrace_split = backtrace_str.split('\n');

View file

@ -1,40 +1,28 @@
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::NestedNodeWithoutChildren; use crate::editor::ed_error::NestedNodeWithoutChildren;
use crate::editor::ed_error::NodeIdNotInGridNodeMap; use crate::editor::ed_error::{NoDefMarkNodeBeforeLineNr, NodeIdNotInGridNodeMap};
use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_model::EdModel;
use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::MarkNodeId;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::util::first_last_index_of; use crate::editor::util::first_last_index_of;
use crate::editor::util::index_of; use crate::editor::util::index_of;
use crate::lang::ast::ExprId; use crate::lang::parse::ASTNodeId;
use crate::ui::text::selection::Selection; use crate::ui::text::selection::Selection;
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult};
use crate::ui::util::{slice_get, slice_get_mut}; use crate::ui::util::{slice_get, slice_get_mut};
use snafu::OptionExt; use snafu::OptionExt;
use std::cmp::Ordering;
use std::fmt; use std::fmt;
use super::markup::nodes::get_root_mark_node_id;
#[derive(Debug)] #[derive(Debug)]
pub struct GridNodeMap { pub struct GridNodeMap {
pub lines: Vec<Vec<MarkNodeId>>, pub lines: Vec<Vec<MarkNodeId>>,
} }
impl GridNodeMap { impl GridNodeMap {
pub fn new() -> GridNodeMap {
GridNodeMap {
lines: vec![vec![]],
}
}
pub fn add_to_line(&mut self, line_nr: usize, len: usize, node_id: MarkNodeId) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
let mut new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
line_ref.append(&mut new_cols_vec);
Ok(())
}
pub fn insert_between_line( pub fn insert_between_line(
&mut self, &mut self,
line_nr: usize, line_nr: usize,
@ -42,18 +30,105 @@ impl GridNodeMap {
len: usize, len: usize,
node_id: MarkNodeId, node_id: MarkNodeId,
) -> UIResult<()> { ) -> UIResult<()> {
let nr_of_lines = self.lines.len();
if line_nr < nr_of_lines {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?; let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
let new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect(); let new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
line_ref.splice(index..index, new_cols_vec); line_ref.splice(index..index, new_cols_vec);
} else if line_nr >= nr_of_lines {
for _ in 0..((line_nr - nr_of_lines) + 1) {
self.push_empty_line();
}
self.insert_between_line(line_nr, index, len, node_id)?;
} else {
LineInsertionFailed {
line_nr,
nr_of_lines,
}
.fail()?;
}
Ok(()) Ok(())
} }
pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> {
if line_nr <= self.lines.len() {
self.lines.insert(line_nr, Vec::new());
Ok(())
} else {
OutOfBounds {
index: line_nr,
collection_name: "code_lines.lines".to_owned(),
len: self.lines.len(),
}
.fail()
}
}
pub fn push_empty_line(&mut self) {
self.lines.push(vec![]);
}
pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> {
// clippy prefers this over if-else
match line_nr.cmp(&self.lines.len()) {
Ordering::Less => {
self.insert_empty_line(line_nr + 1)?;
let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr
if col_nr < line_ref.len() {
let next_line_str: Vec<MarkNodeId> = line_ref.drain(col_nr..).collect();
let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line
*next_line_ref = next_line_str;
}
Ok(())
}
Ordering::Equal => self.insert_empty_line(line_nr + 1),
Ordering::Greater => OutOfBounds {
index: line_nr,
collection_name: "grid_node_map.lines".to_owned(),
len: self.lines.len(),
}
.fail(),
}
}
pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?; let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.remove(index); *line_ref = vec![];
Ok(())
}
pub fn del_line(&mut self, line_nr: usize) {
self.lines.remove(line_nr);
}
pub fn del_at_line(&mut self, line_nr: usize, column: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.remove(column);
Ok(())
}
pub fn del_range_at_line(
&mut self,
line_nr: usize,
col_range: std::ops::Range<usize>,
) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.drain(col_range);
Ok(()) Ok(())
} }
@ -64,16 +139,12 @@ impl GridNodeMap {
line_ref.drain(selection.start_pos.column..selection.end_pos.column); line_ref.drain(selection.start_pos.column..selection.end_pos.column);
} else { } else {
// TODO support multiline unimplemented!("TODO support deleting multiline selection")
} }
Ok(()) Ok(())
} }
/*pub fn new_line(&mut self) {
self.lines.push(vec![])
}*/
pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult<MarkNodeId> { pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult<MarkNodeId> {
let line = slice_get(caret_pos.line, &self.lines)?; let line = slice_get(caret_pos.line, &self.lines)?;
let node_id = slice_get(caret_pos.column, line)?; let node_id = slice_get(caret_pos.column, line)?;
@ -133,15 +204,15 @@ impl GridNodeMap {
} }
} }
// returns start and end pos of Expr2, relevant AST node and MarkNodeId of the corresponding MarkupNode // returns start and end pos of Expr2/Def2, relevant AST node and MarkNodeId of the corresponding MarkupNode
pub fn get_expr_start_end_pos( pub fn get_block_start_end_pos(
&self, &self,
caret_pos: TextPos, caret_pos: TextPos,
ed_model: &EdModel, ed_model: &EdModel,
) -> EdResult<(TextPos, TextPos, ExprId, MarkNodeId)> { ) -> EdResult<(TextPos, TextPos, ASTNodeId, MarkNodeId)> {
let line = slice_get(caret_pos.line, &self.lines)?; let line = slice_get(caret_pos.line, &self.lines)?;
let node_id = slice_get(caret_pos.column, line)?; let node_id = slice_get(caret_pos.column, line)?;
let node = ed_model.markup_node_pool.get(*node_id); let node = ed_model.mark_node_pool.get(*node_id);
if node.is_nested() { if node.is_nested() {
let (start_pos, end_pos) = self.get_nested_start_end_pos(*node_id, ed_model)?; let (start_pos, end_pos) = self.get_nested_start_end_pos(*node_id, ed_model)?;
@ -151,10 +222,7 @@ impl GridNodeMap {
let (first_node_index, last_node_index) = first_last_index_of(*node_id, line)?; let (first_node_index, last_node_index) = first_last_index_of(*node_id, line)?;
let curr_node_id = slice_get(first_node_index, line)?; let curr_node_id = slice_get(first_node_index, line)?;
let curr_ast_node_id = ed_model let curr_ast_node_id = ed_model.mark_node_pool.get(*curr_node_id).get_ast_node_id();
.markup_node_pool
.get(*curr_node_id)
.get_ast_node_id();
let mut expr_start_index = first_node_index; let mut expr_start_index = first_node_index;
let mut expr_end_index = last_node_index; let mut expr_end_index = last_node_index;
@ -165,7 +233,7 @@ impl GridNodeMap {
for i in (0..first_node_index).rev() { for i in (0..first_node_index).rev() {
let prev_pos_node_id = slice_get(i, line)?; let prev_pos_node_id = slice_get(i, line)?;
let prev_ast_node_id = ed_model let prev_ast_node_id = ed_model
.markup_node_pool .mark_node_pool
.get(*prev_pos_node_id) .get(*prev_pos_node_id)
.get_ast_node_id(); .get_ast_node_id();
@ -187,7 +255,7 @@ impl GridNodeMap {
for i in last_node_index..line.len() { for i in last_node_index..line.len() {
let next_pos_node_id = slice_get(i, line)?; let next_pos_node_id = slice_get(i, line)?;
let next_ast_node_id = ed_model let next_ast_node_id = ed_model
.markup_node_pool .mark_node_pool
.get(*next_pos_node_id) .get(*next_pos_node_id)
.get_ast_node_id(); .get_ast_node_id();
@ -204,7 +272,7 @@ impl GridNodeMap {
} }
let correct_mark_node_id = let correct_mark_node_id =
GridNodeMap::get_top_node_with_expr_id(*curr_node_id, &ed_model.markup_node_pool); GridNodeMap::get_top_node_with_expr_id(*curr_node_id, &ed_model.mark_node_pool);
Ok(( Ok((
TextPos { TextPos {
@ -225,12 +293,12 @@ impl GridNodeMap {
// `{` is not the entire Expr2 // `{` is not the entire Expr2
fn get_top_node_with_expr_id( fn get_top_node_with_expr_id(
curr_node_id: MarkNodeId, curr_node_id: MarkNodeId,
markup_node_pool: &SlowPool, mark_node_pool: &SlowPool,
) -> MarkNodeId { ) -> MarkNodeId {
let curr_node = markup_node_pool.get(curr_node_id); let curr_node = mark_node_pool.get(curr_node_id);
if let Some(parent_id) = curr_node.get_parent_id_opt() { if let Some(parent_id) = curr_node.get_parent_id_opt() {
let parent = markup_node_pool.get(parent_id); let parent = mark_node_pool.get(parent_id);
if parent.get_ast_node_id() == curr_node.get_ast_node_id() { if parent.get_ast_node_id() == curr_node.get_ast_node_id() {
parent_id parent_id
@ -247,29 +315,108 @@ impl GridNodeMap {
nested_node_id: MarkNodeId, nested_node_id: MarkNodeId,
ed_model: &EdModel, ed_model: &EdModel,
) -> EdResult<(TextPos, TextPos)> { ) -> EdResult<(TextPos, TextPos)> {
let parent_mark_node = ed_model.markup_node_pool.get(nested_node_id); let left_most_leaf = self.get_leftmost_leaf(nested_node_id, ed_model)?;
let all_child_ids = parent_mark_node.get_children_ids(); let right_most_leaf = self.get_rightmost_leaf(nested_node_id, ed_model)?;
let first_child_id = all_child_ids
let expr_start_pos = ed_model
.grid_node_map
.get_node_position(left_most_leaf, true)?;
let expr_end_pos = ed_model
.grid_node_map
.get_node_position(right_most_leaf, false)?
.increment_col();
Ok((expr_start_pos, expr_end_pos))
}
fn get_leftmost_leaf(
&self,
nested_node_id: MarkNodeId,
ed_model: &EdModel,
) -> EdResult<MarkNodeId> {
let mut children_ids = ed_model
.mark_node_pool
.get(nested_node_id)
.get_children_ids();
let mut first_child_id = 0;
while !children_ids.is_empty() {
first_child_id = *children_ids
.first() .first()
.with_context(|| NestedNodeWithoutChildren { .with_context(|| NestedNodeWithoutChildren {
node_id: nested_node_id, node_id: nested_node_id,
})?; })?;
let last_child_id = all_child_ids
children_ids = ed_model
.mark_node_pool
.get(first_child_id)
.get_children_ids();
}
Ok(first_child_id)
}
fn get_rightmost_leaf(
&self,
nested_node_id: MarkNodeId,
ed_model: &EdModel,
) -> EdResult<MarkNodeId> {
let mut children_ids = ed_model
.mark_node_pool
.get(nested_node_id)
.get_children_ids();
let mut last_child_id = 0;
while !children_ids.is_empty() {
last_child_id = *children_ids
.last() .last()
.with_context(|| NestedNodeWithoutChildren { .with_context(|| NestedNodeWithoutChildren {
node_id: nested_node_id, node_id: nested_node_id,
})?; })?;
let expr_start_pos = ed_model children_ids = ed_model
.grid_node_map .mark_node_pool
.get_node_position(*first_child_id, true)?; .get(last_child_id)
let expr_end_pos = ed_model .get_children_ids();
.grid_node_map }
.get_node_position(*last_child_id, false)?
.increment_col();
Ok((expr_start_pos, expr_end_pos)) Ok(last_child_id)
}
// get id of root mark_node whose ast_node_id points to a DefId
pub fn get_def_mark_node_id_before_line(
&self,
line_nr: usize,
mark_node_pool: &SlowPool,
) -> EdResult<MarkNodeId> {
for curr_line_nr in (0..line_nr).rev() {
let first_col_pos = TextPos {
line: curr_line_nr,
column: 0,
};
if self.node_exists_at_pos(first_col_pos) {
let mark_node_id = self.get_id_at_row_col(first_col_pos)?;
let root_mark_node_id = get_root_mark_node_id(mark_node_id, mark_node_pool);
let ast_node_id = mark_node_pool.get(root_mark_node_id).get_ast_node_id();
if let ASTNodeId::ADefId(_) = ast_node_id {
return Ok(root_mark_node_id);
}
}
}
NoDefMarkNodeBeforeLineNr { line_nr }.fail()
}
}
impl Default for GridNodeMap {
fn default() -> Self {
GridNodeMap {
lines: vec![Vec::new()],
}
} }
} }
@ -282,10 +429,10 @@ impl fmt::Display for GridNodeMap {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
write!(f, "{}", row_str)?; writeln!(f, "{}", row_str)?;
} }
write!(f, " (grid_node_map)")?; writeln!(f, "(grid_node_map, {:?} lines)", self.lines.len())?;
Ok(()) Ok(())
} }

View file

@ -43,7 +43,7 @@ pub fn handle_keydown(
} }
} }
A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, A | S | R | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
F11 => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, F11 => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,

View file

@ -1,9 +1,8 @@
use super::keyboard_input; use super::keyboard_input;
use super::style::CODE_TXT_XY; use super::style::CODE_TXT_XY;
use crate::editor::ed_error::print_ui_err;
use crate::editor::mvc::ed_view; use crate::editor::mvc::ed_view;
use crate::editor::mvc::ed_view::RenderedWgpu; use crate::editor::mvc::ed_view::RenderedWgpu;
use crate::editor::resources::strings::NOTHING_OPENED; use crate::editor::resources::strings::{HELLO_WORLD, NOTHING_OPENED};
use crate::editor::{ use crate::editor::{
config::Config, config::Config,
ed_error::print_err, ed_error::print_err,
@ -20,17 +19,23 @@ use crate::graphics::{
}; };
use crate::lang::expr::Env; use crate::lang::expr::Env;
use crate::lang::pool::Pool; use crate::lang::pool::Pool;
use crate::ui::ui_error::UIError::FileOpenFailed; use crate::ui::text::caret_w_select::CaretPos;
use crate::ui::util::slice_get; use crate::ui::util::path_to_string;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use cgmath::Vector2; use cgmath::Vector2;
use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue};
use pipelines::RectResources; use pipelines::RectResources;
use roc_module::symbol::Interns; use roc_can::builtins::builtin_defs_map;
use roc_module::symbol::{IdentIds, ModuleIds}; use roc_collections::all::MutMap;
use roc_load;
use roc_load::file::LoadedModule;
use roc_module::symbol::IdentIds;
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use std::collections::HashSet;
use std::fs::{self, File};
use std::io::Write;
use std::{error::Error, io, path::Path}; use std::{error::Error, io, path::Path};
use wgpu::{CommandEncoder, RenderPass, TextureView}; use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush; use wgpu_glyph::GlyphBrush;
use winit::{ use winit::{
dpi::PhysicalSize, dpi::PhysicalSize,
@ -48,26 +53,13 @@ use winit::{
/// The editor is actually launched from the CLI if you pass it zero arguments, /// 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. /// or if you provide it 1 or more files or directories to open on launch.
pub fn launch(filepaths: &[&Path]) -> io::Result<()> { pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> {
//TODO support using multiple filepaths run_event_loop(project_dir_path_opt).expect("Error running event loop");
let first_path_opt = if !filepaths.is_empty() {
match slice_get(0, filepaths) {
Ok(path_ref_ref) => Some(*path_ref_ref),
Err(e) => {
eprintln!("{}", e);
None
}
}
} else {
None
};
run_event_loop(first_path_opt).expect("Error running event loop");
Ok(()) Ok(())
} }
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> { fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
env_logger::init(); env_logger::init();
// Open window and create a surface // Open window and create a surface
@ -134,51 +126,37 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let env_arena = Bump::new(); let env_arena = Bump::new();
let code_arena = Bump::new(); let code_arena = Bump::new();
let (file_path_str, code_str) = read_main_roc_file(project_dir_path_opt);
println!("Loading file {}...", file_path_str);
let file_path = Path::new(&file_path_str);
let loaded_module = load_module(file_path);
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8); let dep_idents = IdentIds::exposed_builtins(8);
let exposed_ident_ids = IdentIds::default(); let exposed_ident_ids = IdentIds::default();
let mut module_ids = ModuleIds::default(); let module_ids = loaded_module.interns.module_ids.clone();
let mod_id = module_ids.get_or_insert(&"ModId123".into());
let interns = Interns {
module_ids,
all_ident_ids: IdentIds::exposed_builtins(8),
};
let env = Env::new( let env = Env::new(
mod_id, loaded_module.module_id,
&env_arena, &env_arena,
&mut env_pool, &mut env_pool,
&mut var_store, &mut var_store,
dep_idents, dep_idents,
&interns.module_ids, &module_ids,
exposed_ident_ids, exposed_ident_ids,
); );
let mut code_str = BumpString::from_str_in("", &code_arena);
let file_path = if let Some(file_path) = file_path_opt {
match std::fs::read_to_string(file_path) {
Ok(file_as_str) => {
code_str = BumpString::from_str_in(&file_as_str, &code_arena);
file_path
}
Err(e) => {
print_ui_err(&FileOpenFailed {
path_str: file_path.to_string_lossy().to_string(),
err_msg: e.to_string(),
});
Path::new("")
}
}
} else {
Path::new("")
};
let ed_model_opt = { let ed_model_opt = {
let ed_model_res = ed_model::init_model(&code_str, file_path, env, &interns, &code_arena); let ed_model_res = ed_model::init_model(
&code_str,
file_path,
env,
loaded_module,
&code_arena,
CaretPos::End,
);
match ed_model_res { match ed_model_res {
Ok(mut ed_model) => { Ok(mut ed_model) => {
@ -251,7 +229,8 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
event: event::WindowEvent::ReceivedCharacter(ch), event: event::WindowEvent::ReceivedCharacter(ch),
.. ..
} => { } => {
let input_outcome_res = app_update::handle_new_char(&ch, &mut app_model); let input_outcome_res =
app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers);
if let Err(e) = input_outcome_res { if let Err(e) = input_outcome_res {
print_err(&e) print_err(&e)
} else if let Ok(InputOutcome::Ignored) = input_outcome_res { } else if let Ok(InputOutcome::Ignored) = input_outcome_res {
@ -314,23 +293,55 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
} }
if let Some(ref rendered_wgpu) = rendered_wgpu_opt { if let Some(ref rendered_wgpu) = rendered_wgpu_opt {
for text_section in &rendered_wgpu.text_sections { draw_rects(
&rendered_wgpu.rects_behind,
&mut encoder,
&frame.view,
&gpu_device,
&rect_resources,
wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)),
);
for text_section in &rendered_wgpu.text_sections_behind {
let borrowed_text = text_section.to_borrowed(); let borrowed_text = text_section.to_borrowed();
glyph_brush.queue(borrowed_text); glyph_brush.queue(borrowed_text);
} }
draw_all_rects( // draw first layer of text
&rendered_wgpu.rects, glyph_brush
.draw_queued(
&gpu_device,
&mut staging_belt,
&mut encoder,
&frame.view,
size.width,
size.height,
)
.expect("Failed to draw first layer of text.");
// draw rects on top of first text layer
draw_rects(
&rendered_wgpu.rects_front,
&mut encoder, &mut encoder,
&frame.view, &frame.view,
&gpu_device, &gpu_device,
&rect_resources, &rect_resources,
&ed_theme, wgpu::LoadOp::Load,
) );
for text_section in &rendered_wgpu.text_sections_front {
let borrowed_text = text_section.to_borrowed();
glyph_brush.queue(borrowed_text);
}
} }
} else { } else {
begin_render_pass(&mut encoder, &frame.view, &ed_theme); begin_render_pass(
&mut encoder,
&frame.view,
wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)),
);
queue_no_file_text( queue_no_file_text(
&size, &size,
@ -341,7 +352,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
); );
} }
// draw all text // draw text
glyph_brush glyph_brush
.draw_queued( .draw_queued(
&gpu_device, &gpu_device,
@ -351,7 +362,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
size.width, size.width,
size.height, size.height,
) )
.expect("Draw queued"); .expect("Failed to draw queued text.");
staging_belt.finish(); staging_belt.finish();
cmd_queue.submit(Some(encoder.finish())); cmd_queue.submit(Some(encoder.finish()));
@ -374,17 +385,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
fn draw_all_rects( fn draw_rects(
all_rects: &[Rect], all_rects: &[Rect],
encoder: &mut CommandEncoder, encoder: &mut CommandEncoder,
texture_view: &TextureView, texture_view: &TextureView,
gpu_device: &wgpu::Device, gpu_device: &wgpu::Device,
rect_resources: &RectResources, rect_resources: &RectResources,
ed_theme: &EdTheme, load_op: LoadOp<wgpu::Color>,
) { ) {
let rect_buffers = create_rect_buffers(gpu_device, encoder, all_rects); let rect_buffers = create_rect_buffers(gpu_device, encoder, all_rects);
let mut render_pass = begin_render_pass(encoder, texture_view, ed_theme); let mut render_pass = begin_render_pass(encoder, texture_view, load_op);
render_pass.set_pipeline(&rect_resources.pipeline); render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
@ -399,16 +410,14 @@ fn draw_all_rects(
fn begin_render_pass<'a>( fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder, encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView, texture_view: &'a TextureView,
ed_theme: &EdTheme, load_op: LoadOp<wgpu::Color>,
) -> RenderPass<'a> { ) -> RenderPass<'a> {
let bg_color = to_wgpu_color(ed_theme.background);
encoder.begin_render_pass(&wgpu::RenderPassDescriptor { encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachment { color_attachments: &[wgpu::RenderPassColorAttachment {
view: texture_view, view: texture_view,
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(bg_color), load: load_op,
store: true, store: true,
}, },
}], }],
@ -417,6 +426,166 @@ fn begin_render_pass<'a>(
}) })
} }
type PathStr = String;
fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathStr, String) {
if let Some(project_dir_path) = project_dir_path_opt {
let mut ls_config = HashSet::new();
ls_config.insert(DirEntryAttr::FullName);
let dir_items = ls(project_dir_path, &ls_config)
.unwrap_or_else(|err| panic!("Failed to list items in project directory: {:?}", err))
.items;
let file_names = dir_items
.iter()
.map(|info_hash_map| {
info_hash_map
.values()
.map(|dir_entry_value| {
if let DirEntryValue::String(file_name) = dir_entry_value {
Some(file_name)
} else {
None
}
})
.flatten() // remove None
.collect::<Vec<&String>>()
})
.flatten();
let roc_file_names: Vec<&String> = file_names
.filter(|file_name| file_name.contains(".roc"))
.collect();
let project_dir_path_str = path_to_string(project_dir_path);
if let Some(&roc_file_name) = roc_file_names.first() {
let full_roc_file_path_str = vec![
project_dir_path_str.clone(),
"/".to_owned(),
roc_file_name.clone(),
]
.join("");
let file_as_str = std::fs::read_to_string(&Path::new(&full_roc_file_path_str))
.unwrap_or_else(|err| panic!("In the provided project {:?}, I found the roc file {}, but I failed to read it: {}", &project_dir_path_str, &full_roc_file_path_str, err));
(full_roc_file_path_str, file_as_str)
} else {
init_new_roc_project(&project_dir_path_str)
}
} else {
init_new_roc_project("new-roc-project")
}
}
// returns path and content of app file
fn init_new_roc_project(project_dir_path_str: &str) -> (PathStr, String) {
let orig_platform_path = Path::new("./examples/hello-world/platform");
let project_dir_path = Path::new(project_dir_path_str);
let roc_file_path_str = vec![project_dir_path_str, "/UntitledApp.roc"].join("");
let roc_file_path = Path::new("./new-roc-project/UntitledApp.roc");
let project_platform_path_str = vec![project_dir_path_str, "/platform"].join("");
let project_platform_path = Path::new(&project_platform_path_str);
if !project_dir_path.exists() {
fs::create_dir(project_dir_path).expect("Failed to create dir for roc project.");
}
copy_roc_platform_if_not_exists(orig_platform_path, project_platform_path, project_dir_path);
let code_str = create_roc_file_if_not_exists(project_dir_path, roc_file_path);
(roc_file_path_str, code_str)
}
// returns contents of file
fn create_roc_file_if_not_exists(project_dir_path: &Path, roc_file_path: &Path) -> String {
if !roc_file_path.exists() {
let mut roc_file = File::create(roc_file_path).unwrap_or_else(|err| {
panic!("No roc file path was passed to the editor, so I wanted to create a new roc project with the file {:?}, but it failed: {}", roc_file_path, err)
});
write!(roc_file, "{}", HELLO_WORLD).unwrap_or_else(|err| {
panic!(
r#"No roc file path was passed to the editor, so I created a new roc project with the file {:?}
I wanted to write roc hello world to that file, but it failed: {:?}"#,
roc_file_path,
err
)
});
HELLO_WORLD.to_string()
} else {
std::fs::read_to_string(roc_file_path).unwrap_or_else(|err| {
panic!(
"I detected an existing {:?} inside {:?}, but I failed to read from it: {}",
roc_file_path, project_dir_path, err
)
})
}
}
fn copy_roc_platform_if_not_exists(
orig_platform_path: &Path,
project_platform_path: &Path,
project_dir_path: &Path,
) {
if !orig_platform_path.exists() && !project_platform_path.exists() {
panic!(
r#"No roc file path was passed to the editor, I wanted to create a new roc project but I could not find the platform at {:?}.
Are you at the root of the roc repository?"#,
orig_platform_path
);
} else if !project_platform_path.exists() {
copy(orig_platform_path, project_dir_path, &CopyOptions::new()).unwrap_or_else(|err|{
panic!(r#"No roc file path was passed to the editor, so I wanted to create a new roc project and roc projects require a platform,
I tried to copy the platform at {:?} to {:?} but it failed: {}"#,
orig_platform_path,
project_platform_path,
err
)
});
}
}
pub fn load_module(src_file: &Path) -> LoadedModule {
let subs_by_module = MutMap::default();
let arena = Bump::new();
let loaded = roc_load::file::load_and_typecheck(
&arena,
src_file.to_path_buf(),
arena.alloc(roc_builtins::std::standard_stdlib()),
src_file.parent().unwrap_or_else(|| {
panic!(
"src_file {:?} did not have a parent directory but I need to have one.",
src_file
)
}),
subs_by_module,
8,
builtin_defs_map,
);
match loaded {
Ok(x) => x,
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
panic!(
"Failed to load module from src_file {:?}. Report: {:?}",
src_file, report
);
}
Err(e) => panic!(
"Failed to load module from src_file {:?}: {:?}",
src_file, e
),
}
}
fn queue_no_file_text( fn queue_no_file_text(
size: &PhysicalSize<u32>, size: &PhysicalSize<u32>,
text: &str, text: &str,

View file

@ -0,0 +1,107 @@
use crate::{
editor::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle},
lang::{ast::ExprId, parse::ASTNodeId},
};
use super::{attribute::Attributes, nodes, nodes::MarkupNode};
pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::EQUALS.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::COMMA.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Blank {
ast_node_id,
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_blank_mn_w_nls(
ast_node_id: ASTNodeId,
parent_id_opt: Option<MarkNodeId>,
nr_of_newlines: usize,
) -> MarkupNode {
MarkupNode::Blank {
ast_node_id,
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: nr_of_newlines,
}
}
pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::COLON.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::LEFT_ACCOLADE.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::RIGHT_ACCOLADE.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::LEFT_SQUARE_BR.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::RIGHT_SQUARE_BR.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt,
newlines_at_end: 0,
}
}

View file

@ -1,2 +1,3 @@
pub mod attribute; pub mod attribute;
pub mod common_nodes;
pub mod nodes; pub mod nodes;

View file

@ -1,42 +1,62 @@
use super::attribute::Attributes; use super::attribute::Attributes;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::ExpectedTextNode; use crate::editor::ed_error::ExpectedTextNode;
use crate::editor::ed_error::GetContentOnNestedNode;
use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired};
use crate::editor::markup::common_nodes::new_blank_mn;
use crate::editor::markup::common_nodes::new_blank_mn_w_nls;
use crate::editor::markup::common_nodes::new_colon_mn;
use crate::editor::markup::common_nodes::new_comma_mn;
use crate::editor::markup::common_nodes::new_equals_mn;
use crate::editor::markup::common_nodes::new_left_accolade_mn;
use crate::editor::markup::common_nodes::new_left_square_mn;
use crate::editor::markup::common_nodes::new_right_accolade_mn;
use crate::editor::markup::common_nodes::new_right_square_mn;
use crate::editor::mvc::tld_value_update::tld_mark_node;
use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::MarkNodeId;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::util::index_of; use crate::editor::util::index_of;
use crate::lang::ast::{Expr2, ExprId, RecordField}; use crate::lang::ast::Def2;
use crate::lang::{expr::Env, pool::PoolStr}; use crate::lang::ast::DefId;
use crate::lang::ast::ExprId;
use crate::lang::ast::RecordField;
use crate::lang::ast::ValueDef;
use crate::lang::parse::ASTNodeId;
use crate::lang::parse::{AppHeader, AST};
use crate::lang::pattern::get_identifier_string;
use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr};
use crate::ui::util::slice_get; use crate::ui::util::slice_get;
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::symbol::Interns;
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
pub enum MarkupNode { pub enum MarkupNode {
Nested { Nested {
ast_node_id: ExprId, ast_node_id: ASTNodeId,
children_ids: Vec<MarkNodeId>, children_ids: Vec<MarkNodeId>,
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
newlines_at_end: usize,
}, },
Text { Text {
content: String, content: String,
ast_node_id: ExprId, ast_node_id: ASTNodeId,
syn_high_style: HighlightStyle, syn_high_style: HighlightStyle,
attributes: Attributes, attributes: Attributes,
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
newlines_at_end: usize,
}, },
Blank { Blank {
ast_node_id: ExprId, ast_node_id: ASTNodeId,
attributes: Attributes, attributes: Attributes,
syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
newlines_at_end: usize,
}, },
} }
impl MarkupNode { impl MarkupNode {
pub fn get_ast_node_id(&self) -> ExprId { pub fn get_ast_node_id(&self) -> ASTNodeId {
match self { match self {
MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, MarkupNode::Nested { ast_node_id, .. } => *ast_node_id,
MarkupNode::Text { ast_node_id, .. } => *ast_node_id, MarkupNode::Text { ast_node_id, .. } => *ast_node_id,
@ -60,9 +80,9 @@ impl MarkupNode {
} }
} }
pub fn get_sibling_ids(&self, markup_node_pool: &SlowPool) -> Vec<MarkNodeId> { pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec<MarkNodeId> {
if let Some(parent_id) = self.get_parent_id_opt() { if let Some(parent_id) = self.get_parent_id_opt() {
let parent_node = markup_node_pool.get(parent_id); let parent_node = mark_node_pool.get(parent_id);
parent_node.get_children_ids() parent_node.get_children_ids()
} else { } else {
@ -74,7 +94,7 @@ impl MarkupNode {
pub fn get_child_indices( pub fn get_child_indices(
&self, &self,
child_id: MarkNodeId, child_id: MarkNodeId,
markup_node_pool: &SlowPool, mark_node_pool: &SlowPool,
) -> EdResult<(usize, usize)> { ) -> EdResult<(usize, usize)> {
match self { match self {
MarkupNode::Nested { children_ids, .. } => { MarkupNode::Nested { children_ids, .. } => {
@ -87,7 +107,7 @@ impl MarkupNode {
mark_child_index_opt = Some(indx); mark_child_index_opt = Some(indx);
} }
let child_mark_node = markup_node_pool.get(mark_child_id); let child_mark_node = mark_node_pool.get(mark_child_id);
// a node that points to the same ast_node as the parent is a ',', '[', ']' // a node that points to the same ast_node as the parent is a ',', '[', ']'
// those are not "real" ast children // those are not "real" ast children
if child_mark_node.get_ast_node_id() != self_ast_id { if child_mark_node.get_ast_node_id() != self_ast_id {
@ -147,15 +167,25 @@ impl MarkupNode {
} }
} }
// can't be &str, this creates borrowing issues pub fn get_content(&self) -> String {
pub fn get_content(&self) -> EdResult<String> {
match self { match self {
MarkupNode::Nested { .. } => GetContentOnNestedNode {}.fail(), MarkupNode::Nested { .. } => "".to_owned(),
MarkupNode::Text { content, .. } => Ok(content.clone()), MarkupNode::Text { content, .. } => content.clone(),
MarkupNode::Blank { .. } => Ok(BLANK_PLACEHOLDER.to_owned()), MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(),
} }
} }
// gets content and adds newline from newline_at_end
pub fn get_full_content(&self) -> String {
let mut full_content = self.get_content();
for _ in 0..self.get_newlines_at_end() {
full_content.push('\n')
}
full_content
}
pub fn get_content_mut(&mut self) -> EdResult<&mut String> { pub fn get_content_mut(&mut self) -> EdResult<&mut String> {
match self { match self {
MarkupNode::Nested { .. } => ExpectedTextNode { MarkupNode::Nested { .. } => ExpectedTextNode {
@ -172,11 +202,10 @@ impl MarkupNode {
} }
} }
pub fn is_all_alphanumeric(&self) -> EdResult<bool> { pub fn is_all_alphanumeric(&self) -> bool {
Ok(self self.get_content()
.get_content()?
.chars() .chars()
.all(|chr| chr.is_ascii_alphanumeric())) .all(|chr| chr.is_ascii_alphanumeric())
} }
pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> { pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> {
@ -209,6 +238,34 @@ impl MarkupNode {
pub fn is_nested(&self) -> bool { pub fn is_nested(&self) -> bool {
matches!(self, MarkupNode::Nested { .. }) matches!(self, MarkupNode::Nested { .. })
} }
pub fn get_newlines_at_end(&self) -> usize {
match self {
MarkupNode::Nested {
newlines_at_end, ..
} => *newlines_at_end,
MarkupNode::Text {
newlines_at_end, ..
} => *newlines_at_end,
MarkupNode::Blank {
newlines_at_end, ..
} => *newlines_at_end,
}
}
pub fn add_newline_at_end(&mut self) {
match self {
MarkupNode::Nested {
newlines_at_end, ..
} => *newlines_at_end += 1,
MarkupNode::Text {
newlines_at_end, ..
} => *newlines_at_end += 1,
MarkupNode::Blank {
newlines_at_end, ..
} => *newlines_at_end += 1,
}
}
} }
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
@ -223,12 +280,13 @@ pub const RIGHT_SQUARE_BR: &str = " ]";
pub const COLON: &str = ": "; pub const COLON: &str = ": ";
pub const COMMA: &str = ", "; pub const COMMA: &str = ", ";
pub const STRING_QUOTES: &str = "\"\""; pub const STRING_QUOTES: &str = "\"\"";
pub const EQUALS: &str = " = ";
fn new_markup_node( fn new_markup_node(
text: String, text: String,
node_id: ExprId, node_id: ASTNodeId,
highlight_style: HighlightStyle, highlight_style: HighlightStyle,
markup_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
) -> MarkNodeId { ) -> MarkNodeId {
let node = MarkupNode::Text { let node = MarkupNode::Text {
content: text, content: text,
@ -236,9 +294,51 @@ fn new_markup_node(
syn_high_style: highlight_style, syn_high_style: highlight_style,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0,
}; };
markup_node_pool.add(node) mark_node_pool.add(node)
}
pub fn def2_to_markup<'a, 'b>(
arena: &'a Bump,
env: &mut Env<'b>,
def2: &Def2,
def2_node_id: DefId,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> EdResult<MarkNodeId> {
let ast_node_id = ASTNodeId::ADefId(def2_node_id);
let mark_node_id = match def2 {
Def2::ValueDef {
identifier_id,
expr_id,
} => {
let expr_mn_id = expr2_to_markup(
arena,
env,
env.pool.get(*expr_id),
*expr_id,
mark_node_pool,
interns,
)?;
let tld_mn = tld_mark_node(
*identifier_id,
expr_mn_id,
ast_node_id,
mark_node_pool,
env,
interns,
)?;
mark_node_pool.add(tld_mn)
}
Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)),
};
Ok(mark_node_id)
} }
// make Markup Nodes: generate String representation, assign Highlighting Style // make Markup Nodes: generate String representation, assign Highlighting Style
@ -247,55 +347,44 @@ pub fn expr2_to_markup<'a, 'b>(
env: &mut Env<'b>, env: &mut Env<'b>,
expr2: &Expr2, expr2: &Expr2,
expr2_node_id: ExprId, expr2_node_id: ExprId,
markup_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
) -> MarkNodeId { interns: &Interns,
match expr2 { ) -> EdResult<MarkNodeId> {
let ast_node_id = ASTNodeId::AExprId(expr2_node_id);
let mark_node_id = match expr2 {
Expr2::SmallInt { text, .. } Expr2::SmallInt { text, .. }
| Expr2::I128 { text, .. } | Expr2::I128 { text, .. }
| Expr2::U128 { text, .. } | Expr2::U128 { text, .. }
| Expr2::Float { text, .. } => { | Expr2::Float { text, .. } => {
let num_str = get_string(env, text); let num_str = get_string(env, text);
new_markup_node( new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool)
num_str,
expr2_node_id,
HighlightStyle::Number,
markup_node_pool,
)
} }
Expr2::Str(text) => new_markup_node( Expr2::Str(text) => new_markup_node(
"\"".to_owned() + text.as_str(env.pool) + "\"", "\"".to_owned() + text.as_str(env.pool) + "\"",
expr2_node_id, ast_node_id,
HighlightStyle::String, HighlightStyle::String,
markup_node_pool, mark_node_pool,
), ),
Expr2::GlobalTag { name, .. } => new_markup_node( Expr2::GlobalTag { name, .. } => new_markup_node(
get_string(env, name), get_string(env, name),
expr2_node_id, ast_node_id,
HighlightStyle::Type, HighlightStyle::Type,
markup_node_pool, mark_node_pool,
), ),
Expr2::Call { expr: expr_id, .. } => { Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id); let expr = env.pool.get(*expr_id);
expr2_to_markup(arena, env, expr, *expr_id, markup_node_pool) expr2_to_markup(arena, env, expr, *expr_id, mark_node_pool, interns)?
} }
Expr2::Var(symbol) => { Expr2::Var(symbol) => {
//TODO make bump_format with arena //TODO make bump_format with arena
let text = format!("{:?}", symbol); let text = format!("{:?}", symbol);
new_markup_node( new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool)
text,
expr2_node_id,
HighlightStyle::Variable,
markup_node_pool,
)
} }
Expr2::List { elems, .. } => { Expr2::List { elems, .. } => {
let mut children_ids = vec![new_markup_node( let mut children_ids =
LEFT_SQUARE_BR.to_string(), vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))];
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
)];
let indexed_node_ids: Vec<(usize, ExprId)> = let indexed_node_ids: Vec<(usize, ExprId)> =
elems.iter(env.pool).copied().enumerate().collect(); elems.iter(env.pool).copied().enumerate().collect();
@ -308,64 +397,43 @@ pub fn expr2_to_markup<'a, 'b>(
env, env,
sub_expr2, sub_expr2,
*node_id, *node_id,
markup_node_pool, mark_node_pool,
)); interns,
)?);
if idx + 1 < elems.len() { if idx + 1 < elems.len() {
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
", ".to_string(),
expr2_node_id,
HighlightStyle::Operator,
markup_node_pool,
));
} }
} }
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None)));
RIGHT_SQUARE_BR.to_string(),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
let list_node = MarkupNode::Nested { let list_node = MarkupNode::Nested {
ast_node_id: expr2_node_id, ast_node_id,
children_ids, children_ids,
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0,
}; };
markup_node_pool.add(list_node) mark_node_pool.add(list_node)
} }
Expr2::EmptyRecord => { Expr2::EmptyRecord => {
let children_ids = vec![ let children_ids = vec![
new_markup_node( mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)),
LEFT_ACCOLADE.to_string(), mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
),
new_markup_node(
RIGHT_ACCOLADE.to_string(),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
),
]; ];
let record_node = MarkupNode::Nested { let record_node = MarkupNode::Nested {
ast_node_id: expr2_node_id, ast_node_id,
children_ids, children_ids,
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0,
}; };
markup_node_pool.add(record_node) mark_node_pool.add(record_node)
} }
Expr2::Record { fields, .. } => { Expr2::Record { fields, .. } => {
let mut children_ids = vec![new_markup_node( let mut children_ids =
LEFT_ACCOLADE.to_string(), vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))];
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
)];
for (idx, field_node_id) in fields.iter_node_ids().enumerate() { for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
let record_field = env.pool.get(field_node_id); let record_field = env.pool.get(field_node_id);
@ -374,21 +442,16 @@ pub fn expr2_to_markup<'a, 'b>(
children_ids.push(new_markup_node( children_ids.push(new_markup_node(
field_name.as_str(env.pool).to_owned(), field_name.as_str(env.pool).to_owned(),
expr2_node_id, ast_node_id,
HighlightStyle::RecordField, HighlightStyle::RecordField,
markup_node_pool, mark_node_pool,
)); ));
match record_field { match record_field {
RecordField::InvalidLabelOnly(_, _) => (), RecordField::InvalidLabelOnly(_, _) => (),
RecordField::LabelOnly(_, _, _) => (), RecordField::LabelOnly(_, _, _) => (),
RecordField::LabeledValue(_, _, sub_expr2_node_id) => { RecordField::LabeledValue(_, _, sub_expr2_node_id) => {
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None)));
COLON.to_string(),
expr2_node_id,
HighlightStyle::Operator,
markup_node_pool,
));
let sub_expr2 = env.pool.get(*sub_expr2_node_id); let sub_expr2 = env.pool.get(*sub_expr2_node_id);
children_ids.push(expr2_to_markup( children_ids.push(expr2_to_markup(
@ -396,66 +459,117 @@ pub fn expr2_to_markup<'a, 'b>(
env, env,
sub_expr2, sub_expr2,
*sub_expr2_node_id, *sub_expr2_node_id,
markup_node_pool, mark_node_pool,
)); interns,
)?);
} }
} }
if idx + 1 < fields.len() { if idx + 1 < fields.len() {
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
", ".to_string(),
expr2_node_id,
HighlightStyle::Operator,
markup_node_pool,
));
} }
} }
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)));
RIGHT_ACCOLADE.to_string(),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
let record_node = MarkupNode::Nested { let record_node = MarkupNode::Nested {
ast_node_id: expr2_node_id, ast_node_id,
children_ids, children_ids,
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0,
}; };
markup_node_pool.add(record_node) mark_node_pool.add(record_node)
} }
Expr2::Blank => markup_node_pool.add(MarkupNode::Blank { Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)),
ast_node_id: expr2_node_id, Expr2::LetValue {
def_id,
body_id: _,
body_var: _,
} => {
let pattern_id = env.pool.get(*def_id).get_pattern_id();
let pattern2 = env.pool.get(pattern_id);
let val_name = get_identifier_string(pattern2, interns)?;
let val_name_mn = MarkupNode::Text {
content: val_name,
ast_node_id,
syn_high_style: HighlightStyle::Variable,
attributes: Attributes::new(), attributes: Attributes::new(),
syn_high_style: HighlightStyle::Blank,
parent_id_opt: None, parent_id_opt: None,
}), newlines_at_end: 0,
};
let val_name_mn_id = mark_node_pool.add(val_name_mn);
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
let value_def = env.pool.get(*def_id);
match value_def {
ValueDef::NoAnnotation {
pattern_id: _,
expr_id,
expr_var: _,
} => {
let body_mn_id = expr2_to_markup(
arena,
env,
env.pool.get(*expr_id),
*expr_id,
mark_node_pool,
interns,
)?;
let body_mn = mark_node_pool.get_mut(body_mn_id);
body_mn.add_newline_at_end();
let full_let_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id],
parent_id_opt: None,
newlines_at_end: 1,
};
mark_node_pool.add(full_let_node)
}
other => {
unimplemented!(
"I don't know how to convert {:?} into a MarkupNode yet.",
other
)
}
}
}
Expr2::RuntimeError() => new_markup_node( Expr2::RuntimeError() => new_markup_node(
"RunTimeError".to_string(), "RunTimeError".to_string(),
expr2_node_id, ast_node_id,
HighlightStyle::Blank, HighlightStyle::Blank,
markup_node_pool, mark_node_pool,
), ),
rest => todo!("implement expr2_to_markup for {:?}", rest), rest => todo!("implement expr2_to_markup for {:?}", rest),
} };
Ok(mark_node_id)
} }
pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut SlowPool) { pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) {
let node = markup_node_pool.get(markup_node_id); let node = mark_node_pool.get(markup_node_id);
if let MarkupNode::Nested { if let MarkupNode::Nested {
ast_node_id: _, ast_node_id: _,
children_ids, children_ids,
parent_id_opt: _, parent_id_opt: _,
newlines_at_end: _,
} = node } = node
{ {
// need to clone because of borrowing issues // need to clone because of borrowing issues
let children_ids_clone = children_ids.clone(); let children_ids_clone = children_ids.clone();
for child_id in children_ids_clone { for child_id in children_ids_clone {
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool); set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool);
} }
} }
} }
@ -463,9 +577,9 @@ pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut Slo
pub fn set_parent_for_all_helper( pub fn set_parent_for_all_helper(
markup_node_id: MarkNodeId, markup_node_id: MarkNodeId,
parent_node_id: MarkNodeId, parent_node_id: MarkNodeId,
markup_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
) { ) {
let node = markup_node_pool.get_mut(markup_node_id); let node = mark_node_pool.get_mut(markup_node_id);
match node { match node {
MarkupNode::Nested { MarkupNode::Nested {
@ -479,7 +593,7 @@ pub fn set_parent_for_all_helper(
let children_ids_clone = children_ids.clone(); let children_ids_clone = children_ids.clone();
for child_id in children_ids_clone { for child_id in children_ids_clone {
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool); set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool);
} }
} }
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
@ -487,23 +601,237 @@ pub fn set_parent_for_all_helper(
} }
} }
fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId {
let mark_node = MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::PackageRelated,
attributes: Attributes::new(),
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(mark_node)
}
fn header_val_mn(
content: String,
expr_id: ExprId,
highlight_style: HighlightStyle,
mark_node_pool: &mut SlowPool,
) -> MarkNodeId {
let mark_node = MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: highlight_style,
attributes: Attributes::new(),
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(mark_node)
}
pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId {
let expr_id = app_header.ast_node_id;
let ast_node_id = ASTNodeId::AExprId(expr_id);
let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool);
let app_name_node_id = header_val_mn(
app_header.app_name.clone(),
expr_id,
HighlightStyle::String,
mark_node_pool,
);
let full_app_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![app_node_id, app_name_node_id],
parent_id_opt: None,
newlines_at_end: 1,
};
let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool);
let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None));
let pack_base_node_id = header_val_mn(
"base: ".to_owned(),
expr_id,
HighlightStyle::RecordField,
mark_node_pool,
);
let pack_val_node_id = header_val_mn(
app_header.packages_base.clone(),
expr_id,
HighlightStyle::String,
mark_node_pool,
);
let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None));
let full_packages_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![
packages_node_id,
pack_left_acc_node_id,
pack_base_node_id,
pack_val_node_id,
pack_right_acc_node_id,
],
parent_id_opt: None,
newlines_at_end: 1,
};
let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool);
let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
let mut import_child_ids: Vec<MarkNodeId> = add_header_mn_list(
&app_header.imports,
expr_id,
HighlightStyle::Import,
mark_node_pool,
);
let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
let mut full_import_children = vec![imports_node_id, imports_left_square_node_id];
full_import_children.append(&mut import_child_ids);
full_import_children.push(imports_right_square_node_id);
let full_import_node = MarkupNode::Nested {
ast_node_id,
children_ids: full_import_children,
parent_id_opt: None,
newlines_at_end: 1,
};
let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool);
let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
let mut provides_val_node_ids: Vec<MarkNodeId> = add_header_mn_list(
&app_header.provides,
expr_id,
HighlightStyle::Provides,
mark_node_pool,
);
let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool);
let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id];
full_provides_children.append(&mut provides_val_node_ids);
full_provides_children.push(provides_right_square_node_id);
full_provides_children.push(provides_end_node_id);
let full_provides_node = MarkupNode::Nested {
ast_node_id,
children_ids: full_provides_children,
parent_id_opt: None,
newlines_at_end: 1,
};
let full_app_node_id = mark_node_pool.add(full_app_node);
let full_packages_node = mark_node_pool.add(full_packages_node);
let full_import_node_id = mark_node_pool.add(full_import_node);
let full_provides_node_id = mark_node_pool.add(full_provides_node);
let header_mark_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![
full_app_node_id,
full_packages_node,
full_import_node_id,
full_provides_node_id,
],
parent_id_opt: None,
newlines_at_end: 1,
};
let header_mn_id = mark_node_pool.add(header_mark_node);
set_parent_for_all(header_mn_id, mark_node_pool);
header_mn_id
}
// Used for provides and imports
fn add_header_mn_list(
str_vec: &[String],
expr_id: ExprId,
highlight_style: HighlightStyle,
mark_node_pool: &mut SlowPool,
) -> Vec<MarkNodeId> {
let nr_of_elts = str_vec.len();
str_vec
.iter()
.enumerate()
.map(|(indx, provide_str)| {
let provide_str = header_val_mn(
provide_str.to_owned(),
expr_id,
highlight_style,
mark_node_pool,
);
if indx != nr_of_elts - 1 {
vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))]
} else {
vec![provide_str]
}
})
.flatten()
.collect()
}
pub fn ast_to_mark_nodes<'a, 'b>(
arena: &'a Bump,
env: &mut Env<'b>,
ast: &AST,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> EdResult<Vec<MarkNodeId>> {
let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)];
for &def_id in ast.def_ids.iter() {
let def2 = env.pool.get(def_id);
let expr2_markup_id = def2_to_markup(arena, env, def2, def_id, mark_node_pool, interns)?;
set_parent_for_all(expr2_markup_id, mark_node_pool);
all_mark_node_ids.push(expr2_markup_id);
}
Ok(all_mark_node_ids)
}
impl fmt::Display for MarkupNode { impl fmt::Display for MarkupNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!( write!(
f, f,
"{} ({})", "{} ({}, {})",
self.node_type_as_string(), self.node_type_as_string(),
self.get_content().unwrap_or_else(|_| "".to_string()) self.get_content(),
self.get_newlines_at_end()
) )
} }
} }
pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String {
let mut full_string = "\n\n(mark_node_tree)\n".to_owned(); let mut full_string = "\n(mark_node_tree)\n".to_owned();
let node = mark_node_pool.get(root_node_id); let node = mark_node_pool.get(root_node_id);
full_string.push_str(&format!("{}", node)); full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id));
tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); tree_as_string_helper(node, 1, &mut full_string, mark_node_pool);
@ -524,11 +852,25 @@ fn tree_as_string_helper(
.to_owned(); .to_owned();
let child = mark_node_pool.get(child_id); let child = mark_node_pool.get(child_id);
let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n");
full_str.push_str(&format!("{}", child)); full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id));
tree_string.push_str(&full_str); tree_string.push_str(&full_str);
tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); tree_as_string_helper(child, level + 1, tree_string, mark_node_pool);
} }
} }
// return to the the root parent_id of the current node
pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId {
let mut curr_mark_node_id = mark_node_id;
let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt();
while let Some(curr_parent_id) = curr_parent_id_opt {
curr_mark_node_id = curr_parent_id;
curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt();
}
curr_mark_node_id
}

View file

@ -1,6 +1,6 @@
mod code_lines; mod code_lines;
mod config; mod config;
mod ed_error; pub mod ed_error;
mod grid_node_map; mod grid_node_map;
mod keyboard_input; mod keyboard_input;
pub mod main; pub mod main;

View file

@ -1,8 +1,8 @@
use super::app_model::AppModel; use super::app_model::AppModel;
use super::ed_update; use super::ed_update;
use crate::editor::ed_error::EdResult;
use crate::window::keyboard_input::Modifiers; use crate::window::keyboard_input::Modifiers;
use winit::event::VirtualKeyCode; use crate::{editor::ed_error::EdResult, window::keyboard_input::from_winit};
use winit::event::{ModifiersState, VirtualKeyCode};
pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> { pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> {
if let Some(ref mut ed_model) = app_model.ed_model_opt { if let Some(ref mut ed_model) = app_model.ed_model_opt {
@ -51,16 +51,26 @@ pub fn pass_keydown_to_focused(
pub enum InputOutcome { pub enum InputOutcome {
Accepted, Accepted,
Ignored, Ignored,
SilentIgnored,
} }
pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResult<InputOutcome> { pub fn handle_new_char(
received_char: &char,
app_model: &mut AppModel,
modifiers_winit: ModifiersState,
) -> EdResult<InputOutcome> {
if let Some(ref mut ed_model) = app_model.ed_model_opt { if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus { if ed_model.has_focus {
let modifiers = from_winit(&modifiers_winit);
if modifiers.new_char_modifiers() {
// shortcuts with modifiers are handled by ed_handle_key_down
return ed_update::handle_new_char(received_char, ed_model); return ed_update::handle_new_char(received_char, ed_model);
} }
} }
}
Ok(InputOutcome::Ignored) Ok(InputOutcome::SilentIgnored)
} }
/* /*

View file

@ -0,0 +1,80 @@
use crate::editor::ed_error::EdResult;
use crate::editor::markup::common_nodes::new_blank_mn_w_nls;
use crate::editor::mvc::app_update::InputOutcome;
use crate::editor::mvc::ed_model::EdModel;
use crate::editor::util::index_of;
use crate::lang::ast::Def2;
use crate::lang::parse::ASTNodeId;
use crate::ui::text::text_pos::TextPos;
// put everything after caret on new line, create a Def2::Blank if there was nothing after the caret.
pub fn break_line(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
let carets = ed_model.get_carets();
for caret_pos in carets.iter() {
let caret_line_nr = caret_pos.line;
// don't allow adding new lines on empty line
if caret_pos.column > 0
&& ed_model.grid_node_map.node_exists_at_pos(TextPos {
line: caret_line_nr,
column: caret_pos.column - 1,
})
{
// one blank line between top level definitions
EdModel::insert_empty_line(
caret_line_nr + 1,
&mut ed_model.code_lines,
&mut ed_model.grid_node_map,
)?;
EdModel::insert_empty_line(
caret_line_nr + 1,
&mut ed_model.code_lines,
&mut ed_model.grid_node_map,
)?;
insert_new_blank(ed_model, caret_pos, caret_pos.line + 2)?;
}
}
ed_model.simple_move_carets_down(2); // one blank line between top level definitions
Ok(InputOutcome::Accepted)
}
pub fn insert_new_blank(
ed_model: &mut EdModel,
caret_pos: &TextPos,
insert_on_line_nr: usize,
) -> EdResult<()> {
let new_line_blank = Def2::Blank;
let new_line_blank_id = ed_model.module.env.pool.add(new_line_blank);
let prev_def_mn_id = ed_model
.grid_node_map
.get_def_mark_node_id_before_line(caret_pos.line + 1, &ed_model.mark_node_pool)?;
let prev_def_mn_id_indx = index_of(prev_def_mn_id, &ed_model.markup_ids)?;
ed_model
.module
.ast
.def_ids
.insert(prev_def_mn_id_indx, new_line_blank_id);
let blank_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls(
ASTNodeId::ADefId(new_line_blank_id),
None,
2,
));
ed_model
.markup_ids
.insert(prev_def_mn_id_indx + 1, blank_mn_id); // + 1 because first markup node is header
ed_model.insert_all_between_line(
insert_on_line_nr, // one blank line between top level definitions
0,
&[blank_mn_id],
)?;
Ok(())
}

View file

@ -1,27 +1,22 @@
use crate::editor::code_lines::CodeLines; use crate::editor::code_lines::CodeLines;
use crate::editor::grid_node_map::GridNodeMap; use crate::editor::grid_node_map::GridNodeMap;
use crate::editor::markup::nodes::ast_to_mark_nodes;
use crate::editor::slow_pool::{MarkNodeId, SlowPool}; use crate::editor::slow_pool::{MarkNodeId, SlowPool};
use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::{ use crate::editor::{
ed_error::EdError::ParseError, ed_error::SrcParseError,
ed_error::{EdResult, MissingParent, NoNodeAtCaretPosition}, ed_error::{EdResult, EmptyCodeString, MissingParent, NoNodeAtCaretPosition},
markup::attribute::Attributes,
markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode},
}; };
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::{Expr2, ExprId}; use crate::lang::expr::Env;
use crate::lang::expr::{str_to_expr2, Env}; use crate::lang::parse::{ASTNodeId, AST};
use crate::lang::pool::PoolStr; use crate::lang::pool::PoolStr;
use crate::lang::scope::Scope; use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect};
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::lines::SelectableLines; use crate::ui::text::lines::SelectableLines;
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use roc_module::symbol::Interns; use roc_load::file::LoadedModule;
use roc_region::all::Region;
use std::path::Path; use std::path::Path;
#[derive(Debug)] #[derive(Debug)]
@ -31,83 +26,98 @@ pub struct EdModel<'a> {
pub code_lines: CodeLines, pub code_lines: CodeLines,
// allows us to map window coordinates to MarkNodeId's // allows us to map window coordinates to MarkNodeId's
pub grid_node_map: GridNodeMap, pub grid_node_map: GridNodeMap,
pub markup_root_id: MarkNodeId, pub markup_ids: Vec<MarkNodeId>, // one root node for every expression
pub markup_node_pool: SlowPool, pub mark_node_pool: SlowPool,
// contains single char dimensions, used to calculate line height, column width... // contains single char dimensions, used to calculate line height, column width...
pub glyph_dim_rect_opt: Option<Rect>, pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool, pub has_focus: bool,
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>,
pub selected_expr_opt: Option<SelectedExpression>, pub selected_block_opt: Option<SelectedBlock>,
pub interns: &'a Interns, // this should eventually come from LoadedModule, see #1442 pub loaded_module: LoadedModule,
pub show_debug_view: bool, pub show_debug_view: bool,
// EdModel is dirty if it has changed since the previous render. // EdModel is dirty if it has changed since the previous render.
pub dirty: bool, pub dirty: bool,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SelectedExpression { pub struct SelectedBlock {
pub ast_node_id: ExprId, pub ast_node_id: ASTNodeId,
pub mark_node_id: MarkNodeId, pub mark_node_id: MarkNodeId,
pub type_str: PoolStr, pub type_str: PoolStr,
} }
pub fn init_model<'a>( pub fn init_model<'a>(
code_str: &'a BumpString, code_str: &'a str,
file_path: &'a Path, file_path: &'a Path,
env: Env<'a>, env: Env<'a>,
interns: &'a Interns, loaded_module: LoadedModule,
code_arena: &'a Bump, code_arena: &'a Bump,
caret_pos: CaretPos, // to set caret position
) -> EdResult<EdModel<'a>> { ) -> EdResult<EdModel<'a>> {
let mut module = EdModule::new(code_str, env, code_arena)?; let mut module = EdModule::new(code_str, env, code_arena)?;
let ast_root_id = module.ast_root_id; let mut mark_node_pool = SlowPool::new();
let mut markup_node_pool = SlowPool::new();
let markup_root_id = if code_str.is_empty() { let markup_ids = if code_str.is_empty() {
let blank_root = MarkupNode::Blank { EmptyCodeString {}.fail()
ast_node_id: ast_root_id,
attributes: Attributes::new(),
syn_high_style: HighlightStyle::Blank,
parent_id_opt: None,
};
markup_node_pool.add(blank_root)
} else { } else {
let ast_root = &module.env.pool.get(ast_root_id); ast_to_mark_nodes(
let temp_markup_root_id = expr2_to_markup(
code_arena, code_arena,
&mut module.env, &mut module.env,
ast_root, &module.ast,
ast_root_id, &mut mark_node_pool,
&mut markup_node_pool, &loaded_module.interns,
); )
set_parent_for_all(temp_markup_root_id, &mut markup_node_pool); }?;
temp_markup_root_id let mut code_lines = CodeLines::default();
let mut grid_node_map = GridNodeMap::default();
let mut line_nr = 0;
let mut col_nr = 0;
for mark_node_id in &markup_ids {
EdModel::insert_mark_node_between_line(
&mut line_nr,
&mut col_nr,
*mark_node_id,
&mut grid_node_map,
&mut code_lines,
&mark_node_pool,
)?
}
let caret = match caret_pos {
CaretPos::Start => CaretWSelect::default(),
CaretPos::Exact(txt_pos) => CaretWSelect::new(txt_pos, None),
CaretPos::End => CaretWSelect::new(code_lines.end_txt_pos(), None),
}; };
let code_lines = EdModel::build_code_lines_from_markup(markup_root_id, &markup_node_pool)?;
let grid_node_map = EdModel::build_node_map_from_markup(markup_root_id, &markup_node_pool)?;
Ok(EdModel { Ok(EdModel {
module, module,
file_path, file_path,
code_lines, code_lines,
grid_node_map, grid_node_map,
markup_root_id, markup_ids,
markup_node_pool, mark_node_pool,
glyph_dim_rect_opt: None, glyph_dim_rect_opt: None,
has_focus: true, has_focus: true,
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)), caret_w_select_vec: NonEmpty::new((caret, None)),
selected_expr_opt: None, selected_block_opt: None,
interns, loaded_module,
show_debug_view: false, show_debug_view: false,
dirty: true, dirty: true,
}) })
} }
impl<'a> EdModel<'a> { impl<'a> EdModel<'a> {
pub fn get_carets(&self) -> Vec<TextPos> {
self.caret_w_select_vec
.iter()
.map(|tup| tup.0.caret_pos)
.collect()
}
pub fn get_curr_mark_node_id(&self) -> UIResult<MarkNodeId> { pub fn get_curr_mark_node_id(&self) -> UIResult<MarkNodeId> {
let caret_pos = self.get_caret(); let caret_pos = self.get_caret();
self.grid_node_map.get_id_at_row_col(caret_pos) self.grid_node_map.get_id_at_row_col(caret_pos)
@ -138,11 +148,11 @@ impl<'a> EdModel<'a> {
pub fn get_curr_child_indices(&self) -> EdResult<(usize, usize)> { pub fn get_curr_child_indices(&self) -> EdResult<(usize, usize)> {
if self.node_exists_at_caret() { if self.node_exists_at_caret() {
let curr_mark_node_id = self.get_curr_mark_node_id()?; let curr_mark_node_id = self.get_curr_mark_node_id()?;
let curr_mark_node = self.markup_node_pool.get(curr_mark_node_id); let curr_mark_node = self.mark_node_pool.get(curr_mark_node_id);
if let Some(parent_id) = curr_mark_node.get_parent_id_opt() { if let Some(parent_id) = curr_mark_node.get_parent_id_opt() {
let parent = self.markup_node_pool.get(parent_id); let parent = self.mark_node_pool.get(parent_id);
parent.get_child_indices(curr_mark_node_id, &self.markup_node_pool) parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool)
} else { } else {
MissingParent { MissingParent {
node_id: curr_mark_node_id, node_id: curr_mark_node_id,
@ -161,7 +171,7 @@ impl<'a> EdModel<'a> {
#[derive(Debug)] #[derive(Debug)]
pub struct EdModule<'a> { pub struct EdModule<'a> {
pub env: Env<'a>, pub env: Env<'a>,
pub ast_root_id: ExprId, pub ast: AST,
} }
// for debugging // for debugging
@ -170,29 +180,17 @@ pub struct EdModule<'a> {
impl<'a> EdModule<'a> { impl<'a> EdModule<'a> {
pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult<EdModule<'a>> { pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult<EdModule<'a>> {
if !code_str.is_empty() { if !code_str.is_empty() {
let mut scope = Scope::new(env.home, env.pool, env.var_store); let parse_res = AST::parse_from_string(code_str, &mut env, ast_arena);
let region = Region::new(0, 0, 0, 0); match parse_res {
Ok(ast) => Ok(EdModule { env, ast }),
let expr2_result = str_to_expr2(ast_arena, code_str, &mut env, &mut scope, region); Err(err) => SrcParseError {
match expr2_result {
Ok((expr2, _output)) => {
let ast_root_id = env.pool.add(expr2);
// for debugging
// dbg!(expr2_to_string(ast_root_id, env.pool));
Ok(EdModule { env, ast_root_id })
}
Err(err) => Err(ParseError {
syntax_err: format!("{:?}", err), syntax_err: format!("{:?}", err),
}), }
.fail(),
} }
} else { } else {
let ast_root_id = env.pool.add(Expr2::Blank); EmptyCodeString {}.fail()
Ok(EdModule { env, ast_root_id })
} }
} }
} }
@ -200,40 +198,49 @@ impl<'a> EdModule<'a> {
#[cfg(test)] #[cfg(test)]
pub mod test_ed_model { pub mod test_ed_model {
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::main::load_module;
use crate::editor::mvc::ed_model; use crate::editor::mvc::ed_model;
use crate::editor::resources::strings::HELLO_WORLD;
use crate::lang::expr::Env; use crate::lang::expr::Env;
use crate::lang::pool::Pool; use crate::lang::pool::Pool;
use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection;
use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl;
use crate::ui::text::caret_w_select::CaretPos;
use crate::ui::text::lines::SelectableLines; use crate::ui::text::lines::SelectableLines;
use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use ed_model::EdModel; use ed_model::EdModel;
use roc_module::symbol::{IdentIds, Interns, ModuleIds}; use roc_load::file::LoadedModule;
use roc_module::symbol::IdentIds;
use roc_module::symbol::ModuleIds;
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use std::fs::File;
use std::io::Write;
use std::path::Path; use std::path::Path;
use std::path::PathBuf;
use tempfile::tempdir;
use uuid::Uuid;
pub fn init_dummy_model<'a>( pub fn init_dummy_model<'a>(
code_str: &'a BumpString, code_str: &'a str,
loaded_module: LoadedModule,
module_ids: &'a ModuleIds,
ed_model_refs: &'a mut EdModelRefs, ed_model_refs: &'a mut EdModelRefs,
code_arena: &'a Bump,
) -> EdResult<EdModel<'a>> { ) -> EdResult<EdModel<'a>> {
let file_path = Path::new(""); let file_path = Path::new("");
let dep_idents = IdentIds::exposed_builtins(8); let dep_idents = IdentIds::exposed_builtins(8);
let exposed_ident_ids = IdentIds::default(); let exposed_ident_ids = IdentIds::default();
let mod_id = ed_model_refs
.interns
.module_ids
.get_or_insert(&"ModId123".into());
let env = Env::new( let env = Env::new(
mod_id, loaded_module.module_id,
&ed_model_refs.env_arena, &ed_model_refs.env_arena,
&mut ed_model_refs.env_pool, &mut ed_model_refs.env_pool,
&mut ed_model_refs.var_store, &mut ed_model_refs.var_store,
dep_idents, dep_idents,
&ed_model_refs.interns.module_ids, module_ids,
exposed_ident_ids, exposed_ident_ids,
); );
@ -241,43 +248,69 @@ pub mod test_ed_model {
code_str, code_str,
file_path, file_path,
env, env,
&ed_model_refs.interns, loaded_module,
&ed_model_refs.code_arena, code_arena,
CaretPos::End,
) )
} }
pub struct EdModelRefs { pub struct EdModelRefs {
code_arena: Bump,
env_arena: Bump, env_arena: Bump,
env_pool: Pool, env_pool: Pool,
var_store: VarStore, var_store: VarStore,
interns: Interns,
} }
pub fn init_model_refs() -> EdModelRefs { pub fn init_model_refs() -> EdModelRefs {
EdModelRefs { EdModelRefs {
code_arena: Bump::new(),
env_arena: Bump::new(), env_arena: Bump::new(),
env_pool: Pool::with_capacity(1024), env_pool: Pool::with_capacity(1024),
var_store: VarStore::default(), var_store: VarStore::default(),
interns: Interns {
module_ids: ModuleIds::default(),
all_ident_ids: IdentIds::exposed_builtins(8),
},
} }
} }
pub fn ed_model_from_dsl<'a>( pub fn ed_model_from_dsl<'a>(
clean_code_str: &'a BumpString, clean_code_str: &'a mut String,
code_lines: &[&str], code_lines: Vec<String>,
ed_model_refs: &'a mut EdModelRefs, ed_model_refs: &'a mut EdModelRefs,
module_ids: &'a ModuleIds,
code_arena: &'a Bump,
) -> Result<EdModel<'a>, String> { ) -> Result<EdModel<'a>, String> {
let code_lines_vec: Vec<String> = (*code_lines).iter().map(|s| s.to_string()).collect(); let full_code = vec![HELLO_WORLD, clean_code_str.as_str()];
let caret_w_select = convert_dsl_to_selection(&code_lines_vec)?; *clean_code_str = full_code.join("\n");
let mut ed_model = init_dummy_model(clean_code_str, ed_model_refs)?; let temp_dir = tempdir().expect("Failed to create temporary directory for test.");
let temp_file_path_buf =
PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join(""));
let temp_file_full_path = temp_dir.path().join(temp_file_path_buf);
ed_model.set_caret(caret_w_select.caret_pos); let mut file = File::create(temp_file_full_path.clone()).expect(&format!(
"Failed to create temporary file for path {:?}",
temp_file_full_path
));
writeln!(file, "{}", clean_code_str).expect(&format!(
"Failed to write {:?} to file: {:?}",
clean_code_str, file
));
let loaded_module = load_module(&temp_file_full_path);
let mut ed_model = init_dummy_model(
clean_code_str,
loaded_module,
module_ids,
ed_model_refs,
code_arena,
)?;
// adjust for header and main function
let nr_hello_world_lines = HELLO_WORLD.matches('\n').count() - 2;
let caret_w_select = convert_dsl_to_selection(&code_lines)?;
let adjusted_caret_pos = TextPos {
line: caret_w_select.caret_pos.line + nr_hello_world_lines,
column: caret_w_select.caret_pos.column,
};
ed_model.set_caret(adjusted_caret_pos);
Ok(ed_model) Ok(ed_model)
} }

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
use super::ed_model::EdModel; use super::ed_model::EdModel;
use crate::editor::config::Config; use crate::editor::config::Config;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::mvc::ed_model::SelectedExpression; use crate::editor::mvc::ed_model::SelectedBlock;
use crate::editor::render_ast::build_code_graphics; use crate::editor::render_ast::build_code_graphics;
use crate::editor::render_debug::build_debug_graphics; use crate::editor::render_debug::build_debug_graphics;
use crate::editor::resources::strings::START_TIP; use crate::editor::resources::strings::START_TIP;
@ -20,33 +20,49 @@ use winit::dpi::PhysicalSize;
#[derive(Debug)] #[derive(Debug)]
pub struct RenderedWgpu { pub struct RenderedWgpu {
pub text_sections: Vec<glyph_brush::OwnedSection>, pub text_sections_behind: Vec<glyph_brush::OwnedSection>, // displayed in front of rect_behind, behind everything else
pub rects: Vec<Rect>, pub text_sections_front: Vec<glyph_brush::OwnedSection>, // displayed in front of everything
pub rects_behind: Vec<Rect>, // displayed at lowest depth
pub rects_front: Vec<Rect>, // displayed in front of text_sections_behind, behind text_sections_front
} }
impl RenderedWgpu { impl RenderedWgpu {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
text_sections: Vec::new(), text_sections_behind: Vec::new(),
rects: Vec::new(), text_sections_front: Vec::new(),
rects_behind: Vec::new(),
rects_front: Vec::new(),
} }
} }
pub fn add_text(&mut self, new_text_section: glyph_brush::OwnedSection) { pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) {
self.text_sections.push(new_text_section); self.text_sections_behind.push(new_text_section);
} }
pub fn add_rect(&mut self, new_rect: Rect) { pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) {
self.rects.push(new_rect); self.text_sections_front.push(new_text_section);
} }
pub fn add_rects(&mut self, new_rects: Vec<Rect>) { pub fn add_rect_behind(&mut self, new_rect: Rect) {
self.rects.extend(new_rects); self.rects_behind.push(new_rect);
}
pub fn add_rects_behind(&mut self, new_rects: Vec<Rect>) {
self.rects_behind.extend(new_rects);
}
pub fn add_rect_front(&mut self, new_rect: Rect) {
self.rects_front.push(new_rect);
} }
pub fn extend(&mut self, rendered_wgpu: RenderedWgpu) { pub fn extend(&mut self, rendered_wgpu: RenderedWgpu) {
self.text_sections.extend(rendered_wgpu.text_sections); self.text_sections_behind
self.rects.extend(rendered_wgpu.rects); .extend(rendered_wgpu.text_sections_behind);
self.text_sections_front
.extend(rendered_wgpu.text_sections_front);
self.rects_behind.extend(rendered_wgpu.rects_behind);
self.rects_front.extend(rendered_wgpu.rects_front);
} }
} }
@ -61,7 +77,10 @@ pub fn model_to_wgpu<'a>(
let mut all_rendered = RenderedWgpu::new(); let mut all_rendered = RenderedWgpu::new();
let tip_txt_coords = (txt_coords.x, txt_coords.y - 4.0 * config.code_font_size); let tip_txt_coords = (
txt_coords.x,
txt_coords.y - (START_TIP.matches('\n').count() as f32 + 1.0) * config.code_font_size,
);
let start_tip_text = owned_section_from_text(&Text { let start_tip_text = owned_section_from_text(&Text {
position: tip_txt_coords.into(), position: tip_txt_coords.into(),
@ -72,15 +91,15 @@ pub fn model_to_wgpu<'a>(
..Default::default() ..Default::default()
}); });
all_rendered.add_text(start_tip_text); all_rendered.add_text_behind(start_tip_text);
let rendered_code_graphics = build_code_graphics( let rendered_code_graphics = build_code_graphics(
ed_model.markup_node_pool.get(ed_model.markup_root_id), &ed_model.markup_ids,
size, size,
txt_coords, txt_coords,
config, config,
glyph_dim_rect, glyph_dim_rect,
&ed_model.markup_node_pool, &ed_model.mark_node_pool,
)?; )?;
all_rendered.extend(rendered_code_graphics); all_rendered.extend(rendered_code_graphics);
@ -93,7 +112,7 @@ pub fn model_to_wgpu<'a>(
let rendered_selection = build_selection_graphics( let rendered_selection = build_selection_graphics(
caret_w_sel_vec, caret_w_sel_vec,
&ed_model.selected_expr_opt, &ed_model.selected_block_opt,
txt_coords, txt_coords,
config, config,
glyph_dim_rect, glyph_dim_rect,
@ -103,7 +122,7 @@ pub fn model_to_wgpu<'a>(
all_rendered.extend(rendered_selection); all_rendered.extend(rendered_selection);
if ed_model.show_debug_view { if ed_model.show_debug_view {
all_rendered.add_text(build_debug_graphics(size, txt_coords, config, ed_model)?); all_rendered.add_text_behind(build_debug_graphics(size, txt_coords, config, ed_model)?);
} }
Ok(all_rendered) Ok(all_rendered)
@ -111,7 +130,7 @@ pub fn model_to_wgpu<'a>(
pub fn build_selection_graphics( pub fn build_selection_graphics(
caret_w_select_vec: Vec<CaretWSelect>, caret_w_select_vec: Vec<CaretWSelect>,
selected_expr_opt: &Option<SelectedExpression>, selected_expr_opt: &Option<SelectedBlock>,
txt_coords: Vector2<f32>, txt_coords: Vector2<f32>,
config: &Config, config: &Config,
glyph_dim_rect: Rect, glyph_dim_rect: Rect,
@ -139,7 +158,7 @@ pub fn build_selection_graphics(
let width = let width =
((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_width); ((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_width);
all_rendered.add_rect(make_selection_rect( all_rendered.add_rect_behind(make_selection_rect(
sel_rect_x, sel_rect_x,
sel_rect_y, sel_rect_y,
width, width,
@ -158,12 +177,12 @@ pub fn build_selection_graphics(
let (tip_rect, tip_text) = let (tip_rect, tip_text) =
tooltip.render_tooltip(&glyph_dim_rect, &config.ed_theme.ui_theme); tooltip.render_tooltip(&glyph_dim_rect, &config.ed_theme.ui_theme);
all_rendered.add_rect(tip_rect); all_rendered.add_rect_front(tip_rect);
all_rendered.add_text(tip_text); all_rendered.add_text_front(tip_text);
} }
} }
all_rendered.add_rect(make_caret_rect( all_rendered.add_rect_front(make_caret_rect(
top_left_x, top_left_x,
top_left_y, top_left_y,
&glyph_dim_rect, &glyph_dim_rect,

View file

@ -25,6 +25,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let int_var = ed_model.module.env.var_store.fresh(); let int_var = ed_model.module.env.var_store.fresh();
@ -37,7 +38,11 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
text: PoolStr::new(&digit_string, &mut ed_model.module.env.pool), text: PoolStr::new(&digit_string, &mut ed_model.module.env.pool),
}; };
ed_model.module.env.pool.set(ast_node_id, expr2_node); ed_model
.module
.env
.pool
.set(ast_node_id.to_expr_id()?, expr2_node);
let int_node = MarkupNode::Text { let int_node = MarkupNode::Text {
content: digit_string, content: digit_string,
@ -45,11 +50,12 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
syn_high_style: HighlightStyle::Number, syn_high_style: HighlightStyle::Number,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
if is_blank_node { if is_blank_node {
ed_model ed_model
.markup_node_pool .mark_node_pool
.replace_node(curr_mark_node_id, int_node); .replace_node(curr_mark_node_id, int_node);
// remove data corresponding to Blank node // remove data corresponding to Blank node
@ -59,11 +65,13 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
ed_model.simple_move_carets_right(char_len); ed_model.simple_move_carets_right(char_len);
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
&digit_char.to_string(), &digit_char.to_string(),
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -85,7 +93,7 @@ pub fn update_int(
.grid_node_map .grid_node_map
.get_offset_to_node_id(old_caret_pos, int_mark_node_id)?; .get_offset_to_node_id(old_caret_pos, int_mark_node_id)?;
let int_mark_node = ed_model.markup_node_pool.get_mut(int_mark_node_id); let int_mark_node = ed_model.mark_node_pool.get_mut(int_mark_node_id);
let int_ast_node_id = int_mark_node.get_ast_node_id(); let int_ast_node_id = int_mark_node.get_ast_node_id();
let content_str_mut = int_mark_node.get_content_mut()?; let content_str_mut = int_mark_node.get_content_mut()?;
@ -98,19 +106,25 @@ pub fn update_int(
} else { } else {
content_str_mut.insert(node_caret_offset, *ch); content_str_mut.insert(node_caret_offset, *ch);
let content_str = int_mark_node.get_content()?; let content_str = int_mark_node.get_content();
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
&ch.to_string(), &ch.to_string(),
int_mark_node_id, int_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update ast // update ast
let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool); let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool);
let int_ast_node = ed_model.module.env.pool.get_mut(int_ast_node_id); let int_ast_node = ed_model
.module
.env
.pool
.get_mut(int_ast_node_id.to_expr_id()?);
match int_ast_node { match int_ast_node {
SmallInt { number, text, .. } => { SmallInt { number, text, .. } => {
update_small_int_num(number, &content_str)?; update_small_int_num(number, &content_str)?;

View file

@ -0,0 +1,185 @@
use roc_module::symbol::Symbol;
use crate::editor::ed_error::EdResult;
use crate::editor::markup::attribute::Attributes;
use crate::editor::markup::common_nodes::new_blank_mn_w_nls;
use crate::editor::markup::common_nodes::new_equals_mn;
use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::app_update::InputOutcome;
use crate::editor::mvc::ed_model::EdModel;
use crate::editor::mvc::ed_update::get_node_context;
use crate::editor::mvc::ed_update::NodeContext;
use crate::editor::syntax_highlight::HighlightStyle;
use crate::lang::ast::{Expr2, ValueDef};
use crate::lang::parse::ASTNodeId;
use crate::lang::pattern::Pattern2;
pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> {
let NodeContext {
old_caret_pos,
curr_mark_node_id,
curr_mark_node,
parent_id_opt,
ast_node_id,
} = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let val_name_string = new_char.to_string();
// safe unwrap because our ArrString has a 30B capacity
let val_expr2_node = Expr2::Blank;
let val_expr_id = ed_model.module.env.pool.add(val_expr2_node);
let ident_id = ed_model
.module
.env
.ident_ids
.add(val_name_string.clone().into());
let var_symbol = Symbol::new(ed_model.module.env.home, ident_id);
let body = Expr2::Var(var_symbol);
let body_id = ed_model.module.env.pool.add(body);
let pattern = Pattern2::Identifier(var_symbol);
let pattern_id = ed_model.module.env.pool.add(pattern);
let value_def = ValueDef::NoAnnotation {
pattern_id,
expr_id: val_expr_id,
expr_var: ed_model.module.env.var_store.fresh(),
};
let def_id = ed_model.module.env.pool.add(value_def);
let expr2_node = Expr2::LetValue {
def_id,
body_id,
body_var: ed_model.module.env.var_store.fresh(),
};
ed_model
.module
.env
.pool
.set(ast_node_id.to_expr_id()?, expr2_node);
let val_name_mark_node = MarkupNode::Text {
content: val_name_string,
ast_node_id,
syn_high_style: HighlightStyle::Variable,
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id),
newlines_at_end: curr_mark_node_nls,
};
let val_name_mn_id = ed_model.add_mark_node(val_name_mark_node);
let equals_mn_id = ed_model.add_mark_node(new_equals_mn(ast_node_id, Some(curr_mark_node_id)));
let body_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls(
ASTNodeId::AExprId(val_expr_id),
Some(curr_mark_node_id),
1,
));
let val_mark_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id],
parent_id_opt,
newlines_at_end: 1,
};
if is_blank_node {
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, val_mark_node);
// remove data corresponding to Blank node
ed_model.del_blank_expr_node(old_caret_pos)?;
let char_len = 1;
ed_model.simple_move_carets_right(char_len);
// update GridNodeMap and CodeLines
ed_model.insert_all_between_line(
old_caret_pos.line,
old_caret_pos.column,
&[val_name_mn_id, equals_mn_id, body_mn_id],
)?;
Ok(InputOutcome::Accepted)
} else {
Ok(InputOutcome::Ignored)
}
}
// TODO reenable this for updating non-top level value defs
/*
pub fn update_let_value(
val_name_mn_id: MarkNodeId,
def_id: NodeId<ValueDef>,
body_id: NodeId<Expr2>,
ed_model: &mut EdModel,
new_char: &char,
) -> EdResult<InputOutcome> {
if new_char.is_ascii_alphanumeric() {
let old_caret_pos = ed_model.get_caret();
// update markup
let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id);
let content_str_mut = val_name_mn_mut.get_content_mut()?;
let old_val_name = content_str_mut.clone();
let node_caret_offset = ed_model
.grid_node_map
.get_offset_to_node_id(old_caret_pos, val_name_mn_id)?;
if node_caret_offset <= content_str_mut.len() {
content_str_mut.insert(node_caret_offset, *new_char);
// update ast
let value_def = ed_model.module.env.pool.get(def_id);
let value_ident_pattern_id = value_def.get_pattern_id();
// TODO no unwrap
let ident_id = ed_model
.module
.env
.ident_ids
.update_key(&old_val_name, content_str_mut)
.unwrap();
let new_var_symbol = Symbol::new(ed_model.module.env.home, ident_id);
ed_model
.module
.env
.pool
.set(value_ident_pattern_id, Pattern2::Identifier(new_var_symbol));
ed_model
.module
.env
.pool
.set(body_id, Expr2::Var(new_var_symbol));
// update GridNodeMap and CodeLines
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
&new_char.to_string(),
val_name_mn_id,
)?;
// update caret
ed_model.simple_move_carets_right(1);
Ok(InputOutcome::Accepted)
} else {
Ok(InputOutcome::Ignored)
}
} else {
Ok(InputOutcome::Ignored)
}
}
*/

View file

@ -1,6 +1,8 @@
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; use crate::editor::ed_error::{MissingParent, UnexpectedASTNode};
use crate::editor::markup::attribute::Attributes; use crate::editor::markup::common_nodes::{
new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn,
};
use crate::editor::markup::nodes; use crate::editor::markup::nodes;
use crate::editor::markup::nodes::MarkupNode; use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::app_update::InputOutcome;
@ -8,9 +10,9 @@ use crate::editor::mvc::ed_model::EdModel;
use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::get_node_context;
use crate::editor::mvc::ed_update::NodeContext; use crate::editor::mvc::ed_update::NodeContext;
use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::MarkNodeId;
use crate::editor::syntax_highlight::HighlightStyle; use crate::lang::ast::ExprId;
use crate::lang::ast::Expr2; use crate::lang::ast::{ast_node_to_string, Expr2};
use crate::lang::ast::{expr2_to_string, ExprId}; use crate::lang::parse::ASTNodeId;
use crate::lang::pool::PoolVec; use crate::lang::pool::PoolVec;
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
@ -24,63 +26,62 @@ pub fn start_new_list(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let expr2_node = Expr2::List { let expr2_node = Expr2::List {
elem_var: ed_model.module.env.var_store.fresh(), elem_var: ed_model.module.env.var_store.fresh(),
elems: PoolVec::empty(ed_model.module.env.pool), elems: PoolVec::empty(ed_model.module.env.pool),
}; };
let mark_node_pool = &mut ed_model.markup_node_pool; ed_model
.module
.env
.pool
.set(ast_node_id.to_expr_id()?, expr2_node);
ed_model.module.env.pool.set(ast_node_id, expr2_node); let left_bracket_node_id = ed_model.add_mark_node(new_left_square_mn(
ast_node_id.to_expr_id()?,
Some(curr_mark_node_id),
));
let left_bracket_node = MarkupNode::Text { let right_bracket_node_id = ed_model.add_mark_node(new_right_square_mn(
content: nodes::LEFT_SQUARE_BR.to_owned(), ast_node_id.to_expr_id()?,
ast_node_id, Some(curr_mark_node_id),
syn_high_style: HighlightStyle::Bracket, ));
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one
};
let left_bracket_node_id = mark_node_pool.add(left_bracket_node);
let right_bracket_node = MarkupNode::Text {
content: nodes::RIGHT_SQUARE_BR.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one
};
let right_bracket_node_id = mark_node_pool.add(right_bracket_node);
let nested_node = MarkupNode::Nested { let nested_node = MarkupNode::Nested {
ast_node_id, ast_node_id,
children_ids: vec![left_bracket_node_id, right_bracket_node_id], children_ids: vec![left_bracket_node_id, right_bracket_node_id],
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
if is_blank_node { if is_blank_node {
mark_node_pool.replace_node(curr_mark_node_id, nested_node); ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, nested_node);
// remove data corresponding to Blank node ed_model.del_blank_expr_node(old_caret_pos)?;
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?;
ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len()); ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::LEFT_SQUARE_BR, nodes::LEFT_SQUARE_BR,
left_bracket_node_id, left_bracket_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column + nodes::LEFT_SQUARE_BR.len(), old_caret_pos.column + nodes::LEFT_SQUARE_BR.len(),
nodes::RIGHT_SQUARE_BR, nodes::RIGHT_SQUARE_BR,
right_bracket_node_id, right_bracket_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -105,10 +106,10 @@ pub fn add_blank_child(
let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt
{ {
let parent = ed_model.markup_node_pool.get(parent_id); let parent = ed_model.mark_node_pool.get(parent_id);
let list_ast_node_id = parent.get_ast_node_id(); let list_ast_node_id = parent.get_ast_node_id();
let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id); let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id.to_expr_id()?);
match list_ast_node { match list_ast_node {
Expr2::List { Expr2::List {
@ -118,11 +119,11 @@ pub fn add_blank_child(
let blank_elt = Expr2::Blank; let blank_elt = Expr2::Blank;
let blank_elt_id = ed_model.module.env.pool.add(blank_elt); let blank_elt_id = ed_model.module.env.pool.add(blank_elt);
Ok((blank_elt_id, list_ast_node_id, parent_id)) Ok((blank_elt_id, list_ast_node_id.to_expr_id()?, parent_id))
} }
_ => UnexpectedASTNode { _ => UnexpectedASTNode {
required_node_type: "List".to_string(), required_node_type: "List".to_string(),
encountered_node_type: expr2_to_string(ast_node_id, ed_model.module.env.pool), encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool),
} }
.fail(), .fail(),
} }
@ -159,7 +160,7 @@ pub fn add_blank_child(
} }
_ => UnexpectedASTNode { _ => UnexpectedASTNode {
required_node_type: "List".to_string(), required_node_type: "List".to_string(),
encountered_node_type: expr2_to_string(ast_node_id, ed_model.module.env.pool), encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool),
} }
.fail(), .fail(),
}?; }?;
@ -173,7 +174,7 @@ pub fn add_blank_child(
ed_model, ed_model,
)?; )?;
let parent = ed_model.markup_node_pool.get_mut(parent_id); let parent = ed_model.mark_node_pool.get_mut(parent_id);
for (indx, child) in new_mark_children.iter().enumerate() { for (indx, child) in new_mark_children.iter().enumerate() {
parent.add_child_at_index(new_child_index + indx, *child)?; parent.add_child_at_index(new_child_index + indx, *child)?;
@ -191,35 +192,26 @@ pub fn update_mark_children(
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<Vec<MarkNodeId>> { ) -> EdResult<Vec<MarkNodeId>> {
let blank_mark_node = MarkupNode::Blank { let blank_mark_node_id = ed_model.add_mark_node(new_blank_mn(
ast_node_id: blank_elt_id, ASTNodeId::AExprId(blank_elt_id),
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt, parent_id_opt,
}; ));
let blank_mark_node_id = ed_model.markup_node_pool.add(blank_mark_node);
let mut children: Vec<MarkNodeId> = vec![]; let mut children: Vec<MarkNodeId> = vec![];
if new_child_index > 1 { if new_child_index > 1 {
let comma_mark_node = MarkupNode::Text { let comma_mark_node_id =
content: nodes::COMMA.to_owned(), ed_model.add_mark_node(new_comma_mn(list_ast_node_id, parent_id_opt));
ast_node_id: list_ast_node_id,
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt,
};
let comma_mark_node_id = ed_model.markup_node_pool.add(comma_mark_node);
ed_model.simple_move_carets_right(nodes::COMMA.len()); ed_model.simple_move_carets_right(nodes::COMMA.len());
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::COMMA, nodes::COMMA,
comma_mark_node_id, comma_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
children.push(comma_mark_node_id); children.push(comma_mark_node_id);
@ -234,11 +226,13 @@ pub fn update_mark_children(
}; };
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column + comma_shift, old_caret_pos.column + comma_shift,
nodes::BLANK_PLACEHOLDER, nodes::BLANK_PLACEHOLDER,
blank_mark_node_id, blank_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(children) Ok(children)

View file

@ -10,7 +10,7 @@ pub fn update_invalid_lookup(
input_str: &str, input_str: &str,
old_pool_str: &PoolStr, old_pool_str: &PoolStr,
curr_mark_node_id: MarkNodeId, curr_mark_node_id: MarkNodeId,
ast_node_id: ExprId, expr_id: ExprId,
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) { if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) {
@ -32,10 +32,10 @@ pub fn update_invalid_lookup(
.module .module
.env .env
.pool .pool
.set(ast_node_id, Expr2::InvalidLookup(new_pool_str)); .set(expr_id, Expr2::InvalidLookup(new_pool_str));
// update MarkupNode // update MarkupNode
let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?; let content_str_mut = curr_mark_node_mut.get_content_mut()?;
content_str_mut.insert_str(caret_offset, input_str); content_str_mut.insert_str(caret_offset, input_str);
@ -43,11 +43,13 @@ pub fn update_invalid_lookup(
ed_model.simple_move_carets_right(input_str.len()); ed_model.simple_move_carets_right(input_str.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
input_str, input_str,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)

View file

@ -1,10 +1,13 @@
pub mod app_model; pub mod app_model;
pub mod app_update; pub mod app_update;
mod break_line;
pub mod ed_model; pub mod ed_model;
pub mod ed_update; pub mod ed_update;
pub mod ed_view; pub mod ed_view;
mod int_update; mod int_update;
mod let_update;
mod list_update; mod list_update;
mod lookup_update; mod lookup_update;
mod record_update; mod record_update;
mod string_update; mod string_update;
pub mod tld_value_update;

View file

@ -2,6 +2,9 @@ use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::MissingParent;
use crate::editor::ed_error::RecordWithoutFields; use crate::editor::ed_error::RecordWithoutFields;
use crate::editor::markup::attribute::Attributes; use crate::editor::markup::attribute::Attributes;
use crate::editor::markup::common_nodes::new_blank_mn;
use crate::editor::markup::common_nodes::new_left_accolade_mn;
use crate::editor::markup::common_nodes::new_right_accolade_mn;
use crate::editor::markup::nodes; use crate::editor::markup::nodes;
use crate::editor::markup::nodes::MarkupNode; use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::app_update::InputOutcome;
@ -12,6 +15,7 @@ use crate::editor::slow_pool::MarkNodeId;
use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::util::index_of; use crate::editor::util::index_of;
use crate::lang::ast::{Expr2, ExprId, RecordField}; use crate::lang::ast::{Expr2, ExprId, RecordField};
use crate::lang::parse::ASTNodeId;
use crate::lang::pool::{PoolStr, PoolVec}; use crate::lang::pool::{PoolStr, PoolVec};
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
use snafu::OptionExt; use snafu::OptionExt;
@ -26,61 +30,44 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let ast_pool = &mut ed_model.module.env.pool; let ast_pool = &mut ed_model.module.env.pool;
let expr2_node = Expr2::EmptyRecord; let expr2_node = Expr2::EmptyRecord;
let mark_node_pool = &mut ed_model.markup_node_pool; ast_pool.set(ast_node_id.to_expr_id()?, expr2_node);
ast_pool.set(ast_node_id, expr2_node); let left_bracket_node_id = ed_model.add_mark_node(new_left_accolade_mn(
ast_node_id.to_expr_id()?,
Some(curr_mark_node_id),
));
let left_bracket_node = MarkupNode::Text { let right_bracket_node_id = ed_model.add_mark_node(new_right_accolade_mn(
content: nodes::LEFT_ACCOLADE.to_owned(), ast_node_id.to_expr_id()?,
ast_node_id, Some(curr_mark_node_id),
syn_high_style: HighlightStyle::Bracket, ));
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one
};
let left_bracket_node_id = mark_node_pool.add(left_bracket_node);
let right_bracket_node = MarkupNode::Text {
content: nodes::RIGHT_ACCOLADE.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one
};
let right_bracket_node_id = mark_node_pool.add(right_bracket_node);
let nested_node = MarkupNode::Nested { let nested_node = MarkupNode::Nested {
ast_node_id, ast_node_id,
children_ids: vec![left_bracket_node_id, right_bracket_node_id], children_ids: vec![left_bracket_node_id, right_bracket_node_id],
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
if is_blank_node { if is_blank_node {
mark_node_pool.replace_node(curr_mark_node_id, nested_node); ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, nested_node);
// remove data corresponding to Blank node ed_model.del_blank_expr_node(old_caret_pos)?;
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?;
ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len()); ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( ed_model.insert_all_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::LEFT_ACCOLADE, &[left_bracket_node_id, right_bracket_node_id],
left_bracket_node_id,
)?;
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column + nodes::LEFT_ACCOLADE.len(),
nodes::RIGHT_ACCOLADE,
right_bracket_node_id,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -100,7 +87,7 @@ pub fn update_empty_record(
if input_chars.all(|ch| ch.is_ascii_alphabetic()) if input_chars.all(|ch| ch.is_ascii_alphabetic())
&& input_chars.all(|ch| ch.is_ascii_lowercase()) && input_chars.all(|ch| ch.is_ascii_lowercase())
{ {
let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id);
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos,
@ -110,8 +97,8 @@ pub fn update_empty_record(
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE if prev_mark_node.get_content() == nodes::LEFT_ACCOLADE
&& curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE && curr_mark_node.get_content() == nodes::RIGHT_ACCOLADE
{ {
// update AST // update AST
let record_var = ed_model.module.env.var_store.fresh(); let record_var = ed_model.module.env.var_store.fresh();
@ -124,7 +111,11 @@ pub fn update_empty_record(
let new_ast_node = Expr2::Record { record_var, fields }; let new_ast_node = Expr2::Record { record_var, fields };
ed_model.module.env.pool.set(ast_node_id, new_ast_node); ed_model
.module
.env
.pool
.set(ast_node_id.to_expr_id()?, new_ast_node);
// update Markup // update Markup
@ -134,12 +125,13 @@ pub fn update_empty_record(
syn_high_style: HighlightStyle::RecordField, syn_high_style: HighlightStyle::RecordField,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0,
}; };
let record_field_node_id = ed_model.markup_node_pool.add(record_field_node); let record_field_node_id = ed_model.add_mark_node(record_field_node);
if let Some(parent_id) = parent_id_opt { if let Some(parent_id) = parent_id_opt {
let parent = ed_model.markup_node_pool.get_mut(parent_id); let parent = ed_model.mark_node_pool.get_mut(parent_id);
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?;
@ -155,11 +147,13 @@ pub fn update_empty_record(
ed_model.simple_move_carets_right(1); ed_model.simple_move_carets_right(1);
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, new_input,
record_field_node_id, record_field_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -183,23 +177,22 @@ pub fn update_record_colon(
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
if let Some(parent_id) = parent_id_opt { if let Some(parent_id) = parent_id_opt {
let curr_ast_node = ed_model.module.env.pool.get(ast_node_id); let curr_ast_node = ed_model.module.env.pool.get(ast_node_id.to_expr_id()?);
let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?;
if let Some(prev_mark_node_id) = prev_mark_node_id_opt { if let Some(prev_mark_node_id) = prev_mark_node_id_opt {
let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id);
let prev_ast_node = ed_model match prev_mark_node.get_ast_node_id() {
.module ASTNodeId::ADefId(_) => Ok(InputOutcome::Ignored),
.env ASTNodeId::AExprId(prev_expr_id) => {
.pool let prev_expr = ed_model.module.env.pool.get(prev_expr_id);
.get(prev_mark_node.get_ast_node_id());
// current and prev node should always point to record when in valid position to add ':' // current and prev node should always point to record when in valid position to add ':'
if matches!(prev_ast_node, Expr2::Record { .. }) if matches!(prev_expr, Expr2::Record { .. })
&& matches!(curr_ast_node, Expr2::Record { .. }) && matches!(curr_ast_node, Expr2::Record { .. })
{ {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.mark_node_pool);
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?;
@ -213,11 +206,13 @@ pub fn update_record_colon(
if ed_model.node_exists_at_caret() { if ed_model.node_exists_at_caret() {
let next_mark_node_id = let next_mark_node_id =
ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?;
let next_mark_node = ed_model.markup_node_pool.get(next_mark_node_id); let next_mark_node =
if next_mark_node.get_content()? == nodes::RIGHT_ACCOLADE { ed_model.mark_node_pool.get(next_mark_node_id);
if next_mark_node.get_content() == nodes::RIGHT_ACCOLADE {
// update AST node // update AST node
let new_field_val = Expr2::Blank; let new_field_val = Expr2::Blank;
let new_field_val_id = ed_model.module.env.pool.add(new_field_val); let new_field_val_id =
ed_model.module.env.pool.add(new_field_val);
let first_field_mut = fields let first_field_mut = fields
.iter_mut(ed_model.module.env.pool) .iter_mut(ed_model.module.env.pool)
@ -235,30 +230,31 @@ pub fn update_record_colon(
let record_colon_node = MarkupNode::Text { let record_colon_node = MarkupNode::Text {
content: record_colon.to_owned(), content: record_colon.to_owned(),
ast_node_id: record_ast_node_id, ast_node_id: ASTNodeId::AExprId(record_ast_node_id),
syn_high_style: HighlightStyle::Operator, syn_high_style: HighlightStyle::Operator,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt: Some(parent_id), parent_id_opt: Some(parent_id),
newlines_at_end: 0,
}; };
let record_colon_node_id = let record_colon_node_id =
ed_model.markup_node_pool.add(record_colon_node); ed_model.add_mark_node(record_colon_node);
ed_model ed_model
.markup_node_pool .mark_node_pool
.get_mut(parent_id) .get_mut(parent_id)
.add_child_at_index(new_child_index, record_colon_node_id)?; .add_child_at_index(
new_child_index,
let record_blank_node = MarkupNode::Blank { record_colon_node_id,
ast_node_id: new_field_val_id, )?;
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt: Some(parent_id),
};
let record_blank_node_id = let record_blank_node_id =
ed_model.markup_node_pool.add(record_blank_node); ed_model.add_mark_node(new_blank_mn(
ASTNodeId::AExprId(new_field_val_id),
Some(parent_id),
));
ed_model ed_model
.markup_node_pool .mark_node_pool
.get_mut(parent_id) .get_mut(parent_id)
.add_child_at_index( .add_child_at_index(
new_child_index + 1, new_child_index + 1,
@ -269,18 +265,10 @@ pub fn update_record_colon(
ed_model.simple_move_carets_right(record_colon.len()); ed_model.simple_move_carets_right(record_colon.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( ed_model.insert_all_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::COLON, &[record_colon_node_id, record_blank_node_id],
record_colon_node_id,
)?;
ed_model.insert_between_line(
old_caret_pos.line,
old_caret_pos.column + nodes::COLON.len(),
nodes::BLANK_PLACEHOLDER,
record_blank_node_id,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -296,6 +284,8 @@ pub fn update_record_colon(
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)
} }
}
}
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)
} }
@ -315,7 +305,7 @@ pub fn update_record_field(
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
// update MarkupNode // update MarkupNode
let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?; let content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
@ -337,11 +327,13 @@ pub fn update_record_field(
ed_model.simple_move_carets_right(new_input.len()); ed_model.simple_move_carets_right(new_input.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, new_input,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update AST Node // update AST Node

View file

@ -7,6 +7,7 @@ use crate::editor::mvc::ed_model::EdModel;
use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::get_node_context;
use crate::editor::mvc::ed_update::NodeContext; use crate::editor::mvc::ed_update::NodeContext;
use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::syntax_highlight::HighlightStyle;
use crate::lang::ast::update_str_expr;
use crate::lang::ast::ArrString; use crate::lang::ast::ArrString;
use crate::lang::ast::Expr2; use crate::lang::ast::Expr2;
use crate::lang::pool::PoolStr; use crate::lang::pool::PoolStr;
@ -27,7 +28,7 @@ pub fn update_small_string(
let new_input = &new_char.to_string(); let new_input = &new_char.to_string();
// update markup // update markup
let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?; let content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
@ -36,7 +37,7 @@ pub fn update_small_string(
if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() {
if old_array_str.len() < ArrString::capacity() { if old_array_str.len() < ArrString::capacity() {
if let Expr2::SmallStr(ref mut mut_array_str) = if let Expr2::SmallStr(ref mut mut_array_str) =
ed_model.module.env.pool.get_mut(ast_node_id) ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?)
{ {
// safe because we checked the length // safe because we checked the length
unsafe { unsafe {
@ -51,17 +52,23 @@ pub fn update_small_string(
let new_ast_node = Expr2::Str(PoolStr::new(&new_str, ed_model.module.env.pool)); let new_ast_node = Expr2::Str(PoolStr::new(&new_str, ed_model.module.env.pool));
ed_model.module.env.pool.set(ast_node_id, new_ast_node); ed_model
.module
.env
.pool
.set(ast_node_id.to_expr_id()?, new_ast_node);
} }
content_str_mut.insert_str(node_caret_offset, new_input); content_str_mut.insert_str(node_caret_offset, new_input);
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, new_input,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update caret // update caret
@ -73,11 +80,7 @@ pub fn update_small_string(
} }
} }
pub fn update_string( pub fn update_string(new_char: char, ed_model: &mut EdModel) -> EdResult<InputOutcome> {
new_input: &str,
old_pool_str: &PoolStr,
ed_model: &mut EdModel,
) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos,
curr_mark_node_id, curr_mark_node_id,
@ -87,31 +90,32 @@ pub fn update_string(
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
// update markup // update markup
let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?; let content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() {
content_str_mut.insert_str(node_caret_offset, new_input); content_str_mut.insert(node_caret_offset, new_char);
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, &new_char.to_string(),
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update ast // update ast
let mut new_string = old_pool_str.as_str(ed_model.module.env.pool).to_owned(); update_str_expr(
new_string.push_str(new_input); ast_node_id.to_expr_id()?,
new_char,
let new_pool_str = PoolStr::new(&new_string, &mut ed_model.module.env.pool); node_caret_offset - 1, // -1 because offset was calculated with quotes
let new_ast_node = Expr2::Str(new_pool_str); &mut ed_model.module.env.pool,
)?;
ed_model.module.env.pool.set(ast_node_id, new_ast_node);
// update caret // update caret
ed_model.simple_move_carets_right(1); ed_model.simple_move_carets_right(1);
@ -133,8 +137,13 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
if curr_mark_node.is_blank() { if curr_mark_node.is_blank() {
let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new()); let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new());
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
ed_model.module.env.pool.set(ast_node_id, new_expr2_node); ed_model
.module
.env
.pool
.set(ast_node_id.to_expr_id()?, new_expr2_node);
let new_string_node = MarkupNode::Text { let new_string_node = MarkupNode::Text {
content: nodes::STRING_QUOTES.to_owned(), content: nodes::STRING_QUOTES.to_owned(),
@ -142,21 +151,24 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
syn_high_style: HighlightStyle::String, syn_high_style: HighlightStyle::String,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
ed_model ed_model
.markup_node_pool .mark_node_pool
.replace_node(curr_mark_node_id, new_string_node); .replace_node(curr_mark_node_id, new_string_node);
// remove data corresponding to Blank node // remove data corresponding to Blank node
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; ed_model.del_blank_expr_node(old_caret_pos)?;
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::STRING_QUOTES, nodes::STRING_QUOTES,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
ed_model.simple_move_carets_right(1); ed_model.simple_move_carets_right(1);

View file

@ -0,0 +1,206 @@
use roc_module::symbol::{Interns, Symbol};
use crate::{
editor::{
ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound},
markup::{
attribute::Attributes,
common_nodes::{new_blank_mn_w_nls, new_equals_mn},
nodes::{set_parent_for_all, MarkupNode},
},
slow_pool::{MarkNodeId, SlowPool},
syntax_highlight::HighlightStyle,
},
lang::{
ast::{Def2, Expr2},
expr::Env,
parse::ASTNodeId,
pattern::{get_identifier_string, Pattern2},
pool::NodeId,
},
ui::text::text_pos::TextPos,
};
use super::{
app_update::InputOutcome,
ed_model::EdModel,
ed_update::{get_node_context, NodeContext},
};
// Top Level Defined Value. example: `main = "Hello, World!"`
pub fn tld_mark_node<'a>(
identifier_id: NodeId<Pattern2>,
expr_mark_node_id: MarkNodeId,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
env: &Env<'a>,
interns: &Interns,
) -> EdResult<MarkupNode> {
let pattern2 = env.pool.get(identifier_id);
let val_name = get_identifier_string(pattern2, interns)?;
let val_name_mn = MarkupNode::Text {
content: val_name,
ast_node_id,
syn_high_style: HighlightStyle::Variable,
attributes: Attributes::new(),
parent_id_opt: None,
newlines_at_end: 0,
};
let val_name_mn_id = mark_node_pool.add(val_name_mn);
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
let full_let_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id],
parent_id_opt: None,
newlines_at_end: 2,
};
Ok(full_let_node)
}
pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> {
let NodeContext {
old_caret_pos,
curr_mark_node_id,
curr_mark_node: _,
parent_id_opt: _,
ast_node_id,
} = get_node_context(ed_model)?;
let val_expr_node = Expr2::Blank;
let val_expr_id = ed_model.module.env.pool.add(val_expr_node);
let val_expr_mn = new_blank_mn_w_nls(ASTNodeId::AExprId(val_expr_id), None, 0);
let val_expr_mn_id = ed_model.mark_node_pool.add(val_expr_mn);
let val_name_string = new_char.to_string();
let ident_id = ed_model
.module
.env
.ident_ids
.add(val_name_string.clone().into());
let module_ident_ids_opt = ed_model
.loaded_module
.interns
.all_ident_ids
.get_mut(&ed_model.module.env.home);
if let Some(module_ident_ids_ref) = module_ident_ids_opt {
// this might create different IdentId for interns and env.ident_ids which may be a problem
module_ident_ids_ref.add(val_name_string.into());
} else {
KeyNotFound {
key_str: format!("{:?}", ed_model.module.env.home),
}
.fail()?
}
let val_symbol = Symbol::new(ed_model.module.env.home, ident_id);
let patt2 = Pattern2::Identifier(val_symbol);
let patt2_id = ed_model.module.env.pool.add(patt2);
let tld_mark_node = tld_mark_node(
patt2_id,
val_expr_mn_id,
ast_node_id,
&mut ed_model.mark_node_pool,
&ed_model.module.env,
&ed_model.loaded_module.interns,
)?;
let new_ast_node = Def2::ValueDef {
identifier_id: patt2_id,
expr_id: val_expr_id,
};
ed_model
.module
.env
.pool
.set(ast_node_id.to_def_id()?, new_ast_node);
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, tld_mark_node);
set_parent_for_all(curr_mark_node_id, &mut ed_model.mark_node_pool);
// remove data corresponding to old Blank node
ed_model.del_line(old_caret_pos.line + 1)?;
ed_model.del_line(old_caret_pos.line)?;
let char_len = 1;
ed_model.simple_move_carets_right(char_len);
let mut curr_line = old_caret_pos.line;
let mut curr_column = old_caret_pos.column;
EdModel::insert_mark_node_between_line(
&mut curr_line,
&mut curr_column,
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
&ed_model.mark_node_pool,
)?;
Ok(InputOutcome::Accepted)
}
pub fn update_tld_val_name(
val_name_mn_id: MarkNodeId,
old_caret_pos: TextPos,
ed_model: &mut EdModel,
new_char: &char,
) -> EdResult<InputOutcome> {
if new_char.is_ascii_alphanumeric() {
// update markup
let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id);
let content_str_mut = val_name_mn_mut.get_content_mut()?;
let old_val_name = content_str_mut.clone();
let node_caret_offset = ed_model
.grid_node_map
.get_offset_to_node_id(old_caret_pos, val_name_mn_id)?;
if node_caret_offset <= content_str_mut.len() {
content_str_mut.insert(node_caret_offset, *new_char);
let update_val_name_res = ed_model
.module
.env
.ident_ids
.update_key(&old_val_name, content_str_mut);
if let Err(err_str) = update_val_name_res {
FailedToUpdateIdentIdName { err_str }.fail()?;
}
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
&new_char.to_string(),
val_name_mn_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
ed_model.simple_move_caret_right(old_caret_pos, 1);
Ok(InputOutcome::Accepted)
} else {
Ok(InputOutcome::Ignored)
}
} else {
Ok(InputOutcome::Ignored)
}
}

View file

@ -1,4 +1,5 @@
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
use super::slow_pool::MarkNodeId;
use crate::editor::mvc::ed_view::RenderedWgpu; use crate::editor::mvc::ed_view::RenderedWgpu;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get};
@ -10,37 +11,49 @@ use winit::dpi::PhysicalSize;
use crate::{editor::config::Config, graphics::colors}; use crate::{editor::config::Config, graphics::colors};
pub fn build_code_graphics<'a>( pub fn build_code_graphics<'a>(
markup_node: &'a MarkupNode, markup_ids: &[MarkNodeId],
size: &PhysicalSize<u32>, size: &PhysicalSize<u32>,
txt_coords: Vector2<f32>, txt_coords: Vector2<f32>,
config: &Config, config: &Config,
glyph_dim_rect: Rect, glyph_dim_rect: Rect,
markup_node_pool: &'a SlowPool, mark_node_pool: &'a SlowPool,
) -> EdResult<RenderedWgpu> { ) -> EdResult<RenderedWgpu> {
let area_bounds = (size.width as f32, size.height as f32); let area_bounds = (size.width as f32, size.height as f32);
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
let mut rendered_wgpu = RenderedWgpu::new(); let mut rendered_wgpu = RenderedWgpu::new();
let (glyph_text_vec, rects) = markup_to_wgpu( let mut all_glyph_text_vec = vec![];
markup_node, let mut all_rects = vec![];
let mut txt_row_col = (0, 0);
for markup_id in markup_ids.iter() {
let mark_node = mark_node_pool.get(*markup_id);
let (mut glyph_text_vec, mut rects) = markup_to_wgpu(
mark_node,
&CodeStyle { &CodeStyle {
ed_theme: &config.ed_theme, ed_theme: &config.ed_theme,
font_size: config.code_font_size, font_size: config.code_font_size,
txt_coords, txt_coords,
glyph_dim_rect, glyph_dim_rect,
}, },
markup_node_pool, &mut txt_row_col,
mark_node_pool,
)?; )?;
all_glyph_text_vec.append(&mut glyph_text_vec);
all_rects.append(&mut rects)
}
let section = gr_text::owned_section_from_glyph_texts( let section = gr_text::owned_section_from_glyph_texts(
glyph_text_vec, all_glyph_text_vec,
txt_coords.into(), txt_coords.into(),
area_bounds, area_bounds,
layout, layout,
); );
rendered_wgpu.add_rects(rects); rendered_wgpu.add_rects_behind(all_rects); // currently only rects for Blank
rendered_wgpu.add_text(section); rendered_wgpu.add_text_behind(section);
Ok(rendered_wgpu) Ok(rendered_wgpu)
} }
@ -55,51 +68,57 @@ struct CodeStyle<'a> {
fn markup_to_wgpu<'a>( fn markup_to_wgpu<'a>(
markup_node: &'a MarkupNode, markup_node: &'a MarkupNode,
code_style: &CodeStyle, code_style: &CodeStyle,
markup_node_pool: &'a SlowPool, txt_row_col: &mut (usize, usize),
mark_node_pool: &'a SlowPool,
) -> EdResult<(Vec<glyph_brush::OwnedText>, Vec<Rect>)> { ) -> EdResult<(Vec<glyph_brush::OwnedText>, Vec<Rect>)> {
let mut wgpu_texts: Vec<glyph_brush::OwnedText> = Vec::new(); let mut wgpu_texts: Vec<glyph_brush::OwnedText> = Vec::new();
let mut rects: Vec<Rect> = Vec::new(); let mut rects: Vec<Rect> = Vec::new();
let mut txt_row_col = (0, 0);
markup_to_wgpu_helper( markup_to_wgpu_helper(
markup_node, markup_node,
&mut wgpu_texts, &mut wgpu_texts,
&mut rects, &mut rects,
code_style, code_style,
&mut txt_row_col, txt_row_col,
markup_node_pool, mark_node_pool,
)?; )?;
Ok((wgpu_texts, rects)) Ok((wgpu_texts, rects))
} }
// TODO use text_row
fn markup_to_wgpu_helper<'a>( fn markup_to_wgpu_helper<'a>(
markup_node: &'a MarkupNode, markup_node: &'a MarkupNode,
wgpu_texts: &mut Vec<glyph_brush::OwnedText>, wgpu_texts: &mut Vec<glyph_brush::OwnedText>,
rects: &mut Vec<Rect>, rects: &mut Vec<Rect>,
code_style: &CodeStyle, code_style: &CodeStyle,
txt_row_col: &mut (usize, usize), txt_row_col: &mut (usize, usize),
markup_node_pool: &'a SlowPool, mark_node_pool: &'a SlowPool,
) -> EdResult<()> { ) -> EdResult<()> {
match markup_node { match markup_node {
MarkupNode::Nested { MarkupNode::Nested {
ast_node_id: _, ast_node_id: _,
children_ids, children_ids,
parent_id_opt: _, parent_id_opt: _,
newlines_at_end,
} => { } => {
for child_id in children_ids.iter() { for child_id in children_ids.iter() {
let child = markup_node_pool.get(*child_id); let child = mark_node_pool.get(*child_id);
markup_to_wgpu_helper( markup_to_wgpu_helper(
child, child,
wgpu_texts, wgpu_texts,
rects, rects,
code_style, code_style,
txt_row_col, txt_row_col,
markup_node_pool, mark_node_pool,
)?; )?;
} }
for _ in 0..*newlines_at_end {
wgpu_texts.push(newline(code_style.font_size));
txt_row_col.0 += 1;
txt_row_col.1 = 0;
}
} }
MarkupNode::Text { MarkupNode::Text {
content, content,
@ -107,14 +126,23 @@ fn markup_to_wgpu_helper<'a>(
syn_high_style, syn_high_style,
attributes: _, attributes: _,
parent_id_opt: _, parent_id_opt: _,
newlines_at_end,
} => { } => {
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?;
let glyph_text = glyph_brush::OwnedText::new(content) let full_content = markup_node.get_full_content();
let glyph_text = glyph_brush::OwnedText::new(full_content)
.with_color(colors::to_slice(*highlight_color)) .with_color(colors::to_slice(*highlight_color))
.with_scale(code_style.font_size); .with_scale(code_style.font_size);
txt_row_col.1 += content.len(); txt_row_col.1 += content.len();
for _ in 0..*newlines_at_end {
txt_row_col.0 += 1;
txt_row_col.1 = 0;
}
wgpu_texts.push(glyph_text); wgpu_texts.push(glyph_text);
} }
MarkupNode::Blank { MarkupNode::Blank {
@ -122,8 +150,11 @@ fn markup_to_wgpu_helper<'a>(
attributes: _, attributes: _,
syn_high_style, syn_high_style,
parent_id_opt: _, parent_id_opt: _,
newlines_at_end,
} => { } => {
let glyph_text = glyph_brush::OwnedText::new(BLANK_PLACEHOLDER) let full_content = markup_node.get_full_content();
let glyph_text = glyph_brush::OwnedText::new(full_content)
.with_color(colors::to_slice(colors::WHITE)) .with_color(colors::to_slice(colors::WHITE))
.with_scale(code_style.font_size); .with_scale(code_style.font_size);
@ -132,7 +163,7 @@ fn markup_to_wgpu_helper<'a>(
let char_width = code_style.glyph_dim_rect.width; let char_width = code_style.glyph_dim_rect.width;
let char_height = code_style.glyph_dim_rect.height; let char_height = code_style.glyph_dim_rect.height;
let hole_rect = Rect { let blank_rect = Rect {
top_left_coords: ( top_left_coords: (
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width, code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
code_style.txt_coords.y code_style.txt_coords.y
@ -144,12 +175,21 @@ fn markup_to_wgpu_helper<'a>(
height: char_height, height: char_height,
color: *highlight_color, color: *highlight_color,
}; };
rects.push(hole_rect); rects.push(blank_rect);
txt_row_col.1 += BLANK_PLACEHOLDER.len(); txt_row_col.1 += BLANK_PLACEHOLDER.len();
wgpu_texts.push(glyph_text); wgpu_texts.push(glyph_text);
for _ in 0..*newlines_at_end {
txt_row_col.0 += 1;
txt_row_col.1 = 0;
}
} }
}; };
Ok(()) Ok(())
} }
fn newline(font_size: f32) -> glyph_brush::OwnedText {
glyph_brush::OwnedText::new("\n").with_scale(font_size)
}

View file

@ -4,7 +4,7 @@ use crate::editor::mvc::ed_model::EdModel;
use crate::graphics::colors; use crate::graphics::colors;
use crate::graphics::colors::from_hsb; use crate::graphics::colors::from_hsb;
use crate::graphics::primitives::text as gr_text; use crate::graphics::primitives::text as gr_text;
use crate::lang::ast::expr2_to_string; use crate::lang::ast::def2_to_string;
use cgmath::Vector2; use cgmath::Vector2;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
@ -19,36 +19,49 @@ pub fn build_debug_graphics(
let area_bounds = (size.width as f32, size.height as f32); let area_bounds = (size.width as f32, size.height as f32);
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
let debug_txt_coords: Vector2<f32> = (txt_coords.x, txt_coords.y * 3.0).into(); let debug_txt_coords: Vector2<f32> = (txt_coords.x * 20.0, txt_coords.y).into();
let carets_text =
glyph_brush::OwnedText::new(format!("carets: {:?}\n\n", ed_model.get_carets()))
.with_color(colors::to_slice(from_hsb(0, 0, 100)))
.with_scale(config.debug_font_size);
let grid_node_map_text = glyph_brush::OwnedText::new(format!("{}", ed_model.grid_node_map)) let grid_node_map_text = glyph_brush::OwnedText::new(format!("{}", ed_model.grid_node_map))
.with_color(colors::to_slice(from_hsb(20, 41, 100))) .with_color(colors::to_slice(from_hsb(20, 41, 100)))
.with_scale(config.code_font_size); .with_scale(config.debug_font_size);
let code_lines_text = glyph_brush::OwnedText::new(format!("{}", ed_model.code_lines)) let code_lines_text = glyph_brush::OwnedText::new(format!("{}", ed_model.code_lines))
.with_color(colors::to_slice(from_hsb(0, 49, 96))) .with_color(colors::to_slice(from_hsb(0, 49, 96)))
.with_scale(config.code_font_size); .with_scale(config.debug_font_size);
let mark_node_tree_text = glyph_brush::OwnedText::new(tree_as_string( let mut mark_node_trees_string = "\nmark node trees:".to_owned();
ed_model.markup_root_id,
&ed_model.markup_node_pool, for mark_id in ed_model.markup_ids[1..].iter() {
)) // 1.. -> skip header
mark_node_trees_string.push_str(&tree_as_string(*mark_id, &ed_model.mark_node_pool));
}
let mark_node_tree_text = glyph_brush::OwnedText::new(mark_node_trees_string)
.with_color(colors::to_slice(from_hsb(266, 31, 96))) .with_color(colors::to_slice(from_hsb(266, 31, 96)))
.with_scale(config.code_font_size); .with_scale(config.debug_font_size);
let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.markup_node_pool)) let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.mark_node_pool))
.with_color(colors::to_slice(from_hsb(110, 45, 82))) .with_color(colors::to_slice(from_hsb(110, 45, 82)))
.with_scale(config.code_font_size); .with_scale(config.debug_font_size);
let ast_node_text = glyph_brush::OwnedText::new(format!( let mut ast_node_text_str = "AST:\n".to_owned();
"\n\n(ast_root)\n{}",
expr2_to_string(ed_model.module.ast_root_id, ed_model.module.env.pool) for def_id in ed_model.module.ast.def_ids.iter() {
)) ast_node_text_str.push_str(&def2_to_string(*def_id, ed_model.module.env.pool))
}
let ast_node_text = glyph_brush::OwnedText::new(ast_node_text_str)
.with_color(colors::to_slice(from_hsb(211, 80, 100))) .with_color(colors::to_slice(from_hsb(211, 80, 100)))
.with_scale(config.code_font_size); .with_scale(config.debug_font_size);
let section = gr_text::owned_section_from_glyph_texts( let section = gr_text::owned_section_from_glyph_texts(
vec![ vec![
carets_text,
grid_node_map_text, grid_node_map_text,
code_lines_text, code_lines_text,
mark_node_tree_text, mark_node_tree_text,

View file

@ -1,3 +1,27 @@
pub const NOTHING_OPENED: &str = "Opening files is not yet supported, execute `cargo run edit` from the root folder of the repo to try the editor."; #![allow(dead_code)]
pub const START_TIP: &str =
"Start by typing '[', '{', '\"' or a number.\nInput chars that would create parse errors will be ignored."; pub const NOTHING_OPENED: &str =
"Execute `cargo run edit` from the root folder of the repo to try the editor.";
pub const START_TIP: &str = r#"Currently supported: lists, records, string, numbers and value definitions.
Use `Ctrl+Shift+Up` or `Cmd+Shift+Up` to select surrounding expression.
Use backspace after `Ctrl+Shift+Up` to delete the selected expression.
`Ctrl+S` or `Cmd+S` to save.
`Ctrl+R` to run.
Input chars that would create parse errors or change formatting will be ignored.
For convenience and consistency, there is only one way to format roc.
"#;
pub const HELLO_WORLD: &str = r#"
app "test-app"
packages { base: "platform" }
imports []
provides [ main ] to base
main = "Hello, world!"
"#;

View file

@ -63,7 +63,7 @@ impl fmt::Display for SlowPool {
"{}: {} ({}) ast_id {:?} {}", "{}: {} ({}) ast_id {:?} {}",
index, index,
node.node_type_as_string(), node.node_type_as_string(),
node.get_content().unwrap_or_else(|_| "".to_string()), node.get_content(),
ast_node_id.parse::<usize>().unwrap(), ast_node_id.parse::<usize>().unwrap(),
child_str child_str
)?; )?;

View file

@ -1 +1 @@
pub const CODE_TXT_XY: (f32, f32) = (40.0, 130.0); pub const CODE_TXT_XY: (f32, f32) = (40.0, 350.0);

View file

@ -14,6 +14,8 @@ pub enum HighlightStyle {
PackageRelated, // app, packages, imports, exposes, provides... PackageRelated, // app, packages, imports, exposes, provides...
Variable, Variable,
RecordField, RecordField,
Import,
Provides,
Blank, Blank,
} }
@ -31,6 +33,8 @@ pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
(PackageRelated, gr_colors::WHITE), (PackageRelated, gr_colors::WHITE),
(Variable, gr_colors::WHITE), (Variable, gr_colors::WHITE),
(RecordField, from_hsb(258, 50, 90)), (RecordField, from_hsb(258, 50, 90)),
(Import, from_hsb(185, 50, 75)),
(Provides, from_hsb(185, 50, 75)),
(Blank, from_hsb(258, 50, 90)), (Blank, from_hsb(258, 50, 90)),
// comment from_hsb(285, 6, 47) or 186, 35, 40 // comment from_hsb(285, 6, 47) or 186, 35, 40
] ]

View file

@ -3,6 +3,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use crate::editor::ed_error::{EdResult, UnexpectedASTNode};
use crate::lang::pattern::{Pattern2, PatternId}; use crate::lang::pattern::{Pattern2, PatternId};
use crate::lang::pool::Pool; use crate::lang::pool::Pool;
use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone};
@ -222,6 +223,17 @@ pub enum Expr2 {
RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */),
} }
// A top level definition, not inside a function. For example: `main = "Hello, world!"`
#[derive(Debug)]
pub enum Def2 {
// ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!"
ValueDef {
identifier_id: NodeId<Pattern2>,
expr_id: NodeId<Expr2>,
},
Blank,
}
#[derive(Debug)] #[derive(Debug)]
pub enum ValueDef { pub enum ValueDef {
WithAnnotation { WithAnnotation {
@ -267,6 +279,48 @@ impl ShallowClone for ValueDef {
} }
} }
impl ValueDef {
pub fn get_expr_id(&self) -> ExprId {
match self {
ValueDef::WithAnnotation { expr_id, .. } => *expr_id,
ValueDef::NoAnnotation { expr_id, .. } => *expr_id,
}
}
pub fn get_pattern_id(&self) -> NodeId<Pattern2> {
match self {
ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id,
ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id,
}
}
}
pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String {
match val_def {
ValueDef::WithAnnotation {
pattern_id,
expr_id,
type_id,
rigids,
expr_var,
} => {
format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var)
}
ValueDef::NoAnnotation {
pattern_id,
expr_id,
expr_var,
} => {
format!(
"NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}",
pool.get(*pattern_id),
expr2_to_string(*expr_id, pool),
expr_var
)
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum FunctionDef { pub enum FunctionDef {
WithAnnotation { WithAnnotation {
@ -402,7 +456,11 @@ pub struct WhenBranch {
// TODO make the inner types private? // TODO make the inner types private?
pub type ExprId = NodeId<Expr2>; pub type ExprId = NodeId<Expr2>;
pub type DefId = NodeId<Def2>;
use RecordField::*; use RecordField::*;
use super::parse::ASTNodeId;
impl RecordField { impl RecordField {
pub fn get_record_field_var(&self) -> &Variable { pub fn get_record_field_var(&self) -> &Variable {
match self { match self {
@ -437,6 +495,13 @@ impl RecordField {
} }
} }
pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String {
match node_id {
ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool),
ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool),
}
}
pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String {
let mut full_string = String::new(); let mut full_string = String::new();
let expr2 = pool.get(node_id); let expr2 = pool.get(node_id);
@ -550,16 +615,118 @@ fn expr2_to_string_helper(
Expr2::SmallInt { text, .. } => { Expr2::SmallInt { text, .. } => {
out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); out_string.push_str(&format!("SmallInt({})", text.as_str(pool)));
} }
Expr2::LetValue {
def_id, body_id, ..
} => {
out_string.push_str(&format!(
"LetValue(def_id: >>{:?}), body_id: >>{:?})",
value_def_to_string(pool.get(*def_id), pool),
pool.get(*body_id)
));
}
other => todo!("Implement for {:?}", other), other => todo!("Implement for {:?}", other),
} }
out_string.push('\n'); out_string.push('\n');
} }
pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String {
let mut full_string = String::new();
let def2 = pool.get(node_id);
match def2 {
Def2::ValueDef {
identifier_id,
expr_id,
} => {
full_string.push_str(&format!(
"Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})",
pool.get(*identifier_id),
expr2_to_string(*expr_id, pool)
));
}
Def2::Blank => {
full_string.push_str("Def2::Blank");
}
}
full_string
}
fn var_to_string(some_var: &Variable, indent_level: usize) -> String { fn var_to_string(some_var: &Variable, indent_level: usize) -> String {
format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var)
} }
// get string from SmallStr or Str
pub fn get_string_from_expr2(node_id: ExprId, pool: &Pool) -> EdResult<String> {
match pool.get(node_id) {
Expr2::SmallStr(arr_string) => Ok(arr_string.as_str().to_string()),
Expr2::Str(pool_str) => Ok(pool_str.as_str(pool).to_owned()),
other => UnexpectedASTNode {
required_node_type: "SmallStr or Str",
encountered_node_type: format!("{:?}", other),
}
.fail()?,
}
}
pub fn update_str_expr(
node_id: ExprId,
new_char: char,
insert_index: usize,
pool: &mut Pool,
) -> EdResult<()> {
let str_expr = pool.get_mut(node_id);
enum Either {
MyString(String),
MyPoolStr(PoolStr),
Done,
}
let insert_either = match str_expr {
Expr2::SmallStr(arr_string) => {
let insert_res = arr_string.try_insert(insert_index as u8, new_char);
match insert_res {
Ok(_) => Either::Done,
_ => {
let mut new_string = arr_string.as_str().to_string();
new_string.insert(insert_index, new_char);
Either::MyString(new_string)
}
}
}
Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str),
other => UnexpectedASTNode {
required_node_type: "SmallStr or Str",
encountered_node_type: format!("{:?}", other),
}
.fail()?,
};
match insert_either {
Either::MyString(new_string) => {
let new_pool_str = PoolStr::new(&new_string, pool);
pool.set(node_id, Expr2::Str(new_pool_str))
}
Either::MyPoolStr(old_pool_str) => {
let mut new_string = old_pool_str.as_str(pool).to_owned();
new_string.insert(insert_index, new_char);
let new_pool_str = PoolStr::new(&new_string, pool);
pool.set(node_id, Expr2::Str(new_pool_str))
}
Either::Done => (),
}
Ok(())
}
#[test] #[test]
fn size_of_expr() { fn size_of_expr() {
assert_eq!(std::mem::size_of::<Expr2>(), crate::lang::pool::NODE_BYTES); assert_eq!(std::mem::size_of::<Expr2>(), crate::lang::pool::NODE_BYTES);

View file

@ -3,10 +3,11 @@
#![allow(unused_imports)] #![allow(unused_imports)]
use bumpalo::{collections::Vec as BumpVec, Bump}; use bumpalo::{collections::Vec as BumpVec, Bump};
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::FromIterator;
use crate::lang::ast::{ use crate::lang::ast::{
expr2_to_string, ClosureExtra, Expr2, ExprId, FloatVal, IntStyle, IntVal, RecordField, expr2_to_string, value_def_to_string, ClosureExtra, Def2, Expr2, ExprId, FloatVal, IntStyle,
WhenBranch, IntVal, RecordField, ValueDef, WhenBranch,
}; };
use crate::lang::def::{ use crate::lang::def::{
canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References,
@ -288,6 +289,25 @@ pub fn to_expr_id<'a>(
(env.add(expr, region), output) (env.add(expr, region), output)
} }
pub fn str_to_def2<'a>(
arena: &'a Bump,
input: &'a str,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> Result<Vec<Def2>, SyntaxError<'a>> {
match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) {
Ok(vec_loc_def) => Ok(defs_to_defs2(
arena,
env,
scope,
arena.alloc(vec_loc_def),
region,
)),
Err(fail) => Err(fail),
}
}
pub fn str_to_expr2<'a>( pub fn str_to_expr2<'a>(
arena: &'a Bump, arena: &'a Bump,
input: &'a str, input: &'a str,
@ -296,20 +316,23 @@ pub fn str_to_expr2<'a>(
region: Region, region: Region,
) -> Result<(Expr2, self::Output), SyntaxError<'a>> { ) -> Result<(Expr2, self::Output), SyntaxError<'a>> {
match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) {
Ok(loc_expr) => { Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)),
let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr));
Ok(to_expr2(
env,
scope,
arena.alloc(desugared_loc_expr.value),
region,
))
}
Err(fail) => Err(fail), Err(fail) => Err(fail),
} }
} }
fn loc_expr_to_expr2<'a>(
arena: &'a Bump,
loc_expr: Located<Expr<'a>>,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> (Expr2, self::Output) {
let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr));
to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region)
}
pub fn to_expr2<'a>( pub fn to_expr2<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, scope: &mut Scope,
@ -967,6 +990,74 @@ pub fn to_expr2<'a>(
} }
} }
pub fn defs_to_defs2<'a>(
arena: &'a Bump,
env: &mut Env<'a>,
scope: &mut Scope,
parsed_defs: &'a BumpVec<roc_region::all::Loc<roc_parse::ast::Def<'a>>>,
region: Region,
) -> Vec<Def2> {
use roc_parse::ast::Expr::*;
parsed_defs
.iter()
.map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region))
.collect()
}
pub fn to_def2_from_def<'a>(
arena: &'a Bump,
env: &mut Env<'a>,
scope: &mut Scope,
parsed_def: &'a roc_parse::ast::Def<'a>,
region: Region,
) -> Def2 {
use roc_parse::ast::Def::*;
match parsed_def {
SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region),
SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region),
Body(&loc_pattern, &loc_expr) => {
// TODO loc_pattern use identifier
let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0;
let expr_id = env.pool.add(expr2);
use roc_parse::ast::Pattern::*;
match loc_pattern.value {
Identifier(_) => {
let (_, pattern2) = to_pattern2(
env,
scope,
PatternType::TopLevelDef,
&loc_pattern.value,
region,
);
let pattern_id = env.pool.add(pattern2);
// TODO support with annotation
Def2::ValueDef {
identifier_id: pattern_id,
expr_id,
}
}
other => {
unimplemented!(
"I don't yet know how to convert the pattern {:?} into an expr2",
other
)
}
}
}
other => {
unimplemented!(
"I don't know how to make an expr2 from this def yet: {:?}",
other
)
}
}
}
fn flatten_str_literal<'a>( fn flatten_str_literal<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, scope: &mut Scope,
@ -1412,6 +1503,7 @@ fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret
Def::AnnotationOnly { .. } => todo!(), Def::AnnotationOnly { .. } => todo!(),
Def::Value(value_def) => { Def::Value(value_def) => {
let def_id = pool.add(value_def); let def_id = pool.add(value_def);
let body_id = pool.add(ret); let body_id = pool.add(ret);
Expr2::LetValue { Expr2::LetValue {

View file

@ -3,7 +3,8 @@ pub mod constrain;
mod def; mod def;
pub mod expr; pub mod expr;
mod module; mod module;
mod pattern; pub mod parse;
pub mod pattern;
pub mod pool; pub mod pool;
pub mod roc_file; pub mod roc_file;
pub mod scope; pub mod scope;

98
editor/src/lang/parse.rs Normal file
View file

@ -0,0 +1,98 @@
use std::fmt::Debug;
use crate::{
editor::ed_error::ASTNodeIdWithoutExprId, editor::ed_error::EdResult, lang::scope::Scope,
};
use bumpalo::Bump;
use roc_parse::parser::SyntaxError;
use roc_region::all::Region;
use super::{
ast::{DefId, Expr2, ExprId},
expr::{str_to_def2, Env},
};
#[derive(Debug)]
pub struct AST {
pub header: AppHeader,
pub def_ids: Vec<DefId>,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ASTNodeId {
ADefId(DefId),
AExprId(ExprId),
}
impl ASTNodeId {
pub fn to_expr_id(&self) -> EdResult<ExprId> {
match self {
ASTNodeId::AExprId(expr_id) => Ok(*expr_id),
_ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?,
}
}
pub fn to_def_id(&self) -> EdResult<DefId> {
match self {
ASTNodeId::ADefId(def_id) => Ok(*def_id),
_ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?,
}
}
}
#[derive(Debug)]
pub struct AppHeader {
pub app_name: String,
pub packages_base: String,
pub imports: Vec<String>,
pub provides: Vec<String>,
pub ast_node_id: ExprId, // TODO probably want to use HeaderId
}
impl AST {
pub fn parse_from_string<'a>(
code_str: &'a str,
env: &mut Env<'a>,
ast_arena: &'a Bump,
) -> Result<AST, SyntaxError<'a>> {
let blank_line_indx = code_str
.find("\n\n")
.expect("I was expecting a double newline to split header and rest of code.");
let header_str = &code_str[0..blank_line_indx];
let tail_str = &code_str[blank_line_indx..];
let mut scope = Scope::new(env.home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
let mut def_ids = Vec::<DefId>::new();
let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?;
for def2 in def2_vec {
let def_id = env.pool.add(def2);
def_ids.push(def_id);
}
let ast_node_id = env.pool.add(Expr2::Blank);
Ok(AST {
header: AppHeader::parse_from_string(header_str, ast_node_id),
def_ids,
})
}
}
impl AppHeader {
// TODO don't use mock struct and actually parse string
pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> Self {
AppHeader {
app_name: "\"untitled-app\"".to_owned(),
packages_base: "\"platform\"".to_owned(),
imports: vec![],
provides: vec!["main".to_owned()],
ast_node_id,
}
}
}

View file

@ -1,6 +1,7 @@
#![allow(clippy::all)] #![allow(clippy::all)]
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_imports)] #![allow(unused_imports)]
use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant};
use crate::lang::ast::{ExprId, FloatVal, IntVal}; use crate::lang::ast::{ExprId, FloatVal, IntVal};
use crate::lang::expr::{to_expr_id, Env, Output}; use crate::lang::expr::{to_expr_id, Env, Output};
use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone};
@ -9,7 +10,7 @@ use bumpalo::collections::Vec as BumpVec;
use roc_can::expr::unescape_char; use roc_can::expr::unescape_char;
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
use roc_collections::all::BumpMap; use roc_collections::all::BumpMap;
use roc_module::symbol::Symbol; use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
@ -482,6 +483,17 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
symbols symbols
} }
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult<String> {
match pattern {
Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()),
other => UnexpectedPattern2Variant {
required_pattern2: "Identifier".to_string(),
encountered_pattern2: format!("{:?}", other),
}
.fail()?,
}
}
pub fn symbols_and_variables_from_pattern( pub fn symbols_and_variables_from_pattern(
pool: &Pool, pool: &Pool,
initial: &Pattern2, initial: &Pattern2,

View file

@ -17,6 +17,6 @@ mod window;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
pub fn launch(filepaths: &[&Path]) -> io::Result<()> { pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> {
editor::main::launch(filepaths) editor::main::launch(project_dir_path_opt)
} }

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,12 @@ pub struct CaretWSelect {
pub selection_opt: Option<Selection>, pub selection_opt: Option<Selection>,
} }
pub enum CaretPos {
Start,
Exact(TextPos),
End,
}
fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> { fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> {
if start_pos == end_pos { if start_pos == end_pos {
Ok(None) Ok(None)
@ -146,7 +152,7 @@ pub mod test_caret_w_select {
// Retrieve selection and position from formatted string // Retrieve selection and position from formatted string
pub fn convert_dsl_to_selection(lines: &[String]) -> Result<CaretWSelect, String> { pub fn convert_dsl_to_selection(lines: &[String]) -> Result<CaretWSelect, String> {
let lines_str: String = lines.join(""); let lines_str: String = lines.join("\n");
let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str) let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str)
.expect("Selection test DSL parsing failed"); .expect("Selection test DSL parsing failed");

View file

@ -13,14 +13,12 @@ use crate::ui::text::{
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use crate::ui::util::is_newline; use crate::ui::util::is_newline;
use crate::window::keyboard_input::Modifiers; use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use std::cmp::max; use std::cmp::max;
use std::cmp::min; use std::cmp::min;
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
pub trait Lines { pub trait Lines {
fn get_line(&self, line_nr: usize) -> UIResult<&str>; fn get_line_ref(&self, line_nr: usize) -> UIResult<&str>;
fn line_len(&self, line_nr: usize) -> UIResult<usize>; fn line_len(&self, line_nr: usize) -> UIResult<usize>;
@ -28,7 +26,7 @@ pub trait Lines {
fn nr_of_chars(&self) -> usize; fn nr_of_chars(&self) -> usize;
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>; fn all_lines_as_string(&self) -> String;
fn is_last_line(&self, line_nr: usize) -> bool; fn is_last_line(&self, line_nr: usize) -> bool;
@ -83,7 +81,7 @@ pub trait MutSelectableLines {
fn insert_str(&mut self, new_str: &str) -> UIResult<()>; fn insert_str(&mut self, new_str: &str) -> UIResult<()>;
fn pop_char(&mut self) -> UIResult<()>; fn backspace(&mut self) -> UIResult<()>;
fn del_selection(&mut self) -> UIResult<()>; fn del_selection(&mut self) -> UIResult<()>;
} }
@ -114,7 +112,7 @@ pub fn move_caret_left<T: Lines>(
} else { } else {
let curr_line_len = lines.line_len(old_line_nr - 1)?; let curr_line_len = lines.line_len(old_line_nr - 1)?;
(old_line_nr - 1, curr_line_len - 1) (old_line_nr - 1, curr_line_len)
} }
} else { } else {
(old_line_nr, old_col_nr - 1) (old_line_nr, old_col_nr - 1)
@ -185,7 +183,7 @@ pub fn move_caret_right<T: Lines>(
let is_last_line = lines.is_last_line(old_line_nr); let is_last_line = lines.is_last_line(old_line_nr);
if !is_last_line { if !is_last_line {
if old_col_nr + 1 > curr_line_len - 1 { if old_col_nr + 1 > curr_line_len {
(old_line_nr + 1, 0) (old_line_nr + 1, 0)
} else { } else {
(old_line_nr, old_col_nr + 1) (old_line_nr, old_col_nr + 1)
@ -263,7 +261,9 @@ pub fn move_caret_up<T: Lines>(
let prev_line_len = lines.line_len(old_line_nr - 1)?; let prev_line_len = lines.line_len(old_line_nr - 1)?;
if prev_line_len <= old_col_nr { if prev_line_len <= old_col_nr {
(old_line_nr - 1, prev_line_len - 1) let new_column = if prev_line_len > 0 { prev_line_len } else { 0 };
(old_line_nr - 1, new_column)
} else { } else {
(old_line_nr - 1, old_col_nr) (old_line_nr - 1, old_col_nr)
} }
@ -331,7 +331,9 @@ pub fn move_caret_down<T: Lines>(
if next_line_len <= old_col_nr { if next_line_len <= old_col_nr {
if !is_last_line { if !is_last_line {
(old_line_nr + 1, next_line_len - 1) let new_column = if next_line_len > 0 { next_line_len } else { 0 };
(old_line_nr + 1, new_column)
} else { } else {
(old_line_nr + 1, next_line_len) (old_line_nr + 1, next_line_len)
} }
@ -382,7 +384,7 @@ pub fn move_caret_home<T: Lines>(
let curr_line_nr = caret_w_select.caret_pos.line; let curr_line_nr = caret_w_select.caret_pos.line;
let old_col_nr = caret_w_select.caret_pos.column; let old_col_nr = caret_w_select.caret_pos.column;
let curr_line_str = lines.get_line(curr_line_nr)?; let curr_line_str = lines.get_line_ref(curr_line_nr)?;
let line_char_iter = curr_line_str.chars(); let line_char_iter = curr_line_str.chars();
let mut first_no_space_char_col = 0; let mut first_no_space_char_col = 0;

View file

@ -2,4 +2,5 @@ pub mod big_text_area;
pub mod caret_w_select; pub mod caret_w_select;
pub mod lines; pub mod lines;
pub mod selection; pub mod selection;
mod text_buffer;
pub mod text_pos; pub mod text_pos;

View file

@ -0,0 +1,193 @@
use std::path::Path;
use crate::ui::{
ui_error::{OutOfBounds, TextBufReadFailed, UIResult},
util::{path_to_string, reader_from_path},
};
use snafu::ensure;
use super::{selection::Selection, text_pos::TextPos};
use std::io::BufRead;
// Do not use for large amounts of text.
// This should become a trait in the future and be implemented by a SmallTextBuffer and Rope(for large amounts of text)
#[derive(Debug)]
pub struct TextBuffer {
pub lines: Vec<String>,
}
impl TextBuffer {
pub fn from_path(path: &Path) -> UIResult<Self> {
let buf_reader = reader_from_path(path)?;
let mut lines: Vec<String> = Vec::new();
for line in buf_reader.lines() {
match line {
Ok(line_str) => lines.push(line_str),
Err(e) => {
TextBufReadFailed {
path_str: path_to_string(path),
err_msg: e.to_string(),
}
.fail()?;
}
}
}
Ok(TextBuffer { lines })
}
pub fn nr_of_chars(&self) -> usize {
let mut nr_of_chars = 0;
for line in self.lines.iter() {
nr_of_chars += line.len();
}
nr_of_chars
}
pub fn nr_of_lines(&self) -> usize {
self.lines.len()
}
pub fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> {
self.ensure_bounds(line_nr)?;
// safe unwrap because we checked the length
Ok(self.lines.get(line_nr).unwrap())
}
pub fn line_len(&self, line_nr: usize) -> UIResult<usize> {
Ok(self.get_line_ref(line_nr)?.len())
}
fn ensure_bounds(&self, line_nr: usize) -> UIResult<()> {
ensure!(
line_nr < self.nr_of_lines(),
OutOfBounds {
index: line_nr,
collection_name: "TextBuffer",
len: self.nr_of_lines(),
}
);
Ok(())
}
fn ensure_bounds_txt_pos(&self, txt_pos: TextPos) -> UIResult<()> {
ensure!(
txt_pos.line < self.nr_of_lines(),
OutOfBounds {
index: txt_pos.line,
collection_name: "TextBuffer",
len: self.nr_of_lines(),
}
);
let line_ref = self.get_line_ref(txt_pos.line)?;
let line_len = line_ref.len();
ensure!(
txt_pos.column <= line_len,
OutOfBounds {
index: txt_pos.column,
collection_name: format!("Line in TextBuffer: {}", line_ref),
len: line_len,
}
);
Ok(())
}
pub fn all_lines_ref(&self) -> &[String] {
&self.lines
}
pub fn get_selected_str(&self, selection: Selection) -> UIResult<String> {
let start_line_nr = selection.start_pos.line;
let start_col_nr = selection.start_pos.column;
let end_line_nr = selection.end_pos.line;
let end_col_nr = selection.end_pos.column;
let mut selected_str = String::new();
if end_line_nr > start_line_nr {
selected_str.push_str(&self.get_line_ref(start_line_nr)?[start_col_nr..]);
for line_nr in start_line_nr + 1..end_line_nr - 1 {
selected_str.push_str(self.get_line_ref(line_nr)?);
}
selected_str.push_str(&self.get_line_ref(end_line_nr)?[..end_col_nr]);
} else {
// start_line_nr == end_line_nr
selected_str.push_str(&self.get_line_ref(start_line_nr)?[start_col_nr..end_col_nr]);
}
Ok(selected_str)
}
pub fn insert_str(&mut self, txt_pos: TextPos, new_str: &str) -> UIResult<()> {
self.ensure_bounds_txt_pos(txt_pos)?;
// safe unwrap because we checked the length
self.lines
.get_mut(txt_pos.line)
.unwrap()
.insert_str(txt_pos.column, new_str);
Ok(())
}
pub fn backspace_char(&mut self, txt_pos: TextPos) -> UIResult<()> {
if txt_pos.column > 0 {
let prev_col_pos = TextPos {
line: txt_pos.line,
column: txt_pos.column - 1,
};
self.ensure_bounds_txt_pos(prev_col_pos)?;
let line_ref = self.lines.get_mut(prev_col_pos.line).unwrap(); // safe because of earlier bounds check
line_ref.remove(prev_col_pos.column);
} else if txt_pos.line > 0 {
self.lines.remove(txt_pos.line);
}
Ok(())
}
pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> {
self.ensure_bounds_txt_pos(selection.start_pos)?;
self.ensure_bounds_txt_pos(selection.end_pos)?;
let start_line_nr = selection.start_pos.line;
let start_col_nr = selection.start_pos.column;
let end_line_nr = selection.end_pos.line;
let end_col_nr = selection.end_pos.column;
if end_line_nr > start_line_nr {
// remove in reverse to prevent shifting indices
if end_col_nr == self.line_len(end_line_nr)? {
self.lines.remove(end_line_nr);
} else {
let line_ref = self.lines.get_mut(end_line_nr).unwrap(); // safe because of earlier bounds check
line_ref.replace_range(..end_col_nr, "");
}
self.lines.drain(start_line_nr + 1..end_line_nr);
let line_ref = self.lines.get_mut(start_line_nr).unwrap(); // safe because of earlier bounds check
line_ref.replace_range(start_col_nr.., "")
} else {
// selection.end_pos.line == selection.start_pos.line
let line_ref = self.lines.get_mut(selection.start_pos.line).unwrap(); // safe because of earlier bounds check
line_ref.replace_range(selection.start_pos.column..selection.end_pos.column, "")
}
Ok(())
}
}

View file

@ -1,3 +1,5 @@
use std::io;
use snafu::{Backtrace, Snafu}; use snafu::{Backtrace, Snafu};
//import errors as follows: //import errors as follows:
@ -8,6 +10,16 @@ use snafu::{Backtrace, Snafu};
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))] #[snafu(visibility(pub))]
pub enum UIError { pub enum UIError {
#[snafu(display(
"LineInsertionFailed: line_nr ({}) needs to be <= nr_of_lines ({}).",
line_nr,
nr_of_lines
))]
LineInsertionFailed {
line_nr: usize,
nr_of_lines: usize,
backtrace: Backtrace,
},
#[snafu(display( #[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.", "OutOfBounds: index {} was out of bounds for {} with length {}.",
index, index,
@ -34,6 +46,13 @@ pub enum UIError {
))] ))]
FileOpenFailed { path_str: String, err_msg: String }, FileOpenFailed { path_str: String, err_msg: String },
#[snafu(display(
"FileWriteFailed: failed to write to file with path {}, I got this IO error: {}.",
path_str,
source
))]
FileWriteFailed { source: io::Error, path_str: String },
#[snafu(display("TextBufReadFailed: the file {} could be opened but we encountered the following error while trying to read it: {}.", path_str, err_msg))] #[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 },

View file

@ -1,6 +1,6 @@
use super::ui_error::{OutOfBounds, UIResult}; use super::ui_error::{FileOpenFailed, FileWriteFailed, OutOfBounds, UIResult};
use snafu::OptionExt; use snafu::{OptionExt, ResultExt};
use std::slice::SliceIndex; use std::{fs::File, io::BufReader, path::Path, slice::SliceIndex};
pub fn is_newline(char_ref: &char) -> bool { pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}', '\n']; let newline_codes = vec!['\u{d}', '\n'];
@ -33,3 +33,27 @@ pub fn slice_get_mut<T>(
Ok(elt_ref) Ok(elt_ref)
} }
pub fn reader_from_path(path: &Path) -> UIResult<BufReader<File>> {
match File::open(path) {
Ok(file) => Ok(BufReader::new(file)),
Err(e) => FileOpenFailed {
path_str: path_to_string(path),
err_msg: e.to_string(),
}
.fail()?,
}
}
pub fn path_to_string(path: &Path) -> String {
let mut path_str = String::new();
path_str.push_str(&path.to_string_lossy());
path_str
}
pub fn write_to_file(path: &Path, content: &str) -> UIResult<()> {
std::fs::write(path, content).with_context(|| FileWriteFailed {
path_str: path_to_string(path),
})
}

View file

@ -27,6 +27,17 @@ impl Modifiers {
active active
} }
// returns true if modifiers are active that can be active when the user wants to insert a new char; e.g.: shift+a to make A
pub fn new_char_modifiers(&self) -> bool {
self.no_modifiers()
|| (self.shift && !self.ctrl && !self.alt && !self.logo) // e.g.: shift+a to make A
|| (self.cmd_or_ctrl() && self.alt) // e.g.: ctrl+alt+2 to make @ on azerty keyboard
}
fn no_modifiers(&self) -> bool {
!self.shift && !self.ctrl && !self.alt && !self.logo
}
} }
pub fn no_mods() -> Modifiers { pub fn no_mods() -> Modifiers {

5
editor/tests/README.md Normal file
View file

@ -0,0 +1,5 @@
# Where are the tests?
We have a lot of tests at the end of source files, this allows us to test functions that are not exposed by the editor itself.
`editor/mvc/ed_update.rs` and `editor/ui/text/big_text_area.rs` have many important tests.

View file

@ -1,4 +1,4 @@
text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" )* } text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" | "=" )* }
caret = {"┃"} caret = {"┃"}