Merge pull request #1087 from rtfeldman/move_caret

Move AST carets right
This commit is contained in:
Richard Feldman 2021-03-20 21:45:06 -04:00 committed by GitHub
commit 753909d561
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 848 additions and 245 deletions

View File

@ -78,6 +78,8 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* Searchbar for examples/docs. With permission search strings could be shared with the platform/package authors so they know exactly what their users are struggling with.
* Show productivity/feature tips on startup. Show link to page with all tips. Allow not seeing tips next time.
* Search friendly editor docs inside the editor. Offer to send search string to Roc maintainers when no results, or if no results were clicked.
* File history timeline view. Show timeline with commits that changed this file, the number of lines added and deleted as well as which user made the changes.
* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to apppear, after which you click to apply the fix you want :( .
#### Autocomplete

View File

@ -1,3 +1,4 @@
use crate::editor::slow_pool::SlowNodeId;
use colored::*;
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
@ -9,6 +10,15 @@ use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum EdError {
#[snafu(display(
"CaretNotFound: No carets were found in the expected node with id {}",
node_id
))]
CaretNotFound {
node_id: SlowNodeId,
backtrace: Backtrace,
},
#[snafu(display("ClipboardReadFailed: could not get clipboard contents: {}", err_msg))]
ClipboardReadFailed { err_msg: String },
@ -21,6 +31,31 @@ pub enum EdError {
))]
ClipboardInitFailed { err_msg: String },
#[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))]
GetContentOnNestedNode { backtrace: Backtrace },
#[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))]
KeyNotFound {
key_str: String,
backtrace: Backtrace,
},
#[snafu(display("NestedNodeWithoutChildren: tried to retrieve child from Nested MarkupNode with id {} but it had no children.", node_id))]
NestedNodeWithoutChildren {
node_id: SlowNodeId,
backtrace: Backtrace,
},
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
NestedNodeMissingChild {
node_id: SlowNodeId,
children_ids: Vec<SlowNodeId>,
backtrace: Backtrace,
},
#[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 },
#[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.",
index,
@ -34,12 +69,6 @@ pub enum EdError {
backtrace: Backtrace,
},
#[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))]
KeyNotFound {
key_str: String,
backtrace: Backtrace,
},
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))]
ParseError { syntax_err: String },

View File

