new caret structure progress, renamed BigSelectedText to BigTextArea

This commit is contained in:
Anton-4 2021-03-22 20:13:49 +01:00
parent e1c70fddec
commit 6ab71f9ab9
17 changed files with 738 additions and 555 deletions

7
Cargo.lock generated
View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use super::keyboard_input;
use super::style::CODE_TXT_XY;
use super::util::slice_get;
use crate::ui::util::slice_get;
use crate::editor::ed_error::print_ui_err;
use crate::editor::resources::strings::NOTHING_OPENED;
use crate::editor::slow_pool::SlowPool;
@ -153,21 +153,27 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
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) {
Ok(file_as_str) => {
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(),
err_msg: e.to_string(),
}),
}
});
Path::new("")
},
}
} else {
Path::new("")
};
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 {
Ok(mut ed_model) => {

View File

@ -1,4 +1,3 @@
use crate::editor::mvc::ed_model::LeafIndex;
use crate::editor::{
ed_error::{CaretNotFound, EdResult, NodeWithoutAttributes},
markup::attribute::Caret,
@ -7,90 +6,6 @@ use crate::editor::{
};
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()?;

View File

@ -11,3 +11,4 @@ mod style;
mod syntax_highlight;
mod theme;
mod util;
mod code_lines;

View File

@ -1,10 +1,23 @@
use std::path::Path;
use crate::ui::text::selection::validate_raw_sel;
use crate::ui::text::selection::RawSelection;
use crate::ui::text::selection::Selection;
use crate::ui::ui_error::UIResult;
use crate::ui::text::lines::MoveCaretFun;
use crate::editor::code_lines::CodeLines;
use crate::ui::text::text_pos::TextPos;
use crate::ui::text::{
lines,
lines::Lines,
lines::SelectableLines,
};
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::editor::slow_pool::{SlowNodeId, SlowPool};
use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::{
ed_error::EdError::ParseError,
ed_error::EdResult,
markup::attribute::{Attributes, Caret},
markup::caret::{move_carets_right_for_node, set_caret_at_start},
markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode},
};
use crate::graphics::primitives::rect::Rect;
@ -15,25 +28,23 @@ use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use roc_region::all::Region;
use std::collections::HashSet;
use winit::event::VirtualKeyCode;
pub type LeafIndex = usize;
use nonempty::NonEmpty;
#[derive(Debug)]
pub struct EdModel<'a> {
pub module: EdModule<'a>,
pub code_as_str: &'a str,
pub code_lines: CodeLines,
pub markup_root_id: SlowNodeId,
pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool,
// This HashSet may have less elements than there are carets. There can be multiple carets for a single node.
caret_nodes: HashSet<(SlowNodeId, LeafIndex)>,
dfs_ordered_leaves: Vec<SlowNodeId>,
caret_w_select_vec: NonEmpty<(CaretWSelect, Option<SlowNodeId>)>,
// Option<SlowNodeId>: MarkupNode that corresponds to caret position, Option because this SlowNodeId is only calculated when it needs to be used.
}
pub fn init_model<'a>(
code_str: &'a BumpString,
file_path: &'a Path,
env: Env<'a>,
code_arena: &'a Bump,
markup_node_pool: &mut SlowPool,
@ -65,62 +76,203 @@ pub fn init_model<'a>(
temp_markup_root_id
};
let mut dfs_ordered_leaves = Vec::new();
markup_node_pool.get(markup_root_id).get_dfs_leaves(
markup_root_id,
markup_node_pool,
&mut dfs_ordered_leaves,
);
// unwrap because it's not possible to only have a single Nested node without children.
let first_leaf_id = dfs_ordered_leaves.first().unwrap();
let node_w_caret_id = set_caret_at_start(*first_leaf_id, markup_node_pool)?;
Ok(EdModel {
module,
code_as_str: code_str,
code_lines: CodeLines::from_bump_str(code_str),
markup_root_id,
glyph_dim_rect_opt: None,
has_focus: true,
caret_nodes: vec![(node_w_caret_id, 0)].into_iter().collect(),
dfs_ordered_leaves,
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
})
}
impl<'a> EdModel<'a> {
pub fn handle_key_down(
&mut self,
_modifiers: &Modifiers,
modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
markup_node_pool: &mut SlowPool,
) -> EdResult<()> {
match virtual_keycode {
VirtualKeyCode::Right => {
let mut new_caret_nodes: Vec<(SlowNodeId, LeafIndex)> = Vec::new();
for (caret_node_id_ref, leaf_index) in self.caret_nodes.iter() {
let caret_node_id = *caret_node_id_ref;
let next_leaf_id_opt = self.get_next_leaf(*leaf_index);
new_caret_nodes.extend(move_carets_right_for_node(
caret_node_id,
*leaf_index,
next_leaf_id_opt,
markup_node_pool,
)?);
self.move_caret_right(modifiers)?;
}
self.caret_nodes = new_caret_nodes.into_iter().collect();
}
VirtualKeyCode::Left => unimplemented!("TODO"),
VirtualKeyCode::Left => {
self.move_caret_left(modifiers)?;
},
_ => (),
};
Ok(())
}
pub fn get_next_leaf(&self, index: usize) -> Option<SlowNodeId> {
self.dfs_ordered_leaves.get(index + 1).copied()
pub fn move_caret(&mut self, move_fun: MoveCaretFun<CodeLines>, modifiers: &Modifiers) -> UIResult<()> {
for caret_tup in self.caret_w_select_vec.iter_mut() {
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
caret_tup.1 = None;
}
Ok(())
}
}
impl<'a> SelectableLines for EdModel<'a> {
fn get_caret(self) -> TextPos {
self.caret_w_select_vec.first().0.caret_pos
}
// keeps active selection
fn set_caret(&mut self, caret_pos: TextPos) {
let caret_tup = self.caret_w_select_vec.first_mut();
caret_tup.0.caret_pos = caret_pos;
caret_tup.1 = None;
}
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_left;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_right;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_up;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_down;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_home;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let move_fun: MoveCaretFun<CodeLines> = lines::move_caret_end;
EdModel::move_caret(self, move_fun, modifiers)?;
Ok(())
}
fn get_selection(&self) -> Option<Selection> {
self.caret_w_select_vec.first().0.selection_opt
}
fn is_selection_active(&self) -> bool {
self.get_selection().is_some()
}
fn get_selected_str(&self) -> UIResult<Option<String>> {
if let Some(selection) = self.get_selection() {
let start_line_index = selection.start_pos.line;
let start_col = selection.start_pos.column;
let end_line_index = selection.end_pos.line;
let end_col = selection.end_pos.column;
if start_line_index == end_line_index {
let line_ref = self.code_lines.get_line(start_line_index)?;
Ok(
Some(
line_ref[start_col..end_col].to_string() )
)
} else {
let full_str = String::new();
// TODO
Ok(
Some(
full_str
)
)
}
} else {
Ok(None)
}
}
fn set_raw_sel(&mut self, raw_sel: RawSelection) -> UIResult<()> {
self.caret_w_select_vec.first_mut().0.selection_opt = Some(validate_raw_sel(raw_sel)?);
Ok(())
}
fn set_sel_none(&mut self) {
self.caret_w_select_vec.first_mut().0.selection_opt = None;
}
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
self.caret_w_select_vec.first_mut().0 = caret_w_sel;
}
fn select_all(&mut self) -> UIResult<()> {
if self.code_lines.nr_of_chars() > 0 {
let last_pos = self.last_text_pos()?;
self.set_raw_sel(RawSelection {
start_pos: TextPos { line: 0, column: 0 },
end_pos: last_pos,
})?;
self.set_caret(last_pos);
}
Ok(())
}
fn last_text_pos(&self) -> UIResult<TextPos> {
let nr_of_lines = self.code_lines.lines.len();
let last_line_index = nr_of_lines - 1;
let last_line = self.code_lines.get_line(last_line_index)?;
Ok(
TextPos {
line: self.code_lines.lines.len() - 1,
column: last_line.len(),
}
)
}
fn handle_key_down(
&mut self,
modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
) -> UIResult<()> {
match virtual_keycode {
Left => self.move_caret_left(modifiers),
Up => self.move_caret_up(modifiers),
Right => self.move_caret_right(modifiers),
Down => self.move_caret_down(modifiers),
A => {
if modifiers.ctrl {
self.select_all()
} else {
Ok(())
}
}
Home => self.move_caret_home(modifiers),
End => self.move_caret_end(modifiers),
_ => Ok(()),
}
}
}

