Merge branch 'trunk' into generated-docs-folder

This commit is contained in:
Chadtech 2021-03-25 00:28:31 -04:00 committed by GitHub
commit 90b1c57f1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1169 additions and 997 deletions

7
Cargo.lock generated
View file

@ -1994,6 +1994,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "nonempty"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fa586da3e43cc7df44aae0e21ed2e743218b876de3f38035683d30bd8a3828e"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@ -3035,6 +3041,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"maplit", "maplit",
"nonempty",
"page_size", "page_size",
"palette", "palette",
"pest", "pest",

View file

@ -151,10 +151,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
.expect("TODO gracefully handle block_on failing"); .expect("TODO gracefully handle block_on failing");
} }
} }
Err(LoadingProblem::ParsingFailedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report);
}
Err(LoadingProblem::NoPlatform(report)) => {
print!("{}", report); print!("{}", report);
} }
Err(other) => { Err(other) => {

View file

@ -57,7 +57,7 @@ pub fn gen_and_eval<'a>(
let mut loaded = match loaded { let mut loaded = match loaded {
Ok(v) => v, Ok(v) => v,
Err(LoadingProblem::ParsingFailedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
return Ok(ReplOutput::Problems(vec![report])); return Ok(ReplOutput::Problems(vec![report]));
} }
Err(e) => { Err(e) => {

View file

@ -17,6 +17,5 @@ isEmpty : Dict * * -> Bool
## See for example #Result.map, #List.map, and #Set.map. ## See for example #Result.map, #List.map, and #Set.map.
map : map :
Dict beforeKey beforeValue, Dict beforeKey beforeValue,
({ key: beforeKey, value: beforeValue } -> ({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue })
{ key: afterKey, value: afterValue } -> Dict afterKey afterValue
) -> Dict afterKey afterValue

View file

@ -784,6 +784,10 @@ enum Msg<'a> {
}, },
FailedToParse(ParseProblem<'a, SyntaxError<'a>>), FailedToParse(ParseProblem<'a, SyntaxError<'a>>),
FailedToReadFile {
filename: PathBuf,
error: io::ErrorKind,
},
} }
#[derive(Debug)] #[derive(Debug)]
@ -996,18 +1000,16 @@ pub enum LoadingProblem<'a> {
FileProblem { FileProblem {
filename: PathBuf, filename: PathBuf,
error: io::ErrorKind, error: io::ErrorKind,
msg: &'static str,
}, },
ParsingFailed(ParseProblem<'a, SyntaxError<'a>>), ParsingFailed(ParseProblem<'a, SyntaxError<'a>>),
UnexpectedHeader(String), UnexpectedHeader(String),
/// there is no platform (likely running an Interface module)
NoPlatform(String),
MsgChannelDied, MsgChannelDied,
ErrJoiningWorkerThreads, ErrJoiningWorkerThreads,
TriedToImportAppModule, TriedToImportAppModule,
/// a formatted report of parsing failure
ParsingFailedReport(String), /// a formatted report
FormattedReport(String),
} }
pub enum Phases { pub enum Phases {
@ -1399,6 +1401,14 @@ where
Err(LoadingProblem::ParsingFailed(problem)) => { Err(LoadingProblem::ParsingFailed(problem)) => {
msg_tx.send(Msg::FailedToParse(problem)).unwrap(); msg_tx.send(Msg::FailedToParse(problem)).unwrap();
} }
Err(LoadingProblem::FileProblem {
filename,
error,
}) => {
msg_tx
.send(Msg::FailedToReadFile { filename, error })
.unwrap();
}
Err(other) => { Err(other) => {
return Err(other); return Err(other);
} }
@ -1457,6 +1467,16 @@ where
let worker_listeners = worker_listeners.into_bump_slice(); let worker_listeners = worker_listeners.into_bump_slice();
let msg_tx = msg_tx.clone(); let msg_tx = msg_tx.clone();
macro_rules! shut_down_worker_threads {
() => {
for listener in worker_listeners {
listener
.send(WorkerMsg::Shutdown)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
}
};
}
// The root module will have already queued up messages to process, // The root module will have already queued up messages to process,
// and processing those messages will in turn queue up more messages. // and processing those messages will in turn queue up more messages.
for msg in msg_rx.iter() { for msg in msg_rx.iter() {
@ -1490,12 +1510,7 @@ where
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty()); debug_assert!(msg_rx.is_empty());
// Shut down all the worker threads. shut_down_worker_threads!();
for listener in worker_listeners {
listener
.send(WorkerMsg::Shutdown)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
}
return Ok(LoadResult::Monomorphized(finish_specialization( return Ok(LoadResult::Monomorphized(finish_specialization(
state, state,
@ -1503,50 +1518,29 @@ where
exposed_to_host, exposed_to_host,
)?)); )?));
} }
Msg::FailedToParse(problem) => { Msg::FailedToReadFile { filename, error } => {
// Shut down all the worker threads. shut_down_worker_threads!();
for listener in worker_listeners {
listener let buf = to_file_problem_report(&filename, error);
.send(WorkerMsg::Shutdown) return Err(LoadingProblem::FormattedReport(buf));
.map_err(|_| LoadingProblem::MsgChannelDied)?;
} }
use roc_reporting::report::{ Msg::FailedToParse(problem) => {
parse_problem, RocDocAllocator, DEFAULT_PALETTE, shut_down_worker_threads!();
};
// TODO this is not in fact safe let module_ids = Arc::try_unwrap(state.arc_modules)
let src = unsafe { from_utf8_unchecked(problem.bytes) };
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
let mut module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
panic!("There were still outstanding Arc references to module_ids") panic!("There were still outstanding Arc references to module_ids")
}) })
.into_inner() .into_inner()
.into_module_ids(); .into_module_ids();
let module_id = let buf = to_parse_problem_report(
module_ids.get_or_insert(&"find module name somehow?".into()); problem,
let interns = Interns {
module_ids, module_ids,
all_ident_ids: state.constrained_ident_ids, state.constrained_ident_ids,
}; );
return Err(LoadingProblem::FormattedReport(buf));
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let starting_line = 0;
let report =
parse_problem(&alloc, problem.filename.clone(), starting_line, problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
return Err(LoadingProblem::ParsingFailedReport(buf));
} }
msg => { msg => {
// This is where most of the main thread's work gets done. // This is where most of the main thread's work gets done.
@ -2086,6 +2080,9 @@ fn update<'a>(
Msg::FailedToParse(_) => { Msg::FailedToParse(_) => {
unreachable!(); unreachable!();
} }
Msg::FailedToReadFile { .. } => {
unreachable!();
}
} }
} }
@ -2142,72 +2139,8 @@ fn finish_specialization(
} }
Valid(To::NewPackage(p_or_p)) => p_or_p, Valid(To::NewPackage(p_or_p)) => p_or_p,
other => { other => {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; let buf = to_missing_platform_report(state.root_id, other);
use ven_pretty::DocAllocator; return Err(LoadingProblem::FormattedReport(buf));
let module_id = state.root_id;
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&[], module_id, &interns);
let report = {
match other {
Valid(_) => unreachable!(),
NotSpecified => {
let doc = alloc.stack(vec![
alloc.reflow("I could not find a platform based on your input file."),
alloc.reflow(r"Does the module header contain an entry that looks like this:"),
alloc
.parser_suggestion(" packages { base: \"platform\" }")
.indent(4),
alloc.reflow("See also TODO."),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsInterface => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsPkgConfig => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a package config file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
}
};
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
return Err(LoadingProblem::NoPlatform(buf));
} }
}; };
@ -2360,7 +2293,7 @@ fn load_pkg_config<'a>(
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
} }
Err(fail) => Err(LoadingProblem::ParsingFailed( Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, bytes), SyntaxError::Header(fail).into_parse_problem(filename, "", bytes),
)), )),
} }
} }
@ -2368,7 +2301,6 @@ fn load_pkg_config<'a>(
Err(err) => Err(LoadingProblem::FileProblem { Err(err) => Err(LoadingProblem::FileProblem {
filename, filename,
error: err.kind(), error: err.kind(),
msg: "while reading a Pkg-Config.roc file",
}), }),
} }
} }
@ -2633,7 +2565,7 @@ fn parse_header<'a>(
module_timing, module_timing,
)), )),
Err(fail) => Err(LoadingProblem::ParsingFailed( Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, src_bytes), SyntaxError::Header(fail).into_parse_problem(filename, "", src_bytes),
)), )),
} }
} }
@ -2670,7 +2602,6 @@ fn load_filename<'a>(
Err(err) => Err(LoadingProblem::FileProblem { Err(err) => Err(LoadingProblem::FileProblem {
filename, filename,
error: err.kind(), error: err.kind(),
msg: "in `load_filename`",
}), }),
} }
} }
@ -3683,9 +3614,11 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
let parsed_defs = match module_defs().parse(&arena, parse_state) { let parsed_defs = match module_defs().parse(&arena, parse_state) {
Ok((_, success, _state)) => success, Ok((_, success, _state)) => success,
Err((_, fail, _)) => { Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed( return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem(
fail.into_parse_problem(header.module_path, source), header.module_path,
)); header.header_src,
source,
)));
} }
}; };
@ -4180,3 +4113,179 @@ where
Ok(()) Ok(())
} }
fn to_file_problem_report(filename: &PathBuf, error: io::ErrorKind) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
let src_lines: Vec<&str> = Vec::new();
let mut module_ids = ModuleIds::default();
let module_id = module_ids.get_or_insert(&"find module name somehow?".into());
let interns = Interns::default();
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let report = match error {
io::ErrorKind::NotFound => {
let doc = alloc.stack(vec![
alloc.reflow(r"I am looking for this file, but it's not there:"),
alloc
.parser_suggestion(filename.to_str().unwrap())
.indent(4),
alloc.concat(vec![
alloc.reflow(r"Is the file supposed to be there? "),
alloc.reflow("Maybe there is a typo in the file name?"),
]),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "FILE NOT FOUND".to_string(),
}
}
io::ErrorKind::PermissionDenied => {
let doc = alloc.stack(vec![
alloc.reflow(r"I don't have the required permissions to read this file:"),
alloc
.parser_suggestion(filename.to_str().unwrap())
.indent(4),
alloc.concat(vec![
alloc.reflow(r"Is it the right file? Maybe change its permissions?")
]),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "PERMISSION DENIED".to_string(),
}
}
_ => {
let error = std::io::Error::from(error);
let formatted = format!("{}", error);
let doc = alloc.concat(vec![
alloc.reflow(r"I tried to read this file, but ran into a "),
alloc.text(formatted),
alloc.reflow(r" problem."),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "FILE PROBLEM".to_string(),
}
}
};
let mut buf = String::new();
let palette = DEFAULT_PALETTE;
report.render_color_terminal(&mut buf, &alloc, &palette);
buf
}
fn to_parse_problem_report<'a>(
problem: ParseProblem<'a, SyntaxError<'a>>,
mut module_ids: ModuleIds,
all_ident_ids: MutMap<ModuleId, IdentIds>,
) -> String {
use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE};
// TODO this is not in fact safe
let src = unsafe { from_utf8_unchecked(problem.bytes) };
let mut src_lines: Vec<&str> = problem.prefix.lines().collect();
src_lines.extend(src.lines().skip(1));
let module_id = module_ids.get_or_insert(&"find module name somehow?".into());
let interns = Interns {
module_ids,
all_ident_ids,
};
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let starting_line = 0;
let report = parse_problem(&alloc, problem.filename.clone(), starting_line, problem);
let mut buf = String::new();
let palette = DEFAULT_PALETTE;
report.render_color_terminal(&mut buf, &alloc, &palette);
buf
}
fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
use PlatformPath::*;
// Report parsing and canonicalization problems
let interns = Interns::default();
let alloc = RocDocAllocator::new(&[], module_id, &interns);
let report = {
match other {
Valid(_) => unreachable!(),
NotSpecified => {
let doc = alloc.stack(vec![
alloc.reflow("I could not find a platform based on your input file."),
alloc.reflow(r"Does the module header contain an entry that looks like this:"),
alloc
.parser_suggestion(" packages { base: \"platform\" }")
.indent(4),
alloc.reflow("See also TODO."),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsInterface => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsPkgConfig => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a package config file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
}
};
let palette = DEFAULT_PALETTE;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
buf
}

View file

@ -0,0 +1,8 @@
interface MissingDep
exposes [ unit ]
imports [ ThisFileIsMissing ]
Unit : [ Unit ]
unit : Unit
unit = Unit

View file

@ -32,24 +32,50 @@ mod test_load {
// HELPERS // HELPERS
fn multiple_modules(files: Vec<(&str, &str)>) -> LoadedModule { fn multiple_modules(files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
multiple_modules_help(files).unwrap() use roc_load::file::LoadingProblem;
let arena = Bump::new();
let arena = &arena;
match multiple_modules_help(arena, files) {
Err(io_error) => panic!("IO trouble: {:?}", io_error),
Ok(Err(LoadingProblem::FormattedReport(buf))) => Err(buf),
Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)),
Ok(Ok(mut loaded_module)) => {
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
Ok(loaded_module)
}
}
} }
fn multiple_modules_help(mut files: Vec<(&str, &str)>) -> Result<LoadedModule, std::io::Error> { fn multiple_modules_help<'a>(
arena: &'a Bump,
mut files: Vec<(&str, &str)>,
) -> Result<Result<LoadedModule, roc_load::file::LoadingProblem<'a>>, std::io::Error> {
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use tempfile::tempdir; use tempfile::tempdir;
let arena = Bump::new();
let arena = &arena;
let stdlib = roc_builtins::std::standard_stdlib(); let stdlib = roc_builtins::std::standard_stdlib();
let mut file_handles: Vec<_> = Vec::new(); let mut file_handles: Vec<_> = Vec::new();
let exposed_types = MutMap::default(); let exposed_types = MutMap::default();
let loaded = {
// create a temporary directory // create a temporary directory
let dir = tempdir()?; let dir = tempdir()?;
@ -83,7 +109,7 @@ mod test_load {
roc_load::file::load_and_typecheck( roc_load::file::load_and_typecheck(
arena, arena,
full_file_path, full_file_path,
&stdlib, arena.alloc(stdlib),
dir.path(), dir.path(),
exposed_types, exposed_types,
8, 8,
@ -93,26 +119,7 @@ mod test_load {
dir.close()?; dir.close()?;
result Ok(result)
};
let mut loaded_module = loaded.expect("failed to load module");
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
Ok(loaded_module)
} }
fn load_fixture( fn load_fixture(
@ -134,9 +141,9 @@ mod test_load {
); );
let mut loaded_module = match loaded { let mut loaded_module = match loaded {
Ok(x) => x, Ok(x) => x,
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => { Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!("{}", report);
} }
Err(e) => panic!("{:?}", e), Err(e) => panic!("{:?}", e),
}; };
@ -285,7 +292,8 @@ mod test_load {
), ),
), ),
]; ];
multiple_modules(modules);
assert!(multiple_modules(modules).is_ok());
} }
#[test] #[test]
@ -517,61 +525,69 @@ mod test_load {
); );
} }
// #[test] #[test]
// fn load_records() { fn parse_problem() {
// use roc::types::{ErrorType, Mismatch, Problem, TypeExt}; let modules = vec![(
"Main",
indoc!(
r#"
app "test-app" packages { blah: "./blah" } provides [ main ] to blah
// let subs_by_module = MutMap::default(); main = [
// let loaded_module = "#
// load_fixture("interface_with_deps", "Records", subs_by_module); ),
)];
// // NOTE: `a` here is unconstrained, so unifies with <type error> match multiple_modules(modules) {
// let expected_types = hashmap! { Err(report) => assert_eq!(
// "Records.intVal" => "a", report,
// }; indoc!(
"
\u{1b}[36m UNFINISHED LIST \u{1b}[0m
// let a = ErrorType::FlexVar("a".into()); I cannot find the end of this list:
// let mut record = SendMap::default(); \u{1b}[36m3\u{1b}[0m\u{1b}[36m\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m
// record.insert("x".into(), a); \u{1b}[31m^\u{1b}[0m
// let problem = Problem::Mismatch( You could change it to something like \u{1b}[33m[ 1, 2, 3 ]\u{1b}[0m or even just \u{1b}[33m[]\u{1b}[0m.
// Mismatch::TypeMismatch, Anything where there is an open and a close square bracket, and where
// ErrorType::Record(SendMap::default(), TypeExt::Closed), the elements of the list are separated by commas.
// ErrorType::Record(record, TypeExt::FlexOpen("b".into())),
// );
// assert_eq!(loaded_module.problems, vec![problem]); \u{1b}[4mNote\u{1b}[0m: I may be confused by indentation"
// assert_eq!(expected_types.len(), loaded_module.declarations.len()); )
),
Ok(_) => unreachable!("we expect failure here"),
}
}
// let mut subs = loaded_module.solved.into_inner(); #[test]
#[should_panic(
expected = "FileProblem { filename: \"tests/fixtures/build/interface_with_deps/invalid$name.roc\", error: NotFound }"
)]
fn file_not_found() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module);
// for decl in loaded_module.declarations { expect_types(
// let def = match decl { loaded_module,
// Declare(def) => def, hashmap! {
// rec_decl @ DeclareRec(_) => { "str" => "Str",
// panic!( },
// "Unexpected recursive def in module declarations: {:?}", );
// rec_decl }
// );
// }
// cycle @ InvalidCycle(_, _) => {
// panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
// }
// };
// for (symbol, expr_var) in def.pattern_vars { #[test]
// let content = subs.get(expr_var).content; #[should_panic(expected = "FILE NOT FOUND")]
fn imported_file_not_found() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("no_deps", "MissingDep", subs_by_module);
// name_all_type_vars(expr_var, &mut subs); expect_types(
loaded_module,
// let actual_str = content_to_string(content, &mut subs); hashmap! {
// let expected_type = expected_types.get(symbol.as_str()).unwrap_or_else(|| { "str" => "Str",
// panic!("Defs included an unexpected symbol: {:?}", symbol) },
// }); );
}
// assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str()));
// }
// }
// }
} }

View file

@ -69,7 +69,7 @@ mod test_mono {
let mut loaded = match loaded { let mut loaded = match loaded {
Ok(x) => x, Ok(x) => x,
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => { Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!();
} }

View file

@ -360,6 +360,7 @@ impl<'a> SyntaxError<'a> {
pub fn into_parse_problem( pub fn into_parse_problem(
self, self,
filename: std::path::PathBuf, filename: std::path::PathBuf,
prefix: &'a str,
bytes: &'a [u8], bytes: &'a [u8],
) -> ParseProblem<'a, SyntaxError<'a>> { ) -> ParseProblem<'a, SyntaxError<'a>> {
ParseProblem { ParseProblem {
@ -368,6 +369,7 @@ impl<'a> SyntaxError<'a> {
problem: self, problem: self,
filename, filename,
bytes, bytes,
prefix,
} }
} }
} }
@ -679,6 +681,8 @@ pub struct ParseProblem<'a, T> {
pub problem: T, pub problem: T,
pub filename: std::path::PathBuf, pub filename: std::path::PathBuf,
pub bytes: &'a [u8], pub bytes: &'a [u8],
/// prefix is usually the header (for parse problems in the body), or empty
pub prefix: &'a str,
} }
pub trait Parser<'a, Output, Error> { pub trait Parser<'a, Output, Error> {

View file

@ -129,7 +129,7 @@ mod test_reporting {
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let problem = fail.into_parse_problem(filename.clone(), src.as_bytes()); let problem = fail.into_parse_problem(filename.clone(), "", src.as_bytes());
let doc = parse_problem(&alloc, filename, 0, problem); let doc = parse_problem(&alloc, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf) callback(doc.pretty(&alloc).append(alloc.line()), buf)
@ -190,8 +190,11 @@ mod test_reporting {
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
use roc_parse::parser::SyntaxError; use roc_parse::parser::SyntaxError;
let problem = let problem = SyntaxError::Header(fail).into_parse_problem(
SyntaxError::Header(fail).into_parse_problem(filename.clone(), src.as_bytes()); filename.clone(),
"",
src.as_bytes(),
);
let doc = parse_problem(&alloc, filename, 0, problem); let doc = parse_problem(&alloc, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf) callback(doc.pretty(&alloc).append(alloc.line()), buf)

View file

@ -74,7 +74,7 @@ pub fn helper<'a>(
let mut loaded = match loaded { let mut loaded = match loaded {
Ok(x) => x, Ok(x) => x,
Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => { Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!();
} }

View file

@ -122,7 +122,7 @@ pub fn files_to_documentations(
builtin_defs_map, builtin_defs_map,
) { ) {
Ok(mut loaded) => files_docs.extend(loaded.documentation.drain().map(|x| x.1)), Ok(mut loaded) => files_docs.extend(loaded.documentation.drain().map(|x| x.1)),
Err(LoadingProblem::ParsingFailedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
println!("{}", report); println!("{}", report);
panic!(); panic!();
} }

View file

@ -1,5 +1,5 @@
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::{Path, PathBuf}; use std::path::PathBuf;
#[cfg(test)] #[cfg(test)]
mod test_docs { mod test_docs {

View file

@ -48,6 +48,7 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [
"yaml_conf" "yaml_conf"
], default-features = false } ], default-features = false }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
nonempty = "0.6.0"
[dependencies.bytemuck] [dependencies.bytemuck]
version = "1.4" version = "1.4"

View file

@ -0,0 +1,76 @@
use crate::ui::text::lines::Lines;
use crate::ui::ui_error::UIResult;
use crate::ui::util::slice_get;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
#[derive(Debug)]
pub struct CodeLines {
pub lines: Vec<String>,
pub nr_of_chars: usize,
}
impl CodeLines {
pub fn from_str(code_str: &str) -> CodeLines {
CodeLines {
lines: split_inclusive(code_str),
nr_of_chars: code_str.len(),
}
}
}
//TODO use rust's split_inclusive once it's no longer unstable
fn split_inclusive(code_str: &str) -> Vec<String> {
let mut split_vec: Vec<String> = Vec::new();
let mut temp_str = String::new();
for token in code_str.chars() {
if token != '\n' {
temp_str.push(token);
} else {
split_vec.push(temp_str);
temp_str = String::new();
temp_str.push(token);
}
}
if !temp_str.is_empty() {
split_vec.push(temp_str);
}
split_vec
}
impl Lines for CodeLines {
fn get_line(&self, line_nr: usize) -> UIResult<&str> {
let line_string = slice_get(line_nr, &self.lines)?;
Ok(&line_string)
}
fn line_len(&self, line_nr: usize) -> UIResult<usize> {
self.get_line(line_nr).map(|line| line.len())
}
fn nr_of_lines(&self) -> usize {
self.lines.len()
}
fn nr_of_chars(&self) -> usize {
self.nr_of_chars
}
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> {
let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena);
for line in &self.lines {
lines.push_str(&line);
}
lines
}
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
}
}

View file

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

View file

@ -1,6 +1,5 @@
use super::keyboard_input; use super::keyboard_input;
use super::style::CODE_TXT_XY; use super::style::CODE_TXT_XY;
use super::util::slice_get;
use crate::editor::ed_error::print_ui_err; use crate::editor::ed_error::print_ui_err;
use crate::editor::resources::strings::NOTHING_OPENED; use crate::editor::resources::strings::NOTHING_OPENED;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
@ -21,6 +20,7 @@ use crate::graphics::{
use crate::lang::expr::Env; use crate::lang::expr::Env;
use crate::lang::pool::Pool; use crate::lang::pool::Pool;
use crate::ui::ui_error::UIError::FileOpenFailed; use crate::ui::ui_error::UIError::FileOpenFailed;
use crate::ui::util::slice_get;
use bumpalo::collections::String as BumpString; use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use cgmath::Vector2; use cgmath::Vector2;
@ -153,21 +153,33 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let mut code_str = BumpString::from_str_in("", &code_arena); let mut code_str = BumpString::from_str_in("", &code_arena);
if let Some(file_path) = file_path_opt { let file_path = if let Some(file_path) = file_path_opt {
match std::fs::read_to_string(file_path) { match std::fs::read_to_string(file_path) {
Ok(file_as_str) => { Ok(file_as_str) => {
code_str = BumpString::from_str_in(&file_as_str, &code_arena); code_str = BumpString::from_str_in(&file_as_str, &code_arena);
file_path
} }
Err(e) => print_ui_err(&FileOpenFailed { Err(e) => {
print_ui_err(&FileOpenFailed {
path_str: file_path.to_string_lossy().to_string(), path_str: file_path.to_string_lossy().to_string(),
err_msg: e.to_string(), err_msg: e.to_string(),
}), });
Path::new("")
} }
} }
} else {
Path::new("")
};
let ed_model_opt = { let ed_model_opt = {
let ed_model_res = ed_model::init_model(&code_str, env, &code_arena, &mut markup_node_pool); let ed_model_res = ed_model::init_model(
&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) => {
@ -256,7 +268,6 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
virtual_keycode, virtual_keycode,
keyboard_modifiers, keyboard_modifiers,
&mut app_model, &mut app_model,
&mut markup_node_pool,
); );
if let Err(e) = keydown_res { if let Err(e) = keydown_res {

View file

@ -1,130 +0,0 @@
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

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

View file

@ -1,18 +1,13 @@
use super::attribute::Attributes; use super::attribute::Attributes;
use crate::editor::ed_error::GetContentOnNestedNode; use crate::editor::slow_pool::SlowNodeId;
use crate::editor::ed_error::NodeWithoutAttributes; use crate::editor::slow_pool::SlowPool;
use crate::editor::{ use crate::editor::syntax_highlight::HighlightStyle;
ed_error::EdResult,
slow_pool::{SlowNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
use crate::lang::{ use crate::lang::{
ast::Expr2, ast::Expr2,
expr::Env, expr::Env,
pool::{NodeId, PoolStr}, pool::{NodeId, PoolStr},
}; };
use bumpalo::Bump; use bumpalo::Bump;
use snafu::OptionExt;
#[derive(Debug)] #[derive(Debug)]
pub enum MarkupNode { pub enum MarkupNode {
@ -38,117 +33,6 @@ pub enum MarkupNode {
pub const BLANK_PLACEHOLDER: &str = " "; 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 { 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()
} }

View file

@ -1,3 +1,4 @@
mod code_lines;
mod config; mod config;
mod ed_error; mod ed_error;
mod keyboard_input; mod keyboard_input;

View file

@ -1,6 +1,6 @@
use super::app_model::AppModel; use super::app_model::AppModel;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::slow_pool::SlowPool; use crate::ui::text::lines::SelectableLines;
use crate::window::keyboard_input::from_winit; use crate::window::keyboard_input::from_winit;
use winit::event::{ModifiersState, VirtualKeyCode}; use winit::event::{ModifiersState, VirtualKeyCode};
@ -38,13 +38,12 @@ pub fn pass_keydown_to_focused(
modifiers_winit: &ModifiersState, modifiers_winit: &ModifiersState,
virtual_keycode: VirtualKeyCode, virtual_keycode: VirtualKeyCode,
app_model: &mut AppModel, app_model: &mut AppModel,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> { ) -> EdResult<()> {
let modifiers = from_winit(modifiers_winit); let modifiers = from_winit(modifiers_winit);
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 {
ed_model.handle_key_down(&modifiers, virtual_keycode, markup_node_pool)?; ed_model.handle_key_down(&modifiers, virtual_keycode)?;
} }
} }

View file

@ -1,39 +1,48 @@
use crate::editor::code_lines::CodeLines;
use crate::editor::slow_pool::{SlowNodeId, SlowPool}; use crate::editor::slow_pool::{SlowNodeId, 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,
ed_error::EdResult, ed_error::EdResult,
markup::attribute::{Attributes, Caret}, 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}, markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode},
}; };
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::Expr2; use crate::lang::ast::Expr2;
use crate::lang::expr::{str_to_expr2, Env}; use crate::lang::expr::{str_to_expr2, Env};
use crate::lang::scope::Scope; use crate::lang::scope::Scope;
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 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 roc_region::all::Region; use roc_region::all::Region;
use std::collections::HashSet; use std::path::Path;
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
use VirtualKeyCode::*;
pub type LeafIndex = usize;
#[derive(Debug)] #[derive(Debug)]
pub struct EdModel<'a> { pub struct EdModel<'a> {
pub module: EdModule<'a>, pub module: EdModule<'a>,
pub code_as_str: &'a str, pub file_path: &'a Path,
pub code_lines: CodeLines,
pub markup_root_id: SlowNodeId, pub markup_root_id: SlowNodeId,
pub glyph_dim_rect_opt: Option<Rect>, pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool, pub has_focus: bool,
// This HashSet may have less elements than there are carets. There can be multiple carets for a single node. // Option<SlowNodeId>: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used.
caret_nodes: HashSet<(SlowNodeId, LeafIndex)>, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<SlowNodeId>)>,
dfs_ordered_leaves: Vec<SlowNodeId>,
} }
pub fn init_model<'a>( pub fn init_model<'a>(
code_str: &'a BumpString, code_str: &'a BumpString,
file_path: &'a Path,
env: Env<'a>, env: Env<'a>,
code_arena: &'a Bump, code_arena: &'a Bump,
markup_node_pool: &mut SlowPool, markup_node_pool: &mut SlowPool,
@ -65,62 +74,178 @@ pub fn init_model<'a>(
temp_markup_root_id 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 { Ok(EdModel {
module, module,
code_as_str: code_str, file_path,
code_lines: CodeLines::from_str(code_str),
markup_root_id, markup_root_id,
glyph_dim_rect_opt: None, glyph_dim_rect_opt: None,
has_focus: true, has_focus: true,
caret_nodes: vec![(node_w_caret_id, 0)].into_iter().collect(), caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
dfs_ordered_leaves,
}) })
} }
impl<'a> EdModel<'a> { impl<'a> EdModel<'a> {
pub fn handle_key_down( pub fn move_caret(
&mut self, &mut self,
_modifiers: &Modifiers, move_fun: MoveCaretFun<CodeLines>,
virtual_keycode: VirtualKeyCode, modifiers: &Modifiers,
markup_node_pool: &mut SlowPool, ) -> UIResult<()> {
) -> EdResult<()> { for caret_tup in self.caret_w_select_vec.iter_mut() {
match virtual_keycode { caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
VirtualKeyCode::Right => { caret_tup.1 = None;
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(); Ok(())
} }
VirtualKeyCode::Left => unimplemented!("TODO"), }
_ => (),
}; 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(()) Ok(())
} }
pub fn get_next_leaf(&self, index: usize) -> Option<SlowNodeId> { fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
self.dfs_ordered_leaves.get(index + 1).copied() 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(()),
}
} }
} }