@ -3,6 +3,7 @@ use crate::editor::mvc::app_model::AppModel;
use crate::editor::mvc::app_update::{
handle_copy, handle_cut, handle_paste, pass_keydown_to_focused,
};
use crate::editor::slow_pool::SlowPool;
use winit::event::VirtualKeyCode::*;
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
@ -11,6 +12,7 @@ pub fn handle_keydown(
virtual_keycode: VirtualKeyCode,
modifiers: ModifiersState,
app_model: &mut AppModel,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
if let ElementState::Released = elem_state {
return Ok(());
@ -18,7 +20,7 @@ pub fn handle_keydown(
match virtual_keycode {
Left | Up | Right | Down => {
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model, markup_node_pool)?
}
Copy => handle_copy(app_model)?,
@ -40,7 +42,9 @@ pub fn handle_keydown(
}
}
A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,
A | Home | End => {
pass_keydown_to_focused(&modifiers, virtual_keycode, app_model, markup_node_pool)?
}
_ => (),
}

View File

@ -3,6 +3,7 @@ use super::style::CODE_TXT_XY;
use super::util::slice_get;
use crate::editor::ed_error::print_ui_err;
use crate::editor::resources::strings::NOTHING_OPENED;
use crate::editor::slow_pool::SlowPool;
use crate::editor::{
config::Config,
ed_error::print_err,
@ -131,6 +132,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let mut env_pool = Pool::with_capacity(1024);
let env_arena = Bump::new();
let code_arena = Bump::new();
let mut markup_node_pool = SlowPool::new();
let mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8);
@ -165,7 +167,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
}
let ed_model_opt = {
let ed_model_res = ed_model::init_model(&code_str, env, &code_arena);
let ed_model_res = ed_model::init_model(&code_str, env, &code_arena, &mut markup_node_pool);
match ed_model_res {
Ok(mut ed_model) => {
@ -254,6 +256,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
virtual_keycode,
keyboard_modifiers,
&mut app_model,
&mut markup_node_pool,
);
if let Err(e) = keydown_res {
@ -290,6 +293,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
&size,
CODE_TXT_XY.into(),
&config,
&markup_node_pool,
);
match text_and_rects_res {

View File

@ -1,197 +0,0 @@
#![allow(dead_code)]
use super::syntax_highlight::HighlightStyle;
use crate::lang::{
ast::Expr2,
expr::Env,
pool::{NodeId, PoolStr},
};
use bumpalo::Bump;
#[derive(Debug)]
pub enum MarkupNode {
// TODO add parent field, necessary for moving caret to next node
Nested {
ast_node_id: NodeId<Expr2>,
children: Vec<MarkupNode>,
},
Text {
content: String,
ast_node_id: NodeId<Expr2>,
syn_high_style: HighlightStyle,
attributes: Vec<Attribute>,
},
Hole {
ast_node_id: NodeId<Expr2>,
attributes: Vec<Attribute>,
syn_high_style: HighlightStyle,
},
}
#[derive(Debug)]
pub enum Attribute {
Caret { offset_col: usize },
SelectionStart { offset_col: usize },
SelectionEnd { offset_col: usize },
// Highlight is used for example when searching for a specific string to highlight all search results in the module
HighlightStart { offset_col: usize },
HighlightEnd { offset_col: usize },
// Underline is used for warnings and errors
UnderlineStart { offset_col: usize },
UnderlineEnd { offset_col: usize },
}
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
pool_str.as_str(env.pool).to_owned()
}
fn new_markup(text: String, node_id: NodeId<Expr2>, highlight_style: HighlightStyle) -> MarkupNode {
MarkupNode::Text {
content: text,
ast_node_id: node_id,
syn_high_style: highlight_style,
attributes: Vec::new(),
}
}
// make Markup Nodes: generate String representation, assign Highlighting Style
pub fn expr2_to_markup<'a, 'b>(arena: &'a Bump, env: &mut Env<'b>, expr2: &Expr2) -> MarkupNode {
// TODO find way to add current expr2 to pool
let node_id = env.pool.add(Expr2::Hole);
match expr2 {
Expr2::SmallInt { text, .. }
| Expr2::I128 { text, .. }
| Expr2::U128 { text, .. }
| Expr2::Float { text, .. } => {
new_markup(get_string(env, &text), node_id, HighlightStyle::Number)
}
Expr2::Str(text) => new_markup(
"\"".to_owned() + text.as_str(env.pool) + "\"",
node_id,
HighlightStyle::String,
),
Expr2::GlobalTag { name, .. } => {
new_markup(get_string(env, &name), node_id, HighlightStyle::Type)
}
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
expr2_to_markup(arena, env, expr)
}
Expr2::Var(symbol) => {
//TODO make bump_format with arena
let text = format!("{:?}", symbol);
new_markup(text, node_id, HighlightStyle::Variable)
}
Expr2::List { elems, .. } => {
let mut children: Vec<MarkupNode> = Vec::new();
children.push(new_markup(
"[ ".to_string(),
node_id,
HighlightStyle::Bracket,
));
for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id);
children.push(expr2_to_markup(arena, env, sub_expr2));
if idx + 1 < elems.len() {
children.push(new_markup(
", ".to_string(),
node_id,
HighlightStyle::Operator,
));
}
}
children.push(new_markup(
"] ".to_string(),
node_id,
HighlightStyle::Bracket,
));
MarkupNode::Nested {
ast_node_id: node_id,
children,
}
}
Expr2::Record { fields, .. } => {
let mut children: Vec<MarkupNode> = Vec::new();
children.push(new_markup(
"{ ".to_string(),
node_id,
HighlightStyle::Bracket,
));
for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(field_node_id);
let field_name = pool_field_name.as_str(env.pool);
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
children.push(new_markup(
field_name.to_string(),
node_id,
HighlightStyle::RecordField,
));
children.push(new_markup(
": ".to_string(),
node_id,
HighlightStyle::Operator,
));
children.push(expr2_to_markup(arena, env, sub_expr2));
if idx + 1 < fields.len() {
children.push(new_markup(
", ".to_string(),
node_id,
HighlightStyle::Operator,
));
}
}
children.push(new_markup(
" }".to_string(),
node_id,
HighlightStyle::Bracket,
));
MarkupNode::Nested {
ast_node_id: node_id,
children,
}
}
Expr2::Hole => MarkupNode::Hole {
ast_node_id: node_id,
attributes: Vec::new(),
syn_high_style: HighlightStyle::Hole,
},
rest => todo!("implement expr2_to_markup for {:?}", rest),
}
}
pub fn set_caret_at_start(markup_node: &mut MarkupNode) {
match markup_node {
MarkupNode::Nested {
ast_node_id: _,
children,
} => {
if let Some(child) = children.first_mut() {
set_caret_at_start(child)
}
}
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes,
} => attributes.push(Attribute::Caret { offset_col: 0 }),
MarkupNode::Hole {
ast_node_id: _,
attributes,
syn_high_style: _,
} => attributes.push(Attribute::Caret { offset_col: 0 }),
};
}

View File

