From 6c0a7f60eb51e4043a8ae79093500fa0831010d0 Mon Sep 17 00:00:00 2001 From: Poliorcetics Date: Mon, 22 Jul 2024 16:47:27 +0200 Subject: [PATCH 1/7] contrib: add nushell completions (#11262) --- contrib/completion/hx.nu | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 contrib/completion/hx.nu diff --git a/contrib/completion/hx.nu b/contrib/completion/hx.nu new file mode 100644 index 000000000..b7bb542c0 --- /dev/null +++ b/contrib/completion/hx.nu @@ -0,0 +1,25 @@ +# Completions for Helix: +# +# NOTE: the `+N` syntax is not supported in Nushell (https://github.com/nushell/nushell/issues/13418) +# so it has not been specified here and will not be proposed in the autocompletion of Nushell. +# The help message won't be overriden though, so it will still be present here + +def health_categories [] { ["all", "clipboard", "languages"] } + +def grammar_categories [] { ["fetch", "build"] } + +# A post-modern text editor. +export extern hx [ + --help(-h), # Prints help information + --tutor, # Loads the tutorial + --health: string@health_categories = "all", # Checks for potential errors in editor setup + --grammar(-g): string@grammar_categories, # Fetches or builds tree-sitter grammars listed in `languages.toml` + --config(-c): glob, # Specifies a file to use for configuration + -v, # Increases logging verbosity each use for up to 3 times + --log: glob, # Specifies a file to use for logging + --version(-V), # Prints version information + --vsplit, # Splits all given files vertically into different windows + --hsplit, # Splits all given files horizontally into different windows + --working-dir(-w): glob, # Specify an initial working directory + ...files: glob, # Sets the input file to use, position can also be specified via file[:row[:col]] +] From 70a9477ec8221f25e7ee978f0fa0217f98c861c6 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 22 Jul 2024 09:55:12 -0500 Subject: [PATCH 2/7] Add `:mv` as an alias for `:move` (#11256) `mv` is the familiar shell command to move or rename a file. Add this to Helix as an alias for `:move`. --- book/src/generated/typable-cmd.md | 2 +- helix-term/src/commands/typed.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 100c65953..f48e1490a 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -85,6 +85,6 @@ | `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. | | `:clear-register` | Clear given register. If no argument is provided, clear all registers. | | `:redraw` | Clear and re-render the whole UI | -| `:move` | Move the current buffer and its corresponding file to a different path | +| `:move`, `:mv` | Move the current buffer and its corresponding file to a different path | | `:yank-diagnostic` | Yank diagnostic(s) under primary cursor to register, or clipboard by default | | `:read`, `:r` | Load a file into buffer | diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 88ee22527..530d78097 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -3122,7 +3122,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ }, TypableCommand { name: "move", - aliases: &[], + aliases: &["mv"], doc: "Move the current buffer and its corresponding file to a different path", fun: move_buffer, signature: CommandSignature::positional(&[completers::filename]), From f5231196bc1659846352e4c3c6d9a6a3c60c38bb Mon Sep 17 00:00:00 2001 From: Hamir Mahal Date: Mon, 22 Jul 2024 11:23:16 -0700 Subject: [PATCH 3/7] fix: usage of `node12 which is deprecated` (#11277) * chore: changes from formatting on save * fix: usage of `node12 which is deprecated` --- .github/workflows/build.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ba46ce56..93fcb9816 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: - master merge_group: schedule: - - cron: '00 01 * * *' + - cron: "00 01 * * *" jobs: check: @@ -17,10 +17,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v4 - name: Install stable toolchain - uses: helix-editor/rust-toolchain@v1 - with: - profile: minimal - override: true + uses: dtolnay/rust-toolchain@1.70 - uses: Swatinem/rust-cache@v2 with: @@ -119,4 +116,3 @@ jobs: git diff-files --quiet \ || (echo "Run 'cargo xtask docgen', commit the changes and push again" \ && exit 1) - From d47e085fe06dd04188a17cc32faf22a86a70622d Mon Sep 17 00:00:00 2001 From: karei Date: Tue, 23 Jul 2024 00:54:52 +0300 Subject: [PATCH 4/7] Revert `kanagawa` diff colour change from #11187 (#11270) --- runtime/themes/kanagawa.toml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/runtime/themes/kanagawa.toml b/runtime/themes/kanagawa.toml index 2d3836513..dabece68d 100644 --- a/runtime/themes/kanagawa.toml +++ b/runtime/themes/kanagawa.toml @@ -64,12 +64,9 @@ info = "dragonBlue" hint = "waveAqua1" ## Diff -"diff.plus" = "winterGreen" -"diff.plus.gutter" = "autumnGreen" -"diff.minus" = "winterRed" -"diff.minus.gutter" = "autumnRed" -"diff.delta" = "winterBlue" -"diff.delta.gutter" = "autumnYellow" +"diff.plus" = "autumnGreen" +"diff.minus" = "autumnRed" +"diff.delta" = "autumnYellow" ## Syntax highlighting "attribute" = "waveRed" From 86795a9dc7a0f7fdd9a40f5b1e4a12b0c87b8d96 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 22 Jul 2024 16:56:26 -0500 Subject: [PATCH 5/7] Return document display name from the '%' special register (#11275) --- helix-view/src/register.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/helix-view/src/register.rs b/helix-view/src/register.rs index 166d2027d..3a2e1b7cc 100644 --- a/helix-view/src/register.rs +++ b/helix-view/src/register.rs @@ -5,7 +5,6 @@ use helix_core::NATIVE_LINE_ENDING; use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider, ClipboardType}, - document::SCRATCH_BUFFER_NAME, Editor, }; @@ -61,14 +60,7 @@ impl Registers { Some(RegisterValues::new(doc.selection(view.id).fragments(text))) } '%' => { - let doc = doc!(editor); - - let path = doc - .path() - .as_ref() - .map(|p| p.to_string_lossy()) - .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); - + let path = doc!(editor).display_name(); Some(RegisterValues::new(iter::once(path))) } '*' | '+' => Some(read_from_clipboard( From 0d62656c987ec32f44d19ad7ab02c120c6344470 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:05:34 +0200 Subject: [PATCH 6/7] build(deps): bump the rust-dependencies group with 5 updates (#11281) Bumps the rust-dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | [thiserror](https://github.com/dtolnay/thiserror) | `1.0.62` | `1.0.63` | | [toml](https://github.com/toml-rs/toml) | `0.8.14` | `0.8.15` | | [tokio](https://github.com/tokio-rs/tokio) | `1.38.0` | `1.38.1` | | [cc](https://github.com/rust-lang/cc-rs) | `1.1.5` | `1.1.6` | | [libloading](https://github.com/nagisa/rust_libloading) | `0.8.4` | `0.8.5` | Updates `thiserror` from 1.0.62 to 1.0.63 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.62...1.0.63) Updates `toml` from 0.8.14 to 0.8.15 - [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.14...toml-v0.8.15) Updates `tokio` from 1.38.0 to 1.38.1 - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.0...tokio-1.38.1) Updates `cc` from 1.1.5 to 1.1.6 - [Release notes](https://github.com/rust-lang/cc-rs/releases) - [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.5...cc-v1.1.6) Updates `libloading` from 0.8.4 to 0.8.5 - [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.4...0.8.5) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust-dependencies - dependency-name: toml dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust-dependencies - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust-dependencies - dependency-name: cc dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust-dependencies - dependency-name: libloading dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b43e0210..dfb204c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,9 +136,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cfg-if" @@ -1681,9 +1681,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets 0.52.0", @@ -2345,18 +2345,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -2422,9 +2422,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", @@ -2463,9 +2463,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" dependencies = [ "serde", "serde_spanned", @@ -2484,9 +2484,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" dependencies = [ "indexmap", "serde", From 1d0a3d49d38e9d882fed573b836729f5b1410076 Mon Sep 17 00:00:00 2001 From: Ingrid Date: Tue, 23 Jul 2024 19:54:00 +0200 Subject: [PATCH 7/7] Consistently maintain view position (#10559) * replicate t-monaghan's changes * remove View.offset in favour of Document.view_data.view_position * improve access patterns for Document.view_data * better borrow checker wrangling with doc_mut!() * reintroduce ensure_cursor_in_view in handle_config_events since we sorted out the borrow checker issues using partial borrows, there's nothing stopping us from going back to the simpler implementation * introduce helper functions on Document .view_offset, set_view_offset * fix rebase breakage --- helix-term/src/application.rs | 6 +-- helix-term/src/commands.rs | 57 +++++++++++++++++----------- helix-term/src/commands/lsp.rs | 3 +- helix-term/src/commands/typed.rs | 2 +- helix-term/src/ui/editor.rs | 21 +++++++---- helix-term/src/ui/mod.rs | 6 +-- helix-view/src/document.rs | 51 ++++++++++++++++++++++--- helix-view/src/editor.rs | 15 ++++---- helix-view/src/lib.rs | 9 +++-- helix-view/src/view.rs | 64 +++++++++++++++++--------------- 10 files changed, 150 insertions(+), 84 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b7123e972..60bc5b7cf 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -395,9 +395,9 @@ impl Application { // reset view position in case softwrap was enabled/disabled let scrolloff = self.editor.config().scrolloff; - for (view, _) in self.editor.tree.views_mut() { - let doc = &self.editor.documents[&view.doc]; - view.ensure_cursor_in_view(doc, scrolloff) + for (view, _) in self.editor.tree.views() { + let doc = doc_mut!(self.editor, &view.doc); + view.ensure_cursor_in_view(doc, scrolloff); } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7e0bee92b..2c5d2783b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1032,6 +1032,7 @@ fn goto_window(cx: &mut Context, align: Align) { let count = cx.count() - 1; let config = cx.editor.config(); let (view, doc) = current!(cx.editor); + let view_offset = doc.view_offset(view.id); let height = view.inner_height(); @@ -1044,15 +1045,15 @@ fn goto_window(cx: &mut Context, align: Align) { let last_visual_line = view.last_visual_line(doc); let visual_line = match align { - Align::Top => view.offset.vertical_offset + scrolloff + count, - Align::Center => view.offset.vertical_offset + (last_visual_line / 2), + Align::Top => view_offset.vertical_offset + scrolloff + count, + Align::Center => view_offset.vertical_offset + (last_visual_line / 2), Align::Bottom => { - view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count) + view_offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count) } }; let visual_line = visual_line - .max(view.offset.vertical_offset + scrolloff) - .min(view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff)); + .max(view_offset.vertical_offset + scrolloff) + .min(view_offset.vertical_offset + last_visual_line.saturating_sub(scrolloff)); let pos = view .pos_at_visual_coords(doc, visual_line as u16, 0, false) @@ -1665,6 +1666,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor use Direction::*; let config = cx.editor.config(); let (view, doc) = current!(cx.editor); + let mut view_offset = doc.view_offset(view.id); let range = doc.selection(view.id).primary(); let text = doc.text().slice(..); @@ -1681,15 +1683,19 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor let doc_text = doc.text().slice(..); let viewport = view.inner_area(doc); let text_fmt = doc.text_format(viewport.width, None); - let mut annotations = view.text_annotations(&*doc, None); - (view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset( + (view_offset.anchor, view_offset.vertical_offset) = char_idx_at_visual_offset( doc_text, - view.offset.anchor, - view.offset.vertical_offset as isize + offset, + view_offset.anchor, + view_offset.vertical_offset as isize + offset, 0, &text_fmt, - &annotations, + // &annotations, + &view.text_annotations(&*doc, None), ); + doc.set_view_offset(view.id, view_offset); + + let doc_text = doc.text().slice(..); + let mut annotations = view.text_annotations(&*doc, None); if sync_cursor { let movement = match cx.editor.mode { @@ -1716,14 +1722,16 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor return; } + let view_offset = doc.view_offset(view.id); + let mut head; match direction { Forward => { let off; (head, off) = char_idx_at_visual_offset( doc_text, - view.offset.anchor, - (view.offset.vertical_offset + scrolloff) as isize, + view_offset.anchor, + (view_offset.vertical_offset + scrolloff) as isize, 0, &text_fmt, &annotations, @@ -1736,8 +1744,8 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor Backward => { head = char_idx_at_visual_offset( doc_text, - view.offset.anchor, - (view.offset.vertical_offset + height - scrolloff - 1) as isize, + view_offset.anchor, + (view_offset.vertical_offset + height - scrolloff - 1) as isize, 0, &text_fmt, &annotations, @@ -5124,7 +5132,7 @@ fn split(editor: &mut Editor, action: Action) { let (view, doc) = current!(editor); let id = doc.id(); let selection = doc.selection(view.id).clone(); - let offset = view.offset; + let offset = doc.view_offset(view.id); editor.switch(id, action); @@ -5133,7 +5141,7 @@ fn split(editor: &mut Editor, action: Action) { doc.set_selection(view.id, selection); // match the view scroll offset (switch doesn't handle this fully // since the selection is only matched after the split) - view.offset = offset; + doc.set_view_offset(view.id, offset); } fn hsplit(cx: &mut Context) { @@ -5228,14 +5236,21 @@ fn align_view_middle(cx: &mut Context) { return; } let doc_text = doc.text().slice(..); - let annotations = view.text_annotations(doc, None); let pos = doc.selection(view.id).primary().cursor(doc_text); - let pos = - visual_offset_from_block(doc_text, view.offset.anchor, pos, &text_fmt, &annotations).0; + let pos = visual_offset_from_block( + doc_text, + doc.view_offset(view.id).anchor, + pos, + &text_fmt, + &view.text_annotations(doc, None), + ) + .0; - view.offset.horizontal_offset = pos + let mut offset = doc.view_offset(view.id); + offset.horizontal_offset = pos .col .saturating_sub((view.inner_area(doc).width as usize) / 2); + doc.set_view_offset(view.id, offset); } fn scroll_up(cx: &mut Context) { @@ -6117,7 +6132,7 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) { // This is not necessarily exact if there is virtual text like soft wrap. // It's ok though because the extra jump labels will not be rendered. - let start = text.line_to_char(text.char_to_line(view.offset.anchor)); + let start = text.line_to_char(text.char_to_line(doc.view_offset(view.id).anchor)); let end = text.line_to_char(view.estimate_last_doc_line(doc) + 1); let primary_selection = doc.selection(view.id).primary(); diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 3b9efb431..9194377c4 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -1299,7 +1299,8 @@ fn compute_inlay_hints_for_view( // than computing all the hints for the full file (which could be dozens of time // longer than the view is). let view_height = view.inner_height(); - let first_visible_line = doc_text.char_to_line(view.offset.anchor.min(doc_text.len_chars())); + let first_visible_line = + doc_text.char_to_line(doc.view_offset(view_id).anchor.min(doc_text.len_chars())); let first_line = first_visible_line.saturating_sub(view_height); let last_line = first_visible_line .saturating_add(view_height.saturating_mul(2)) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 530d78097..720d32ac8 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1587,7 +1587,7 @@ fn tree_sitter_highlight_name( // Query the same range as the one used in syntax highlighting. let range = { // Calculate viewport byte ranges: - let row = text.char_to_line(view.offset.anchor.min(text.len_chars())); + let row = text.char_to_line(doc.view_offset(view.id).anchor.min(text.len_chars())); // Saturating subs to make it inclusive zero indexing. let last_line = text.len_lines().saturating_sub(1); let height = view.inner_area(doc).height; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index c151a7dd5..f7541fe25 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -93,6 +93,8 @@ impl EditorView { let theme = &editor.theme; let config = editor.config(); + let view_offset = doc.view_offset(view.id); + let text_annotations = view.text_annotations(doc, Some(theme)); let mut decorations = DecorationManager::default(); @@ -119,13 +121,13 @@ impl EditorView { } let syntax_highlights = - Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme); + Self::doc_syntax_highlights(doc, view_offset.anchor, inner.height, theme); let mut overlay_highlights = - Self::empty_highlight_iter(doc, view.offset.anchor, inner.height); + Self::empty_highlight_iter(doc, view_offset.anchor, inner.height); let overlay_syntax_highlights = Self::overlay_syntax_highlights( doc, - view.offset.anchor, + view_offset.anchor, inner.height, &text_annotations, ); @@ -203,7 +205,7 @@ impl EditorView { surface, inner, doc, - view.offset, + view_offset, &text_annotations, syntax_highlights, overlay_highlights, @@ -259,11 +261,13 @@ impl EditorView { .and_then(|config| config.rulers.as_ref()) .unwrap_or(editor_rulers); + let view_offset = doc.view_offset(view.id); + rulers .iter() // View might be horizontally scrolled, convert from absolute distance // from the 1st column to relative distance from left of viewport - .filter_map(|ruler| ruler.checked_sub(1 + view.offset.horizontal_offset as u16)) + .filter_map(|ruler| ruler.checked_sub(1 + view_offset.horizontal_offset as u16)) .filter(|ruler| ruler < &viewport.width) .map(|ruler| viewport.clip_left(ruler).with_width(1)) .for_each(|area| surface.set_style(area, ruler_theme)) @@ -825,6 +829,7 @@ impl EditorView { let inner_area = view.inner_area(doc); let selection = doc.selection(view.id); + let view_offset = doc.view_offset(view.id); let primary = selection.primary(); let text_format = doc.text_format(viewport.width, None); for range in selection.iter() { @@ -835,11 +840,11 @@ impl EditorView { visual_offset_from_block(text, cursor, cursor, &text_format, text_annotations).0; // if the cursor is horizontally in the view - if col >= view.offset.horizontal_offset - && inner_area.width > (col - view.offset.horizontal_offset) as u16 + if col >= view_offset.horizontal_offset + && inner_area.width > (col - view_offset.horizontal_offset) as u16 { let area = Rect::new( - inner_area.x + (col - view.offset.horizontal_offset) as u16, + inner_area.x + (col - view_offset.horizontal_offset) as u16, view.area.y, 1, view.area.height, diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 10f4104ab..6a3e198c1 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -83,7 +83,7 @@ pub fn raw_regex_prompt( let (view, doc) = current!(cx.editor); let doc_id = view.doc; let snapshot = doc.selection(view.id).clone(); - let offset_snapshot = view.offset; + let offset_snapshot = doc.view_offset(view.id); let config = cx.editor.config(); let mut prompt = Prompt::new( @@ -95,7 +95,7 @@ pub fn raw_regex_prompt( PromptEvent::Abort => { let (view, doc) = current!(cx.editor); doc.set_selection(view.id, snapshot.clone()); - view.offset = offset_snapshot; + doc.set_view_offset(view.id, offset_snapshot); } PromptEvent::Update | PromptEvent::Validate => { // skip empty input @@ -136,7 +136,7 @@ pub fn raw_regex_prompt( Err(err) => { let (view, doc) = current!(cx.editor); doc.set_selection(view.id, snapshot.clone()); - view.offset = offset_snapshot; + doc.set_view_offset(view.id, offset_snapshot); if event == PromptEvent::Validate { let callback = async move { diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index f3ace89e5..532f4c344 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -37,9 +37,12 @@ use helix_core::{ ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction, }; -use crate::editor::Config; -use crate::events::{DocumentDidChange, SelectionDidChange}; -use crate::{DocumentId, Editor, Theme, View, ViewId}; +use crate::{ + editor::Config, + events::{DocumentDidChange, SelectionDidChange}, + view::ViewPosition, + DocumentId, Editor, Theme, View, ViewId, +}; /// 8kB of buffer space for encoding and decoding `Rope`s. const BUF_SIZE: usize = 8192; @@ -130,6 +133,7 @@ pub struct Document { pub(crate) id: DocumentId, text: Rope, selections: HashMap, + view_data: HashMap, /// Inlay hints annotations for the document, by view. /// @@ -265,6 +269,7 @@ impl fmt::Debug for Document { .field("selections", &self.selections) .field("inlay_hints_oudated", &self.inlay_hints_oudated) .field("text_annotations", &self.inlay_hints) + .field("view_data", &self.view_data) .field("path", &self.path) .field("encoding", &self.encoding) .field("restore_cursor", &self.restore_cursor) @@ -656,6 +661,7 @@ impl Document { selections: HashMap::default(), inlay_hints: HashMap::default(), inlay_hints_oudated: false, + view_data: Default::default(), indent_style: DEFAULT_INDENT, line_ending, restore_cursor: false, @@ -1184,12 +1190,14 @@ impl Document { self.set_selection(view_id, Selection::single(origin.anchor, origin.head)); } - /// Initializes a new selection for the given view if it does not - /// already have one. + /// Initializes a new selection and view_data for the given view + /// if it does not already have them. pub fn ensure_view_init(&mut self, view_id: ViewId) { if self.selections.get(&view_id).is_none() { self.reset_selection(view_id); } + + self.view_data_mut(view_id); } /// Mark document as recent used for MRU sorting @@ -1235,6 +1243,12 @@ impl Document { .ensure_invariants(self.text.slice(..)); } + for view_data in self.view_data.values_mut() { + view_data.view_position.anchor = transaction + .changes() + .map_pos(view_data.view_position.anchor, Assoc::Before); + } + // if specified, the current selection should instead be replaced by transaction.selection if let Some(selection) = transaction.selection() { self.selections.insert( @@ -1759,6 +1773,28 @@ impl Document { &self.selections } + fn view_data(&self, view_id: ViewId) -> &ViewData { + self.view_data + .get(&view_id) + .expect("This should only be called after ensure_view_init") + } + + fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData { + self.view_data.entry(view_id).or_default() + } + + pub(crate) fn get_view_offset(&self, view_id: ViewId) -> Option { + Some(self.view_data.get(&view_id)?.view_position) + } + + pub fn view_offset(&self, view_id: ViewId) -> ViewPosition { + self.view_data(view_id).view_position + } + + pub fn set_view_offset(&mut self, view_id: ViewId, new_offset: ViewPosition) { + self.view_data_mut(view_id).view_position = new_offset; + } + pub fn relative_path(&self) -> Option> { self.path .as_deref() @@ -2034,6 +2070,11 @@ impl Document { } } +#[derive(Debug, Default)] +pub struct ViewData { + view_position: ViewPosition, +} + #[derive(Clone, Debug)] pub enum FormatterError { SpawningFailed { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index cead30d7c..ba7337f22 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,5 +1,4 @@ use crate::{ - align_view, annotations::diagnostics::{DiagnosticFilter, InlineDiagnosticsConfig}, document::{ DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint, @@ -11,8 +10,7 @@ use crate::{ register::Registers, theme::{self, Theme}, tree::{self, Tree}, - view::ViewPosition, - Align, Document, DocumentId, View, ViewId, + Document, DocumentId, View, ViewId, }; use dap::StackFrame; use helix_vcs::DiffProviderRegistry; @@ -1530,16 +1528,17 @@ impl Editor { } fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) { + let scrolloff = self.config().scrolloff; let view = self.tree.get_mut(current_view); - view.doc = doc_id; - view.offset = ViewPosition::default(); + view.doc = doc_id; let doc = doc_mut!(self, &doc_id); + doc.ensure_view_init(view.id); view.sync_changes(doc); doc.mark_as_focused(); - align_view(doc, view, Align::Center); + view.ensure_cursor_in_view(doc, scrolloff) } pub fn switch(&mut self, id: DocumentId, action: Action) { @@ -1899,8 +1898,8 @@ impl Editor { pub fn ensure_cursor_in_view(&mut self, id: ViewId) { let config = self.config(); - let view = self.tree.get_mut(id); - let doc = &self.documents[&view.doc]; + let view = self.tree.get(id); + let doc = doc_mut!(self, &view.doc); view.ensure_cursor_in_view(doc, config.scrolloff) } diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 5628c830c..d54b49ef5 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -47,11 +47,12 @@ pub enum Align { Bottom, } -pub fn align_view(doc: &Document, view: &mut View, align: Align) { +pub fn align_view(doc: &mut Document, view: &View, align: Align) { let doc_text = doc.text().slice(..); let cursor = doc.selection(view.id).primary().cursor(doc_text); let viewport = view.inner_area(doc); let last_line_height = viewport.height.saturating_sub(1); + let mut view_offset = doc.view_offset(view.id); let relative = match align { Align::Center => last_line_height / 2, @@ -60,15 +61,15 @@ pub fn align_view(doc: &Document, view: &mut View, align: Align) { }; let text_fmt = doc.text_format(viewport.width, None); - let annotations = view.text_annotations(doc, None); - (view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset( + (view_offset.anchor, view_offset.vertical_offset) = char_idx_at_visual_offset( doc_text, cursor, -(relative as isize), 0, &text_fmt, - &annotations, + &view.text_annotations(doc, None), ); + doc.set_view_offset(view.id, view_offset); } pub use document::Document; diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index af4fdfe4e..fb83c4b86 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -128,7 +128,6 @@ pub struct ViewPosition { #[derive(Clone)] pub struct View { pub id: ViewId, - pub offset: ViewPosition, pub area: Rect, pub doc: DocumentId, pub jumps: JumpList, @@ -173,11 +172,6 @@ impl View { Self { id: ViewId::default(), doc, - offset: ViewPosition { - anchor: 0, - horizontal_offset: 0, - vertical_offset: 0, - }, area: Rect::default(), // will get calculated upon inserting into tree jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel docs_access_history: Vec::new(), @@ -240,9 +234,10 @@ impl View { doc: &Document, scrolloff: usize, ) -> Option { + let view_offset = doc.get_view_offset(self.id)?; let doc_text = doc.text().slice(..); let viewport = self.inner_area(doc); - let vertical_viewport_end = self.offset.vertical_offset + viewport.height as usize; + let vertical_viewport_end = view_offset.vertical_offset + viewport.height as usize; let text_fmt = doc.text_format(viewport.width, None); let annotations = self.text_annotations(doc, None); @@ -256,7 +251,7 @@ impl View { }; let cursor = doc.selection(self.id).primary().cursor(doc_text); - let mut offset = self.offset; + let mut offset = view_offset; let off = visual_offset_from_anchor( doc_text, offset.anchor, @@ -321,22 +316,22 @@ impl View { } // if we are not centering return None if view position is unchanged - if !CENTERING && offset == self.offset { + if !CENTERING && offset == view_offset { return None; } Some(offset) } - pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) { + pub fn ensure_cursor_in_view(&self, doc: &mut Document, scrolloff: usize) { if let Some(offset) = self.offset_coords_to_in_view_center::(doc, scrolloff) { - self.offset = offset; + doc.set_view_offset(self.id, offset); } } - pub fn ensure_cursor_in_view_center(&mut self, doc: &Document, scrolloff: usize) { + pub fn ensure_cursor_in_view_center(&self, doc: &mut Document, scrolloff: usize) { if let Some(offset) = self.offset_coords_to_in_view_center::(doc, scrolloff) { - self.offset = offset; + doc.set_view_offset(self.id, offset); } else { align_view(doc, self, Align::Center); } @@ -354,7 +349,7 @@ impl View { #[inline] pub fn estimate_last_doc_line(&self, doc: &Document) -> usize { let doc_text = doc.text().slice(..); - let line = doc_text.char_to_line(self.offset.anchor.min(doc_text.len_chars())); + let line = doc_text.char_to_line(doc.view_offset(self.id).anchor.min(doc_text.len_chars())); // Saturating subs to make it inclusive zero indexing. (line + self.inner_height()) .min(doc_text.len_lines()) @@ -368,9 +363,10 @@ impl View { let viewport = self.inner_area(doc); let text_fmt = doc.text_format(viewport.width, None); let annotations = self.text_annotations(doc, None); + let view_offset = doc.view_offset(self.id); // last visual line in view is trivial to compute - let visual_height = self.offset.vertical_offset + viewport.height as usize; + let visual_height = doc.view_offset(self.id).vertical_offset + viewport.height as usize; // fast path when the EOF is not visible on the screen, if self.estimate_last_doc_line(doc) < doc_text.len_lines() - 1 { @@ -380,7 +376,7 @@ impl View { // translate to document line let pos = visual_offset_from_anchor( doc_text, - self.offset.anchor, + view_offset.anchor, usize::MAX, &text_fmt, &annotations, @@ -388,7 +384,7 @@ impl View { ); match pos { - Ok((Position { row, .. }, _)) => row.saturating_sub(self.offset.vertical_offset), + Ok((Position { row, .. }, _)) => row.saturating_sub(view_offset.vertical_offset), Err(PosAfterMaxRow) => visual_height.saturating_sub(1), Err(PosBeforeAnchorRow) => 0, } @@ -403,13 +399,15 @@ impl View { text: RopeSlice, pos: usize, ) -> Option { + let view_offset = doc.view_offset(self.id); + let viewport = self.inner_area(doc); let text_fmt = doc.text_format(viewport.width, None); let annotations = self.text_annotations(doc, None); let mut pos = visual_offset_from_anchor( text, - self.offset.anchor, + view_offset.anchor, pos, &text_fmt, &annotations, @@ -417,14 +415,14 @@ impl View { ) .ok()? .0; - if pos.row < self.offset.vertical_offset { + if pos.row < view_offset.vertical_offset { return None; } - pos.row -= self.offset.vertical_offset; + pos.row -= view_offset.vertical_offset; if pos.row >= viewport.height as usize { return None; } - pos.col = pos.col.saturating_sub(self.offset.horizontal_offset); + pos.col = pos.col.saturating_sub(view_offset.horizontal_offset); Some(pos) } @@ -488,7 +486,7 @@ impl View { doc, cursor, width, - self.offset.horizontal_offset, + doc.view_offset(self.id).horizontal_offset, config, )); } @@ -535,13 +533,14 @@ impl View { ignore_virtual_text: bool, ) -> Option { let text = doc.text().slice(..); + let view_offset = doc.view_offset(self.id); - let text_row = row as usize + self.offset.vertical_offset; - let text_col = column as usize + self.offset.horizontal_offset; + let text_row = row as usize + view_offset.vertical_offset; + let text_col = column as usize + view_offset.horizontal_offset; let (char_idx, virt_lines) = char_idx_at_visual_offset( text, - self.offset.anchor, + view_offset.anchor, text_row as isize, text_col, &text_fmt, @@ -689,11 +688,12 @@ mod tests { let mut view = View::new(DocumentId::default(), GutterConfig::default()); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( @@ -863,11 +863,12 @@ mod tests { ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( &doc, @@ -892,11 +893,12 @@ mod tests { ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( &doc, @@ -915,11 +917,12 @@ mod tests { let mut view = View::new(DocumentId::default(), GutterConfig::default()); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hi! こんにちは皆さん"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( @@ -998,11 +1001,12 @@ mod tests { let mut view = View::new(DocumentId::default(), GutterConfig::default()); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hèl̀l̀ò world!"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords(