mirror of
https://github.com/helix-editor/helix.git
synced 2025-01-19 13:37:06 +04:00
Finish hiding doc.state / State as an implementation detail.
This commit is contained in:
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 @@ pub fn skip_over_prev<F>(slice: RopeSlice, pos: &mut usize, fun: F)
|
||||
*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 @@ pub fn new(doc: Rope) -> Self {
|
||||
// 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 fn on_next_key(
|
||||
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…
Reference in New Issue
Block a user