@ -0,0 +1,123 @@
#![allow(dead_code)]
use crate::editor::ed_error::{CaretNotFound, EdResult};
use snafu::ensure;
#[derive(Debug, Copy, Clone)]
pub struct Caret {
pub offset_col: usize,
}
impl Caret {
pub fn new_attr(offset_col: usize) -> Attribute {
Attribute::Caret {
caret: Caret { offset_col },
}
}
}
#[derive(Debug)]
pub struct SelectionStart {
offset_col: usize,
}
#[derive(Debug)]
pub struct SelectionEnd {
offset_col: usize,
}
// Highlight is used for example when searching for a specific string to highlight all search results in the module
#[derive(Debug)]
pub struct HighlightStart {
offset_col: usize,
}
#[derive(Debug)]
pub struct HighlightEnd {
offset_col: usize,
}
// Underline is used for warnings and errors
#[derive(Debug)]
pub struct UnderlineStart {
offset_col: usize,
}
#[derive(Debug)]
pub struct UnderlineEnd {
offset_col: usize,
}
#[derive(Debug)]
pub enum Attribute {
// Rust does not yet support types for enum variants so we have to do it like this
Caret { caret: Caret },
SelectionStart { selection_start: SelectionStart },
SelectionEnd { selection_end: SelectionEnd },
HighlightStart { highlight_start: HighlightStart },
HighlightEnd { highlight_end: HighlightEnd },
UnderlineStart { underline_start: UnderlineStart },
UnderlineEnd { underline_end: UnderlineEnd },
}
#[derive(Debug)]
pub struct Attributes {
pub all: Vec<Attribute>,
}
impl Attributes {
pub fn new() -> Attributes {
Attributes { all: Vec::new() }
}
pub fn add(&mut self, attr: Attribute) {
self.all.push(attr);
}
pub fn add_caret(&mut self, offset_col: usize) {
self.all.push(Attribute::Caret {
caret: Caret { offset_col },
});
}
pub fn get_mut_carets(&mut self) -> Vec<&mut Caret> {
let mut carets = Vec::new();
for attr in self.all.iter_mut() {
if let Attribute::Caret { caret } = attr {
carets.push(caret)
}
}
carets
}
pub fn get_carets(&self) -> Vec<Caret> {
let mut carets = Vec::new();
for attr in self.all.iter() {
if let Attribute::Caret { caret } = attr {
carets.push(*caret)
}
}
carets
}
pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> EdResult<()> {
let old_len = self.all.len();
self.all.retain(|attr| {
if let Attribute::Caret { caret } = attr {
caret.offset_col != offset_col
} else {
true
}
});
let new_len = self.all.len();
ensure!(old_len != new_len, CaretNotFound { node_id });
Ok(())
}
}

View File

