Merge remote-tracking branch 'origin/trunk' into type-start-parse-error

This commit is contained in:
Richard Feldman 2021-03-30 23:10:43 -04:00
commit a1de6925c8
42 changed files with 1581 additions and 480 deletions

View file

@ -4,6 +4,8 @@ const RocResult = utils.RocResult;
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const TAG_WIDTH = 8;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Opaque = ?[*]u8; const Opaque = ?[*]u8;
@ -502,6 +504,49 @@ pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes); utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
} }
pub fn listWalkUntil(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, dec: Dec, output: Opaque) callconv(.C) void {
// [ Continue a, Stop a ]
const CONTINUE: usize = 0;
if (accum_width == 0) {
return;
}
if (list.isEmpty()) {
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
return;
}
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, TAG_WIDTH + accum_width) catch unreachable);
@memcpy(alloc + TAG_WIDTH, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| {
var i: usize = 0;
const size = list.len();
while (i < size) : (i += 1) {
const element = source_ptr + i * element_width;
stepper_caller(stepper, element, alloc + TAG_WIDTH, alloc);
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, alloc));
if (usizes[0] != 0) {
// decrement refcount of the remaining items
i += 1;
while (i < size) : (i += 1) {
dec(source_ptr + i * element_width);
}
break;
}
}
}
@memcpy(output orelse unreachable, alloc + TAG_WIDTH, accum_width);
std.heap.c_allocator.free(alloc[0 .. TAG_WIDTH + accum_width]);
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
// List.contains : List k, k -> Bool // List.contains : List k, k -> Bool
pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool { pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool {
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {

View file

@ -12,6 +12,7 @@ comptime {
exportListFn(list.listMapWithIndex, "map_with_index"); exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if"); exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk"); exportListFn(list.listWalk, "walk");
exportListFn(list.listWalkUntil, "walkUntil");
exportListFn(list.listWalkBackwards, "walk_backwards"); exportListFn(list.listWalkBackwards, "walk_backwards");
exportListFn(list.listKeepOks, "keep_oks"); exportListFn(list.listKeepOks, "keep_oks");
exportListFn(list.listKeepErrs, "keep_errs"); exportListFn(list.listKeepErrs, "keep_errs");

View file

@ -195,7 +195,7 @@ interface List2
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. These operations are all ## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. These operations are all
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. ## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! ## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood!
List elem : @List elem List elem : [ @List elem ]
## Initialize ## Initialize

View file

@ -1,7 +1,11 @@
interface Set2 interface Set
exposes [ empty, isEmpty, len, add, drop, map ] exposes [ Set, empty, isEmpty, len, add, drop, map ]
imports [] imports []
## Set
## A Set is an unordered collection of unique elements.
Set elem : [ @Set elem ]
## An empty set. ## An empty set.
empty : Set * empty : Set *

View file

@ -70,6 +70,7 @@ pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks"; pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs"; pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
pub const LIST_WALK: &str = "roc_builtins.list.walk"; pub const LIST_WALK: &str = "roc_builtins.list.walk";
pub const LIST_WALK_UNTIL: &str = "roc_builtins.list.walkUntil";
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards"; pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains"; pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";

View file

@ -771,6 +771,34 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
fn until_type(content: SolvedType) -> SolvedType {
// [ LT, EQ, GT ]
SolvedType::TagUnion(
vec![
(TagName::Global("Continue".into()), vec![content.clone()]),
(TagName::Global("Stop".into()), vec![content]),
],
Box::new(SolvedType::EmptyTagUnion),
)
}
// walkUntil : List elem, (elem -> accum -> [ Continue accum, Stop accum ]), accum -> accum
add_type(
Symbol::LIST_WALK_UNTIL,
top_level_function(
vec![
list_type(flex(TVAR1)),
closure(
vec![flex(TVAR1), flex(TVAR2)],
TVAR3,
Box::new(until_type(flex(TVAR2))),
),
flex(TVAR2),
],
Box::new(flex(TVAR2)),
),
);
// keepIf : List elem, (elem -> Bool) -> List elem // keepIf : List elem, (elem -> Bool) -> List elem
add_type( add_type(
Symbol::LIST_KEEP_IF, Symbol::LIST_KEEP_IF,

View file

@ -89,6 +89,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_KEEP_ERRS=> list_keep_errs, LIST_KEEP_ERRS=> list_keep_errs,
LIST_WALK => list_walk, LIST_WALK => list_walk,
LIST_WALK_BACKWARDS => list_walk_backwards, LIST_WALK_BACKWARDS => list_walk_backwards,
LIST_WALK_UNTIL => list_walk_until,
DICT_TEST_HASH => dict_hash_test_only, DICT_TEST_HASH => dict_hash_test_only,
DICT_LEN => dict_len, DICT_LEN => dict_len,
DICT_EMPTY => dict_empty, DICT_EMPTY => dict_empty,
@ -231,6 +232,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_KEEP_ERRS=> list_keep_errs, Symbol::LIST_KEEP_ERRS=> list_keep_errs,
Symbol::LIST_WALK => list_walk, Symbol::LIST_WALK => list_walk,
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards, Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
Symbol::LIST_WALK_UNTIL => list_walk_until,
Symbol::DICT_TEST_HASH => dict_hash_test_only, Symbol::DICT_TEST_HASH => dict_hash_test_only,
Symbol::DICT_LEN => dict_len, Symbol::DICT_LEN => dict_len,
Symbol::DICT_EMPTY => dict_empty, Symbol::DICT_EMPTY => dict_empty,
@ -2094,60 +2096,17 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum /// List.walk : List elem, (elem -> accum -> accum), accum -> accum
fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); lowlevel_3(symbol, LowLevel::ListWalk, var_store)
let func_var = var_store.fresh();
let accum_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(func_var, Var(Symbol::ARG_2)),
(accum_var, Var(Symbol::ARG_3)),
],
ret_var: accum_var,
};
defn(
symbol,
vec![
(list_var, Symbol::ARG_1),
(func_var, Symbol::ARG_2),
(accum_var, Symbol::ARG_3),
],
var_store,
body,
accum_var,
)
} }
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum /// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); lowlevel_3(symbol, LowLevel::ListWalkBackwards, var_store)
let func_var = var_store.fresh(); }
let accum_var = var_store.fresh();
let body = RunLowLevel { /// List.walkUntil : List elem, (elem, accum -> [ Continue accum, Stop accum ]), accum -> accum
op: LowLevel::ListWalkBackwards, fn list_walk_until(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![ lowlevel_3(symbol, LowLevel::ListWalkUntil, var_store)
(list_var, Var(Symbol::ARG_1)),
(func_var, Var(Symbol::ARG_2)),
(accum_var, Var(Symbol::ARG_3)),
],
ret_var: accum_var,
};
defn(
symbol,
vec![
(list_var, Symbol::ARG_1),
(func_var, Symbol::ARG_2),
(accum_var, Symbol::ARG_3),
],
var_store,
body,
accum_var,
)
} }
/// List.sum : List (Num a) -> Num a /// List.sum : List (Num a) -> Num a

View file

@ -8,7 +8,7 @@ use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
list_map2, list_map3, list_map_with_index, list_prepend, list_product, list_repeat, list_map2, list_map3, list_map_with_index, list_prepend, list_product, list_repeat,
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards, list_reverse, list_set, list_single, list_sum, list_walk_help,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
@ -3879,57 +3879,30 @@ fn run_low_level<'a, 'ctx, 'env>(
list_contains(env, layout_ids, elem, elem_layout, list) list_contains(env, layout_ids, elem, elem_layout, list)
} }
ListWalk => { ListWalk => list_walk_help(
debug_assert_eq!(args.len(), 3);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::EmptyList) => default,
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk(
env, env,
layout_ids, layout_ids,
scope,
parent, parent,
list, args,
element_layout, crate::llvm::build_list::ListWalk::Walk,
func,
func_layout,
default,
default_layout,
), ),
_ => unreachable!("invalid list layout"), ListWalkUntil => list_walk_help(
}
}
ListWalkBackwards => {
// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
debug_assert_eq!(args.len(), 3);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::EmptyList) => default,
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk_backwards(
env, env,
layout_ids, layout_ids,
scope,
parent, parent,
list, args,
element_layout, crate::llvm::build_list::ListWalk::WalkUntil,
func, ),
func_layout, ListWalkBackwards => list_walk_help(
default, env,
default_layout, layout_ids,
scope,
parent,
args,
crate::llvm::build_list::ListWalk::WalkBackwards,
), ),
_ => unreachable!("invalid list layout"),
}
}
ListSum => { ListSum => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);

View file

