mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
Merge branch 'trunk' into generated-docs-folder
This commit is contained in:
commit
90b1c57f1f
33 changed files with 1169 additions and 997 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
8
compiler/load/tests/fixtures/build/no_deps/MissingDep.roc
vendored
Normal file
8
compiler/load/tests/fixtures/build/no_deps/MissingDep.roc
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
interface MissingDep
|
||||||
|
exposes [ unit ]
|
||||||
|
imports [ ThisFileIsMissing ]
|
||||||
|
|
||||||
|
Unit : [ Unit ]
|
||||||
|
|
||||||
|
unit : Unit
|
||||||
|
unit = Unit
|
|
@ -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());
|
)
|
||||||
|
),
|
||||||
// let mut subs = loaded_module.solved.into_inner();
|
Ok(_) => unreachable!("we expect failure here"),
|
||||||
|
}
|
||||||
// for decl in loaded_module.declarations {
|
}
|
||||||
// let def = match decl {
|
|
||||||
// Declare(def) => def,
|
#[test]
|
||||||
// rec_decl @ DeclareRec(_) => {
|
#[should_panic(
|
||||||
// panic!(
|
expected = "FileProblem { filename: \"tests/fixtures/build/interface_with_deps/invalid$name.roc\", error: NotFound }"
|
||||||
// "Unexpected recursive def in module declarations: {:?}",
|
)]
|
||||||
// rec_decl
|
fn file_not_found() {
|
||||||
// );
|
let subs_by_module = MutMap::default();
|
||||||
// }
|
let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module);
|
||||||
// cycle @ InvalidCycle(_, _) => {
|
|
||||||
// panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
|
expect_types(
|
||||||
// }
|
loaded_module,
|
||||||
// };
|
hashmap! {
|
||||||
|
"str" => "Str",
|
||||||
// for (symbol, expr_var) in def.pattern_vars {
|
},
|
||||||
// let content = subs.get(expr_var).content;
|
);
|
||||||
|
}
|
||||||
// name_all_type_vars(expr_var, &mut subs);
|
|
||||||
|
#[test]
|
||||||
// let actual_str = content_to_string(content, &mut subs);
|
#[should_panic(expected = "FILE NOT FOUND")]
|
||||||
// let expected_type = expected_types.get(symbol.as_str()).unwrap_or_else(|| {
|
fn imported_file_not_found() {
|
||||||
// panic!("Defs included an unexpected symbol: {:?}", symbol)
|
let subs_by_module = MutMap::default();
|
||||||
// });
|
let loaded_module = load_fixture("no_deps", "MissingDep", subs_by_module);
|
||||||
|
|
||||||
// assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str()));
|
expect_types(
|
||||||
// }
|
loaded_module,
|
||||||
// }
|
hashmap! {
|
||||||
// }
|
"str" => "Str",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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!();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
76
editor/src/editor/code_lines.rs
Normal file
76
editor/src/editor/code_lines.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)?
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod attribute;
|
pub mod attribute;
|
||||||
pub mod caret;
|
|
||||||
pub mod nodes;
|
pub mod nodes;
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod code_lines;
|
||||||
mod config;
|
mod config;
|
||||||
mod ed_error;
|
mod ed_error;
|
||||||
mod keyboard_input;
|
mod keyboard_input;
|
||||||
|
|
|
@ -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)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)?;
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue