diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index ce8a09a99..63f44566b 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -18,6 +18,7 @@ integration = [] [dependencies] helix-stdx = { path = "../helix-stdx" } helix-loader = { path = "../helix-loader" } +helix-parsec = { path = "../helix-parsec" } ropey = { version = "1.6.1", default-features = false, features = ["simd"] } smallvec = "1.13" diff --git a/helix-core/src/mark.rs b/helix-core/src/mark.rs new file mode 100644 index 000000000..85a4d8f9e --- /dev/null +++ b/helix-core/src/mark.rs @@ -0,0 +1,8 @@ +use std::num::NonZeroUsize; + +use crate::Selection; + +pub struct Mark { + doc_id: NonZeroUsize, + selection: Selection, +} diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index a382a7186..1e36288fb 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -11,6 +11,7 @@ use crate::{ movement::Direction, Assoc, ChangeSet, RopeGraphemes, RopeSlice, }; +use helix_parsec::{seq, take_until, Parser}; use helix_stdx::rope::{self, RopeSliceExt}; use smallvec::{smallvec, SmallVec}; use std::{borrow::Cow, iter, slice}; @@ -389,6 +390,32 @@ impl Range { pub fn into_byte_range(&self, text: RopeSlice) -> (usize, usize) { (text.char_to_byte(self.from()), text.char_to_byte(self.to())) } + + pub fn to_string(self) -> String { + format!("({},{})", self.anchor, self.head) + } +} + +impl TryFrom<&str> for Range { + type Error = String; + + fn try_from(value: &str) -> Result { + let parser = seq!( + "(", + take_until(|c| c == ','), + ",", + take_until(|c| c == ')'), + ")" + ); + match parser.parse(value) { + Ok((_tail, (_, anchor, _, head, _))) => Ok(Self { + anchor: anchor.parse::().map_err(|e| e.to_string())?, + head: head.parse::().map_err(|e| e.to_string())?, + old_visual_position: None, + }), + Err(e) => Err(e.to_string()), + } + } } impl From<(usize, usize)> for Range { @@ -888,6 +915,54 @@ mod test { use super::*; use crate::Rope; + #[test] + fn parse_range() -> Result<(), String> { + // sometimes we want Ok, someteimes we want Err, but we never want a panic + assert_eq!( + Range::try_from("(0,28)"), + Ok(Range { + anchor: 0, + head: 28, + old_visual_position: None + }) + ); + assert_eq!( + Range::try_from("(3456789,123456789)"), + Ok(Range { + anchor: 3456789, + head: 123456789, + old_visual_position: None + }) + ); + assert_eq!(Range::try_from("(,)"), Err("(,)".to_string())); + assert_eq!( + Range::try_from("(asdf,asdf)"), + Err("invalid digit found in string".to_string()) + ); + assert_eq!(Range::try_from("()"), Err("()".to_string())); + assert_eq!( + Range::try_from("(-4,ALSK)"), + Err("invalid digit found in string".to_string()) + ); + assert_eq!( + Range::try_from("(⦡⓼␀⍆ⴉ├⺶⍄⾨,⦡⓼␀⍆ⴉ├⺶⍄⾨)"), + Err("invalid digit found in string".to_string()) + ); + Ok(()) + } + + #[test] + fn display_range() { + assert_eq!( + Range { + anchor: 72, + head: 28, + old_visual_position: None, + } + .to_string(), + "(72,28)".to_string(), + ); + } #[test] #[should_panic] fn test_new_empty() { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6ee721486..8a70b4b49 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -17,7 +17,7 @@ pub use typed::*; use helix_core::{ char_idx_at_visual_offset, chars::char_is_word, - comment, + comment, coords_at_pos, doc_formatter::TextFormat, encoding, find_workspace, graphemes::{self, next_grapheme_boundary, RevRopeGraphemes}, @@ -567,14 +567,7 @@ impl MappableCommand { command_palette, "Open command palette", goto_word, "Jump to a two-character label", extend_to_word, "Extend to a two-character label", - add_mark_file, "Bookmark a file", - add_mark_line, "Bookmark a perticular line in a file", - remove_mark_file, "Remove current file Bookmark if it exists", - remove_mark_line, "Remove current line Bookmark if it exists", - goto_nth_mark, "Move cursor to the Nth bookmark", - goto_next_mark, "Move cursor to the next bookmark", - goto_prev_mark, "Move cursor to the prev bookmark", - mark_picker, "Open mark picker", + register_mark, "Register a bookmark", ); } @@ -6129,30 +6122,29 @@ fn goto_word(cx: &mut Context) { fn extend_to_word(cx: &mut Context) { jump_to_word(cx, Movement::Extend) } -fn add_mark_file(cx: &mut Context) { - todo!(); -} -fn add_mark_line(cx: &mut Context) { - todo!(); -} -fn remove_mark_file(cx: &mut Context) { - todo!(); -} -fn remove_mark_line(cx: &mut Context) { - todo!(); -} -fn goto_nth_mark(cx: &mut Context) { - todo!(); -} -fn goto_next_mark(cx: &mut Context) { - todo!(); -} -fn goto_prev_mark(cx: &mut Context) { - todo!(); -} -fn mark_picker(cx: &mut Context) { - todo!(); +fn register_mark(cx: &mut Context) { + let register_name = cx.register.unwrap_or('^').clone(); + let (view, doc) = current!(cx.editor); + let primary_selection = doc.selection(view.id).primary().clone(); + let range_start_pos = match primary_selection.direction() { + Direction::Forward => coords_at_pos(doc.text().slice(0..), primary_selection.anchor), + Direction::Backward => coords_at_pos(doc.text().slice(0..), primary_selection.head), + }; + cx.editor + .registers + .write( + register_name, + vec![format!("{}:{}", doc.id(), primary_selection.to_string())], + ) + .unwrap(); + + cx.editor.set_status(format!( + "Saved location line {}, row {} to [{}]", + range_start_pos.row + 1, + range_start_pos.col + 1, + register_name + )); } fn jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 5a3e8eed4..ee503826d 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -332,6 +332,9 @@ pub fn default() -> HashMap { "C-a" => increment, "C-x" => decrement, + // just for debugging I'll find something better later + "1" => register_mark, + }); let mut select = normal.clone(); select.merge_nodes(keymap!({ "Select mode" diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index c0d850fdc..a229f01ea 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -24,54 +24,8 @@ use std::{ }; const JUMP_LIST_CAPACITY: usize = 30; -const MARK_LIST_CAPACITY: usize = 30; type Jump = (DocumentId, Selection); -type Mark = (DocumentId, Selection); - -#[derive(Debug, Clone)] -pub struct MarkList { - marks: VecDeque, - current: usize, -} - -impl MarkList { - pub fn new(initial: Jump) -> Self { - todo!(); - } - - pub fn push(&mut self, jump: Jump) { - todo!() - } - - pub fn forward(&mut self, count: usize) -> Option<&Jump> { - todo!(); - } - - // Taking view and doc to prevent unnecessary cloning when jump is not required. - pub fn backward(&mut self, view_id: ViewId, doc: &mut Document, count: usize) -> Option<&Jump> { - todo!(); - } - - pub fn nth(&mut self, view_id: ViewId, doc: &mut Document, count: usize) -> Option<&Jump> { - todo!(); - } - - pub fn remove(&mut self, doc_id: &DocumentId) { - todo!(); - } - - pub fn iter(&self) -> impl DoubleEndedIterator { - self.marks.iter() - } - - /// Applies a [`Transaction`] of changes to the jumplist. - /// This is necessary to ensure that changes to documents do not leave jump-list - /// selections pointing to parts of the text which no longer exist. - fn apply(&mut self, transaction: &Transaction, doc: &Document) { - todo!(); - } -} #[derive(Debug, Clone)] pub struct JumpList { @@ -177,7 +131,6 @@ pub struct View { pub area: Rect, pub doc: DocumentId, pub jumps: JumpList, - pub marks: MarkList, // documents accessed from this view from the oldest one to last viewed one pub docs_access_history: Vec, /// the last modified files before the current one @@ -221,7 +174,6 @@ impl View { doc, area: Rect::default(), // will get calculated upon inserting into tree jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel - marks: MarkList::new((doc, Selection::point(0))), // TODO: use actual sel docs_access_history: Vec::new(), last_modified_docs: [None, None], object_selections: Vec::new(),