From 1b6d61767da0cfc7c6e9e1422fd171d167bb90da Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sat, 8 Jun 2024 13:29:31 +0200 Subject: [PATCH 01/23] setup harpoon bookmarks code skeleton --- helix-term/src/commands.rs | 33 ++++++++++++++++++++++++++ helix-view/src/editor.rs | 2 ++ helix-view/src/gutter.rs | 12 ++++++++++ helix-view/src/view.rs | 48 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6e037a471ffc..6ee7214860dd 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -567,6 +567,14 @@ 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", ); } @@ -6121,6 +6129,31 @@ 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 jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { let doc = doc!(cx.editor); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1708b3b4e053..b17f1092da29 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -680,6 +680,8 @@ pub enum GutterType { Spacer, /// Highlight local changes Diff, + /// Bookmarks + Mark, } impl std::str::FromStr for GutterType { diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 36f719f795a8..709da856a41b 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -32,6 +32,7 @@ impl GutterType { GutterType::LineNumbers => line_numbers(editor, doc, view, theme, is_focused), GutterType::Spacer => padding(editor, doc, view, theme, is_focused), GutterType::Diff => diff(editor, doc, view, theme, is_focused), + GutterType::Mark => mark(editor, doc, view, theme, is_focused), } } @@ -41,6 +42,7 @@ impl GutterType { GutterType::LineNumbers => line_numbers_width(view, doc), GutterType::Spacer => 1, GutterType::Diff => 1, + GutterType::Mark => 1, } } } @@ -86,6 +88,16 @@ pub fn diagnostic<'doc>( ) } +pub fn mark<'doc>( + _editor: &'doc Editor, + doc: &'doc Document, + _view: &View, + theme: &Theme, + _is_focused: bool, +) -> GutterFn<'doc> { + todo!() +} + pub fn diff<'doc>( _editor: &'doc Editor, doc: &'doc Document, diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index a229f01ea66a..c0d850fdc83a 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -24,8 +24,54 @@ 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 { @@ -131,6 +177,7 @@ 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 @@ -174,6 +221,7 @@ 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(), From 148f1c7c689b0a857f61ee4d4f3349905177c320 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sat, 8 Jun 2024 19:14:26 +0200 Subject: [PATCH 02/23] add code to dump selection location into register --- helix-core/Cargo.toml | 1 + helix-core/src/mark.rs | 8 ++++ helix-core/src/selection.rs | 75 ++++++++++++++++++++++++++++++++ helix-term/src/commands.rs | 56 ++++++++++-------------- helix-term/src/keymap/default.rs | 3 ++ helix-view/src/view.rs | 48 -------------------- 6 files changed, 111 insertions(+), 80 deletions(-) create mode 100644 helix-core/src/mark.rs diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index ce8a09a99e3b..63f44566b200 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 000000000000..85a4d8f9e452 --- /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 a382a71861c1..1e36288fb4a0 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 6ee7214860dd..8a70b4b497f3 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 5a3e8eed42da..ee503826d53b 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 c0d850fdc83a..a229f01ea66a 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(), From bb7e922ff9854b717188df8f207a9b0db2e30dd5 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sat, 8 Jun 2024 22:06:51 +0200 Subject: [PATCH 03/23] add integration test for adding mark --- helix-term/tests/test/movement.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs index 77098a33615b..48b2ae1b9ae3 100644 --- a/helix-term/tests/test/movement.rs +++ b/helix-term/tests/test/movement.rs @@ -65,6 +65,31 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn register_mark() -> anyhow::Result<()> { + // add a mark and then immediately paste it out + test(( + indoc! {"\ + Lorem + ipsum + dolor#[| + sit]# + amet." + }, + "1\"^p", + indoc! {"\ + Lorem + ipsum + dolor + sit#[|1:(24,19)]# + amet." + }, + )) + .await?; + + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn surround_by_character() -> anyhow::Result<()> { // Only pairs matching the passed character count From b672490066bf10958fb458d8244ebb20baa4f2ec Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sat, 8 Jun 2024 22:44:46 +0200 Subject: [PATCH 04/23] make a start on jumping to bookmarks --- Cargo.lock | 1 + helix-term/src/commands.rs | 27 +++++++++++++++++++++++++++ helix-term/src/keymap/default.rs | 1 + 3 files changed, 29 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7e4ebbb8fca9..c33e0720e41d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,6 +1211,7 @@ dependencies = [ "globset", "hashbrown", "helix-loader", + "helix-parsec", "helix-stdx", "imara-diff", "indoc", diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8a70b4b497f3..7c215910fdd0 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -568,6 +568,7 @@ impl MappableCommand { goto_word, "Jump to a two-character label", extend_to_word, "Extend to a two-character label", register_mark, "Register a bookmark", + goto_mark, "Goto a bookmark", ); } @@ -6123,6 +6124,32 @@ fn extend_to_word(cx: &mut Context) { jump_to_word(cx, Movement::Extend) } +pub fn goto_mark(cx: &mut Context) { + let register_name = cx.register.unwrap_or('^').clone(); + let register_content = cx.editor.registers.read(register_name, cx.editor); + let res = match register_content { + Some(values) => values + .into_iter() + .next() + .map(|c| c.into_owned()) + .map(|s| { + let mut split_iter = s.split(":").into_iter(); + let doc_id = split_iter.next().unwrap(); + let range_tupel = split_iter.next().unwrap(); + log::debug!("doc id: {:?}", &doc_id); + log::debug!("range_tuple: {:?}", &range_tupel); + }) + .ok_or(format!( + "Register {} did not contain anything", + register_name + )), + None => Err(format!( + "Register {} did not contain anything", + register_name + )), + }; +} + fn register_mark(cx: &mut Context) { let register_name = cx.register.unwrap_or('^').clone(); let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index ee503826d53b..615731d085e6 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -334,6 +334,7 @@ pub fn default() -> HashMap { "C-x" => decrement, // just for debugging I'll find something better later "1" => register_mark, + "2" => goto_mark, }); let mut select = normal.clone(); From 719fb097fb3abba3444aeac428d9c83de7473a6d Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sat, 8 Jun 2024 23:02:17 +0200 Subject: [PATCH 05/23] wip --- helix-term/src/commands.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7c215910fdd0..8866ff55d1f9 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6148,6 +6148,19 @@ pub fn goto_mark(cx: &mut Context) { register_name )), }; + // let picker = Picker::new(items, (), |cx, meta, action| { + // cx.editor.switch(meta.id, action); + // }) + // .with_preview(|editor, meta| { + // let doc = &editor.documents.get(&meta.id)?; + // let &view_id = doc.selections().keys().next()?; + // let line = doc + // .selection(view_id) + // .primary() + // .cursor_line(doc.text().slice(..)); + // Some((meta.id.into(), Some((line, line)))) + // }); + // cx.push_layer(Box::new(overlaid(picker))); } fn register_mark(cx: &mut Context) { From 70addbc870e4d299f66d72a9ab30c48dd9179d8b Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 10:59:03 +0200 Subject: [PATCH 06/23] first working version of marks --- Cargo.lock | 1 + helix-term/Cargo.toml | 44 ++++++++++++++------- helix-term/src/commands.rs | 81 +++++++++++++++++++++++++++----------- helix-view/src/lib.rs | 9 ++++- 4 files changed, 96 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c33e0720e41d..7ec3332ef3a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1356,6 +1356,7 @@ dependencies = [ "helix-event", "helix-loader", "helix-lsp", + "helix-parsec", "helix-stdx", "helix-tui", "helix-vcs", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index c66aa06212eb..4d2bee8532e6 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -31,16 +31,32 @@ helix-lsp = { path = "../helix-lsp" } helix-dap = { path = "../helix-dap" } helix-vcs = { path = "../helix-vcs" } helix-loader = { path = "../helix-loader" } +helix-parsec = { path = "../helix-parsec" } anyhow = "1" once_cell = "1.19" -tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } -tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } -crossterm = { version = "0.28", features = ["event-stream"] } +tokio = { version = "1", features = [ + "rt", + "rt-multi-thread", + "io-util", + "io-std", + "time", + "process", + "macros", + "fs", + "parking_lot", +] } +tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = [ + "crossterm", +] } +crossterm = { version = "0.27", features = ["event-stream"] } signal-hook = "0.3" tokio-stream = "0.1" -futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } +futures-util = { version = "0.3", features = [ + "std", + "async-await", +], default-features = false } arc-swap = { version = "1.7.1" } termini = "1" @@ -53,14 +69,13 @@ log = "0.4" nucleo.workspace = true ignore = "0.4" # markdown doc rendering -pulldown-cmark = { version = "0.12", default-features = false } +pulldown-cmark = { version = "0.11", default-features = false } # file type detection content_inspector = "0.2.4" -thiserror = "1.0" # opening URLs -open = "5.3.0" -url = "2.5.2" +open = "5.1.3" +url = "2.5.0" # config toml = "0.8" @@ -69,15 +84,15 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } # ripgrep for global search -grep-regex = "0.1.13" -grep-searcher = "0.1.14" +grep-regex = "0.1.12" +grep-searcher = "0.1.13" -[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 +[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } -libc = "0.2.158" +libc = "0.2.155" [target.'cfg(target_os = "macos")'.dependencies] -crossterm = { version = "0.28", features = ["event-stream", "use-dev-tty", "libc"] } +crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] } [build-dependencies] helix-loader = { path = "../helix-loader" } @@ -85,5 +100,4 @@ helix-loader = { path = "../helix-loader" } [dev-dependencies] smallvec = "1.13" indoc = "2.0.5" -tempfile = "3.12.0" -same-file = "1.0.1" +tempfile = "3.10.1" diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8866ff55d1f9..c3d3cf2490f2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5,6 +5,7 @@ pub(crate) mod typed; pub use dap::*; use futures_util::FutureExt; use helix_event::status; +use helix_parsec::{seq, take_until, Parser}; use helix_stdx::{ path::expand_tilde, rope::{self, RopeSliceExt}, @@ -44,9 +45,10 @@ use helix_view::{ info::Info, input::KeyEvent, keyboard::KeyCode, + register::RegisterValues, theme::Style, tree, - view::View, + view::{self, View}, Document, DocumentId, Editor, ViewId, }; @@ -6124,30 +6126,63 @@ fn extend_to_word(cx: &mut Context) { jump_to_word(cx, Movement::Extend) } +fn read_from_register(editor: &mut Editor, reg: char) -> Option { + editor.registers.read(reg, editor) +} + pub fn goto_mark(cx: &mut Context) { let register_name = cx.register.unwrap_or('^').clone(); - let register_content = cx.editor.registers.read(register_name, cx.editor); - let res = match register_content { - Some(values) => values - .into_iter() - .next() - .map(|c| c.into_owned()) - .map(|s| { - let mut split_iter = s.split(":").into_iter(); - let doc_id = split_iter.next().unwrap(); - let range_tupel = split_iter.next().unwrap(); - log::debug!("doc id: {:?}", &doc_id); - log::debug!("range_tuple: {:?}", &range_tupel); - }) - .ok_or(format!( - "Register {} did not contain anything", - register_name - )), - None => Err(format!( - "Register {} did not contain anything", - register_name - )), - }; + let registers_vals = read_from_register(&mut cx.editor, register_name); + let blurb = registers_vals + .unwrap() + .into_iter() + .next() + .map(|c| c.into_owned()); + // let register_content = editor.registers.read(register_name, editor); + // let reg_str = match values { + // Some(values) => + // .ok_or(format!( + // "Register {} did not contain anything", + // register_name + // )), + // None => Err(format!( + // "Register {} did not contain anything", + // register_name + // )), + // }; + match blurb { + Some(s) => { + let parser = seq!( + take_until(|c| c == ':'), + ":(", + take_until(|c| c == ','), + ",", + take_until(|c| c == ')'), + ")" + ); + let (_tail, (doc_id_str, _, anchor_str, _, head_str, _)) = parser.parse(&s).unwrap(); + let anchor = anchor_str.parse().unwrap(); + let head = head_str.parse().unwrap(); + let doc_id: DocumentId = doc_id_str.try_into().unwrap(); + let range = Range { + anchor, + head, + old_visual_position: None, + }; + cx.editor.switch(doc_id, Action::Replace); + let (view, doc) = current!(cx.editor); + let (min_idx, max_idx) = if anchor < head { + (anchor, head) + } else { + (head, anchor) + }; + let new_range = range.put_cursor(doc.text().slice(..), min_idx, false); + let new_range = new_range.put_cursor(doc.text().slice(..), max_idx, true); + doc.set_selection(view.id, new_range.into()); + } + None => (), + // Err(e) => log::error!("{}", e), + } // let picker = Picker::new(items, (), |cx, meta, action| { // cx.editor.switch(meta.id, action); // }) diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index d54b49ef5400..a18da2517d8e 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -18,7 +18,7 @@ pub mod theme; pub mod tree; pub mod view; -use std::num::NonZeroUsize; +use std::num::{NonZeroUsize, ParseIntError}; // uses NonZeroUsize so Option use a byte rather than two #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -31,6 +31,13 @@ impl Default for DocumentId { } } +impl TryFrom<&str> for DocumentId { + type Error = ParseIntError; + + fn try_from(value: &str) -> Result { + Ok(Self(value.parse::()?)) + } +} impl std::fmt::Display for DocumentId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}", self.0)) From cfbc46cfa21f3a1a43152608eaee59d6ac284f29 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 12:43:09 +0200 Subject: [PATCH 07/23] make marks take into account all selection ranges --- helix-term/src/commands.rs | 104 +++++++++++++++---------------------- 1 file changed, 42 insertions(+), 62 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c3d3cf2490f2..59162dc41f64 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5,7 +5,7 @@ pub(crate) mod typed; pub use dap::*; use futures_util::FutureExt; use helix_event::status; -use helix_parsec::{seq, take_until, Parser}; +use helix_parsec::{sep, seq, take_until, Parser}; use helix_stdx::{ path::expand_tilde, rope::{self, RopeSliceExt}, @@ -48,7 +48,7 @@ use helix_view::{ register::RegisterValues, theme::Style, tree, - view::{self, View}, + view::View, Document, DocumentId, Editor, ViewId, }; @@ -73,6 +73,7 @@ use std::{ future::Future, io::Read, num::NonZeroUsize, + str::FromStr, }; use std::{ @@ -6126,100 +6127,79 @@ fn extend_to_word(cx: &mut Context) { jump_to_word(cx, Movement::Extend) } -fn read_from_register(editor: &mut Editor, reg: char) -> Option { - editor.registers.read(reg, editor) +fn read_from_register(editor: &Editor, reg: char) -> Option { + editor.registers.read(reg, &editor) } pub fn goto_mark(cx: &mut Context) { let register_name = cx.register.unwrap_or('^').clone(); - let registers_vals = read_from_register(&mut cx.editor, register_name); + let registers_vals = read_from_register(cx.editor, register_name); let blurb = registers_vals .unwrap() .into_iter() .next() .map(|c| c.into_owned()); - // let register_content = editor.registers.read(register_name, editor); - // let reg_str = match values { - // Some(values) => - // .ok_or(format!( - // "Register {} did not contain anything", - // register_name - // )), - // None => Err(format!( - // "Register {} did not contain anything", - // register_name - // )), - // }; match blurb { Some(s) => { - let parser = seq!( - take_until(|c| c == ':'), - ":(", + let doc_id_parser = seq!(take_until(|c| c == ':'), ":"); + let range_parser = seq!( + "(", take_until(|c| c == ','), ",", take_until(|c| c == ')'), ")" ); - let (_tail, (doc_id_str, _, anchor_str, _, head_str, _)) = parser.parse(&s).unwrap(); - let anchor = anchor_str.parse().unwrap(); - let head = head_str.parse().unwrap(); + let multiple_ranges_parser = sep(range_parser, ","); + let (tail, (doc_id_str, _)) = doc_id_parser.parse(&s).unwrap(); + let (tail, v) = multiple_ranges_parser.parse(tail).unwrap(); + let mut ranges = v + .iter() + .map(|tup| { + let (_, anchor_str, _, head_str, _) = tup; + let anchor: usize = ::from_str(anchor_str).unwrap(); + let head: usize = ::from_str(head_str).unwrap(); + Range { + anchor, + head, + old_visual_position: None, + } + }) + .rev(); + // reverse the iterators so the first range will end up as the primary when we push them + let doc_id: DocumentId = doc_id_str.try_into().unwrap(); - let range = Range { - anchor, - head, - old_visual_position: None, - }; cx.editor.switch(doc_id, Action::Replace); let (view, doc) = current!(cx.editor); - let (min_idx, max_idx) = if anchor < head { - (anchor, head) - } else { - (head, anchor) - }; - let new_range = range.put_cursor(doc.text().slice(..), min_idx, false); - let new_range = new_range.put_cursor(doc.text().slice(..), max_idx, true); - doc.set_selection(view.id, new_range.into()); + let last_range = ranges.next().unwrap(); // there is always at least one range + let mut selection = Selection::from(last_range); + for r in ranges { + selection = selection.push(r); + } + doc.set_selection(view.id, selection); } None => (), - // Err(e) => log::error!("{}", e), } - // let picker = Picker::new(items, (), |cx, meta, action| { - // cx.editor.switch(meta.id, action); - // }) - // .with_preview(|editor, meta| { - // let doc = &editor.documents.get(&meta.id)?; - // let &view_id = doc.selections().keys().next()?; - // let line = doc - // .selection(view_id) - // .primary() - // .cursor_line(doc.text().slice(..)); - // Some((meta.id.into(), Some((line, line)))) - // }); - // cx.push_layer(Box::new(overlaid(picker))); } 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), - }; + let ranges = doc.selection(view.id).ranges(); + let ranges_str = ranges + .iter() + .map(|r| r.to_string()) + .collect::>() + .join(","); cx.editor .registers .write( register_name, - vec![format!("{}:{}", doc.id(), primary_selection.to_string())], + vec![format!("{}:{}", doc.id(), ranges_str.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 - )); + cx.editor + .set_status(format!("Saved selection bookmark to [{}]", register_name)); } fn jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { From 89b63a23cbc06f6052a88e7fcbfab2606ab790e2 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 13:53:32 +0200 Subject: [PATCH 08/23] make mark commands typeable --- helix-term/src/commands.rs | 73 -------------------- helix-term/src/commands/typed.rs | 114 +++++++++++++++++++++++++++++++ helix-term/src/keymap/default.rs | 2 - 3 files changed, 114 insertions(+), 75 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 59162dc41f64..788037c341c8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -570,8 +570,6 @@ impl MappableCommand { command_palette, "Open command palette", goto_word, "Jump to a two-character label", extend_to_word, "Extend to a two-character label", - register_mark, "Register a bookmark", - goto_mark, "Goto a bookmark", ); } @@ -6131,77 +6129,6 @@ fn read_from_register(editor: &Editor, reg: char) -> Option { editor.registers.read(reg, &editor) } -pub fn goto_mark(cx: &mut Context) { - let register_name = cx.register.unwrap_or('^').clone(); - let registers_vals = read_from_register(cx.editor, register_name); - let blurb = registers_vals - .unwrap() - .into_iter() - .next() - .map(|c| c.into_owned()); - match blurb { - Some(s) => { - let doc_id_parser = seq!(take_until(|c| c == ':'), ":"); - let range_parser = seq!( - "(", - take_until(|c| c == ','), - ",", - take_until(|c| c == ')'), - ")" - ); - let multiple_ranges_parser = sep(range_parser, ","); - let (tail, (doc_id_str, _)) = doc_id_parser.parse(&s).unwrap(); - let (tail, v) = multiple_ranges_parser.parse(tail).unwrap(); - let mut ranges = v - .iter() - .map(|tup| { - let (_, anchor_str, _, head_str, _) = tup; - let anchor: usize = ::from_str(anchor_str).unwrap(); - let head: usize = ::from_str(head_str).unwrap(); - Range { - anchor, - head, - old_visual_position: None, - } - }) - .rev(); - // reverse the iterators so the first range will end up as the primary when we push them - - let doc_id: DocumentId = doc_id_str.try_into().unwrap(); - cx.editor.switch(doc_id, Action::Replace); - let (view, doc) = current!(cx.editor); - let last_range = ranges.next().unwrap(); // there is always at least one range - let mut selection = Selection::from(last_range); - for r in ranges { - selection = selection.push(r); - } - doc.set_selection(view.id, selection); - } - None => (), - } -} - -fn register_mark(cx: &mut Context) { - let register_name = cx.register.unwrap_or('^').clone(); - let (view, doc) = current!(cx.editor); - let ranges = doc.selection(view.id).ranges(); - let ranges_str = ranges - .iter() - .map(|r| r.to_string()) - .collect::>() - .join(","); - cx.editor - .registers - .write( - register_name, - vec![format!("{}:{}", doc.id(), ranges_str.to_string())], - ) - .unwrap(); - - cx.editor - .set_status(format!("Saved selection bookmark to [{}]", register_name)); -} - fn jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { let doc = doc!(cx.editor); let alphabet = &cx.editor.config().jump_label_alphabet; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 7ad0369fc1bd..33373295364d 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::fmt::Write; use std::io::BufReader; use std::ops::Deref; @@ -446,6 +447,105 @@ fn new_file( Ok(()) } +fn register_mark( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + }; + let register_name: char = args + .first() + .map_or_else( + || cx.editor.selected_register, + |s| s.as_ref().chars().next(), + ) + .unwrap_or('^'); + let (view, doc) = current!(cx.editor); + let ranges = doc.selection(view.id).ranges(); + let ranges_str = ranges + .iter() + .map(|r| r.to_string()) + .collect::>() + .join(","); + cx.editor + .registers + .write( + register_name, + vec![format!("{}:{}", doc.id(), ranges_str.to_string())], + ) + .unwrap(); + + cx.editor + .set_status(format!("Saved selection bookmark to [{}]", register_name)); + Ok(()) +} + +fn goto_mark( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + }; + let register_name: char = args + .first() + .map_or_else( + || cx.editor.selected_register, + |s| s.as_ref().chars().next(), + ) + .unwrap_or('^'); + let registers_vals = read_from_register(cx.editor, register_name); + let blurb = registers_vals + .unwrap() + .into_iter() + .next() + .map(|c| c.into_owned()); + match blurb { + Some(s) => { + let doc_id_parser = seq!(take_until(|c| c == ':'), ":"); + let range_parser = seq!( + "(", + take_until(|c| c == ','), + ",", + take_until(|c| c == ')'), + ")" + ); + let multiple_ranges_parser = sep(range_parser, ","); + let (tail, (doc_id_str, _)) = doc_id_parser.parse(&s).unwrap(); + let (_tail, v) = multiple_ranges_parser.parse(tail).unwrap(); + let mut ranges = v + .iter() + .map(|tup| { + let (_, anchor_str, _, head_str, _) = tup; + let anchor: usize = ::from_str(anchor_str).unwrap(); + let head: usize = ::from_str(head_str).unwrap(); + Range { + anchor, + head, + old_visual_position: None, + } + }) + .rev(); + // reverse the iterators so the first range will end up as the primary when we push them + + let doc_id: DocumentId = doc_id_str.try_into().unwrap(); + cx.editor.switch(doc_id, Action::Replace); + let (view, doc) = current!(cx.editor); + let last_range = ranges.next().unwrap(); // there is always at least one range + let mut selection = Selection::from(last_range); + for r in ranges { + selection = selection.push(r); + } + doc.set_selection(view.id, selection); + } + None => (), + }; + Ok(()) +} + fn format( cx: &mut compositor::Context, _args: &[Cow], @@ -2633,6 +2733,20 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: new_file, signature: CommandSignature::none(), }, + TypableCommand { + name: "goto_mark", + aliases: &[], + doc: "Go to the selection saved in a register. Register can be provided as argument or selected register else ^ will be used", + fun: goto_mark, + signature: CommandSignature::positional(&[completers::register]), + }, + TypableCommand { + name: "register_mark", + aliases: &[], + doc: "Save current selection into a register. Register can be provided as argument or selected register else ^ will be used", + fun: register_mark, + signature: CommandSignature::positional(&[completers::register]), + }, TypableCommand { name: "format", aliases: &["fmt"], diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 615731d085e6..b143752249e2 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -333,8 +333,6 @@ pub fn default() -> HashMap { "C-a" => increment, "C-x" => decrement, // just for debugging I'll find something better later - "1" => register_mark, - "2" => goto_mark, }); let mut select = normal.clone(); From 2da81b254b88a27de33b378e6268b74cd5ff4116 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 13:57:39 +0200 Subject: [PATCH 09/23] remove stray comment --- helix-term/src/keymap/default.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index b143752249e2..b9656a64dce6 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -332,7 +332,6 @@ pub fn default() -> HashMap { "C-a" => increment, "C-x" => decrement, - // just for debugging I'll find something better later }); let mut select = normal.clone(); From d1e2bff16b603c49a0216fd3630444301389b2dc Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 16:46:36 +0200 Subject: [PATCH 10/23] implement selection update mechanism --- helix-term/src/commands.rs | 4 +- helix-term/src/commands/typed.rs | 121 ++++++++++++++++++------------- helix-view/src/lib.rs | 8 ++ 3 files changed, 82 insertions(+), 51 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 788037c341c8..ba9403802ad3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6125,8 +6125,8 @@ fn extend_to_word(cx: &mut Context) { jump_to_word(cx, Movement::Extend) } -fn read_from_register(editor: &Editor, reg: char) -> Option { - editor.registers.read(reg, &editor) +fn read_from_register(editor: &mut Editor, reg: char) -> Option { + editor.registers.read(reg, &*editor) } fn jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 33373295364d..667d6979ed12 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -467,14 +467,21 @@ fn register_mark( let ranges_str = ranges .iter() .map(|r| r.to_string()) - .collect::>() - .join(","); + .collect::>(); + + // we have to take because of cell + let history = doc.history.take(); + let current_history_point = history.current_revision(); + doc.history.replace(history); + let mut register_val = vec![ + format!("{}", doc.id()), + format!("{}", current_history_point), + ]; + register_val.extend(ranges_str); + cx.editor .registers - .write( - register_name, - vec![format!("{}:{}", doc.id(), ranges_str.to_string())], - ) + .write(register_name, register_val) .unwrap(); cx.editor @@ -482,6 +489,54 @@ fn register_mark( Ok(()) } +fn parse_mark_register_contents( + registers_vals: Option, +) -> Result<(DocumentId, usize, Selection), String> { + match registers_vals { + Some(rv) => { + let mut rv_iter = rv.into_iter(); + let doc_id_str = rv_iter.next().unwrap().into_owned(); + let doc_id: DocumentId = doc_id_str.try_into().unwrap(); + let history_rev_str = rv_iter.next().unwrap().into_owned(); + let history_rev: usize = history_rev_str.parse().unwrap(); + let mut ranges = rv_iter + .map(|tup| { + let s = tup.into_owned(); + let range_parser = seq!( + "(", + take_until(|c| c == ','), + ",", + take_until(|c| c == ')'), + ")" + ); + let (_, (_, anchor_str, _, head_str, _)) = range_parser.parse(&s).unwrap(); + let anchor: usize = ::from_str(anchor_str).unwrap().clone(); + let head: usize = ::from_str(head_str).unwrap(); + Range { + anchor, + head, + old_visual_position: None, + } + }) + // reverse the iterators so the first range will end up as the primary when we push them + .rev(); + let last_range = ranges.next().unwrap(); // there is always at least one range + let mut selection = Selection::from(last_range); + for r in ranges { + selection = selection.push(r); + } + Ok((doc_id, history_rev, selection)) + } + None => Err("Could not parse registry content".to_string()), // I can't figure out how to set status line here because of borrow issues :( + } +} + +fn get_revisions_to_apply(doc: &mut Document, history_rev: usize) -> Option { + let history = doc.history.take(); + let revisions_to_apply = history.changes_since(history_rev); + doc.history.replace(history); + revisions_to_apply +} fn goto_mark( cx: &mut compositor::Context, args: &[Cow], @@ -498,51 +553,19 @@ fn goto_mark( ) .unwrap_or('^'); let registers_vals = read_from_register(cx.editor, register_name); - let blurb = registers_vals - .unwrap() - .into_iter() - .next() - .map(|c| c.into_owned()); - match blurb { - Some(s) => { - let doc_id_parser = seq!(take_until(|c| c == ':'), ":"); - let range_parser = seq!( - "(", - take_until(|c| c == ','), - ",", - take_until(|c| c == ')'), - ")" - ); - let multiple_ranges_parser = sep(range_parser, ","); - let (tail, (doc_id_str, _)) = doc_id_parser.parse(&s).unwrap(); - let (_tail, v) = multiple_ranges_parser.parse(tail).unwrap(); - let mut ranges = v - .iter() - .map(|tup| { - let (_, anchor_str, _, head_str, _) = tup; - let anchor: usize = ::from_str(anchor_str).unwrap(); - let head: usize = ::from_str(head_str).unwrap(); - Range { - anchor, - head, - old_visual_position: None, - } - }) - .rev(); - // reverse the iterators so the first range will end up as the primary when we push them + let (doc_id, history_rev, mut selection) = + parse_mark_register_contents(registers_vals).unwrap(); + cx.editor.switch(doc_id, Action::Replace); - let doc_id: DocumentId = doc_id_str.try_into().unwrap(); - cx.editor.switch(doc_id, Action::Replace); - let (view, doc) = current!(cx.editor); - let last_range = ranges.next().unwrap(); // there is always at least one range - let mut selection = Selection::from(last_range); - for r in ranges { - selection = selection.push(r); - } - doc.set_selection(view.id, selection); - } - None => (), + let (view, doc) = current!(cx.editor); + let revisions_to_apply = get_revisions_to_apply(doc, history_rev); + + selection = match revisions_to_apply { + Some(t) => selection.map(t.changes()), + None => selection, }; + + doc.set_selection(view.id, selection); Ok(()) } diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index a18da2517d8e..cf5d000e35f6 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -38,6 +38,14 @@ impl TryFrom<&str> for DocumentId { Ok(Self(value.parse::()?)) } } + +impl TryFrom for DocumentId { + type Error = ParseIntError; + + fn try_from(value: String) -> Result { + Ok(Self(value.parse::()?)) + } +} impl std::fmt::Display for DocumentId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}", self.0)) From fb73cc5a4080a5d2c4f5c65c7088dc2c0a202615 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 19:36:33 +0200 Subject: [PATCH 11/23] remove unused imports, unused guttercode, and fix a few clippy warnings --- helix-core/src/selection.rs | 8 +++++--- helix-term/src/commands.rs | 4 ++-- helix-term/src/commands/typed.rs | 3 +-- helix-view/src/editor.rs | 2 -- helix-view/src/gutter.rs | 12 ------------ 5 files changed, 8 insertions(+), 21 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 1e36288fb4a0..b5c3cd20851a 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -14,7 +14,7 @@ use crate::{ use helix_parsec::{seq, take_until, Parser}; use helix_stdx::rope::{self, RopeSliceExt}; use smallvec::{smallvec, SmallVec}; -use std::{borrow::Cow, iter, slice}; +use std::{borrow::Cow, fmt::Display, iter, slice}; use tree_sitter::Node; /// A single selection range. @@ -390,9 +390,11 @@ 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 Display for Range { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({},{})", self.anchor, self.head) } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ba9403802ad3..aa21e4d68d7c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5,7 +5,7 @@ pub(crate) mod typed; pub use dap::*; use futures_util::FutureExt; use helix_event::status; -use helix_parsec::{sep, seq, take_until, Parser}; +use helix_parsec::{seq, take_until, Parser}; use helix_stdx::{ path::expand_tilde, rope::{self, RopeSliceExt}, @@ -18,7 +18,7 @@ pub use typed::*; use helix_core::{ char_idx_at_visual_offset, chars::char_is_word, - comment, coords_at_pos, + comment, doc_formatter::TextFormat, encoding, find_workspace, graphemes::{self, next_grapheme_boundary, RevRopeGraphemes}, diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 667d6979ed12..1df89749e189 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,4 +1,3 @@ -use std::borrow::Borrow; use std::fmt::Write; use std::io::BufReader; use std::ops::Deref; @@ -510,7 +509,7 @@ fn parse_mark_register_contents( ")" ); let (_, (_, anchor_str, _, head_str, _)) = range_parser.parse(&s).unwrap(); - let anchor: usize = ::from_str(anchor_str).unwrap().clone(); + let anchor: usize = ::from_str(anchor_str).unwrap(); let head: usize = ::from_str(head_str).unwrap(); Range { anchor, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index b17f1092da29..1708b3b4e053 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -680,8 +680,6 @@ pub enum GutterType { Spacer, /// Highlight local changes Diff, - /// Bookmarks - Mark, } impl std::str::FromStr for GutterType { diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 709da856a41b..36f719f795a8 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -32,7 +32,6 @@ impl GutterType { GutterType::LineNumbers => line_numbers(editor, doc, view, theme, is_focused), GutterType::Spacer => padding(editor, doc, view, theme, is_focused), GutterType::Diff => diff(editor, doc, view, theme, is_focused), - GutterType::Mark => mark(editor, doc, view, theme, is_focused), } } @@ -42,7 +41,6 @@ impl GutterType { GutterType::LineNumbers => line_numbers_width(view, doc), GutterType::Spacer => 1, GutterType::Diff => 1, - GutterType::Mark => 1, } } } @@ -88,16 +86,6 @@ pub fn diagnostic<'doc>( ) } -pub fn mark<'doc>( - _editor: &'doc Editor, - doc: &'doc Document, - _view: &View, - theme: &Theme, - _is_focused: bool, -) -> GutterFn<'doc> { - todo!() -} - pub fn diff<'doc>( _editor: &'doc Editor, doc: &'doc Document, From dba438832c52616fe2cec88188c3b78c309d4142 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 19:54:08 +0200 Subject: [PATCH 12/23] add some comments to register_mark --- helix-term/src/commands/typed.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 1df89749e189..34980505f924 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -461,9 +461,12 @@ fn register_mark( |s| s.as_ref().chars().next(), ) .unwrap_or('^'); + let (view, doc) = current!(cx.editor); - let ranges = doc.selection(view.id).ranges(); - let ranges_str = ranges + + let ranges_str = doc + .selection(view.id) + .ranges() .iter() .map(|r| r.to_string()) .collect::>(); @@ -472,6 +475,11 @@ fn register_mark( let history = doc.history.take(); let current_history_point = history.current_revision(); doc.history.replace(history); + + // doc_id so we know which doc to switch to + // current_history_point so we can apply changes + // to our selection when we restore it. + // the rest of the elements are just the stringified ranges let mut register_val = vec![ format!("{}", doc.id()), format!("{}", current_history_point), @@ -530,12 +538,6 @@ fn parse_mark_register_contents( } } -fn get_revisions_to_apply(doc: &mut Document, history_rev: usize) -> Option { - let history = doc.history.take(); - let revisions_to_apply = history.changes_since(history_rev); - doc.history.replace(history); - revisions_to_apply -} fn goto_mark( cx: &mut compositor::Context, args: &[Cow], @@ -557,7 +559,9 @@ fn goto_mark( cx.editor.switch(doc_id, Action::Replace); let (view, doc) = current!(cx.editor); - let revisions_to_apply = get_revisions_to_apply(doc, history_rev); + let history = doc.history.take(); + let revisions_to_apply = history.changes_since(history_rev); + doc.history.replace(history); selection = match revisions_to_apply { Some(t) => selection.map(t.changes()), From a9a72174c1b24a6e645109266b9292bfce458ebc Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 20:42:17 +0200 Subject: [PATCH 13/23] better error handling in parse_mark_register_contents --- helix-term/src/commands/typed.rs | 59 ++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 34980505f924..5f70d49e900a 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,5 +1,6 @@ use std::fmt::Write; use std::io::BufReader; +use std::num::ParseIntError; use std::ops::Deref; use crate::job::Job; @@ -502,11 +503,23 @@ fn parse_mark_register_contents( match registers_vals { Some(rv) => { let mut rv_iter = rv.into_iter(); - let doc_id_str = rv_iter.next().unwrap().into_owned(); - let doc_id: DocumentId = doc_id_str.try_into().unwrap(); - let history_rev_str = rv_iter.next().unwrap().into_owned(); - let history_rev: usize = history_rev_str.parse().unwrap(); - let mut ranges = rv_iter + + let Some(doc_id) = rv_iter + .next() + .and_then(|c| Some(c.into_owned())) + .and_then(|s| s.try_into().ok()) + else { + return Err("Register did not contain valid document id".to_string()); + }; + let Some(history_rev) = rv_iter + .next() + .and_then(|c| Some(c.into_owned())) + .and_then(|s| s.parse().ok()) + else { + return Err("Register did not contain valid revision number".to_string()); + }; + + let Ok(ranges) = rv_iter .map(|tup| { let s = tup.into_owned(); let range_parser = seq!( @@ -516,25 +529,43 @@ fn parse_mark_register_contents( take_until(|c| c == ')'), ")" ); - let (_, (_, anchor_str, _, head_str, _)) = range_parser.parse(&s).unwrap(); - let anchor: usize = ::from_str(anchor_str).unwrap(); - let head: usize = ::from_str(head_str).unwrap(); - Range { + let Ok((_tail, (_lparen, anchor_str, _comma, head_str, _rparen))) = + range_parser.parse(&s) + else { + return Err(format!("Could not parse range from string: {}", s)); + }; + + let Ok(anchor) = ::from_str(anchor_str) else { + return Err(format!("Could not parse range from string: {}", s)); + }; + let Ok(head) = ::from_str(head_str) else { + return Err(format!("Could not parse range from string: {}", s)); + }; + + Ok(Range { anchor, head, old_visual_position: None, - } + }) }) // reverse the iterators so the first range will end up as the primary when we push them - .rev(); - let last_range = ranges.next().unwrap(); // there is always at least one range + .rev() + .collect::, String>>() + else { + return Err("Some ranges in the register failed to parse!".to_string()); + }; + + let mut ranges_iter = ranges.into_iter(); + + let last_range = ranges_iter.next().unwrap(); // safe since there is always at least one range let mut selection = Selection::from(last_range); - for r in ranges { + for r in ranges_iter { selection = selection.push(r); } + Ok((doc_id, history_rev, selection)) } - None => Err("Could not parse registry content".to_string()), // I can't figure out how to set status line here because of borrow issues :( + None => Err("Register was empty".to_string()), } } From 9cd6a26c5e656dca1e0975de7c2721c7dad51308 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 20:49:02 +0200 Subject: [PATCH 14/23] make more use of anyhow --- helix-term/src/commands/typed.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 5f70d49e900a..355830a5f9bb 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,6 +1,5 @@ use std::fmt::Write; use std::io::BufReader; -use std::num::ParseIntError; use std::ops::Deref; use crate::job::Job; @@ -487,10 +486,7 @@ fn register_mark( ]; register_val.extend(ranges_str); - cx.editor - .registers - .write(register_name, register_val) - .unwrap(); + cx.editor.registers.write(register_name, register_val)?; cx.editor .set_status(format!("Saved selection bookmark to [{}]", register_name)); @@ -499,7 +495,7 @@ fn register_mark( fn parse_mark_register_contents( registers_vals: Option, -) -> Result<(DocumentId, usize, Selection), String> { +) -> anyhow::Result<(DocumentId, usize, Selection)> { match registers_vals { Some(rv) => { let mut rv_iter = rv.into_iter(); @@ -509,14 +505,14 @@ fn parse_mark_register_contents( .and_then(|c| Some(c.into_owned())) .and_then(|s| s.try_into().ok()) else { - return Err("Register did not contain valid document id".to_string()); + return Err(anyhow!("Register did not contain valid document id")); }; let Some(history_rev) = rv_iter .next() .and_then(|c| Some(c.into_owned())) .and_then(|s| s.parse().ok()) else { - return Err("Register did not contain valid revision number".to_string()); + return Err(anyhow!("Register did not contain valid revision number")); }; let Ok(ranges) = rv_iter @@ -552,7 +548,7 @@ fn parse_mark_register_contents( .rev() .collect::, String>>() else { - return Err("Some ranges in the register failed to parse!".to_string()); + return Err(anyhow!("Some ranges in the register failed to parse!")); }; let mut ranges_iter = ranges.into_iter(); @@ -565,7 +561,7 @@ fn parse_mark_register_contents( Ok((doc_id, history_rev, selection)) } - None => Err("Register was empty".to_string()), + None => Err(anyhow!("Register was empty")), } } @@ -584,9 +580,11 @@ fn goto_mark( |s| s.as_ref().chars().next(), ) .unwrap_or('^'); + + // use some helper functions to avoid making the borrow checker angry let registers_vals = read_from_register(cx.editor, register_name); - let (doc_id, history_rev, mut selection) = - parse_mark_register_contents(registers_vals).unwrap(); + let (doc_id, history_rev, mut selection) = parse_mark_register_contents(registers_vals)?; + cx.editor.switch(doc_id, Action::Replace); let (view, doc) = current!(cx.editor); From 9f0fd621272596aba4bf705dfecaf7758e508cf9 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 20:54:29 +0200 Subject: [PATCH 15/23] remove stray file --- helix-core/src/mark.rs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 helix-core/src/mark.rs diff --git a/helix-core/src/mark.rs b/helix-core/src/mark.rs deleted file mode 100644 index 85a4d8f9e452..000000000000 --- a/helix-core/src/mark.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::num::NonZeroUsize; - -use crate::Selection; - -pub struct Mark { - doc_id: NonZeroUsize, - selection: Selection, -} From f3d15778b4007b69f5ccc02f86a61f70d82b167b Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 20:58:03 +0200 Subject: [PATCH 16/23] apply some clippy lints --- helix-term/src/commands/typed.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 355830a5f9bb..71a1827903f4 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -501,15 +501,13 @@ fn parse_mark_register_contents( let mut rv_iter = rv.into_iter(); let Some(doc_id) = rv_iter - .next() - .and_then(|c| Some(c.into_owned())) + .next().map(|c| c.into_owned()) .and_then(|s| s.try_into().ok()) else { return Err(anyhow!("Register did not contain valid document id")); }; let Some(history_rev) = rv_iter - .next() - .and_then(|c| Some(c.into_owned())) + .next().map(|c| c.into_owned()) .and_then(|s| s.parse().ok()) else { return Err(anyhow!("Register did not contain valid revision number")); From 739592991007a44ae8362632985e160b17aa1559 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 21:08:18 +0200 Subject: [PATCH 17/23] add an integration test --- helix-term/src/commands/typed.rs | 6 ++++-- helix-term/tests/test/movement.rs | 30 ++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 71a1827903f4..5a2c65549cf3 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -501,13 +501,15 @@ fn parse_mark_register_contents( let mut rv_iter = rv.into_iter(); let Some(doc_id) = rv_iter - .next().map(|c| c.into_owned()) + .next() + .map(|c| c.into_owned()) .and_then(|s| s.try_into().ok()) else { return Err(anyhow!("Register did not contain valid document id")); }; let Some(history_rev) = rv_iter - .next().map(|c| c.into_owned()) + .next() + .map(|c| c.into_owned()) .and_then(|s| s.parse().ok()) else { return Err(anyhow!("Register did not contain valid revision number")); diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs index 48b2ae1b9ae3..6ea29cb0e300 100644 --- a/helix-term/tests/test/movement.rs +++ b/helix-term/tests/test/movement.rs @@ -66,23 +66,33 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { } #[tokio::test(flavor = "multi_thread")] -async fn register_mark() -> anyhow::Result<()> { +async fn bookmark() -> anyhow::Result<()> { // add a mark and then immediately paste it out test(( indoc! {"\ - Lorem + #[|Lorem]# ipsum - dolor#[| - sit]# - amet." + #(|Lorem)# + ipsum + #(|Lorem)# + ipsum + #(|Lorem)# + ipsum + #(|Lorem)# + ipsum" }, - "1\"^p", + ":register_mark1casdf:goto_mark1", indoc! {"\ - Lorem + #[|asdf]# + ipsum + #(|asdf)# + ipsum + #(|asdf)# + ipsum + #(|asdf)# ipsum - dolor - sit#[|1:(24,19)]# - amet." + #(|asdf)# + ipsum" }, )) .await?; From 90748f703268a50f47cffc09139639d01c12c9b7 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sun, 9 Jun 2024 21:11:14 +0200 Subject: [PATCH 18/23] also colapse current selection during test --- helix-term/tests/test/movement.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs index 6ea29cb0e300..9892a5c5babb 100644 --- a/helix-term/tests/test/movement.rs +++ b/helix-term/tests/test/movement.rs @@ -81,7 +81,9 @@ async fn bookmark() -> anyhow::Result<()> { #(|Lorem)# ipsum" }, - ":register_mark1casdf:goto_mark1", + // make a mark, make changes to the doc, colapse selection by going to end of doc + // then resore mark and see the selection is still good + ":register_mark1casdfge:goto_mark1", indoc! {"\ #[|asdf]# ipsum From c8153f40647b42aef0fb400d71e239abe8aecd7f Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Mon, 10 Jun 2024 20:19:04 +0200 Subject: [PATCH 19/23] use hypthens instead of underscores for typable commands --- helix-term/src/commands/typed.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 5a2c65549cf3..e4dafb1db8fa 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2789,14 +2789,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ signature: CommandSignature::none(), }, TypableCommand { - name: "goto_mark", + name: "goto-mark", aliases: &[], doc: "Go to the selection saved in a register. Register can be provided as argument or selected register else ^ will be used", fun: goto_mark, signature: CommandSignature::positional(&[completers::register]), }, TypableCommand { - name: "register_mark", + name: "register-mark", aliases: &[], doc: "Save current selection into a register. Register can be provided as argument or selected register else ^ will be used", fun: register_mark, From 054e7f70edbf7f8132ee5fc8bcc51f20128b75bb Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Fri, 14 Jun 2024 16:30:49 +0200 Subject: [PATCH 20/23] update test after rename --- book/src/generated/typable-cmd.md | 2 ++ helix-term/tests/test/movement.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index f48e1490a038..fae78466952b 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -16,6 +16,8 @@ | `:write-buffer-close`, `:wbc` | Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt) | | `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) | | `:new`, `:n` | Create a new scratch buffer. | +| `:goto-mark` | Go to the selection saved in a register. Register can be provided as argument or selected register else ^ will be used | +| `:register-mark` | Save current selection into a register. Register can be provided as argument or selected register else ^ will be used | | `:format`, `:fmt` | Format the file using the LSP formatter. | | `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) | | `:line-ending` | Set the document's default line ending. Options: crlf, lf. | diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs index 9892a5c5babb..c57d6e9b35df 100644 --- a/helix-term/tests/test/movement.rs +++ b/helix-term/tests/test/movement.rs @@ -83,7 +83,7 @@ async fn bookmark() -> anyhow::Result<()> { }, // make a mark, make changes to the doc, colapse selection by going to end of doc // then resore mark and see the selection is still good - ":register_mark1casdfge:goto_mark1", + ":register-mark1casdfge:goto-mark1", indoc! {"\ #[|asdf]# ipsum From dadb1a4a20b3a019fdc5f839dab5c02ffc0af407 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Mon, 12 Aug 2024 09:27:13 +0200 Subject: [PATCH 21/23] Update helix-core/src/selection.rs Co-authored-by: TornaxO7 --- helix-core/src/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index b5c3cd20851a..aeb658b47182 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -919,7 +919,7 @@ mod test { #[test] fn parse_range() -> Result<(), String> { - // sometimes we want Ok, someteimes we want Err, but we never want a panic + // sometimes we want Ok, sometimes we want Err, but we never want a panic assert_eq!( Range::try_from("(0,28)"), Ok(Range { From b82036bb3f932e2bffcf466ee7b8e2bebd111760 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Sat, 14 Sep 2024 11:47:34 +0200 Subject: [PATCH 22/23] fix merge conflict in cargo.toml --- helix-term/Cargo.toml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 4d2bee8532e6..12724ba4cf87 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -50,7 +50,7 @@ tokio = { version = "1", features = [ tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = [ "crossterm", ] } -crossterm = { version = "0.27", features = ["event-stream"] } +crossterm = { version = "0.28", features = ["event-stream"] } signal-hook = "0.3" tokio-stream = "0.1" futures-util = { version = "0.3", features = [ @@ -69,13 +69,14 @@ log = "0.4" nucleo.workspace = true ignore = "0.4" # markdown doc rendering -pulldown-cmark = { version = "0.11", default-features = false } +pulldown-cmark = { version = "0.12", default-features = false } # file type detection content_inspector = "0.2.4" +thiserror = "1.0" # opening URLs -open = "5.1.3" -url = "2.5.0" +open = "5.3.0" +url = "2.5.2" # config toml = "0.8" @@ -84,15 +85,19 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } # ripgrep for global search -grep-regex = "0.1.12" -grep-searcher = "0.1.13" +grep-regex = "0.1.13" +grep-searcher = "0.1.14" [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } -libc = "0.2.155" +libc = "0.2.158" [target.'cfg(target_os = "macos")'.dependencies] -crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] } +crossterm = { version = "0.28", features = [ + "event-stream", + "use-dev-tty", + "libc", +] } [build-dependencies] helix-loader = { path = "../helix-loader" } @@ -100,4 +105,5 @@ helix-loader = { path = "../helix-loader" } [dev-dependencies] smallvec = "1.13" indoc = "2.0.5" -tempfile = "3.10.1" +tempfile = "3.12.0" +same-file = "1.0.1" From adb765851795f900d87efe4c893f510b52492550 Mon Sep 17 00:00:00 2001 From: Sam Vente Date: Mon, 16 Sep 2024 10:56:53 +0200 Subject: [PATCH 23/23] ensure cursor in view after goto mark --- helix-term/src/commands/typed.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index e4dafb1db8fa..41404e2d843b 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -581,6 +581,7 @@ fn goto_mark( ) .unwrap_or('^'); + let scrolloff = cx.editor.config().scrolloff; // use some helper functions to avoid making the borrow checker angry let registers_vals = read_from_register(cx.editor, register_name); let (doc_id, history_rev, mut selection) = parse_mark_register_contents(registers_vals)?; @@ -598,6 +599,9 @@ fn goto_mark( }; doc.set_selection(view.id, selection); + + view.ensure_cursor_in_view(doc, scrolloff); + Ok(()) }