@ -0,0 +1,130 @@
use crate::editor::mvc::ed_model::LeafIndex;
use crate::editor::{
ed_error::{CaretNotFound, EdResult, NodeWithoutAttributes},
markup::attribute::Caret,
markup::nodes::MarkupNode,
slow_pool::{SlowNodeId, SlowPool},
};
use snafu::ensure;
// Returns id of node that has Caret attribute
pub fn set_caret_at_start(
markup_node_id: SlowNodeId,
markup_node_pool: &mut SlowPool,
) -> EdResult<SlowNodeId> {
let markup_node = markup_node_pool.get_mut(markup_node_id);
match markup_node {
MarkupNode::Nested {
ast_node_id: _,
children_ids: _,
parent_id_opt: _,
} => NodeWithoutAttributes {}.fail(),
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes,
parent_id_opt: _,
} => {
attributes.add(Caret::new_attr(0));
Ok(markup_node_id)
}
MarkupNode::Blank {
ast_node_id: _,
attributes,
syn_high_style: _,
parent_id_opt: _,
} => {
attributes.add(Caret::new_attr(0));
Ok(markup_node_id)
}
}
}
// Returns nodes containing the carets after the move, as well as its position in a DFS ordered list of leaves.
pub fn move_carets_right_for_node(
node_with_caret_id: SlowNodeId,
caret_id_leaf_index: LeafIndex,
next_leaf_id_opt: Option<SlowNodeId>,
markup_node_pool: &mut SlowPool,
) -> EdResult<Vec<(SlowNodeId, LeafIndex)>> {
let carets = get_carets(node_with_caret_id, markup_node_pool)?;
let node_content = markup_node_pool.get(node_with_caret_id).get_content()?;
ensure!(
!carets.is_empty(),
CaretNotFound {
node_id: node_with_caret_id
}
);
let mut new_nodes_w_carets = Vec::new();
for caret in carets {
let (new_node, new_leaf_index) = if caret.offset_col + 1 < node_content.len() {
increase_caret_offset(node_with_caret_id, caret.offset_col, markup_node_pool)?;
(node_with_caret_id, caret_id_leaf_index)
} else if let Some(next_child_id) = next_leaf_id_opt {
delete_caret(node_with_caret_id, caret.offset_col, markup_node_pool)?;
let next_child = markup_node_pool.get_mut(next_child_id);
let child_attrs = next_child.get_mut_attributes()?;
child_attrs.add_caret(0);
(next_child_id, caret_id_leaf_index + 1)
} else if caret.offset_col + 1 == node_content.len() {
// For last char in editor.
// In other places we jump to start of next node instead of going to end of
// this node, otherwise it would be like there is a space between every node.
increase_caret_offset(node_with_caret_id, caret.offset_col, markup_node_pool)?;
(node_with_caret_id, caret_id_leaf_index)
} else {
// Caret is at its end, keep it here.
(node_with_caret_id, caret_id_leaf_index)
};
new_nodes_w_carets.push((new_node, new_leaf_index));
}
Ok(new_nodes_w_carets)
}
fn get_carets(node_with_caret_id: SlowNodeId, markup_node_pool: &SlowPool) -> EdResult<Vec<Caret>> {
let curr_node = markup_node_pool.get(node_with_caret_id);
let attributes = curr_node.get_attributes()?;
Ok(attributes.get_carets())
}
// this method assumes the current caret position is checked and increasing it will not lead to an invalid caret position
fn increase_caret_offset(
node_id: SlowNodeId,
offset_col: usize,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
let node = markup_node_pool.get_mut(node_id);
let attrs = node.get_mut_attributes()?;
let mut carets = attrs.get_mut_carets();
for caret in carets.iter_mut() {
if caret.offset_col == offset_col {
caret.offset_col += 1;
}
}
Ok(())
}
fn delete_caret(
node_id: SlowNodeId,
offset_col: usize,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
let node = markup_node_pool.get_mut(node_id);
let attrs = node.get_mut_attributes()?;
attrs.delete_caret(offset_col, node_id)?;
Ok(())
}

View File

@ -0,0 +1,3 @@
pub mod attribute;
pub mod caret;
pub mod nodes;

View File

