Finish hiding doc.state / State as an implementation detail.

pull/11/head
Blaž Hrastnik 4 years ago
parent 8ba1e15d29
commit c331721565

@ -1,6 +1,66 @@
use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Direction {
Forward,
Backward,
}
pub fn move_horizontally(
text: RopeSlice,
range: Range,
dir: Direction,
count: usize,
extend: bool,
) -> Range {
let pos = range.head;
let line = text.char_to_line(pos);
// TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way
// we stop calculating past start/end of line.
let pos = match dir {
Direction::Backward => {
let start = text.line_to_char(line);
nth_prev_grapheme_boundary(text, pos, count).max(start)
}
Direction::Forward => {
// Line end is pos at the start of next line - 1
// subtract another 1 because the line ends with \n
let end = text.line_to_char(line + 1).saturating_sub(2);
nth_next_grapheme_boundary(text, pos, count).min(end)
}
};
Range::new(if extend { range.anchor } else { pos }, pos)
}
pub fn move_vertically(
text: RopeSlice,
range: Range,
dir: Direction,
count: usize,
extend: bool,
) -> Range {
let Position { row, col } = coords_at_pos(text, range.head);
let horiz = range.horiz.unwrap_or(col as u32);
let new_line = match dir {
Direction::Backward => row.saturating_sub(count),
Direction::Forward => std::cmp::min(row.saturating_add(count), text.len_lines() - 1),
};
// convert to 0-indexed, subtract another 1 because len_chars() counts \n
let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
let new_col = std::cmp::min(horiz as usize, new_line_len);
let pos = pos_at_coords(text, Position::new(new_line, new_col));
let mut range = Range::new(if extend { range.anchor } else { pos }, pos);
range.horiz = Some(horiz);
range
}
pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
for _ in 0..count {
if pos + 1 == slice.len_chars() {
@ -155,3 +215,24 @@ where
*pos = pos.saturating_sub(1);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_vertical_move() {
let text = Rope::from("abcd\nefg\nwrs");
let slice = text.slice(..);
let pos = pos_at_coords(slice, (0, 4).into());
let range = Range::new(pos, pos);
assert_eq!(
coords_at_pos(
slice,
move_vertically(slice, range, Direction::Forward, 1, false).head
),
(1, 2).into()
);
}
}

@ -1,25 +1,12 @@
use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
use crate::{Rope, Selection};
/// A state represents the current editor state of a single buffer.
#[derive(Clone)]
pub struct State {
// TODO: fields should be private but we need to refactor commands.rs first
pub doc: Rope,
pub selection: Selection,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Direction {
Forward,
Backward,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Granularity {
Character,
Line,
}
impl State {
#[must_use]
pub fn new(doc: Rope) -> Self {
@ -51,102 +38,4 @@ impl State {
// syntax
// foldable
// changeFilter/transactionFilter
pub fn move_range(
&self,
range: Range,
dir: Direction,
granularity: Granularity,
count: usize,
extend: bool,
) -> Range {
let text = self.doc.slice(..);
let pos = range.head;
let line = text.char_to_line(pos);
// TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way
// we stop calculating past start/end of line.
let pos = match (dir, granularity) {
(Direction::Backward, Granularity::Character) => {
let start = text.line_to_char(line);
nth_prev_grapheme_boundary(text, pos, count).max(start)
}
(Direction::Forward, Granularity::Character) => {
// Line end is pos at the start of next line - 1
// subtract another 1 because the line ends with \n
let end = text.line_to_char(line + 1).saturating_sub(2);
nth_next_grapheme_boundary(text, pos, count).min(end)
}
(_, Granularity::Line) => return move_vertically(text, dir, range, count, extend),
};
Range::new(if extend { range.anchor } else { pos }, pos)
}
pub fn move_selection(
&self,
dir: Direction,
granularity: Granularity,
count: usize,
) -> Selection {
self.selection
.transform(|range| self.move_range(range, dir, granularity, count, false))
}
pub fn extend_selection(
&self,
dir: Direction,
granularity: Granularity,
count: usize,
) -> Selection {
self.selection
.transform(|range| self.move_range(range, dir, granularity, count, true))
}
}
fn move_vertically(
text: RopeSlice,
dir: Direction,
range: Range,
count: usize,
extend: bool,
) -> Range {
let Position { row, col } = coords_at_pos(text, range.head);
let horiz = range.horiz.unwrap_or(col as u32);
let new_line = match dir {
Direction::Backward => row.saturating_sub(count),
Direction::Forward => std::cmp::min(row.saturating_add(count), text.len_lines() - 1),
};
// convert to 0-indexed, subtract another 1 because len_chars() counts \n
let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
let new_col = std::cmp::min(horiz as usize, new_line_len);
let pos = pos_at_coords(text, Position::new(new_line, new_col));
let mut range = Range::new(if extend { range.anchor } else { pos }, pos);
range.horiz = Some(horiz);
range
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_vertical_move() {
let text = Rope::from("abcd\nefg\nwrs");
let slice = text.slice(..);
let pos = pos_at_coords(slice, (0, 4).into());
let range = Range::new(pos, pos);
assert_eq!(
coords_at_pos(
slice,
move_vertically(slice, Direction::Forward, range, 1, false).head
),
(1, 2).into()
);
}
}

@ -1,11 +1,11 @@
use helix_core::{
comment, coords_at_pos, graphemes,
indent::TAB_WIDTH,
movement, object, pos_at_coords,
movement::{self, Direction},
object, pos_at_coords,
regex::{self, Regex},
register, search, selection,
state::{Direction, Granularity},
Change, ChangeSet, Position, Range, Rope, RopeSlice, Selection, Tendril, Transaction,
register, search, selection, Change, ChangeSet, Position, Range, Rope, RopeSlice, Selection,
Tendril, Transaction,
};
use once_cell::sync::Lazy;
@ -72,36 +72,64 @@ pub type Command = fn(cx: &mut Context);
pub fn move_char_left(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.move_selection(Direction::Backward, Granularity::Character, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_horizontally(
text,
range,
Direction::Backward,
count,
false, /* extend */
)
});
doc.set_selection(selection);
}
pub fn move_char_right(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.move_selection(Direction::Forward, Granularity::Character, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_horizontally(
text,
range,
Direction::Forward,
count,
false, /* extend */
)
});
doc.set_selection(selection);
}
pub fn move_line_up(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.move_selection(Direction::Backward, Granularity::Line, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_vertically(
text,
range,
Direction::Backward,
count,
false, /* extend */
)
});
doc.set_selection(selection);
}
pub fn move_line_down(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.move_selection(Direction::Forward, Granularity::Line, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_vertically(
text,
range,
Direction::Forward,
count,
false, /* extend */
)
});
doc.set_selection(selection);
}
@ -409,36 +437,64 @@ pub fn half_page_down(cx: &mut Context) {
pub fn extend_char_left(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.extend_selection(Direction::Backward, Granularity::Character, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_horizontally(
text,
range,
Direction::Backward,
count,
true, /* extend */
)
});
doc.set_selection(selection);
}
pub fn extend_char_right(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.extend_selection(Direction::Forward, Granularity::Character, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_horizontally(
text,
range,
Direction::Forward,
count,
true, /* extend */
)
});
doc.set_selection(selection);
}
pub fn extend_line_up(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.extend_selection(Direction::Backward, Granularity::Line, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_vertically(
text,
range,
Direction::Backward,
count,
true, /* extend */
)
});
doc.set_selection(selection);
}
pub fn extend_line_down(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
let selection = doc
.state
.extend_selection(Direction::Forward, Granularity::Line, count);
let text = doc.text().slice(..);
let selection = doc.selection().transform(|range| {
movement::move_vertically(
text,
range,
Direction::Forward,
count,
true, /* extend */
)
});
doc.set_selection(selection);
}

@ -31,7 +31,7 @@ pub fn regex_prompt(
prompt: String,
fun: impl Fn(&mut Document, Regex) + 'static,
) -> Prompt {
let snapshot = cx.doc().state.clone();
let snapshot = cx.doc().selection().clone();
Prompt::new(
prompt,
@ -39,9 +39,10 @@ pub fn regex_prompt(
move |editor: &mut Editor, input: &str, event: PromptEvent| {
match event {
PromptEvent::Abort => {
// revert state
// TODO: also revert doc
// TODO: also revert text
let doc = &mut editor.view_mut().doc;
doc.state = snapshot.clone();
doc.set_selection(snapshot.clone());
}
PromptEvent::Validate => {
//
@ -58,7 +59,8 @@ pub fn regex_prompt(
let doc = &mut view.doc;
// revert state to what it was before the last update
doc.state = snapshot.clone();
// TODO: also revert text
doc.set_selection(snapshot.clone());
fun(doc, regex);

@ -17,7 +17,8 @@ pub enum Mode {
}
pub struct Document {
pub state: State, // rope + selection
// rope + selection
state: State,
path: Option<PathBuf>,
/// Current editing mode.

Loading…
Cancel
Save