View file

@ -1,9 +1,12 @@
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::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::CaretWSelect;
use crate::ui::ui_error::MissingGlyphDims; use crate::ui::ui_error::MissingGlyphDims;
use cgmath::Vector2; use cgmath::Vector2;
use snafu::OptionExt; use snafu::OptionExt;
@ -19,12 +22,62 @@ pub fn model_to_wgpu<'a>(
) -> EdResult<(wgpu_glyph::Section<'a>, Vec<Rect>)> { ) -> 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 {})?;
build_code_graphics( let (section, mut rects) = build_code_graphics(
markup_node_pool.get(ed_model.markup_root_id), 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, 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
.caret_w_select_vec
.iter()
.map(|(caret_w_sel, _)| *caret_w_sel)
.collect();
let mut sel_rects =
build_selection_graphics(caret_w_sel_vec, txt_coords, config, glyph_dim_rect)?;
rects.append(&mut sel_rects);
Ok((section, rects))
}
pub fn build_selection_graphics(
caret_w_select_vec: Vec<CaretWSelect>,
txt_coords: Vector2<f32>,
config: &Config,
glyph_dim_rect: Rect,
) -> EdResult<Vec<Rect>> {
let mut rects = Vec::new();
let char_width = glyph_dim_rect.width;
let char_height = glyph_dim_rect.height;
for caret_w_sel in caret_w_select_vec {
let caret_row = caret_w_sel.caret_pos.line as f32;
let caret_col = caret_w_sel.caret_pos.column as f32;
let top_left_x = txt_coords.x + caret_col * char_width;
let top_left_y = txt_coords.y + caret_row * char_height;
rects.push(make_caret_rect(
top_left_x,
top_left_y,
&glyph_dim_rect,
&config.ed_theme.ui_theme,
))
}
Ok(rects)
} }

View file

@ -1,10 +1,8 @@
use super::markup::attribute::{Attribute, Attributes};
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get};
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::graphics::primitives::text as gr_text; use crate::graphics::primitives::text as gr_text;
use crate::ui::text::caret_w_select::make_caret_rect;
use cgmath::Vector2; use cgmath::Vector2;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
@ -67,40 +65,6 @@ fn markup_to_wgpu<'a>(
Ok((wgpu_texts, rects)) Ok((wgpu_texts, rects))
} }
fn draw_attributes(
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 { 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
+ caret_col * char_width;
let top_left_y = code_style.txt_coords.y
+ (txt_row_col.0 as f32) * char_width
+ char_width * 0.2;
make_caret_rect(
top_left_x,
top_left_y,
&code_style.glyph_dim_rect,
&code_style.ed_theme.ui_theme,
)
}
rest => todo!("implement draw_attributes for {:?}", rest),
})
.collect()
}
// 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,
@ -132,7 +96,7 @@ fn markup_to_wgpu_helper<'a>(
content, content,
ast_node_id: _, ast_node_id: _,
syn_high_style, syn_high_style,
attributes, attributes: _,
parent_id_opt: _, parent_id_opt: _,
} => { } => {
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)?;
@ -141,13 +105,12 @@ fn markup_to_wgpu_helper<'a>(
.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);
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
txt_row_col.1 += content.len(); txt_row_col.1 += content.len();
wgpu_texts.push(glyph_text); wgpu_texts.push(glyph_text);
} }
MarkupNode::Blank { MarkupNode::Blank {
ast_node_id: _, ast_node_id: _,
attributes, attributes: _,
syn_high_style, syn_high_style,
parent_id_opt: _, parent_id_opt: _,
} => { } => {
@ -171,8 +134,6 @@ fn markup_to_wgpu_helper<'a>(
}; };
rects.push(hole_rect); rects.push(hole_rect);
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
txt_row_col.1 += BLANK_PLACEHOLDER.len(); txt_row_col.1 += BLANK_PLACEHOLDER.len();
wgpu_texts.push(glyph_text); wgpu_texts.push(glyph_text);
} }