@ -0,0 +1,375 @@
use super::attribute::Attributes;
use crate::editor::ed_error::GetContentOnNestedNode;
use crate::editor::ed_error::NodeWithoutAttributes;
use crate::editor::{
ed_error::EdResult,
slow_pool::{SlowNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
use crate::lang::{
ast::Expr2,
expr::Env,
pool::{NodeId, PoolStr},
};
use bumpalo::Bump;
use snafu::OptionExt;
#[derive(Debug)]
pub enum MarkupNode {
Nested {
ast_node_id: NodeId<Expr2>,
children_ids: Vec<SlowNodeId>,
parent_id_opt: Option<SlowNodeId>,
},
Text {
content: String,
ast_node_id: NodeId<Expr2>,
syn_high_style: HighlightStyle,
attributes: Attributes,
parent_id_opt: Option<SlowNodeId>,
},
Blank {
ast_node_id: NodeId<Expr2>,
attributes: Attributes,
syn_high_style: HighlightStyle,
parent_id_opt: Option<SlowNodeId>,
},
}
pub const BLANK_PLACEHOLDER: &str = " ";
impl MarkupNode {
pub fn get_children_ids(&self) -> Vec<SlowNodeId> {
match self {
MarkupNode::Nested {
ast_node_id: _,
children_ids,
parent_id_opt: _,
} => children_ids.to_vec(),
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes: _,
parent_id_opt: _,
} => Vec::new(),
MarkupNode::Blank {
ast_node_id: _,
attributes: _,
syn_high_style: _,
parent_id_opt: _,
} => Vec::new(),
}
}
// can't be &str, this creates borrowing issues
pub fn get_content(&self) -> EdResult<String> {
match self {
MarkupNode::Nested {
ast_node_id: _,
children_ids: _,
parent_id_opt: _,
} => GetContentOnNestedNode {}.fail(),
MarkupNode::Text {
content,
ast_node_id: _,
syn_high_style: _,
attributes: _,
parent_id_opt: _,
} => Ok(content.clone()),
MarkupNode::Blank {
ast_node_id: _,
attributes: _,
syn_high_style: _,
parent_id_opt: _,
} => Ok(BLANK_PLACEHOLDER.to_owned()),
}
}
// Do Depth First Search and return SlowNodeId's in order of encounter
// The returning vec is used for caret movement
pub fn get_dfs_leaves(
&self,
node_id: SlowNodeId,
markup_node_pool: &SlowPool,
ordered_leaves: &mut Vec<SlowNodeId>,
) {
let children_ids = self.get_children_ids();
if children_ids.is_empty() {
ordered_leaves.push(node_id);
} else {
for child_id in self.get_children_ids() {
let child = markup_node_pool.get(child_id);
child.get_dfs_leaves(child_id, markup_node_pool, ordered_leaves);
}
}
}
pub fn get_mut_attributes(&mut self) -> EdResult<&mut Attributes> {
let attrs_ref = match self {
MarkupNode::Nested { .. } => None,
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes,
parent_id_opt: _,
} => Some(attributes),
MarkupNode::Blank {
ast_node_id: _,
attributes,
syn_high_style: _,
parent_id_opt: _,
} => Some(attributes),
};
attrs_ref.with_context(|| NodeWithoutAttributes {})
}
pub fn get_attributes(&self) -> EdResult<&Attributes> {
let attrs_ref = match self {
MarkupNode::Nested { .. } => None,
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes,
parent_id_opt: _,
} => Some(attributes),
MarkupNode::Blank {
ast_node_id: _,
attributes,
syn_high_style: _,
parent_id_opt: _,
} => Some(attributes),
};
attrs_ref.with_context(|| NodeWithoutAttributes {})
}
}
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
pool_str.as_str(env.pool).to_owned()
}
fn new_markup_node(
text: String,
node_id: NodeId<Expr2>,
highlight_style: HighlightStyle,
markup_node_pool: &mut SlowPool,
) -> SlowNodeId {
let node = MarkupNode::Text {
content: text,
ast_node_id: node_id,
syn_high_style: highlight_style,
attributes: Attributes::new(),
parent_id_opt: None,
};
markup_node_pool.add(node)
}
// make Markup Nodes: generate String representation, assign Highlighting Style
pub fn expr2_to_markup<'a, 'b>(
arena: &'a Bump,
env: &mut Env<'b>,
expr2: &Expr2,
markup_node_pool: &mut SlowPool,
) -> SlowNodeId {
// TODO find way to add current expr2 to pool
let node_id = env.pool.add(Expr2::Blank);
match expr2 {
Expr2::SmallInt { text, .. }
| Expr2::I128 { text, .. }
| Expr2::U128 { text, .. }
| Expr2::Float { text, .. } => new_markup_node(
get_string(env, &text),
node_id,
HighlightStyle::Number,
markup_node_pool,
),
Expr2::Str(text) => new_markup_node(
"\"".to_owned() + text.as_str(env.pool) + "\"",
node_id,
HighlightStyle::String,
markup_node_pool,
),
Expr2::GlobalTag { name, .. } => new_markup_node(
get_string(env, &name),
node_id,
HighlightStyle::Type,
markup_node_pool,
),
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
expr2_to_markup(arena, env, expr, markup_node_pool)
}
Expr2::Var(symbol) => {
//TODO make bump_format with arena
let text = format!("{:?}", symbol);
new_markup_node(text, node_id, HighlightStyle::Variable, markup_node_pool)
}
Expr2::List { elems, .. } => {
let mut children_ids = Vec::new();
children_ids.push(new_markup_node(
"[ ".to_string(),
node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id);
children_ids.push(expr2_to_markup(arena, env, sub_expr2, markup_node_pool));
if idx + 1 < elems.len() {
children_ids.push(new_markup_node(
", ".to_string(),
node_id,
HighlightStyle::Operator,
markup_node_pool,
));
}
}
children_ids.push(new_markup_node(
"] ".to_string(),
node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
let list_node = MarkupNode::Nested {
ast_node_id: node_id,
children_ids,
parent_id_opt: None,
};
markup_node_pool.add(list_node)
}
Expr2::Record { fields, .. } => {
let mut children_ids = Vec::new();
children_ids.push(new_markup_node(
"{ ".to_string(),
node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(field_node_id);
let field_name = pool_field_name.as_str(env.pool);
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
children_ids.push(new_markup_node(
field_name.to_string(),
node_id,
HighlightStyle::RecordField,
markup_node_pool,
));
children_ids.push(new_markup_node(
": ".to_string(),
node_id,
HighlightStyle::Operator,
markup_node_pool,
));
children_ids.push(expr2_to_markup(arena, env, sub_expr2, markup_node_pool));
if idx + 1 < fields.len() {
children_ids.push(new_markup_node(
", ".to_string(),
node_id,
HighlightStyle::Operator,
markup_node_pool,
));
}
}
children_ids.push(new_markup_node(
" }".to_string(),
node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
let record_node = MarkupNode::Nested {
ast_node_id: node_id,
children_ids,
parent_id_opt: None,
};
markup_node_pool.add(record_node)
}
Expr2::Blank => markup_node_pool.add(MarkupNode::Blank {
ast_node_id: node_id,
attributes: Attributes::new(),
syn_high_style: HighlightStyle::Blank,
parent_id_opt: None,
}),
rest => todo!("implement expr2_to_markup for {:?}", rest),
}
}
pub fn set_parent_for_all(markup_node_id: SlowNodeId, markup_node_pool: &mut SlowPool) {
let node = markup_node_pool.get(markup_node_id);
if let MarkupNode::Nested {
ast_node_id: _,
children_ids,
parent_id_opt: _,
} = node
{
// need to clone because of borrowing issues
let children_ids_clone = children_ids.clone();
for child_id in children_ids_clone {
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool);
}
}
}
pub fn set_parent_for_all_helper(
markup_node_id: SlowNodeId,
parent_node_id: SlowNodeId,
markup_node_pool: &mut SlowPool,
) {
let node = markup_node_pool.get_mut(markup_node_id);
match node {
MarkupNode::Nested {
ast_node_id: _,
children_ids,
parent_id_opt,
} => {
*parent_id_opt = Some(parent_node_id);
// need to clone because of borrowing issues
let children_ids_clone = children_ids.clone();
for child_id in children_ids_clone {
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool);
}
}
MarkupNode::Text {
content: _,
ast_node_id: _,
syn_high_style: _,
attributes: _,
parent_id_opt,
} => *parent_id_opt = Some(parent_node_id),
MarkupNode::Blank {
ast_node_id: _,
attributes: _,
syn_high_style: _,
parent_id_opt,
} => *parent_id_opt = Some(parent_node_id),
}
}

View File

@ -6,6 +6,7 @@ mod markup;
mod mvc;
mod render_ast;
mod resources;
mod slow_pool;
mod style;
mod syntax_highlight;
mod theme;

View File

@ -1,6 +1,6 @@
use super::app_model::AppModel;
use crate::editor::ed_error::EdResult;
use crate::ui::ui_error::UIResult;
use crate::editor::slow_pool::SlowPool;
use crate::window::keyboard_input::from_winit;
use winit::event::{ModifiersState, VirtualKeyCode};
@ -36,14 +36,15 @@ pub fn handle_cut(app_model: &mut AppModel) -> EdResult<()> {
pub fn pass_keydown_to_focused(
modifiers_winit: &ModifiersState,
_virtual_keycode: VirtualKeyCode,
virtual_keycode: VirtualKeyCode,
app_model: &mut AppModel,
) -> UIResult<()> {
let _modifiers = from_winit(modifiers_winit);
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
let modifiers = from_winit(modifiers_winit);
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
unimplemented!("TODO");
ed_model.handle_key_down(&modifiers, virtual_keycode, markup_node_pool)?;
}
}

View File

@ -1,56 +1,129 @@
use crate::editor::ed_error::EdError::ParseError;
use crate::editor::ed_error::EdResult;
use crate::editor::markup::{expr2_to_markup, set_caret_at_start, Attribute, MarkupNode};
use crate::editor::slow_pool::{SlowNodeId, SlowPool};
use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::{
ed_error::EdError::ParseError,
ed_error::EdResult,
markup::attribute::{Attributes, Caret},
markup::caret::{move_carets_right_for_node, set_caret_at_start},
markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode},
};
use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::Expr2;
use crate::lang::expr::{str_to_expr2, Env};
use crate::lang::scope::Scope;
use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use roc_region::all::Region;
use std::collections::HashSet;
use winit::event::VirtualKeyCode;
pub type LeafIndex = usize;
#[derive(Debug)]
pub struct EdModel<'a> {
pub module: EdModule<'a>,
pub code_as_str: &'a str,
pub markup_root: MarkupNode,
pub markup_root_id: SlowNodeId,
pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool,
carets: Vec<&'a MarkupNode>,
// This HashSet may have less elements than there are carets. There can be multiple carets for a single node.
caret_nodes: HashSet<(SlowNodeId, LeafIndex)>,
dfs_ordered_leaves: Vec<SlowNodeId>,
}
pub fn init_model<'a>(
code_str: &'a BumpString,
env: Env<'a>,
code_arena: &'a Bump,
markup_node_pool: &mut SlowPool,
) -> EdResult<EdModel<'a>> {
let mut module = EdModule::new(&code_str, env, code_arena)?;
// TODO fix moving issue and insert module.ast_root into pool
let ast_root_id = module.env.pool.add(Expr2::Hole);
let ast_root_id = module.env.pool.add(Expr2::Blank);
let markup_root = if code_str.is_empty() {
MarkupNode::Hole {
let markup_root_id = if code_str.is_empty() {
let blank_root = MarkupNode::Blank {
ast_node_id: ast_root_id,
attributes: vec![Attribute::Caret { offset_col: 0 }],
syn_high_style: HighlightStyle::Hole,
}
attributes: Attributes {
all: vec![Caret::new_attr(0)],
},
syn_high_style: HighlightStyle::Blank,
parent_id_opt: None,
};
markup_node_pool.add(blank_root)
} else {
let mut temp_markup_root = expr2_to_markup(code_arena, &mut module.env, &module.ast_root);
set_caret_at_start(&mut temp_markup_root);
temp_markup_root
let temp_markup_root_id = expr2_to_markup(
code_arena,
&mut module.env,
&module.ast_root,
markup_node_pool,
);
set_parent_for_all(temp_markup_root_id, markup_node_pool);
temp_markup_root_id
};
let mut dfs_ordered_leaves = Vec::new();
markup_node_pool.get(markup_root_id).get_dfs_leaves(
markup_root_id,
markup_node_pool,
&mut dfs_ordered_leaves,
);
// unwrap because it's not possible to only have a single Nested node without children.
let first_leaf_id = dfs_ordered_leaves.first().unwrap();
let node_w_caret_id = set_caret_at_start(*first_leaf_id, markup_node_pool)?;
Ok(EdModel {
module,
code_as_str: code_str,
markup_root,
markup_root_id,
glyph_dim_rect_opt: None,
has_focus: true,
carets: Vec::new(),
caret_nodes: vec![(node_w_caret_id, 0)].into_iter().collect(),
dfs_ordered_leaves,
})
}
impl<'a> EdModel<'a> {
pub fn handle_key_down(
&mut self,
_modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
match virtual_keycode {
VirtualKeyCode::Right => {
let mut new_caret_nodes: Vec<(SlowNodeId, LeafIndex)> = Vec::new();
for (caret_node_id_ref, leaf_index) in self.caret_nodes.iter() {
let caret_node_id = *caret_node_id_ref;
let next_leaf_id_opt = self.get_next_leaf(*leaf_index);
new_caret_nodes.extend(move_carets_right_for_node(
caret_node_id,
*leaf_index,
next_leaf_id_opt,
markup_node_pool,
)?);
}
self.caret_nodes = new_caret_nodes.into_iter().collect();
}
VirtualKeyCode::Left => unimplemented!("TODO"),
_ => (),
};
Ok(())
}
pub fn get_next_leaf(&self, index: usize) -> Option<SlowNodeId> {
self.dfs_ordered_leaves.get(index + 1).copied()
}
}
#[derive(Debug)]
pub struct EdModule<'a> {
pub env: Env<'a>,
@ -78,7 +151,7 @@ impl<'a> EdModule<'a> {
} else {
Ok(EdModule {
env,
ast_root: Expr2::Hole,
ast_root: Expr2::Blank,
})
}
}

View File

@ -2,6 +2,7 @@ use super::ed_model::EdModel;
use crate::editor::config::Config;
use crate::editor::ed_error::EdResult;
use crate::editor::render_ast::build_code_graphics;
use crate::editor::slow_pool::SlowPool;
use crate::graphics::primitives::rect::Rect;
use crate::ui::ui_error::MissingGlyphDims;
use cgmath::Vector2;
@ -14,14 +15,16 @@ pub fn model_to_wgpu<'a>(
size: &PhysicalSize<u32>,
txt_coords: Vector2<f32>,
config: &Config,
markup_node_pool: &'a SlowPool,
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?;
build_code_graphics(
&ed_model.markup_root,
markup_node_pool.get(ed_model.markup_root_id),
size,
txt_coords,
config,
glyph_dim_rect,
markup_node_pool,
)
}

View File

@ -1,4 +1,6 @@
use super::markup::{Attribute, MarkupNode};
use super::markup::attribute::{Attribute, Attributes};
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
use crate::editor::slow_pool::SlowPool;
use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get};
use crate::graphics::primitives::rect::Rect;
use crate::graphics::primitives::text as gr_text;
@ -14,6 +16,7 @@ pub fn build_code_graphics<'a>(
txt_coords: Vector2<f32>,
config: &Config,
glyph_dim_rect: Rect,
markup_node_pool: &'a SlowPool,
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
let area_bounds = (size.width as f32, size.height as f32);
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
@ -26,6 +29,7 @@ pub fn build_code_graphics<'a>(
txt_coords,
glyph_dim_rect,
},
markup_node_pool,
)?;
let section =
@ -44,6 +48,7 @@ struct CodeStyle<'a> {
fn markup_to_wgpu<'a>(
markup_node: &'a MarkupNode,
code_style: &CodeStyle,
markup_node_pool: &'a SlowPool,
) -> EdResult<(Vec<wgpu_glyph::Text<'a>>, Vec<Rect>)> {
let mut wgpu_texts: Vec<wgpu_glyph::Text<'a>> = Vec::new();
let mut rects: Vec<Rect> = Vec::new();
@ -56,29 +61,33 @@ fn markup_to_wgpu<'a>(
&mut rects,
code_style,
&mut txt_row_col,
markup_node_pool,
)?;
Ok((wgpu_texts, rects))
}
fn draw_attributes(
attributes: &[Attribute],
attributes: &Attributes,
txt_row_col: &(usize, usize),
code_style: &CodeStyle,
) -> Vec<Rect> {
let char_width = code_style.glyph_dim_rect.width;
attributes
.all
.iter()
.map(|attr| match attr {
Attribute::Caret { offset_col } => {
Attribute::Caret { caret } => {
let caret_col = caret.offset_col as f32;
let top_left_x = code_style.txt_coords.x
+ (txt_row_col.1 as f32) * char_width
+ (*offset_col as f32) * char_width;
+ caret_col * char_width;
let top_left_y = code_style.txt_coords.y
+ (txt_row_col.0 as f32) * char_width
+ (*offset_col as f32) * char_width;
+ char_width * 0.2;
make_caret_rect(
top_left_x,
@ -99,14 +108,24 @@ fn markup_to_wgpu_helper<'a>(
rects: &mut Vec<Rect>,
code_style: &CodeStyle,
txt_row_col: &mut (usize, usize),
markup_node_pool: &'a SlowPool,
) -> EdResult<()> {
match markup_node {
MarkupNode::Nested {
ast_node_id: _,
children,
children_ids,
parent_id_opt: _,
} => {
for child in children.iter() {
markup_to_wgpu_helper(child, wgpu_texts, rects, code_style, txt_row_col)?;
for child_id in children_ids.iter() {
let child = markup_node_pool.get(*child_id);
markup_to_wgpu_helper(
child,
wgpu_texts,
rects,
code_style,
txt_row_col,
markup_node_pool,
)?;
}
}
MarkupNode::Text {
@ -114,6 +133,7 @@ fn markup_to_wgpu_helper<'a>(
ast_node_id: _,
syn_high_style,
attributes,
parent_id_opt: _,
} => {
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?;
@ -125,13 +145,13 @@ fn markup_to_wgpu_helper<'a>(
txt_row_col.1 += content.len();
wgpu_texts.push(glyph_text);
}
MarkupNode::Hole {
MarkupNode::Blank {
ast_node_id: _,
attributes,
syn_high_style,
parent_id_opt: _,
} => {
let hole_placeholder = " ";
let glyph_text = wgpu_glyph::Text::new(hole_placeholder)
let glyph_text = wgpu_glyph::Text::new(BLANK_PLACEHOLDER)
.with_color(colors::to_slice(colors::WHITE))
.with_scale(code_style.font_size);
@ -153,7 +173,7 @@ fn markup_to_wgpu_helper<'a>(
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
txt_row_col.1 += hole_placeholder.len();
txt_row_col.1 += BLANK_PLACEHOLDER.len();
wgpu_texts.push(glyph_text);
}
};

View File

@ -0,0 +1,32 @@
use crate::editor::markup::nodes::MarkupNode;
pub type SlowNodeId = usize;
#[derive(Debug)]
pub struct SlowPool {
nodes: Vec<MarkupNode>,
}
impl SlowPool {
pub fn new() -> SlowPool {
SlowPool { nodes: Vec::new() }
}
pub fn add(&mut self, node: MarkupNode) -> SlowNodeId {
let id = self.nodes.len();
self.nodes.push(node);
id
}
pub fn get(&self, node_id: usize) -> &MarkupNode {
// unwrap because Pool doesn't return Result either
self.nodes.get(node_id).unwrap()
}
pub fn get_mut(&mut self, node_id: usize) -> &mut MarkupNode {
// unwrap because Pool doesn't return Result either
self.nodes.get_mut(node_id).unwrap()
}
}

View File

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

View File

@ -198,7 +198,7 @@ pub enum Expr2 {
ext_var: Variable, // 4B
arguments: PoolVec<(Variable, NodeId<Expr2>)>, // 8B
},
Hole, // Rendered as empty box in editor
Blank, // Rendered as empty box in editor
// Compiles, but will crash if reached
RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */),

View File

@ -150,7 +150,7 @@ impl Pool {
}
}
fn get_mut<T>(&mut self, node_id: NodeId<T>) -> &mut T {
pub fn get_mut<T>(&mut self, node_id: NodeId<T>) -> &mut T {
unsafe {
let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T;