View File

@ -27,4 +27,13 @@ pub fn model_to_wgpu<'a>(
glyph_dim_rect,
markup_node_pool,
)
//TODO implement method
build_selection_graphics(
ed_model.caret_w_select_vec
size,
txt_coords,
config,
glyph_dim_rect,
)
}

View File

@ -1,3 +1,7 @@
use crate::editor::slow_pool::SlowNodeId;
use crate::ui::text::text_pos::TextPos;
use nonempty::NonEmpty;
use crate::ui::text::caret_w_select::CaretWSelect;
use super::markup::attribute::{Attribute, Attributes};
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
use crate::editor::slow_pool::SlowPool;
@ -67,40 +71,6 @@ fn markup_to_wgpu<'a>(
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
fn markup_to_wgpu_helper<'a>(
markup_node: &'a MarkupNode,
@ -132,7 +102,7 @@ fn markup_to_wgpu_helper<'a>(
content,
ast_node_id: _,
syn_high_style,
attributes,
attributes: _,
parent_id_opt: _,
} => {
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?;
@ -141,7 +111,6 @@ fn markup_to_wgpu_helper<'a>(
.with_color(colors::to_slice(*highlight_color))
.with_scale(code_style.font_size);
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
txt_row_col.1 += content.len();
wgpu_texts.push(glyph_text);
}
@ -171,8 +140,6 @@ fn markup_to_wgpu_helper<'a>(
};
rects.push(hole_rect);
rects.extend(draw_attributes(attributes, txt_row_col, code_style));
txt_row_col.1 += BLANK_PLACEHOLDER.len();
wgpu_texts.push(glyph_text);
}

View File

@ -3,17 +3,6 @@ use snafu::OptionExt;
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
pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>(
hash_map: &'a HashMap<K, V>,

View File

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

View File

@ -4,8 +4,9 @@
use crate::ui::text::{
caret_w_select::CaretWSelect,
lines,
lines::{Lines, MutSelectableLines, SelectableLines},
selection::{validate_raw_sel, validate_selection, RawSelection, Selection},
selection::{validate_raw_sel, RawSelection, Selection},
text_pos::TextPos,
};
use crate::ui::ui_error::{
@ -20,7 +21,6 @@ use bumpalo::Bump;
use ropey::Rope;
use snafu::ensure;
use std::{
cmp::{max, min},
fmt,
fs::File,
io,
@ -28,14 +28,14 @@ use std::{
};
use winit::event::{VirtualKeyCode, VirtualKeyCode::*};
pub struct BigSelectableText {
pub struct BigTextArea {
pub caret_w_select: CaretWSelect,
text_rope: Rope,
pub path_str: String,
arena: Bump,
}
impl BigSelectableText {
impl BigTextArea {
fn check_bounds(&self, char_indx: usize) -> UIResult<()> {
ensure!(
char_indx <= self.text_rope.len_chars(),
@ -71,17 +71,13 @@ impl BigSelectableText {
}
}
fn validate_sel_opt(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> {
Ok(Some(validate_selection(start_pos, end_pos)?))
}
impl Lines for BigSelectableText {
impl Lines for BigTextArea {
fn get_line(&self, line_nr: usize) -> UIResult<&str> {
ensure!(
line_nr < self.nr_of_lines(),
OutOfBounds {
index: line_nr,
collection_name: "BigSelectableText",
collection_name: "BigTextArea",
len: self.nr_of_lines(),
}
);
@ -121,9 +117,13 @@ impl Lines for BigSelectableText {
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 {
self.caret_w_select.caret_pos
}
@ -133,347 +133,39 @@ impl SelectableLines for BigSelectableText {
}
fn move_caret_left(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection();
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);
self.caret_w_select = lines::move_caret_left(self, self.caret_w_select, modifiers)?;
Ok(())
}
fn move_caret_right(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection();
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);
self.caret_w_select = lines::move_caret_right(self, self.caret_w_select, modifiers)?;
Ok(())
}
fn move_caret_up(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection();
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);
self.caret_w_select = lines::move_caret_up(self, self.caret_w_select, modifiers)?;
Ok(())
}
fn move_caret_down(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let old_selection_opt = self.get_selection();
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);
self.caret_w_select = lines::move_caret_down(self, self.caret_w_select, modifiers)?;
Ok(())
}
fn move_caret_home(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let curr_line_nr = self.caret_w_select.caret_pos.line;
let old_col_nr = self.caret_w_select.caret_pos.column;
self.caret_w_select = lines::move_caret_home(self, self.caret_w_select, modifiers)?;
let curr_line_str = self.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
};
self.caret_w_select.move_caret_w_mods(
TextPos {
line: curr_line_nr,
column: new_col_nr,
},
modifiers,
)
Ok(())
}
fn move_caret_end(&mut self, modifiers: &Modifiers) -> UIResult<()> {
let curr_line_nr = self.caret_w_select.caret_pos.line;
let curr_line_len = self.line_len(curr_line_nr)?;
self.caret_w_select = lines::move_caret_end(self, self.caret_w_select, modifiers)?;
let new_col = if let Some(last_char) = self.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,
};
self.caret_w_select.move_caret_w_mods(new_pos, modifiers)
Ok(())
}
fn get_selection(&self) -> Option<Selection> {
@ -484,7 +176,7 @@ impl SelectableLines for BigSelectableText {
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 {
let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel);
@ -493,12 +185,9 @@ impl SelectableLines for BigSelectableText {
let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx);
if let Some(line_str_ref) = rope_slice.as_str() {
Ok(Some(line_str_ref))
Ok(Some(line_str_ref.to_string()))
} else {
// happens very rarely
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Ok(Some(arena_str_ref))
Ok(Some(rope_slice.chunks().collect::<String>()))
}
} else {
Ok(None)
@ -511,13 +200,17 @@ impl SelectableLines for BigSelectableText {
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) {
self.caret_w_select.selection_opt = None;
}
fn select_all(&mut self) -> UIResult<()> {
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 {
start_pos: TextPos { line: 0, column: 0 },
@ -530,12 +223,10 @@ impl SelectableLines for BigSelectableText {
Ok(())
}
fn last_text_pos(&self) -> TextPos {
fn last_text_pos(&self) -> UIResult<TextPos> {
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(
@ -563,7 +254,7 @@ impl SelectableLines for BigSelectableText {
}
}
impl MutSelectableLines for BigSelectableText {
impl MutSelectableLines for BigTextArea {
fn insert_char(&mut self, new_char: &char) -> UIResult<()> {
if self.is_selection_active() {
self.del_selection()?;
@ -658,7 +349,7 @@ impl MutSelectableLines for BigSelectableText {
}
}
impl Default for BigSelectableText {
impl Default for BigTextArea {
fn default() -> Self {
let caret_w_select = CaretWSelect::default();
let text_rope = Rope::from_str("");
@ -674,11 +365,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 path_str = path_to_string(path);
Ok(BigSelectableText {
Ok(BigTextArea {
text_rope,
path_str,
..Default::default()
@ -687,10 +378,10 @@ pub fn from_path(path: &Path) -> UIResult<BigSelectableText> {
#[allow(dead_code)]
// 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);
BigSelectableText {
BigTextArea {
text_rope,
..Default::default()
}
@ -723,9 +414,9 @@ fn rope_from_path(path: &Path) -> UIResult<Rope> {
}
// need to explicitly omit arena
impl fmt::Debug for BigSelectableText {
impl fmt::Debug for BigTextArea {
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("text_rope", &self.text_rope)
.field("path_str", &self.path_str)
@ -736,8 +427,8 @@ impl fmt::Debug for BigSelectableText {
#[cfg(test)]
pub mod test_big_sel_text {
use crate::ui::text::{
big_selectable_text::from_str,
big_selectable_text::BigSelectableText,
big_text_area::from_str,
big_text_area::BigTextArea,
caret_w_select::CaretWSelect,
lines::{Lines, MutSelectableLines, SelectableLines},
selection::validate_selection,
@ -843,7 +534,7 @@ pub mod test_big_sel_text {
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(
&lines
.iter()
@ -853,7 +544,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();
for i in 0..big_sel_text.nr_of_lines() {
@ -963,7 +654,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 mut big_text = big_text_from_dsl_str(&lines_string_slice);
let caret_w_select = convert_dsl_to_selection(&lines_string_slice).unwrap();
@ -1141,7 +832,7 @@ pub mod test_big_sel_text {
Ok(())
}
type MoveCaretFun = fn(&mut BigSelectableText, &Modifiers) -> UIResult<()>;
type MoveCaretFun = fn(&mut BigTextArea, &Modifiers) -> UIResult<()>;
// Convert nice string representations and compare results
fn assert_move(
@ -1720,7 +1411,7 @@ pub mod test_big_sel_text {
#[test]
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(&["a|"], &["|a"], &no_mods(), move_caret_home)?;
assert_move(&["|a"], &["|a"], &no_mods(), move_caret_home)?;
@ -1834,7 +1525,7 @@ pub mod test_big_sel_text {
#[test]
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(&["|a"], &["a|"], &no_mods(), move_caret_end)?;
assert_move(&["a|"], &["a|"], &no_mods(), move_caret_end)?;
@ -2467,7 +2158,7 @@ pub mod test_big_sel_text {
#[test]
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(&["|a"], &["|a"], &shift_pressed(), move_caret_home)?;
assert_move(&["a|"], &["|[a]"], &shift_pressed(), move_caret_home)?;
@ -2587,7 +2278,7 @@ pub mod test_big_sel_text {
#[test]
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(&["|a"], &["[a]|"], &shift_pressed(), move_caret_end)?;
assert_move(&["a|"], &["a|"], &shift_pressed(), move_caret_end)?;
@ -3253,7 +2944,7 @@ pub mod test_big_sel_text {
#[test]
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)?;
@ -3318,7 +3009,7 @@ pub mod test_big_sel_text {
#[test]
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)?;

View File

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

View File

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

View File

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

View File

@ -34,6 +34,11 @@ pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<Selection> {
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> {
ensure!(
start_pos.line <= end_pos.line,

View File

@ -1,5 +1,21 @@
use super::ui_error::{UIResult, OutOfBounds};
use snafu::OptionExt;
use std::slice::SliceIndex;
pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}', '\n'];
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)
}