View file

@ -1,18 +1,6 @@
use super::ed_error::{EdResult, KeyNotFound, OutOfBounds}; use super::ed_error::{EdResult, KeyNotFound};
use snafu::OptionExt; use snafu::OptionExt;
use std::collections::HashMap; use std::collections::HashMap;
use std::slice::SliceIndex;
// replace vec methods that return Option with ones that return Result and proper Error
pub fn slice_get<T>(index: usize, slice: &[T]) -> EdResult<&<usize as SliceIndex<[T]>>::Output> {
let elt_ref = slice.get(index).context(OutOfBounds {
index,
collection_name: "Slice",
len: slice.len(),
})?;
Ok(elt_ref)
}
// replace HashMap method that returns Option with one that returns Result and proper Error // replace HashMap method that returns Option with one that returns Result and proper Error
pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>(

View file

@ -1,4 +1,4 @@
pub mod text; pub mod text;
pub mod theme; pub mod theme;
pub mod ui_error; pub mod ui_error;
mod util; pub mod util;

View file

@ -4,8 +4,9 @@
use crate::ui::text::{ use crate::ui::text::{
caret_w_select::CaretWSelect, caret_w_select::CaretWSelect,
lines,
lines::{Lines, MutSelectableLines, SelectableLines}, lines::{Lines, MutSelectableLines, SelectableLines},
selection::{validate_raw_sel, validate_selection, RawSelection, Selection}, selection::{validate_raw_sel, RawSelection, Selection},
text_pos::TextPos, text_pos::TextPos,
}; };
use crate::ui::ui_error::{ use crate::ui::ui_error::{
@ -19,23 +20,17 @@ use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use ropey::Rope; use ropey::Rope;
use snafu::ensure; use snafu::ensure;
use std::{ use std::{fmt, fs::File, io, path::Path};
cmp::{max, min},
fmt,
fs::File,
io,
path::Path,
};
use winit::event::{VirtualKeyCode, VirtualKeyCode::*}; use winit::event::{VirtualKeyCode, VirtualKeyCode::*};
pub struct BigSelectableText { pub struct BigTextArea {
pub caret_w_select: CaretWSelect, pub caret_w_select: CaretWSelect,
text_rope: Rope, text_rope: Rope,
pub path_str: String, pub path_str: String,
arena: Bump, arena: Bump,
} }
impl BigSelectableText { impl BigTextArea {
fn check_bounds(&self, char_indx: usize) -> UIResult<()> { fn check_bounds(&self, char_indx: usize) -> UIResult<()> {
ensure!( ensure!(
char_indx <= self.text_rope.len_chars(), char_indx <= self.text_rope.len_chars(),
@ -71,17 +66,13 @@ impl BigSelectableText {
} }
} }
fn validate_sel_opt(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> { impl Lines for BigTextArea {
Ok(Some(validate_selection(start_pos, end_pos)?))
}
impl Lines for BigSelectableText {
fn get_line(&self, line_nr: usize) -> UIResult<&str> { fn get_line(&self, line_nr: usize) -> UIResult<&str> {
ensure!( ensure!(
line_nr < self.nr_of_lines(), line_nr < self.nr_of_lines(),
OutOfBounds { OutOfBounds {
index: line_nr, index: line_nr,
collection_name: "BigSelectableText", collection_name: "BigTextArea",
len: self.nr_of_lines(), len: self.nr_of_lines(),
} }
); );
@ -121,9 +112,13 @@ impl Lines for BigSelectableText {
lines lines
} }
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
}
} }
impl SelectableLines for BigSelectableText { 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
} }
@ -133,347 +128,39 @@ impl SelectableLines for BigSelectableText {
} }
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_left(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => {
(old_selection.start_pos.line, old_selection.start_pos.column)
}
None => unreachable!(),
}
} else if old_col_nr == 0 {
if old_line_nr == 0 {
(0, 0)
} else {
let curr_line_len = self.line_len(old_line_nr - 1)?;
(old_line_nr - 1, curr_line_len - 1)
}
} else {
(old_line_nr, old_col_nr - 1)
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos >= old_selection.end_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
old_selection.end_pos,
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
TextPos {
line: old_line_nr,
column: old_col_nr,
},
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_right(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else {
let curr_line = self.get_line(old_line_nr)?;
if let Some(last_char) = curr_line.chars().last() {
if is_newline(&last_char) {
if old_col_nr + 1 > curr_line.len() - 1 {
(old_line_nr + 1, 0)
} else {
(old_line_nr, old_col_nr + 1)
}
} else if old_col_nr < curr_line.len() {
(old_line_nr, old_col_nr + 1)
} else {
(old_line_nr, old_col_nr)
}
} else {
(old_line_nr, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else {
validate_sel_opt(
old_selection.start_pos,
TextPos {
line: line_nr,
column: col_nr,
},
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: old_line_nr,
column: old_col_nr,
},
TextPos {
line: line_nr,
column: col_nr,
},
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_up(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => {
(old_selection.start_pos.line, old_selection.start_pos.column)
}
None => unreachable!(),
}
} else if old_line_nr == 0 {
(old_line_nr, 0)
} else {
let prev_line_len = self.line_len(old_line_nr - 1)?;
if prev_line_len <= old_col_nr {
(old_line_nr - 1, prev_line_len - 1)
} else {
(old_line_nr - 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_selection.end_pos <= old_caret_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(
min(old_selection.start_pos, new_caret_pos),
max(old_selection.start_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection(); self.caret_w_select = lines::move_caret_down(self, self.caret_w_select, modifiers)?;
let old_caret_pos = self.caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if old_line_nr + 1 >= self.nr_of_lines() {
let curr_line_len = self.line_len(old_line_nr)?;
(old_line_nr, curr_line_len)
} else {
let next_line = self.get_line(old_line_nr + 1)?;
if next_line.len() <= old_col_nr {
if let Some(last_char) = next_line.chars().last() {
if is_newline(&last_char) {
(old_line_nr + 1, next_line.len() - 1)
} else {
(old_line_nr + 1, next_line.len())
}
} else {
(old_line_nr + 1, 0)
}
} else {
(old_line_nr + 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(
min(old_selection.end_pos, new_caret_pos),
max(old_selection.end_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
self.caret_w_select = CaretWSelect::new(new_caret_pos, new_selection_opt);
Ok(()) Ok(())
} }
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let curr_line_nr = self.caret_w_select.caret_pos.line; self.caret_w_select = lines::move_caret_home(self, self.caret_w_select, modifiers)?;
let old_col_nr = self.caret_w_select.caret_pos.column;
let curr_line_str = self.get_line(curr_line_nr)?; Ok(())
let line_char_iter = curr_line_str.chars();
let mut first_no_space_char_col = 0;
let mut non_space_found = false;
for c in line_char_iter {
if !c.is_whitespace() {
non_space_found = true;
break;
} else {
first_no_space_char_col += 1;
}
}
if !non_space_found {
first_no_space_char_col = 0;
}
let new_col_nr = if first_no_space_char_col == old_col_nr {
0
} else {
first_no_space_char_col
};
self.caret_w_select.move_caret_w_mods(
TextPos {
line: curr_line_nr,
column: new_col_nr,
},
modifiers,
)
} }
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> { fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let curr_line_nr = self.caret_w_select.caret_pos.line; self.caret_w_select = lines::move_caret_end(self, self.caret_w_select, modifiers)?;
let curr_line_len = self.line_len(curr_line_nr)?;
let new_col = if let Some(last_char) = self.last_char(curr_line_nr)? { Ok(())
if is_newline(&last_char) {
curr_line_len - 1
} else {
curr_line_len
}
} else {
0
};
let new_pos = TextPos {
line: curr_line_nr,
column: new_col,
};
self.caret_w_select.move_caret_w_mods(new_pos, modifiers)
} }
fn get_selection(&self) -> Option<Selection> { fn get_selection(&self) -> Option<Selection> {
@ -484,7 +171,7 @@ impl SelectableLines for BigSelectableText {
self.get_selection().is_some() self.get_selection().is_some()
} }
fn get_selected_str(&self) -> UIResult<Option<&str>> { fn get_selected_str(&self) -> UIResult<Option<String>> {
if let Some(val_sel) = self.caret_w_select.selection_opt { if let Some(val_sel) = self.caret_w_select.selection_opt {
let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel); let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel);
@ -493,12 +180,9 @@ impl SelectableLines for BigSelectableText {
let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx); let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx);
if let Some(line_str_ref) = rope_slice.as_str() { if let Some(line_str_ref) = rope_slice.as_str() {
Ok(Some(line_str_ref)) Ok(Some(line_str_ref.to_string()))
} else { } else {
// happens very rarely Ok(Some(rope_slice.chunks().collect::<String>()))
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Ok(Some(arena_str_ref))
} }
} else { } else {
Ok(None) Ok(None)
@ -511,13 +195,17 @@ impl SelectableLines for BigSelectableText {
Ok(()) Ok(())
} }
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
self.caret_w_select = caret_w_sel;
}
fn set_sel_none(&mut self) { fn set_sel_none(&mut self) {
self.caret_w_select.selection_opt = None; self.caret_w_select.selection_opt = None;
} }
fn select_all(&mut self) -> UIResult<()> { fn select_all(&mut self) -> UIResult<()> {
if self.nr_of_chars() > 0 { if self.nr_of_chars() > 0 {
let last_pos = self.last_text_pos(); let last_pos = self.last_text_pos()?;
self.set_raw_sel(RawSelection { self.set_raw_sel(RawSelection {
start_pos: TextPos { line: 0, column: 0 }, start_pos: TextPos { line: 0, column: 0 },
@ -530,12 +218,8 @@ impl SelectableLines for BigSelectableText {
Ok(()) Ok(())
} }
fn last_text_pos(&self) -> TextPos { fn last_text_pos(&self) -> UIResult<TextPos> {
self.char_indx_to_pos(self.nr_of_chars()) Ok(self.char_indx_to_pos(self.nr_of_chars()))
}
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
Ok(self.get_line(line_nr)?.chars().last())
} }
fn handle_key_down( fn handle_key_down(
@ -563,7 +247,7 @@ impl SelectableLines for BigSelectableText {
} }
} }
impl MutSelectableLines for BigSelectableText { impl MutSelectableLines for BigTextArea {
fn insert_char(&mut self, new_char: &char) -> UIResult<()> { fn insert_char(&mut self, new_char: &char) -> UIResult<()> {
if self.is_selection_active() { if self.is_selection_active() {
self.del_selection()?; self.del_selection()?;
@ -658,7 +342,7 @@ impl MutSelectableLines for BigSelectableText {
} }
} }
impl Default for BigSelectableText { impl Default for BigTextArea {
fn default() -> Self { fn default() -> Self {
let caret_w_select = CaretWSelect::default(); let caret_w_select = CaretWSelect::default();
let text_rope = Rope::from_str(""); let text_rope = Rope::from_str("");
@ -674,11 +358,11 @@ impl Default for BigSelectableText {
} }
} }
pub fn from_path(path: &Path) -> UIResult<BigSelectableText> { pub fn from_path(path: &Path) -> UIResult<BigTextArea> {
let text_rope = rope_from_path(path)?; let text_rope = rope_from_path(path)?;
let path_str = path_to_string(path); let path_str = path_to_string(path);
Ok(BigSelectableText { Ok(BigTextArea {
text_rope, text_rope,
path_str, path_str,
..Default::default() ..Default::default()
@ -687,10 +371,10 @@ pub fn from_path(path: &Path) -> UIResult<BigSelectableText> {
#[allow(dead_code)] #[allow(dead_code)]
// used by tests but will also be used in the future // used by tests but will also be used in the future
pub fn from_str(text: &str) -> BigSelectableText { pub fn from_str(text: &str) -> BigTextArea {
let text_rope = Rope::from_str(text); let text_rope = Rope::from_str(text);
BigSelectableText { BigTextArea {
text_rope, text_rope,
..Default::default() ..Default::default()
} }
@ -723,9 +407,9 @@ fn rope_from_path(path: &Path) -> UIResult<Rope> {
} }
// need to explicitly omit arena // need to explicitly omit arena
impl fmt::Debug for BigSelectableText { impl fmt::Debug for BigTextArea {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BigSelectableText") f.debug_struct("BigTextArea")
.field("caret_w_select", &self.caret_w_select) .field("caret_w_select", &self.caret_w_select)
.field("text_rope", &self.text_rope) .field("text_rope", &self.text_rope)
.field("path_str", &self.path_str) .field("path_str", &self.path_str)
@ -736,8 +420,8 @@ impl fmt::Debug for BigSelectableText {
#[cfg(test)] #[cfg(test)]
pub mod test_big_sel_text { pub mod test_big_sel_text {
use crate::ui::text::{ use crate::ui::text::{
big_selectable_text::from_str, big_text_area::from_str,
big_selectable_text::BigSelectableText, big_text_area::BigTextArea,
caret_w_select::CaretWSelect, caret_w_select::CaretWSelect,
lines::{Lines, MutSelectableLines, SelectableLines}, lines::{Lines, MutSelectableLines, SelectableLines},
selection::validate_selection, selection::validate_selection,
@ -843,7 +527,7 @@ pub mod test_big_sel_text {
Ok(elt_ref) Ok(elt_ref)
} }
pub fn big_text_from_dsl_str(lines: &[String]) -> BigSelectableText { pub fn big_text_from_dsl_str(lines: &[String]) -> BigTextArea {
from_str( from_str(
&lines &lines
.iter() .iter()
@ -853,7 +537,7 @@ pub mod test_big_sel_text {
) )
} }
pub fn all_lines_vec(big_sel_text: &BigSelectableText) -> Vec<String> { pub fn all_lines_vec(big_sel_text: &BigTextArea) -> Vec<String> {
let mut lines: Vec<String> = Vec::new(); let mut lines: Vec<String> = Vec::new();
for i in 0..big_sel_text.nr_of_lines() { for i in 0..big_sel_text.nr_of_lines() {
@ -963,7 +647,7 @@ pub mod test_big_sel_text {
} }
} }
pub fn gen_big_text(lines: &[&str]) -> Result<BigSelectableText, String> { pub fn gen_big_text(lines: &[&str]) -> Result<BigTextArea, String> {
let lines_string_slice: Vec<String> = lines.iter().map(|l| l.to_string()).collect(); let lines_string_slice: Vec<String> = lines.iter().map(|l| l.to_string()).collect();
let mut big_text = big_text_from_dsl_str(&lines_string_slice); let mut big_text = big_text_from_dsl_str(&lines_string_slice);
let caret_w_select = convert_dsl_to_selection(&lines_string_slice).unwrap(); let caret_w_select = convert_dsl_to_selection(&lines_string_slice).unwrap();
@ -1141,7 +825,7 @@ pub mod test_big_sel_text {
Ok(()) Ok(())
} }
type MoveCaretFun = fn(&mut BigSelectableText, &Modifiers) -> UIResult<()>; type MoveCaretFun = fn(&mut BigTextArea, &Modifiers) -> UIResult<()>;
// Convert nice string representations and compare results // Convert nice string representations and compare results
fn assert_move( fn assert_move(
@ -1720,7 +1404,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn move_home() -> Result<(), String> { fn move_home() -> Result<(), String> {
let move_caret_home = BigSelectableText::move_caret_home; let move_caret_home = BigTextArea::move_caret_home;
assert_move(&["|"], &["|"], &no_mods(), move_caret_home)?; assert_move(&["|"], &["|"], &no_mods(), move_caret_home)?;
assert_move(&["a|"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["a|"], &["|a"], &no_mods(), move_caret_home)?;
assert_move(&["|a"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["|a"], &["|a"], &no_mods(), move_caret_home)?;
@ -1834,7 +1518,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn move_end() -> Result<(), String> { fn move_end() -> Result<(), String> {
let move_caret_end = BigSelectableText::move_caret_end; let move_caret_end = BigTextArea::move_caret_end;
assert_move(&["|"], &["|"], &no_mods(), move_caret_end)?; assert_move(&["|"], &["|"], &no_mods(), move_caret_end)?;
assert_move(&["|a"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["|a"], &["a|"], &no_mods(), move_caret_end)?;
assert_move(&["a|"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["a|"], &["a|"], &no_mods(), move_caret_end)?;
@ -2467,7 +2151,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn start_selection_home() -> Result<(), String> { fn start_selection_home() -> Result<(), String> {
let move_caret_home = BigSelectableText::move_caret_home; let move_caret_home = BigTextArea::move_caret_home;
assert_move(&["|"], &["|"], &shift_pressed(), move_caret_home)?; assert_move(&["|"], &["|"], &shift_pressed(), move_caret_home)?;
assert_move(&["|a"], &["|a"], &shift_pressed(), move_caret_home)?; assert_move(&["|a"], &["|a"], &shift_pressed(), move_caret_home)?;
assert_move(&["a|"], &["|[a]"], &shift_pressed(), move_caret_home)?; assert_move(&["a|"], &["|[a]"], &shift_pressed(), move_caret_home)?;
@ -2587,7 +2271,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn start_selection_end() -> Result<(), String> { fn start_selection_end() -> Result<(), String> {
let move_caret_end = BigSelectableText::move_caret_end; let move_caret_end = BigTextArea::move_caret_end;
assert_move(&["|"], &["|"], &shift_pressed(), move_caret_end)?; assert_move(&["|"], &["|"], &shift_pressed(), move_caret_end)?;
assert_move(&["|a"], &["[a]|"], &shift_pressed(), move_caret_end)?; assert_move(&["|a"], &["[a]|"], &shift_pressed(), move_caret_end)?;
assert_move(&["a|"], &["a|"], &shift_pressed(), move_caret_end)?; assert_move(&["a|"], &["a|"], &shift_pressed(), move_caret_end)?;
@ -3253,7 +2937,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn end_selection_home() -> Result<(), String> { fn end_selection_home() -> Result<(), String> {
let move_caret_home = BigSelectableText::move_caret_home; let move_caret_home = BigTextArea::move_caret_home;
assert_move(&["[a]|"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["[a]|"], &["|a"], &no_mods(), move_caret_home)?;
assert_move(&["|[a]"], &["|a"], &no_mods(), move_caret_home)?; assert_move(&["|[a]"], &["|a"], &no_mods(), move_caret_home)?;
assert_move(&[" |[a]"], &["| a"], &no_mods(), move_caret_home)?; assert_move(&[" |[a]"], &["| a"], &no_mods(), move_caret_home)?;
@ -3318,7 +3002,7 @@ pub mod test_big_sel_text {
#[test] #[test]
fn end_selection_end() -> Result<(), String> { fn end_selection_end() -> Result<(), String> {
let move_caret_end = BigSelectableText::move_caret_end; let move_caret_end = BigTextArea::move_caret_end;
assert_move(&["|[a]"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["|[a]"], &["a|"], &no_mods(), move_caret_end)?;
assert_move(&["[a]|"], &["a|"], &no_mods(), move_caret_end)?; assert_move(&["[a]|"], &["a|"], &no_mods(), move_caret_end)?;
assert_move(&[" a|[ ]"], &[" a |"], &no_mods(), move_caret_end)?; assert_move(&[" a|[ ]"], &[" a |"], &no_mods(), move_caret_end)?;

View file

@ -37,7 +37,7 @@ impl CaretWSelect {
} }
} }
pub fn move_caret_w_mods(&mut self, new_pos: TextPos, mods: &Modifiers) -> UIResult<()> { pub fn move_caret_w_mods(&self, new_pos: TextPos, mods: &Modifiers) -> UIResult<CaretWSelect> {
let old_caret_pos = self.caret_pos; let old_caret_pos = self.caret_pos;
// one does not simply move the caret // one does not simply move the caret
@ -75,10 +75,7 @@ impl CaretWSelect {
None None
}; };
self.caret_pos = new_pos; Ok(CaretWSelect::new(new_pos, valid_sel_opt))
self.selection_opt = valid_sel_opt;
Ok(())
} }
} }

View file

@ -1,13 +1,18 @@
// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license // Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::selection::validate_sel_opt;
use crate::ui::text::{ use crate::ui::text::{
selection::{RawSelection, Selection}, selection::{RawSelection, Selection},
text_pos::TextPos, text_pos::TextPos,
}; };
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use crate::ui::util::is_newline;
use crate::window::keyboard_input::Modifiers; use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString; use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use std::cmp::max;
use std::cmp::min;
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
pub trait Lines { pub trait Lines {
@ -19,8 +24,9 @@ pub trait Lines {
fn nr_of_chars(&self) -> usize; fn nr_of_chars(&self) -> usize;
// TODO use pool allocation here
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>; fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>;
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>>;
} }
pub trait SelectableLines { pub trait SelectableLines {
@ -44,17 +50,17 @@ pub trait SelectableLines {
fn is_selection_active(&self) -> bool; fn is_selection_active(&self) -> bool;
fn get_selected_str(&self) -> UIResult<Option<&str>>; fn get_selected_str(&self) -> UIResult<Option<String>>;
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()>; fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()>;
fn set_sel_none(&mut self); fn set_sel_none(&mut self);
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect);
fn select_all(&mut self) -> UIResult<()>; fn select_all(&mut self) -> UIResult<()>;
fn last_text_pos(&self) -> TextPos; fn last_text_pos(&self) -> UIResult<TextPos>;
fn last_char(&self, line_nr: usize) -> UIResult<Option<char>>;
fn handle_key_down( fn handle_key_down(
&mut self, &mut self,
@ -75,3 +81,362 @@ pub trait MutSelectableLines {
fn del_selection(&mut self) -> UIResult<()>; fn del_selection(&mut self) -> UIResult<()>;
} }
// T: Lines
pub type MoveCaretFun<T> = fn(&T, CaretWSelect, &Modifiers) -> UIResult<CaretWSelect>;
pub fn move_caret_left<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_col_nr == 0 {
if old_line_nr == 0 {
(0, 0)
} else {
let curr_line_len = lines.line_len(old_line_nr - 1)?;
(old_line_nr - 1, curr_line_len - 1)
}
} else {
(old_line_nr, old_col_nr - 1)
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos >= old_selection.end_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
old_selection.end_pos,
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: line_nr,
column: col_nr,
},
TextPos {
line: old_line_nr,
column: old_col_nr,
},
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_right<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else {
let curr_line = lines.get_line(old_line_nr)?;
if let Some(last_char) = curr_line.chars().last() {
if is_newline(&last_char) {
if old_col_nr + 1 > curr_line.len() - 1 {
(old_line_nr + 1, 0)
} else {
(old_line_nr, old_col_nr + 1)
}
} else if old_col_nr < curr_line.len() {
(old_line_nr, old_col_nr + 1)
} else {
(old_line_nr, old_col_nr)
}
} else {
(old_line_nr, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else {
validate_sel_opt(
old_selection.start_pos,
TextPos {
line: line_nr,
column: col_nr,
},
)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
TextPos {
line: old_line_nr,
column: old_col_nr,
},
TextPos {
line: line_nr,
column: col_nr,
},
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_up<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_line_nr == 0 {
(old_line_nr, 0)
} else {
let prev_line_len = lines.line_len(old_line_nr - 1)?;
if prev_line_len <= old_col_nr {
(old_line_nr - 1, prev_line_len - 1)
} else {
(old_line_nr - 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_selection.end_pos <= old_caret_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
validate_sel_opt(
min(old_selection.start_pos, new_caret_pos),
max(old_selection.start_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(new_caret_pos, old_selection.end_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_down<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let old_selection_opt = caret_w_select.selection_opt;
let old_caret_pos = caret_w_select.caret_pos;
let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column;
let shift_pressed = modifiers.shift;
let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if old_line_nr + 1 >= lines.nr_of_lines() {
let curr_line_len = lines.line_len(old_line_nr)?;
(old_line_nr, curr_line_len)
} else {
let next_line = lines.get_line(old_line_nr + 1)?;
if next_line.len() <= old_col_nr {
if let Some(last_char) = next_line.chars().last() {
if is_newline(&last_char) {
(old_line_nr + 1, next_line.len() - 1)
} else {
(old_line_nr + 1, next_line.len())
}
} else {
(old_line_nr + 1, 0)
}
} else {
(old_line_nr + 1, old_col_nr)
}
};
let new_caret_pos = TextPos {
line: line_nr,
column: col_nr,
};
let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
validate_sel_opt(
min(old_selection.end_pos, new_caret_pos),
max(old_selection.end_pos, new_caret_pos),
)?
}
} else {
validate_sel_opt(old_selection.start_pos, new_caret_pos)?
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
validate_sel_opt(
min(old_caret_pos, new_caret_pos),
max(old_caret_pos, new_caret_pos),
)?
} else {
None
}
} else {
None
};
Ok(CaretWSelect::new(new_caret_pos, new_selection_opt))
}
pub fn move_caret_home<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let curr_line_nr = caret_w_select.caret_pos.line;
let old_col_nr = caret_w_select.caret_pos.column;
let curr_line_str = lines.get_line(curr_line_nr)?;
let line_char_iter = curr_line_str.chars();
let mut first_no_space_char_col = 0;
let mut non_space_found = false;
for c in line_char_iter {
if !c.is_whitespace() {
non_space_found = true;
break;
} else {
first_no_space_char_col += 1;
}
}
if !non_space_found {
first_no_space_char_col = 0;
}
let new_col_nr = if first_no_space_char_col == old_col_nr {
0
} else {
first_no_space_char_col
};
caret_w_select.move_caret_w_mods(
TextPos {
line: curr_line_nr,
column: new_col_nr,
},
modifiers,
)
}
pub fn move_caret_end<T: Lines>(
lines: &T,
caret_w_select: CaretWSelect,
modifiers: &Modifiers,
) -> UIResult<CaretWSelect> {
let curr_line_nr = caret_w_select.caret_pos.line;
let curr_line_len = lines.line_len(curr_line_nr)?;
let new_col = if let Some(last_char) = lines.last_char(curr_line_nr)? {
if is_newline(&last_char) {
curr_line_len - 1
} else {
curr_line_len
}
} else {
0
};
let new_pos = TextPos {
line: curr_line_nr,
column: new_col,
};
caret_w_select.move_caret_w_mods(new_pos, modifiers)
}

View file

@ -1,4 +1,4 @@
pub mod big_selectable_text; pub mod big_text_area;
pub mod caret_w_select; pub mod caret_w_select;
pub mod lines; pub mod lines;
pub mod selection; pub mod selection;

View file

@ -34,6 +34,10 @@ pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<Selection> {
validate_selection(raw_sel.start_pos, raw_sel.end_pos) validate_selection(raw_sel.start_pos, raw_sel.end_pos)
} }
pub fn validate_sel_opt(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> {
Ok(Some(validate_selection(start_pos, end_pos)?))
}
pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult<Selection> { pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult<Selection> {
ensure!( ensure!(
start_pos.line <= end_pos.line, start_pos.line <= end_pos.line,

View file

@ -1,5 +1,20 @@
use super::ui_error::{OutOfBounds, UIResult};
use snafu::OptionExt;
use std::slice::SliceIndex;
pub fn is_newline(char_ref: &char) -> bool { pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}', '\n']; let newline_codes = vec!['\u{d}', '\n'];
newline_codes.contains(char_ref) newline_codes.contains(char_ref)
} }
// replace vec 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> {
let elt_ref = slice.get(index).context(OutOfBounds {
index,
collection_name: "Slice",
len: slice.len(),
})?;
Ok(elt_ref)
}