@ -863,45 +863,34 @@ pub fn list_product<'a, 'ctx, 'env>(
builder.build_load(accum_alloca, "load_final_acum") builder.build_load(accum_alloca, "load_final_acum")
} }
/// List.walk : List elem, (elem -> accum -> accum), accum -> accum pub enum ListWalk {
pub fn list_walk<'a, 'ctx, 'env>( Walk,
env: &Env<'a, 'ctx, 'env>, WalkBackwards,
layout_ids: &mut LayoutIds<'a>, WalkUntil,
parent: FunctionValue<'ctx>, WalkBackwardsUntil,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
func: BasicValueEnum<'ctx>,
func_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
list_walk_generic(
env,
layout_ids,
parent,
list,
element_layout,
func,
func_layout,
default,
default_layout,
&bitcode::LIST_WALK,
)
} }
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum pub fn list_walk_help<'a, 'ctx, 'env>(
pub fn list_walk_backwards<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
scope: &crate::llvm::build::Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
list: BasicValueEnum<'ctx>, args: &[roc_module::symbol::Symbol],
element_layout: &Layout<'a>, variant: ListWalk,
func: BasicValueEnum<'ctx>,
func_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
list_walk_generic( use crate::llvm::build::load_symbol_and_layout;
debug_assert_eq!(args.len(), 3);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::EmptyList) => default,
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk_generic(
env, env,
layout_ids, layout_ids,
parent, parent,
@ -911,8 +900,10 @@ pub fn list_walk_backwards<'a, 'ctx, 'env>(
func_layout, func_layout,
default, default,
default_layout, default_layout,
&bitcode::LIST_WALK_BACKWARDS, variant,
) ),
_ => unreachable!("invalid list layout"),
}
} }
fn list_walk_generic<'a, 'ctx, 'env>( fn list_walk_generic<'a, 'ctx, 'env>(
@ -925,10 +916,17 @@ fn list_walk_generic<'a, 'ctx, 'env>(
func_layout: &Layout<'a>, func_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>, default: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>, default_layout: &Layout<'a>,
zig_function: &str, variant: ListWalk,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; let builder = env.builder;
let zig_function = match variant {
ListWalk::Walk => bitcode::LIST_WALK,
ListWalk::WalkBackwards => bitcode::LIST_WALK_BACKWARDS,
ListWalk::WalkUntil => bitcode::LIST_WALK_UNTIL,
ListWalk::WalkBackwardsUntil => todo!(),
};
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let list_i128 = complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128"); let list_i128 = complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128");
@ -961,6 +959,8 @@ fn list_walk_generic<'a, 'ctx, 'env>(
let result_ptr = env.builder.build_alloca(default.get_type(), "result"); let result_ptr = env.builder.build_alloca(default.get_type(), "result");
match variant {
ListWalk::Walk | ListWalk::WalkBackwards => {
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
@ -976,6 +976,27 @@ fn list_walk_generic<'a, 'ctx, 'env>(
], ],
zig_function, zig_function,
); );
}
ListWalk::WalkUntil | ListWalk::WalkBackwardsUntil => {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
call_void_bitcode_fn(
env,
&[
list_i128,
env.builder
.build_bitcast(transform_ptr, u8_ptr, "to_opaque"),
stepper_caller.into(),
env.builder.build_bitcast(default_ptr, u8_ptr, "to_u8_ptr"),
alignment_iv.into(),
element_width.into(),
default_width.into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
env.builder.build_bitcast(result_ptr, u8_ptr, "to_opaque"),
],
zig_function,
);
}
}
env.builder.build_load(result_ptr, "load_result") env.builder.build_load(result_ptr, "load_result")
} }

View file

@ -111,12 +111,16 @@ fn generate_module_doc<'a>(
}, },
Alias { Alias {
name: _, name,
vars: _, vars: _,
ann: _, ann: _,
} => } => {
// TODO let entry = DocEntry {
{ name: name.value.to_string(),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
acc.push(entry);
(acc, None) (acc, None)
} }
@ -139,7 +143,9 @@ fn comments_or_new_lines_to_docs<'a>(
docs.push_str(doc_str); docs.push_str(doc_str);
docs.push('\n'); docs.push('\n');
} }
Newline | LineComment(_) => {} Newline | LineComment(_) => {
docs = String::new();
}
} }
} }
if docs.is_empty() { if docs.is_empty() {

View file

@ -32,6 +32,7 @@ pub enum LowLevel {
ListMapWithIndex, ListMapWithIndex,
ListKeepIf, ListKeepIf,
ListWalk, ListWalk,
ListWalkUntil,
ListWalkBackwards, ListWalkBackwards,
ListSum, ListSum,
ListProduct, ListProduct,

View file

@ -915,6 +915,7 @@ define_builtins! {
24 LIST_MAP2: "map2" 24 LIST_MAP2: "map2"
25 LIST_MAP3: "map3" 25 LIST_MAP3: "map3"
26 LIST_PRODUCT: "product" 26 LIST_PRODUCT: "product"
27 LIST_WALK_UNTIL: "walkUntil"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -655,8 +655,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]), ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]), ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]), ListWalk | ListWalkUntil | ListWalkBackwards => {
ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]), arena.alloc_slice_copy(&[owned, irrelevant, owned])
}
ListSum | ListProduct => arena.alloc_slice_copy(&[borrowed]), ListSum | ListProduct => arena.alloc_slice_copy(&[borrowed]),
// TODO when we have lists with capacity (if ever) // TODO when we have lists with capacity (if ever)

View file

@ -2839,11 +2839,69 @@ pub fn with_hole<'a>(
variant_var, variant_var,
name: tag_name, name: tag_name,
arguments: args, arguments: args,
.. ext_var,
} => { } => {
use crate::layout::UnionVariant::*; use crate::layout::UnionVariant::*;
let arena = env.arena; let arena = env.arena;
let desc = env.subs.get_without_compacting(variant_var);
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = desc.content {
let mut loc_pattern_args = vec![];
let mut loc_expr_args = vec![];
let proc_symbol = env.unique_symbol();
for arg_var in arg_vars {
let arg_symbol = env.unique_symbol();
let loc_pattern =
Located::at_zero(roc_can::pattern::Pattern::Identifier(arg_symbol));
let loc_expr = Located::at_zero(roc_can::expr::Expr::Var(arg_symbol));
loc_pattern_args.push((arg_var, loc_pattern));
loc_expr_args.push((arg_var, loc_expr));
}
let loc_body = Located::at_zero(roc_can::expr::Expr::Tag {
variant_var: ret_var,
name: tag_name,
arguments: loc_expr_args,
ext_var,
});
let inserted = procs.insert_anonymous(
env,
proc_symbol,
variant_var,
loc_pattern_args,
loc_body,
CapturedSymbols::None,
ret_var,
layout_cache,
);
match inserted {
Ok(layout) => {
return Stmt::Let(
assigned,
call_by_pointer(env, procs, proc_symbol, layout),
layout,
hole,
);
}
Err(runtime_error) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"RuntimeError {} line {} {:?}",
file!(),
line!(),
runtime_error,
)));
}
}
}
let res_variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs); let res_variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs);
let variant = match res_variant { let variant = match res_variant {

View file

@ -4613,7 +4613,6 @@ mod test_reporting {
#[test] #[test]
fn type_apply_stray_dot() { fn type_apply_stray_dot() {
// TODO good message
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
@ -6052,6 +6051,39 @@ mod test_reporting {
) )
} }
#[test]
fn applied_tag_function() {
report_problem_as(
indoc!(
r#"
x : List [ Foo Str ]
x = List.map [ 1, 2 ] Foo
x
"#
),
indoc!(
r#"
TYPE MISMATCH
Something is off with the body of the `x` definition:
1 x : List [ Foo Str ]
2 x = List.map [ 1, 2 ] Foo
^^^^^^^^^^^^^^^^^^^^^
This `map` call produces:
List [ Foo Num a ]
But the type annotation on `x` says it should be:
List [ Foo Str ]
"#
),
)
}
#[test] #[test]
fn pattern_in_parens_open() { fn pattern_in_parens_open() {
report_problem_as( report_problem_as(

View file

@ -566,6 +566,107 @@ mod solve_expr {
); );
} }
#[test]
fn applied_tag() {
infer_eq_without_problem(
indoc!(
r#"
List.map [ "a", "b" ] \elem -> Foo elem
"#
),
"List [ Foo Str ]*",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function() {
infer_eq_without_problem(
indoc!(
r#"
foo = Foo
foo "hi"
"#
),
"[ Foo Str ]*",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function_list_map() {
infer_eq_without_problem(
indoc!(
r#"
List.map [ "a", "b" ] Foo
"#
),
"List [ Foo Str ]*",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function_list() {
infer_eq_without_problem(
indoc!(
r#"
[ \x -> Bar x, Foo ]
"#
),
"List (a -> [ Bar a, Foo a ]*)",
)
}
// Tests (Func, TagUnion)
#[test]
fn applied_tag_function_list_other_way() {
infer_eq_without_problem(
indoc!(
r#"
[ Foo, \x -> Bar x ]
"#
),
"List (a -> [ Bar a, Foo a ]*)",
)
}
// Tests (Func, TagUnion)
#[test]
fn applied_tag_function_record() {
infer_eq_without_problem(
indoc!(
r#"
foo = Foo
{
x: [ foo, Foo ],
y: [ foo, \x -> Foo x ],
z: [ foo, \x,y -> Foo x y ]
}
"#
),
"{ x : List [ Foo ]*, y : List (a -> [ Foo a ]*), z : List (b, c -> [ Foo b c ]*) }",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function_with_annotation() {
infer_eq_without_problem(
indoc!(
r#"
x : List [ Foo I64 ]
x = List.map [ 1, 2 ] Foo
x
"#
),
"List [ Foo I64 ]",
)
}
#[test] #[test]
fn def_2_arg_closure() { fn def_2_arg_closure() {
infer_eq( infer_eq(

View file

@ -320,6 +320,32 @@ fn list_walk_substraction() {
assert_evals_to!(r#"List.walk [ 1, 2 ] Num.sub 1"#, 2, i64); assert_evals_to!(r#"List.walk [ 1, 2 ] Num.sub 1"#, 2, i64);
} }
#[test]
fn list_walk_until_sum() {
assert_evals_to!(
r#"List.walkUntil [ 1, 2 ] (\a,b -> Continue (a + b)) 0"#,
3,
i64
);
}
#[test]
fn list_walk_until_even_prefix_sum() {
assert_evals_to!(
r#"
helper = \a, b ->
if Num.isEven a then
Continue (a + b)
else
Stop b
List.walkUntil [ 2, 4, 8, 9 ] helper 0"#,
2 + 4 + 8,
i64
);
}
#[test] #[test]
fn list_keep_if_empty_list_of_int() { fn list_keep_if_empty_list_of_int() {
assert_evals_to!( assert_evals_to!(

View file

@ -3,6 +3,7 @@
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::{RocList, RocStr};
#[test] #[test]
fn applied_tag_nothing_ir() { fn applied_tag_nothing_ir() {
@ -974,3 +975,61 @@ fn newtype_wrapper() {
|x: &i64| *x |x: &i64| *x
); );
} }
#[test]
fn applied_tag_function() {
assert_evals_to!(
indoc!(
r#"
x : List [ Foo Str ]
x = List.map [ "a", "b" ] Foo
x
"#
),
RocList::from_slice(&[
RocStr::from_slice("a".as_bytes()),
RocStr::from_slice("b".as_bytes())
]),
RocList<RocStr>
);
}
#[test]
fn applied_tag_function_result() {
assert_evals_to!(
indoc!(
r#"
x : List (Result Str *)
x = List.map [ "a", "b" ] Ok
x
"#
),
RocList::from_slice(&[
(1, RocStr::from_slice("a".as_bytes())),
(1, RocStr::from_slice("b".as_bytes()))
]),
RocList<(i64, RocStr)>
);
}
#[test]
fn applied_tag_function_linked_list() {
assert_evals_to!(
indoc!(
r#"
ConsList a : [ Nil, Cons a (ConsList a) ]
x : List (ConsList Str)
x = List.map2 [ "a", "b" ] [ Nil, Cons "c" Nil ] Cons
when List.first x is
Ok (Cons "a" Nil) -> 1
_ -> 0
"#
),
1,
i64
);
}

View file

@ -1,4 +1,6 @@
use roc_collections::all::{get_shared, relative_complement, union, MutMap, SendSet}; use roc_collections::all::{
default_hasher, get_shared, relative_complement, union, MutMap, SendSet,
};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_types::boolean_algebra::Bool; use roc_types::boolean_algebra::Bool;
@ -1069,6 +1071,12 @@ fn unify_flat_type(
problems problems
} }
} }
(TagUnion(tags, ext), Func(args, closure, ret)) if tags.len() == 1 => {
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, true)
}
(Func(args, closure, ret), TagUnion(tags, ext)) if tags.len() == 1 => {
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, false)
}
(other1, other2) => mismatch!( (other1, other2) => mismatch!(
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}", "Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
other1, other1,
@ -1250,3 +1258,54 @@ fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
Content::RecursionVar { .. } Content::RecursionVar { .. }
) )
} }
#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
fn unify_tag_union_and_func(
tags: &MutMap<TagName, Vec<Variable>>,
args: &Vec<Variable>,
subs: &mut Subs,
pool: &mut Pool,
ctx: &Context,
ext: &Variable,
ret: &Variable,
closure: &Variable,
left: bool,
) -> Outcome {
use FlatType::*;
let (tag_name, payload) = tags.iter().next().unwrap();
if payload.is_empty() {
let mut new_tags = MutMap::with_capacity_and_hasher(1, default_hasher());
new_tags.insert(tag_name.clone(), args.to_owned());
let content = Structure(TagUnion(new_tags, *ext));
let new_tag_union_var = fresh(subs, pool, ctx, content);
let problems = if left {
unify_pool(subs, pool, new_tag_union_var, *ret)
} else {
unify_pool(subs, pool, *ret, new_tag_union_var)
};
if problems.is_empty() {
let desc = if left {
subs.get(ctx.second)
} else {
subs.get(ctx.first)
};
subs.union(ctx.first, ctx.second, desc);
}
problems
} else {
mismatch!(
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
TagUnion(tags.clone(), *ext),
Func(args.to_owned(), *closure, *ret)
)
}
}

View file

@ -2,6 +2,9 @@ interface Test
exposes [ singleline, multiline, multiparagraph, codeblock ] exposes [ singleline, multiline, multiparagraph, codeblock ]
imports [] imports []
## This is a block
Block : [ @Block ]
## Single line documentation. ## Single line documentation.
singleline : Bool -> Bool singleline : Bool -> Bool

View file

@ -1,3 +1,6 @@
#[macro_use]
extern crate pretty_assertions;
use roc_docs::{documentation_to_template_data, files_to_documentations, ModuleEntry}; use roc_docs::{documentation_to_template_data, files_to_documentations, ModuleEntry};
use std::path::PathBuf; use std::path::PathBuf;
@ -20,6 +23,10 @@ mod test_docs {
}; };
let expected_entries = vec![ let expected_entries = vec![
ModuleEntry {
name: "Block".to_string(),
docs: "<p>This is a block</p>\n".to_string(),
},
ModuleEntry { ModuleEntry {
name: "singleline".to_string(), name: "singleline".to_string(),
docs: "<p>Single line documentation.</p>\n".to_string(), docs: "<p>Single line documentation.</p>\n".to_string(),

View file

@ -78,7 +78,7 @@ 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. * 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. * 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. * 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. * 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. Arrow navigation should allow you to quickly view different versions of the file.
* 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 :( . * 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 #### Autocomplete

View file

@ -1,4 +1,4 @@
use crate::editor::slow_pool::SlowNodeId; use crate::editor::slow_pool::MarkNodeId;
use colored::*; use colored::*;
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
@ -15,7 +15,7 @@ pub enum EdError {
node_id node_id
))] ))]
CaretNotFound { CaretNotFound {
node_id: SlowNodeId, node_id: MarkNodeId,
backtrace: Backtrace, backtrace: Backtrace,
}, },
@ -31,25 +31,65 @@ pub enum EdError {
))] ))]
ClipboardInitFailed { err_msg: String }, ClipboardInitFailed { err_msg: String },
#[snafu(display(
"ExpectedTextNode: the function {} expected a Text node, got {} instead.",
function_name,
node_type
))]
ExpectedTextNode {
function_name: String,
node_type: 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 },
#[snafu(display(
"IndexOfFailed: Element {} was not found in collection {}.",
elt_str,
collection_str
))]
IndexOfFailed {
elt_str: String,
collection_str: String,
backtrace: Backtrace,
},
#[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))] #[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))]
KeyNotFound { KeyNotFound {
key_str: String, key_str: String,
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display("NestedNodeWithoutChildren: tried to retrieve child from Nested MarkupNode with id {} but it had no children.", node_id))] #[snafu(display(
NestedNodeWithoutChildren { "MissingParent: MarkupNode with id {} should have a parent but there was none.",
node_id: SlowNodeId, node_id
))]
MissingParent {
node_id: MarkNodeId,
backtrace: Backtrace, 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))] #[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 { NestedNodeMissingChild {
node_id: SlowNodeId, node_id: MarkNodeId,
children_ids: Vec<SlowNodeId>, children_ids: Vec<MarkNodeId>,
backtrace: Backtrace,
},
#[snafu(display(
"NestedNodeRequired: required a Nested node at this position, node was a {}.",
node_type
))]
NestedNodeRequired {
node_type: 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: MarkNodeId,
backtrace: Backtrace, backtrace: Backtrace,
}, },
@ -72,6 +112,9 @@ 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 }, ParseError { syntax_err: String },
#[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmpyRecord."))]
RecordWithoutFields { backtrace: Backtrace },
#[snafu(display("UIError: {}", msg))] #[snafu(display("UIError: {}", msg))]
UIErrorBacktrace { msg: String, backtrace: Backtrace }, UIErrorBacktrace { msg: String, backtrace: Backtrace },
} }

View file

@ -0,0 +1,52 @@
use crate::editor::slow_pool::MarkNodeId;
use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult;
use crate::ui::util::{slice_get, slice_get_mut};
#[derive(Debug)]
pub struct GridNodeMap {
pub lines: Vec<Vec<MarkNodeId>>,
}
impl GridNodeMap {
pub fn new() -> GridNodeMap {
GridNodeMap {
lines: vec![vec![]],
}
}
pub fn add_to_line(&mut self, line: usize, len: usize, node_id: MarkNodeId) -> UIResult<()> {
let line_ref = slice_get_mut(line, &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(
&mut self,
line: usize,
index: usize,
len: usize,
node_id: MarkNodeId,
) -> UIResult<()> {
let line_ref = slice_get_mut(line, &mut self.lines)?;
let new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
line_ref.splice(index..index, new_cols_vec);
Ok(())
}
/*pub fn new_line(&mut self) {
self.lines.push(vec![])
}*/
pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult<MarkNodeId> {
let line = slice_get(caret_pos.line, &self.lines)?;
let node_id = slice_get(caret_pos.column, line)?;
Ok(*node_id)
}
}

View file

@ -1,8 +1,9 @@
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::ed_error::print_ui_err;
use crate::editor::mvc::ed_view;
use crate::editor::mvc::ed_view::RenderedWgpu;
use crate::editor::resources::strings::NOTHING_OPENED; use crate::editor::resources::strings::NOTHING_OPENED;
use crate::editor::slow_pool::SlowPool;
use crate::editor::{ use crate::editor::{
config::Config, config::Config,
ed_error::print_err, ed_error::print_err,
@ -132,7 +133,6 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let mut env_pool = Pool::with_capacity(1024); let mut env_pool = Pool::with_capacity(1024);
let env_arena = Bump::new(); let env_arena = Bump::new();
let code_arena = Bump::new(); let code_arena = Bump::new();
let mut markup_node_pool = SlowPool::new();
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);
@ -173,13 +173,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
}; };
let ed_model_opt = { let ed_model_opt = {
let ed_model_res = ed_model::init_model( let ed_model_res = ed_model::init_model(&code_str, file_path, env, &code_arena);
&code_str,
file_path,
env,
&code_arena,
&mut markup_node_pool,
);
match ed_model_res { match ed_model_res {
Ok(mut ed_model) => { Ok(mut ed_model) => {
@ -194,6 +188,8 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
} }
}; };
let mut rendered_wgpu_opt: Option<RenderedWgpu> = None;
let mut app_model = AppModel::init(ed_model_opt); let mut app_model = AppModel::init(ed_model_opt);
let mut keyboard_modifiers = ModifiersState::empty(); let mut keyboard_modifiers = ModifiersState::empty();
@ -298,21 +294,25 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
.output; .output;
if let Some(ref mut ed_model) = app_model.ed_model_opt { if let Some(ref mut ed_model) = app_model.ed_model_opt {
// TODO only calculate if markup_root has changed if rendered_wgpu_opt.is_none() || ed_model.dirty {
let text_and_rects_res = super::mvc::ed_view::model_to_wgpu( let rendered_wgpu_res =
ed_model, ed_view::model_to_wgpu(ed_model, &size, CODE_TXT_XY.into(), &config);
&size,
CODE_TXT_XY.into(),
&config,
&markup_node_pool,
);
match text_and_rects_res { match rendered_wgpu_res {
Ok((text_section, rects)) => { Ok(rendered_wgpu) => rendered_wgpu_opt = Some(rendered_wgpu),
glyph_brush.queue(text_section); Err(e) => print_err(&e),
}
ed_model.dirty = false;
}
if let Some(ref rendered_wgpu) = rendered_wgpu_opt {
let borrowed_text = rendered_wgpu.text.to_borrowed();
glyph_brush.queue(borrowed_text);
draw_all_rects( draw_all_rects(
&rects, &rendered_wgpu.rects,
&mut encoder, &mut encoder,
&frame.view, &frame.view,
&gpu_device, &gpu_device,
@ -320,8 +320,6 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
&ed_theme, &ed_theme,
) )
} }
Err(e) => print_err(&e),
}
} else { } else {
queue_no_file_text( queue_no_file_text(
&size, &size,

View file

@ -1,5 +1,9 @@
use super::attribute::Attributes; use super::attribute::Attributes;
use crate::editor::slow_pool::SlowNodeId; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::ExpectedTextNode;
use crate::editor::ed_error::GetContentOnNestedNode;
use crate::editor::ed_error::NestedNodeRequired;
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::lang::{ use crate::lang::{
@ -13,36 +17,131 @@ use bumpalo::Bump;
pub enum MarkupNode { pub enum MarkupNode {
Nested { Nested {
ast_node_id: NodeId<Expr2>, ast_node_id: NodeId<Expr2>,
children_ids: Vec<SlowNodeId>, children_ids: Vec<MarkNodeId>,
parent_id_opt: Option<SlowNodeId>, parent_id_opt: Option<MarkNodeId>,
}, },
Text { Text {
content: String, content: String,
ast_node_id: NodeId<Expr2>, ast_node_id: NodeId<Expr2>,
syn_high_style: HighlightStyle, syn_high_style: HighlightStyle,
attributes: Attributes, attributes: Attributes,
parent_id_opt: Option<SlowNodeId>, parent_id_opt: Option<MarkNodeId>,
}, },
Blank { Blank {
ast_node_id: NodeId<Expr2>, ast_node_id: NodeId<Expr2>,
attributes: Attributes, attributes: Attributes,
syn_high_style: HighlightStyle, syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank
parent_id_opt: Option<SlowNodeId>, parent_id_opt: Option<MarkNodeId>,
}, },
} }
pub const BLANK_PLACEHOLDER: &str = " "; impl MarkupNode {
pub fn get_ast_node_id(&self) -> NodeId<Expr2> {
match self {
MarkupNode::Nested { ast_node_id, .. } => *ast_node_id,
MarkupNode::Text { ast_node_id, .. } => *ast_node_id,
MarkupNode::Blank { ast_node_id, .. } => *ast_node_id,
}
}
pub fn get_parent_id_opt(&self) -> Option<MarkNodeId> {
match self {
MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt,
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt,
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt,
}
}
pub fn get_children_ids(&self) -> Vec<MarkNodeId> {
match self {
MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(),
MarkupNode::Text { .. } => vec![],
MarkupNode::Blank { .. } => vec![],
}
}
pub fn get_sibling_ids(&self, markup_node_pool: &SlowPool) -> Vec<MarkNodeId> {
if let Some(parent_id) = self.get_parent_id_opt() {
let parent_node = markup_node_pool.get(parent_id);
parent_node.get_children_ids()
} else {
vec![]
}
}
// can't be &str, this creates borrowing issues
pub fn get_content(&self) -> EdResult<String> {
match self {
MarkupNode::Nested { .. } => GetContentOnNestedNode {}.fail(),
MarkupNode::Text { content, .. } => Ok(content.clone()),
MarkupNode::Blank { .. } => Ok(BLANK_PLACEHOLDER.to_owned()),
}
}
pub fn get_content_mut(&mut self) -> EdResult<&mut String> {
match self {
MarkupNode::Nested { .. } => ExpectedTextNode {
function_name: "set_content".to_owned(),
node_type: self.node_type_as_string(),
}
.fail(),
MarkupNode::Text { content, .. } => Ok(content),
MarkupNode::Blank { .. } => ExpectedTextNode {
function_name: "set_content".to_owned(),
node_type: self.node_type_as_string(),
}
.fail(),
}
}
pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> {
if let MarkupNode::Nested { children_ids, .. } = self {
children_ids.splice(index..index, vec![child_id]);
} else {
NestedNodeRequired {
node_type: self.node_type_as_string(),
}
.fail()?;
}
Ok(())
}
pub fn node_type_as_string(&self) -> String {
let type_str = match self {
MarkupNode::Nested { .. } => "Nested",
MarkupNode::Text { .. } => "Text",
MarkupNode::Blank { .. } => "Blank",
};
type_str.to_owned()
}
pub fn is_blank(&self) -> bool {
matches!(self, MarkupNode::Blank { .. })
}
pub fn is_nested(&self) -> bool {
matches!(self, MarkupNode::Nested { .. })
}
}
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
pool_str.as_str(env.pool).to_owned() pool_str.as_str(env.pool).to_owned()
} }
pub const BLANK_PLACEHOLDER: &str = " ";
pub const LEFT_ACCOLADE: &str = "{ ";
pub const RIGHT_ACCOLADE: &str = " }";
pub const COLON: &str = ": ";
fn new_markup_node( fn new_markup_node(
text: String, text: String,
node_id: NodeId<Expr2>, node_id: NodeId<Expr2>,
highlight_style: HighlightStyle, highlight_style: HighlightStyle,
markup_node_pool: &mut SlowPool, markup_node_pool: &mut SlowPool,
) -> SlowNodeId { ) -> MarkNodeId {
let node = MarkupNode::Text { let node = MarkupNode::Text {
content: text, content: text,
ast_node_id: node_id, ast_node_id: node_id,
@ -60,7 +159,7 @@ pub fn expr2_to_markup<'a, 'b>(
env: &mut Env<'b>, env: &mut Env<'b>,
expr2: &Expr2, expr2: &Expr2,
markup_node_pool: &mut SlowPool, markup_node_pool: &mut SlowPool,
) -> SlowNodeId { ) -> MarkNodeId {
// TODO find way to add current expr2 to pool // TODO find way to add current expr2 to pool
let node_id = env.pool.add(Expr2::Blank); let node_id = env.pool.add(Expr2::Blank);
@ -134,7 +233,7 @@ pub fn expr2_to_markup<'a, 'b>(
} }
Expr2::Record { fields, .. } => { Expr2::Record { fields, .. } => {
let mut children_ids = vec![new_markup_node( let mut children_ids = vec![new_markup_node(
"{ ".to_string(), LEFT_ACCOLADE.to_string(),
node_id, node_id,
HighlightStyle::Bracket, HighlightStyle::Bracket,
markup_node_pool, markup_node_pool,
@ -155,7 +254,7 @@ pub fn expr2_to_markup<'a, 'b>(
)); ));
children_ids.push(new_markup_node( children_ids.push(new_markup_node(
": ".to_string(), COLON.to_string(),
node_id, node_id,
HighlightStyle::Operator, HighlightStyle::Operator,
markup_node_pool, markup_node_pool,
@ -174,7 +273,7 @@ pub fn expr2_to_markup<'a, 'b>(
} }
children_ids.push(new_markup_node( children_ids.push(new_markup_node(
" }".to_string(), RIGHT_ACCOLADE.to_string(),
node_id, node_id,
HighlightStyle::Bracket, HighlightStyle::Bracket,
markup_node_pool, markup_node_pool,
@ -198,7 +297,7 @@ pub fn expr2_to_markup<'a, 'b>(
} }
} }
pub fn set_parent_for_all(markup_node_id: SlowNodeId, markup_node_pool: &mut SlowPool) { pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut SlowPool) {
let node = markup_node_pool.get(markup_node_id); let node = markup_node_pool.get(markup_node_id);
if let MarkupNode::Nested { if let MarkupNode::Nested {
@ -217,17 +316,17 @@ pub fn set_parent_for_all(markup_node_id: SlowNodeId, markup_node_pool: &mut Slo
} }
pub fn set_parent_for_all_helper( pub fn set_parent_for_all_helper(
markup_node_id: SlowNodeId, markup_node_id: MarkNodeId,
parent_node_id: SlowNodeId, parent_node_id: MarkNodeId,
markup_node_pool: &mut SlowPool, markup_node_pool: &mut SlowPool,
) { ) {
let node = markup_node_pool.get_mut(markup_node_id); let node = markup_node_pool.get_mut(markup_node_id);
match node { match node {
MarkupNode::Nested { MarkupNode::Nested {
ast_node_id: _,
children_ids, children_ids,
parent_id_opt, parent_id_opt,
..
} => { } => {
*parent_id_opt = Some(parent_node_id); *parent_id_opt = Some(parent_node_id);
@ -238,18 +337,7 @@ pub fn set_parent_for_all_helper(
set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool); set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool);
} }
} }
MarkupNode::Text { MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
content: _, MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
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

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

View file

@ -1,4 +1,5 @@
use super::app_model::AppModel; use super::app_model::AppModel;
use super::ed_update;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::ui::text::lines::SelectableLines; use crate::ui::text::lines::SelectableLines;
use crate::window::keyboard_input::from_winit; use crate::window::keyboard_input::from_winit;
@ -50,10 +51,10 @@ pub fn pass_keydown_to_focused(
Ok(()) Ok(())
} }
pub fn handle_new_char(_received_char: &char, app_model: &mut AppModel) -> EdResult<()> { pub fn handle_new_char(received_char: &char, 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 {
if ed_model.has_focus { if ed_model.has_focus {
unimplemented!("TODO"); ed_update::handle_new_char(received_char, ed_model)?;
} }
} }

View file

@ -1,5 +1,6 @@
use crate::editor::code_lines::CodeLines; use crate::editor::code_lines::CodeLines;
use crate::editor::slow_pool::{SlowNodeId, SlowPool}; use crate::editor::grid_node_map::GridNodeMap;
use crate::editor::slow_pool::{MarkNodeId, SlowPool};
use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::{ use crate::editor::{
ed_error::EdError::ParseError, ed_error::EdError::ParseError,
@ -10,34 +11,31 @@ use crate::editor::{
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::Expr2; use crate::lang::ast::Expr2;
use crate::lang::expr::{str_to_expr2, Env}; use crate::lang::expr::{str_to_expr2, Env};
use crate::lang::pool::NodeId;
use crate::lang::scope::Scope; use crate::lang::scope::Scope;
use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::lines::MoveCaretFun;
use crate::ui::text::selection::validate_raw_sel;
use crate::ui::text::selection::RawSelection;
use crate::ui::text::selection::Selection;
use crate::ui::text::text_pos::TextPos;
use crate::ui::text::{lines, lines::Lines, lines::SelectableLines};
use crate::ui::ui_error::UIResult;
use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString; use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use roc_region::all::Region; use roc_region::all::Region;
use std::path::Path; use std::path::Path;
use winit::event::VirtualKeyCode;
use VirtualKeyCode::*;
#[derive(Debug)] #[derive(Debug)]
pub struct EdModel<'a> { pub struct EdModel<'a> {
pub module: EdModule<'a>, pub module: EdModule<'a>,
pub file_path: &'a Path, pub file_path: &'a Path,
pub code_lines: CodeLines, pub code_lines: CodeLines,
pub markup_root_id: SlowNodeId, // allows us to map window coordinates to MarkNodeId's
pub grid_node_map: GridNodeMap,
pub markup_root_id: MarkNodeId,
pub markup_node_pool: SlowPool,
// 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,
// Option<SlowNodeId>: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used. // Option<MarkNodeId>: MarkupNode that corresponds to caret position, Option because this MarkNodeId is only calculated when it needs to be used.
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<SlowNodeId>)>, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>,
// EdModel is dirty if it has changed since the previous render.
pub dirty: bool,
} }
pub fn init_model<'a>( pub fn init_model<'a>(
@ -45,11 +43,11 @@ pub fn init_model<'a>(
file_path: &'a Path, file_path: &'a Path,
env: Env<'a>, env: Env<'a>,
code_arena: &'a Bump, code_arena: &'a Bump,
markup_node_pool: &mut SlowPool,
) -> 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)?;
// TODO fix moving issue and insert module.ast_root into pool
let ast_root_id = module.env.pool.add(Expr2::Blank); let ast_root_id = module.ast_root_id;
let mut markup_node_pool = SlowPool::new();
let markup_root_id = if code_str.is_empty() { let markup_root_id = if code_str.is_empty() {
let blank_root = MarkupNode::Blank { let blank_root = MarkupNode::Blank {
@ -63,196 +61,36 @@ pub fn init_model<'a>(
markup_node_pool.add(blank_root) markup_node_pool.add(blank_root)
} else { } else {
let temp_markup_root_id = expr2_to_markup( let ast_root = &module.env.pool.get(ast_root_id);
code_arena,
&mut module.env, let temp_markup_root_id =
&module.ast_root, expr2_to_markup(code_arena, &mut module.env, ast_root, &mut markup_node_pool);
markup_node_pool, set_parent_for_all(temp_markup_root_id, &mut markup_node_pool);
);
set_parent_for_all(temp_markup_root_id, markup_node_pool);
temp_markup_root_id temp_markup_root_id
}; };
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: CodeLines::from_str(code_str), code_lines,
grid_node_map,
markup_root_id, markup_root_id,
markup_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((CaretWSelect::default(), None)),
dirty: true,
}) })
} }
impl<'a> EdModel<'a> {
pub fn move_caret(
&mut self,
move_fun: MoveCaretFun<CodeLines>,
modifiers: &Modifiers,
) -> UIResult<()> {
for caret_tup in self.caret_w_select_vec.iter_mut() {
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
caret_tup.1 = None;
}
Ok(())
}
}
impl<'a> SelectableLines for EdModel<'a> {
fn get_caret(self) -> TextPos {
self.caret_w_select_vec.first().0.caret_pos
}
// keeps active selection
fn set_caret(&mut self, caret_pos: TextPos) {
let caret_tup = self.caret_w_select_vec.first_mut();
caret_tup.0.caret_pos = caret_pos;
caret_tup.1 = None;
}
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_left;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_right;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_up;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_down;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_home;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_end;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn get_selection(&self) -> Option<Selection> {
self.caret_w_select_vec.first().0.selection_opt
}
fn is_selection_active(&self) -> bool {
self.get_selection().is_some()
}
fn get_selected_str(&self) -> UIResult<Option<String>> {
if let Some(selection) = self.get_selection() {
let start_line_index = selection.start_pos.line;
let start_col = selection.start_pos.column;
let end_line_index = selection.end_pos.line;
let end_col = selection.end_pos.column;
if start_line_index == end_line_index {
let line_ref = self.code_lines.get_line(start_line_index)?;
Ok(Some(line_ref[start_col..end_col].to_string()))
} else {
let full_str = String::new();
// TODO
Ok(Some(full_str))
}
} else {
Ok(None)
}
}
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()> {
self.caret_w_select_vec.first_mut().0.selection_opt = Some(validate_raw_sel(raw_sel)?);
Ok(())
}
fn set_sel_none(&mut self) {
self.caret_w_select_vec.first_mut().0.selection_opt = None;
}
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
self.caret_w_select_vec.first_mut().0 = caret_w_sel;
}
fn select_all(&mut self) -> UIResult<()> {
if self.code_lines.nr_of_chars() > 0 {
let last_pos = self.last_text_pos()?;
self.set_raw_sel(RawSelection {
start_pos: TextPos { line: 0, column: 0 },
end_pos: last_pos,
})?;
self.set_caret(last_pos);
}
Ok(())
}
fn last_text_pos(&self) -> UIResult<TextPos> {
let nr_of_lines = self.code_lines.lines.len();
let last_line_index = nr_of_lines - 1;
let last_line = self.code_lines.get_line(last_line_index)?;
Ok(TextPos {
line: self.code_lines.lines.len() - 1,
column: last_line.len(),
})
}
fn handle_key_down(
&mut self,
modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
) -> UIResult<()> {
match virtual_keycode {
Left => self.move_caret_left(modifiers),
Up => self.move_caret_up(modifiers),
Right => self.move_caret_right(modifiers),
Down => self.move_caret_down(modifiers),
A => {
if modifiers.ctrl {
self.select_all()
} else {
Ok(())
}
}
Home => self.move_caret_home(modifiers),
End => self.move_caret_end(modifiers),
_ => Ok(()),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct EdModule<'a> { pub struct EdModule<'a> {
pub env: Env<'a>, pub env: Env<'a>,
pub ast_root: Expr2, pub ast_root_id: NodeId<Expr2>,
} }
impl<'a> EdModule<'a> { impl<'a> EdModule<'a> {
@ -265,19 +103,19 @@ impl<'a> EdModule<'a> {
let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env, &mut scope, region); let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env, &mut scope, region);
match expr2_result { match expr2_result {
Ok((expr2, _output)) => Ok(EdModule { Ok((expr2, _output)) => {
env, let ast_root_id = env.pool.add(expr2);
ast_root: expr2,
}), Ok(EdModule { env, ast_root_id })
}
Err(err) => Err(ParseError { Err(err) => Err(ParseError {
syntax_err: format!("{:?}", err), syntax_err: format!("{:?}", err),
}), }),
} }
} else { } else {
Ok(EdModule { let ast_root_id = env.pool.add(Expr2::Blank);
env,
ast_root: Expr2::Blank, Ok(EdModule { env, ast_root_id })
})
} }
} }
} }

View file

@ -0,0 +1,420 @@
use crate::editor::code_lines::CodeLines;
use crate::editor::ed_error::EdResult;
use crate::editor::grid_node_map::GridNodeMap;
use crate::editor::markup::attribute::Attributes;
use crate::editor::markup::nodes;
use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::ed_model::EdModel;
use crate::editor::mvc::record_update::start_new_record;
use crate::editor::mvc::record_update::update_record_colon;
use crate::editor::mvc::record_update::update_record_field;
use crate::editor::slow_pool::MarkNodeId;
use crate::editor::slow_pool::SlowPool;
use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::util::index_of;
use crate::lang::ast::Expr2;
use crate::lang::pool::{NodeId, PoolStr, PoolVec};
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::lines::MoveCaretFun;
use crate::ui::text::selection::validate_raw_sel;
use crate::ui::text::selection::RawSelection;
use crate::ui::text::selection::Selection;
use crate::ui::text::text_pos::TextPos;
use crate::ui::text::{lines, lines::Lines, lines::SelectableLines};
use crate::ui::ui_error::UIResult;
use crate::ui::util::is_newline;
use crate::window::keyboard_input::Modifiers;
use winit::event::VirtualKeyCode;
use VirtualKeyCode::*;
impl<'a> EdModel<'a> {
pub fn move_caret(
&mut self,
move_fun: MoveCaretFun<CodeLines>,
modifiers: &Modifiers,
) -> UIResult<()> {
for caret_tup in self.caret_w_select_vec.iter_mut() {
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
caret_tup.1 = None;
}
Ok(())
}
// TODO delete once we use `GridNodeMap` for everything that now uses `CodeLines`.
// disregards EdModel.code_lines because the caller knows the resulting caret position will be valid.
// allows us to prevent multiple updates to EdModel.code_lines
pub fn simple_move_carets_right(&mut self) {
for caret_tup in self.caret_w_select_vec.iter_mut() {
caret_tup.0.caret_pos.column += 1;
caret_tup.1 = None;
}
}
pub fn build_node_map_from_markup(
markup_root_id: MarkNodeId,
markup_node_pool: &SlowPool,
) -> EdResult<GridNodeMap> {
let mut grid_node_map = GridNodeMap::new();
EdModel::build_grid_node_map(markup_root_id, &mut grid_node_map, markup_node_pool)?;
Ok(grid_node_map)
}
fn build_grid_node_map(
node_id: MarkNodeId,
grid_node_map: &mut GridNodeMap,
markup_node_pool: &SlowPool,
) -> EdResult<()> {
let node = markup_node_pool.get(node_id);
if node.is_nested() {
for child_id in node.get_children_ids() {
EdModel::build_grid_node_map(child_id, grid_node_map, markup_node_pool)?;
}
} else {
let node_content_str = node.get_content()?;
grid_node_map.add_to_line(0, node_content_str.len(), node_id)?;
}
Ok(())
}
pub fn build_code_lines_from_markup(
markup_root_id: MarkNodeId,
markup_node_pool: &SlowPool,
) -> EdResult<CodeLines> {
let mut all_code_string = String::new();
EdModel::build_markup_string(markup_root_id, &mut all_code_string, markup_node_pool)?;
let code_lines = CodeLines::from_str(&all_code_string);
Ok(code_lines)
}
fn build_markup_string(
node_id: MarkNodeId,
all_code_string: &mut String,
markup_node_pool: &SlowPool,
) -> EdResult<()> {
let node = markup_node_pool.get(node_id);
if node.is_nested() {
for child_id in node.get_children_ids() {
EdModel::build_markup_string(child_id, all_code_string, markup_node_pool)?;
}
} else {
let node_content_str = node.get_content()?;
all_code_string.push_str(&node_content_str);
}
Ok(())
}
}
impl<'a> SelectableLines for EdModel<'a> {
fn get_caret(&self) -> TextPos {
self.caret_w_select_vec.first().0.caret_pos
}
// keeps active selection
fn set_caret(&mut self, caret_pos: TextPos) {
let caret_tup = self.caret_w_select_vec.first_mut();
caret_tup.0.caret_pos = caret_pos;
caret_tup.1 = None;
}
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_left;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_right;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_up;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_down;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_home;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_end;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn get_selection(&self) -> Option<Selection> {
self.caret_w_select_vec.first().0.selection_opt
}
fn is_selection_active(&self) -> bool {
self.get_selection().is_some()
}
fn get_selected_str(&self) -> UIResult<Option<String>> {
if let Some(selection) = self.get_selection() {
let start_line_index = selection.start_pos.line;
let start_col = selection.start_pos.column;
let end_line_index = selection.end_pos.line;
let end_col = selection.end_pos.column;
if start_line_index == end_line_index {
let line_ref = self.code_lines.get_line(start_line_index)?;
Ok(Some(line_ref[start_col..end_col].to_string()))
} else {
let full_str = String::new();
// TODO
Ok(Some(full_str))
}
} else {
Ok(None)
}
}
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()> {
self.caret_w_select_vec.first_mut().0.selection_opt = Some(validate_raw_sel(raw_sel)?);
Ok(())
}
fn set_sel_none(&mut self) {
self.caret_w_select_vec.first_mut().0.selection_opt = None;
}
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
self.caret_w_select_vec.first_mut().0 = caret_w_sel;
}
fn select_all(&mut self) -> UIResult<()> {
if self.code_lines.nr_of_chars() > 0 {
let last_pos = self.last_text_pos()?;
self.set_raw_sel(RawSelection {
start_pos: TextPos { line: 0, column: 0 },
end_pos: last_pos,
})?;
self.set_caret(last_pos);
}
Ok(())
}
fn last_text_pos(&self) -> UIResult<TextPos> {
let nr_of_lines = self.code_lines.lines.len();
let last_line_index = nr_of_lines - 1;
let last_line = self.code_lines.get_line(last_line_index)?;
Ok(TextPos {
line: self.code_lines.lines.len() - 1,
column: last_line.len(),
})
}
fn handle_key_down(
&mut self,
modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
) -> UIResult<()> {
match virtual_keycode {
Left => self.move_caret_left(modifiers),
Up => self.move_caret_up(modifiers),
Right => self.move_caret_right(modifiers),
Down => self.move_caret_down(modifiers),
A => {
if modifiers.ctrl {
self.select_all()
} else {
Ok(())
}
}
Home => self.move_caret_home(modifiers),
End => self.move_caret_end(modifiers),
_ => Ok(()),
}
}
}
pub struct NodeContext<'a> {
pub old_caret_pos: TextPos,
pub curr_mark_node_id: MarkNodeId,
pub curr_mark_node: &'a MarkupNode,
pub parent_id_opt: Option<MarkNodeId>,
pub ast_node_id: NodeId<Expr2>,
}
pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult<NodeContext<'a>> {
let old_caret_pos = ed_model.get_caret();
let curr_mark_node_id = ed_model
.grid_node_map
.get_id_at_row_col(ed_model.get_caret())?;
let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id);
let parent_id_opt = curr_mark_node.get_parent_id_opt();
let ast_node_id = curr_mark_node.get_ast_node_id();
Ok(NodeContext {
old_caret_pos,
curr_mark_node_id,
curr_mark_node,
parent_id_opt,
ast_node_id,
})
}
pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<()> {
// TODO set all selections to none
// TODO nested records
ed_model.dirty = true;
match received_char {
'{' => {
start_new_record(ed_model)?;
}
':' => {
// TODO set up Dict if previous char is '{'
update_record_colon(ed_model)?;
}
'\u{8}' | '\u{7f}' => {
// On Linux, '\u{8}' is backspace,
// On macOS '\u{7f}'.
unimplemented!("TODO implement backspace");
}
ch if is_newline(ch) => {
unimplemented!("TODO implement newline");
}
'\u{1}' // Ctrl + A
| '\u{3}' // Ctrl + C
| '\u{16}' // Ctrl + V
| '\u{18}' // Ctrl + X
| '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html
| '\u{f0000}'..='\u{ffffd}' // ^
| '\u{100000}'..='\u{10fffd}' // ^
=> {
// chars that can be ignored
ed_model.dirty = false;
}
ch => {
let old_caret_pos = ed_model.get_caret();
let curr_mark_node_id = ed_model.grid_node_map.get_id_at_row_col(ed_model.get_caret())?;
let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id);
let parent_id_opt = curr_mark_node.get_parent_id_opt();
let ast_node_id = curr_mark_node.get_ast_node_id();
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
if let Some(parent_id) = parent_id_opt {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool);
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)? + 1;
match ast_node_ref {
Expr2::EmptyRecord => {
if curr_mark_node.get_content()? == nodes::LEFT_ACCOLADE {
// update Markup
let record_field_str = &ch.to_string();
let record_field_node = MarkupNode::Text {
content: record_field_str.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::RecordField,
attributes: Attributes::new(),
parent_id_opt: Some(parent_id),
};
let record_field_node_id = ed_model.markup_node_pool.add(record_field_node);
let parent = ed_model.markup_node_pool.get_mut(parent_id);
parent.add_child_at_index(new_child_index, record_field_node_id)?;
// update caret
ed_model.simple_move_carets_right();
// update GridNodeMap
ed_model.grid_node_map.insert_between_line(
old_caret_pos.line,
old_caret_pos.column + 1,
record_field_str.len(),
record_field_node_id,
)?;
// update AST
let record_var = ed_model.module.env.var_store.fresh();
let field_name = PoolStr::new(record_field_str, &mut ed_model.module.env.pool);
let field_var = ed_model.module.env.var_store.fresh();
//TODO actually check if field_str belongs to a previously defined variable
let field_val = Expr2::InvalidLookup(
PoolStr::new(record_field_str, ed_model.module.env.pool)
);
let field_val_id = ed_model.module.env.pool.add(field_val);
let first_field = (field_name, field_var, field_val_id);
let fields = PoolVec::new(
vec![first_field].into_iter(),
&mut ed_model.module.env.pool,
);
let new_ast_node =
Expr2::Record {
record_var,
fields,
};
ed_model.module.env.pool.set(ast_node_id, new_ast_node);
} else {
unimplemented!("TODO handle this else")
}
},
Expr2::Record { record_var:_, fields } => {
update_record_field(
&ch.to_string(),
old_caret_pos,
curr_mark_node_id,
new_child_index,
fields,
ed_model,
)?;
},
other => {
unimplemented!("TODO implement updating of Expr2 {:?}.", other)
},
}
}
}
}
Ok(())
}

View file

@ -1,9 +1,7 @@
use super::ed_model::EdModel; use super::ed_model::EdModel;
use crate::editor::code_lines::CodeLines;
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::render_ast::build_code_graphics; use crate::editor::render_ast::build_code_graphics;
use crate::editor::slow_pool::SlowPool;
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::ui::text::caret_w_select::make_caret_rect; use crate::ui::text::caret_w_select::make_caret_rect;
use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::caret_w_select::CaretWSelect;
@ -12,33 +10,30 @@ use cgmath::Vector2;
use snafu::OptionExt; use snafu::OptionExt;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
#[derive(Debug)]
pub struct RenderedWgpu {
pub text: glyph_brush::OwnedSection,
pub rects: Vec<Rect>,
}
// create text and rectangles based on EdModel's markup_root // create text and rectangles based on EdModel's markup_root
pub fn model_to_wgpu<'a>( pub fn model_to_wgpu<'a>(
ed_model: &'a mut EdModel, ed_model: &'a mut EdModel,
size: &PhysicalSize<u32>, size: &PhysicalSize<u32>,
txt_coords: Vector2<f32>, txt_coords: Vector2<f32>,
config: &Config, config: &Config,
markup_node_pool: &'a SlowPool, ) -> EdResult<RenderedWgpu> {
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> {
let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?; let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?;
let (section, mut rects) = build_code_graphics( let (section, mut rects) = build_code_graphics(
markup_node_pool.get(ed_model.markup_root_id), ed_model.markup_node_pool.get(ed_model.markup_root_id),
size, size,
txt_coords, txt_coords,
config, config,
glyph_dim_rect, glyph_dim_rect,
markup_node_pool, &ed_model.markup_node_pool,
)?; )?;
let mut all_code_string = String::new();
for txt in section.text.iter() {
all_code_string.push_str(txt.text);
}
ed_model.code_lines = CodeLines::from_str(&all_code_string);
let caret_w_sel_vec = ed_model let caret_w_sel_vec = ed_model
.caret_w_select_vec .caret_w_select_vec
.iter() .iter()
@ -50,7 +45,10 @@ pub fn model_to_wgpu<'a>(
rects.append(&mut sel_rects); rects.append(&mut sel_rects);
Ok((section, rects)) Ok(RenderedWgpu {
text: section,
rects,
})
} }
pub fn build_selection_graphics( pub fn build_selection_graphics(
@ -69,7 +67,7 @@ pub fn build_selection_graphics(
let top_left_x = txt_coords.x + caret_col * char_width; let top_left_x = txt_coords.x + caret_col * char_width;
let top_left_y = txt_coords.y + caret_row * char_height; let top_left_y = txt_coords.y + caret_row * char_height + 0.1 * char_height;
rects.push(make_caret_rect( rects.push(make_caret_rect(
top_left_x, top_left_x,

View file

@ -1,4 +1,6 @@
pub mod app_model; pub mod app_model;
pub mod app_update; pub mod app_update;
pub mod ed_model; pub mod ed_model;
pub mod ed_update;
pub mod ed_view; pub mod ed_view;
mod record_update;

View file

@ -0,0 +1,233 @@
use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::MissingParent;
use crate::editor::ed_error::RecordWithoutFields;
use crate::editor::markup::attribute::Attributes;
use crate::editor::markup::nodes;
use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::ed_model::EdModel;
use crate::editor::mvc::ed_update::get_node_context;
use crate::editor::mvc::ed_update::NodeContext;
use crate::editor::slow_pool::MarkNodeId;
use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::util::index_of;
use crate::lang::ast::Expr2;
use crate::lang::pool::NodeId;
use crate::lang::pool::PoolStr;
use crate::lang::pool::PoolVec;
use crate::ui::text::text_pos::TextPos;
use roc_types::subs::Variable;
use snafu::OptionExt;
pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<()> {
let NodeContext {
old_caret_pos,
curr_mark_node_id,
curr_mark_node,
parent_id_opt,
ast_node_id,
} = get_node_context(&ed_model)?;
let is_blank_node = curr_mark_node.is_blank();
let ast_pool = &mut ed_model.module.env.pool;
let expr2_node = Expr2::EmptyRecord;
let mark_node_pool = &mut ed_model.markup_node_pool;
ast_pool.set(ast_node_id, expr2_node);
let left_bracket_node = MarkupNode::Text {
content: nodes::LEFT_ACCOLADE.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id),
};
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),
};
let right_bracket_node_id = mark_node_pool.add(right_bracket_node);
let nested_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![left_bracket_node_id, right_bracket_node_id],
parent_id_opt,
};
if is_blank_node {
mark_node_pool.replace_node(curr_mark_node_id, nested_node);
for _ in 0..nodes::LEFT_ACCOLADE.len() {
ed_model.simple_move_carets_right();
}
// update GridNodeMap
ed_model.grid_node_map.add_to_line(
old_caret_pos.line,
nodes::LEFT_ACCOLADE.len(),
left_bracket_node_id,
)?;
ed_model.grid_node_map.add_to_line(
old_caret_pos.line,
nodes::RIGHT_ACCOLADE.len(),
right_bracket_node_id,
)?;
}
Ok(())
}
pub fn update_record_field(
new_input: &str,
old_caret_pos: TextPos,
curr_mark_node_id: MarkNodeId,
new_child_index: usize,
record_fields: &PoolVec<(PoolStr, Variable, NodeId<Expr2>)>,
ed_model: &mut EdModel,
) -> EdResult<()> {
if new_child_index == 2 {
// update MarkupNode
let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?;
content_str_mut.push_str(new_input);
// update caret
for _ in 0..new_input.len() {
ed_model.simple_move_carets_right();
}
// update GridNodeMap
ed_model.grid_node_map.insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
new_input.len(),
curr_mark_node_id,
)?;
// update AST Node
let first_field = record_fields
.iter(ed_model.module.env.pool)
.next()
.with_context(|| RecordWithoutFields {})?;
let mut new_field_name = String::new();
first_field.0.as_str(ed_model.module.env.pool).to_string();
new_field_name.push_str(new_input);
let new_pool_str = PoolStr::new(&new_field_name, &mut ed_model.module.env.pool);
let first_field_mut = record_fields
.iter_mut(ed_model.module.env.pool)
.next()
.with_context(|| RecordWithoutFields {})?;
first_field_mut.0 = new_pool_str;
Ok(())
} else {
unimplemented!("TODO implement updating of other fields of record.")
}
}
pub fn update_record_colon(ed_model: &mut EdModel) -> EdResult<()> {
let NodeContext {
old_caret_pos,
curr_mark_node_id,
curr_mark_node,
parent_id_opt,
ast_node_id,
} = get_node_context(&ed_model)?;
if let Some(parent_id) = parent_id_opt {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool);
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)? + 1;
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
match ast_node_ref {
Expr2::Record {
record_var: _,
fields,
} => {
// update Markup
let record_colon = nodes::COLON;
let record_colon_node = MarkupNode::Text {
content: record_colon.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::new(),
parent_id_opt: Some(parent_id),
};
let record_colon_node_id = ed_model.markup_node_pool.add(record_colon_node);
ed_model
.markup_node_pool
.get_mut(parent_id)
.add_child_at_index(new_child_index, record_colon_node_id)?;
let record_blank_node = MarkupNode::Blank {
ast_node_id,
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt: Some(parent_id),
};
let record_blank_node_id = ed_model.markup_node_pool.add(record_blank_node);
ed_model
.markup_node_pool
.get_mut(parent_id)
.add_child_at_index(new_child_index + 1, record_blank_node_id)?;
// update caret
for _ in 0..record_colon.len() {
ed_model.simple_move_carets_right();
}
// update GridNodeMap
ed_model.grid_node_map.add_to_line(
old_caret_pos.line,
nodes::COLON.len(),
record_colon_node_id,
)?;
ed_model.grid_node_map.add_to_line(
old_caret_pos.line,
nodes::BLANK_PLACEHOLDER.len(),
record_blank_node_id,
)?;
// update AST node
let new_field_val = Expr2::Blank;
let new_field_val_id = ed_model.module.env.pool.add(new_field_val);
let first_field_mut = fields
.iter_mut(ed_model.module.env.pool)
.next()
.with_context(|| RecordWithoutFields {})?;
first_field_mut.2 = new_field_val_id;
}
other => unimplemented!("TODO implement updating of Expr2 {:?}.", other),
}
Ok(())
} else {
MissingParent {
node_id: curr_mark_node_id,
}
.fail()
}
}

View file

@ -15,7 +15,7 @@ pub fn build_code_graphics<'a>(
config: &Config, config: &Config,
glyph_dim_rect: Rect, glyph_dim_rect: Rect,
markup_node_pool: &'a SlowPool, markup_node_pool: &'a SlowPool,
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> { ) -> EdResult<(glyph_brush::OwnedSection, Vec<Rect>)> {
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);
@ -47,8 +47,8 @@ fn markup_to_wgpu<'a>(
markup_node: &'a MarkupNode, markup_node: &'a MarkupNode,
code_style: &CodeStyle, code_style: &CodeStyle,
markup_node_pool: &'a SlowPool, markup_node_pool: &'a SlowPool,
) -> EdResult<(Vec<wgpu_glyph::Text<'a>>, Vec<Rect>)> { ) -> EdResult<(Vec<glyph_brush::OwnedText>, Vec<Rect>)> {
let mut wgpu_texts: Vec<wgpu_glyph::Text<'a>> = 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); let mut txt_row_col = (0, 0);
@ -68,7 +68,7 @@ fn markup_to_wgpu<'a>(
// TODO use text_row // 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<wgpu_glyph::Text<'a>>, 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),
@ -101,7 +101,7 @@ fn markup_to_wgpu_helper<'a>(
} => { } => {
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 = wgpu_glyph::Text::new(&content) let glyph_text = glyph_brush::OwnedText::new(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);
@ -114,22 +114,25 @@ fn markup_to_wgpu_helper<'a>(
syn_high_style, syn_high_style,
parent_id_opt: _, parent_id_opt: _,
} => { } => {
let glyph_text = wgpu_glyph::Text::new(BLANK_PLACEHOLDER) let glyph_text = glyph_brush::OwnedText::new(BLANK_PLACEHOLDER)
.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);
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 char_width = code_style.glyph_dim_rect.width;
let char_height = code_style.glyph_dim_rect.height;
let hole_rect = Rect { let hole_rect = Rect {
top_left_coords: ( top_left_coords: (
code_style.txt_coords.x code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
+ (txt_row_col.0 as f32) * code_style.glyph_dim_rect.height,
code_style.txt_coords.y code_style.txt_coords.y
+ (txt_row_col.1 as f32) * code_style.glyph_dim_rect.width, + (txt_row_col.0 as f32) * char_height
+ 0.1 * char_height,
) )
.into(), .into(),
width: code_style.glyph_dim_rect.width, width: char_width,
height: code_style.glyph_dim_rect.height, height: char_height,
color: *highlight_color, color: *highlight_color,
}; };
rects.push(hole_rect); rects.push(hole_rect);

View file

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

View file

@ -1,4 +1,5 @@
use super::ed_error::{EdResult, KeyNotFound}; use super::ed_error::{EdResult, KeyNotFound};
use crate::editor::ed_error::IndexOfFailed;
use snafu::OptionExt; use snafu::OptionExt;
use std::collections::HashMap; use std::collections::HashMap;
@ -13,3 +14,20 @@ pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>(
Ok(value) Ok(value)
} }
pub fn index_of<T: ::std::fmt::Debug + std::cmp::Eq>(elt: T, slice: &[T]) -> EdResult<usize> {
let index = slice
.iter()
.position(|slice_elt| *slice_elt == elt)
.with_context(|| {
let elt_str = format!("{:?}", elt);
let collection_str = format!("{:?}", slice);
IndexOfFailed {
elt_str,
collection_str,
}
})?;
Ok(index)
}

View file

@ -84,12 +84,12 @@ fn section_from_text<'a>(
} }
pub fn section_from_glyph_text( pub fn section_from_glyph_text(
text: Vec<wgpu_glyph::Text>, text: Vec<glyph_brush::OwnedText>,
screen_position: (f32, f32), screen_position: (f32, f32),
area_bounds: (f32, f32), area_bounds: (f32, f32),
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>, layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
) -> wgpu_glyph::Section { ) -> glyph_brush::OwnedSection {
Section { glyph_brush::OwnedSection {
screen_position, screen_position,
bounds: area_bounds, bounds: area_bounds,
layout, layout,

View file

@ -99,6 +99,7 @@ pub enum Expr2 {
Str(PoolStr), // 8B Str(PoolStr), // 8B
// Lookups // Lookups
Var(Symbol), // 8B Var(Symbol), // 8B
InvalidLookup(PoolStr), // 8B
List { List {
list_var: Variable, // 4B - required for uniqueness of the list list_var: Variable, // 4B - required for uniqueness of the list

View file

@ -352,7 +352,7 @@ impl<'a, T: 'a + Sized> PoolVec<T> {
self.pool_list_iter(pool) self.pool_list_iter(pool)
} }
pub fn iter_mut(&mut self, pool: &'a Pool) -> impl ExactSizeIterator<Item = &'a mut T> { pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator<Item = &'a mut T> {
self.pool_list_iter_mut(pool) self.pool_list_iter_mut(pool)
} }

View file

@ -119,7 +119,7 @@ impl Lines for BigTextArea {
} }
impl SelectableLines for BigTextArea { impl SelectableLines for BigTextArea {
fn get_caret(self) -> TextPos { fn get_caret(&self) -> TextPos {
self.caret_w_select.caret_pos self.caret_w_select.caret_pos
} }

View file

@ -30,7 +30,7 @@ pub trait Lines {
} }
pub trait SelectableLines { pub trait SelectableLines {
fn get_caret(self) -> TextPos; fn get_caret(&self) -> TextPos;
fn set_caret(&mut self, caret_pos: TextPos); fn set_caret(&mut self, caret_pos: TextPos);

View file

@ -8,7 +8,7 @@ pub fn is_newline(char_ref: &char) -> bool {
newline_codes.contains(char_ref) newline_codes.contains(char_ref)
} }
// replace vec method that return Option with one that return Result and proper Error // replace slice method that return Option with one that return Result and proper Error
pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex<[T]>>::Output> { pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex<[T]>>::Output> {
let elt_ref = slice.get(index).context(OutOfBounds { let elt_ref = slice.get(index).context(OutOfBounds {
index, index,
@ -18,3 +18,18 @@ pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex
Ok(elt_ref) Ok(elt_ref)
} }
pub fn slice_get_mut<T>(
index: usize,
slice: &mut [T],
) -> UIResult<&mut <usize as SliceIndex<[T]>>::Output> {
let slice_len = slice.len();
let elt_ref = slice.get_mut(index).context(OutOfBounds {
index,
collection_name: "Slice",
len: slice_len,
})?;
Ok(elt_ref)
}