From 67829976faca1c2fdeb3964c25cb35bc41f0b8df Mon Sep 17 00:00:00 2001 From: VuiMuich Date: Tue, 19 Oct 2021 11:37:38 +0200 Subject: Add `C-j` and `C-k` to keybinds for picker (#876) * Add `C-j` and `C-k` for moving down/up in pickers * Add new binds to keymap doc--- helix-term/src/ui/picker.rs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 9be2a73e..6f584178 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -336,6 +336,10 @@ impl Component for Picker { code: KeyCode::BackTab, .. } + | KeyEvent { + code: KeyCode::Char('k'), + modifiers: KeyModifiers::CONTROL, + } | KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::CONTROL, @@ -349,6 +353,10 @@ impl Component for Picker { | KeyEvent { code: KeyCode::Tab, .. } + | KeyEvent { + code: KeyCode::Char('j'), + modifiers: KeyModifiers::CONTROL, + } | KeyEvent { code: KeyCode::Char('n'), modifiers: KeyModifiers::CONTROL, -- cgit v1.2.3-70-g09d2 From 3b032e8e1fd342261b153aeb375f9c0e8d882b34 Mon Sep 17 00:00:00 2001 From: Daniel S Poulin Date: Thu, 21 Oct 2021 21:02:05 -0400 Subject: First stab at ignoring compressed files from picker (#767) --- helix-term/src/ui/mod.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 810a9966..30a9ec6b 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -91,9 +91,25 @@ pub fn regex_prompt( } pub fn file_picker(root: PathBuf) -> FilePicker { - use ignore::Walk; + use ignore::{types::TypesBuilder, WalkBuilder}; use std::time; - let files = Walk::new(&root).filter_map(|entry| { + + // We want to exclude files that the editor can't handle yet + let mut type_builder = TypesBuilder::new(); + let mut walk_builder = WalkBuilder::new(&root); + let walk_builder = match type_builder.add( + "compressed", + "*.{zip,gz,bz2,zst,lzo,sz,tgz,tbz2,lz,lz4,lzma,lzo,z,Z,xz,7z,rar,cab}", + ) { + Err(_) => &walk_builder, + _ => { + type_builder.negate("all"); + let excluded_types = type_builder.build().unwrap(); + walk_builder.types(excluded_types) + } + }; + + let files = walk_builder.build().filter_map(|entry| { let entry = entry.ok()?; // Path::is_dir() traverses symlinks, so we use it over DirEntry::is_dir if entry.path().is_dir() { -- cgit v1.2.3-70-g09d2 From 182a59b5528075c0171756bff71275db8a7995f0 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 22 Oct 2021 12:07:41 +0900 Subject: Update to rust 1.56 + 2021 edition --- flake.lock | 68 ++++++++++++++++++++++++++++++++++----------- flake.nix | 2 ++ helix-core/Cargo.toml | 2 +- helix-lsp/Cargo.toml | 2 +- helix-syntax/Cargo.toml | 2 +- helix-term/Cargo.toml | 2 +- helix-term/src/ui/menu.rs | 32 ++++++++++----------- helix-term/src/ui/picker.rs | 23 +++++---------- helix-tui/Cargo.toml | 2 +- helix-view/Cargo.toml | 2 +- helix-view/src/theme.rs | 1 - 11 files changed, 82 insertions(+), 56 deletions(-) (limited to 'helix-term/src/ui') diff --git a/flake.lock b/flake.lock index 21e44c6e..2029d580 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "devshell": { "locked": { - "lastModified": 1630239564, - "narHash": "sha256-lv7atkVE1+dFw0llmzONsbSIo5ao85KpNSRoFk4K8vU=", + "lastModified": 1632436039, + "narHash": "sha256-OtITeVWcKXn1SpVEnImpTGH91FycCskGBPqmlxiykv4=", "owner": "numtide", "repo": "devshell", - "rev": "bd86d3a2bb28ce4d223315e0eca0d59fef8a0a73", + "rev": "7a7a7aa0adebe5488e5abaec688fd9ae0f8ea9c6", "type": "github" }, "original": { @@ -15,6 +15,21 @@ "type": "github" } }, + "flake-utils": { + "locked": { + "lastModified": 1623875721, + "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "flakeCompat": { "flake": false, "locked": { @@ -37,14 +52,16 @@ "nixpkgs": [ "nixpkgs" ], - "rustOverlay": "rustOverlay" + "rustOverlay": [ + "rust-overlay" + ] }, "locked": { - "lastModified": 1631254163, - "narHash": "sha256-8+nOGLH1fXwWnNMTQq/Igk434BzZF5Vld45xLDLiNDQ=", + "lastModified": 1634796585, + "narHash": "sha256-CW4yx6omk5qCXUIwXHp/sztA7u0SpyLq9NEACPnkiz8=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "432d8504a32232e8d74710024d5bf5cc31767651", + "rev": "a84a2137a396f303978f1d48341e0390b0e16a8b", "type": "github" }, "original": { @@ -55,11 +72,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1631206977, - "narHash": "sha256-o3Dct9aJ5ht5UaTUBzXrRcK1RZt2eG5/xSlWJuUCVZM=", + "lastModified": 1634782485, + "narHash": "sha256-psfh4OQSokGXG0lpq3zKFbhOo3QfoeudRcaUnwMRkQo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4f6d8095fd51954120a1d08ea5896fe42dc3923b", + "rev": "34ad3ffe08adfca17fcb4e4a47bb5f3b113687be", "type": "github" }, "original": { @@ -69,21 +86,40 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1628186154, + "narHash": "sha256-r2d0wvywFnL9z4iptztdFMhaUIAaGzrSs7kSok0PgmE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "06552b72346632b6943c8032e57e702ea12413bf", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "flakeCompat": "flakeCompat", "nixCargoIntegration": "nixCargoIntegration", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" } }, - "rustOverlay": { - "flake": false, + "rust-overlay": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + }, "locked": { - "lastModified": 1631240108, - "narHash": "sha256-ffsTkAGyQLxu4E28nVcqwc8xFL/H1UEwrRw2ITI43Aw=", + "lastModified": 1634869268, + "narHash": "sha256-RVAcEFlFU3877Mm4q/nbXGEYTDg/wQNhzmXGMTV6wBs=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "3a29d5e726b855d9463eb5dfe04f1ec14d413289", + "rev": "c02c2d86354327317546501af001886fbb53d374", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bcc9383e..c1145268 100644 --- a/flake.nix +++ b/flake.nix @@ -3,9 +3,11 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + rust-overlay.url = "github:oxalica/rust-overlay"; nixCargoIntegration = { url = "github:yusdacra/nix-cargo-integration"; inputs.nixpkgs.follows = "nixpkgs"; + inputs.rustOverlay.follows = "rust-overlay"; }; flakeCompat = { url = "github:edolstra/flake-compat"; diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 51096453..93ebb133 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-core" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "Helix editor core editing primitives" categories = ["editor"] diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index b4c8c139..455407ad 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-lsp" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "LSP client implementation for Helix project" categories = ["editor"] diff --git a/helix-syntax/Cargo.toml b/helix-syntax/Cargo.toml index 9c2b8275..122fa460 100644 --- a/helix-syntax/Cargo.toml +++ b/helix-syntax/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-syntax" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "Tree-sitter grammars support" categories = ["editor"] diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 244d3c13..78afab01 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -3,7 +3,7 @@ name = "helix-term" version = "0.4.1" description = "A post-modern text editor." authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" categories = ["editor", "command-line-utilities"] repository = "https://github.com/helix-editor/helix" diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 055593fd..1130089d 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -64,25 +64,23 @@ impl Menu { } pub fn score(&mut self, pattern: &str) { - // need to borrow via pattern match otherwise it complains about simultaneous borrow - let Self { - ref mut matcher, - ref mut matches, - ref options, - .. - } = *self; - // reuse the matches allocation - matches.clear(); - matches.extend(options.iter().enumerate().filter_map(|(index, option)| { - let text = option.filter_text(); - // TODO: using fuzzy_indices could give us the char idx for match highlighting - matcher - .fuzzy_match(text, pattern) - .map(|score| (index, score)) - })); + self.matches.clear(); + self.matches.extend( + self.options + .iter() + .enumerate() + .filter_map(|(index, option)| { + let text = option.filter_text(); + // TODO: using fuzzy_indices could give us the char idx for match highlighting + self.matcher + .fuzzy_match(text, pattern) + .map(|score| (index, score)) + }), + ); // matches.sort_unstable_by_key(|(_, score)| -score); - matches.sort_unstable_by_key(|(index, _score)| options[*index].sort_text()); + self.matches + .sort_unstable_by_key(|(index, _score)| self.options[*index].sort_text()); // reset cursor position self.cursor = None; diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 6f584178..1f08cf13 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -233,37 +233,28 @@ impl Picker { } pub fn score(&mut self) { - // need to borrow via pattern match otherwise it complains about simultaneous borrow - let Self { - ref mut matcher, - ref mut matches, - ref filters, - ref format_fn, - .. - } = *self; - let pattern = &self.prompt.line; // reuse the matches allocation - matches.clear(); - matches.extend( + self.matches.clear(); + self.matches.extend( self.options .iter() .enumerate() .filter_map(|(index, option)| { // filter options first before matching - if !filters.is_empty() { - filters.binary_search(&index).ok()?; + if !self.filters.is_empty() { + self.filters.binary_search(&index).ok()?; } // TODO: maybe using format_fn isn't the best idea here - let text = (format_fn)(option); + let text = (self.format_fn)(option); // TODO: using fuzzy_indices could give us the char idx for match highlighting - matcher + self.matcher .fuzzy_match(&text, pattern) .map(|score| (index, score)) }), ); - matches.sort_unstable_by_key(|(_, score)| -score); + self.matches.sort_unstable_by_key(|(_, score)| -score); // reset cursor position self.cursor = 0; diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 2b42d299..f0c0d7e2 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Blaž Hrastnik "] description = """ A library to build rich terminal user interfaces or dashboards """ -edition = "2018" +edition = "2021" license = "MPL-2.0" categories = ["editor"] repository = "https://github.com/helix-editor/helix" diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index fce3fdd1..ef09b964 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-view" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "UI abstractions for use in backends" categories = ["editor"] diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 9c33685b..757316bd 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -1,6 +1,5 @@ use std::{ collections::HashMap, - convert::TryFrom, path::{Path, PathBuf}, }; -- cgit v1.2.3-70-g09d2 From 0f886af4b993c836bb2d522f6e036362593ff8b8 Mon Sep 17 00:00:00 2001 From: Oskar Nehlin Date: Sat, 23 Oct 2021 13:06:40 +0200 Subject: Add commands for moving between splits with a direction (#860) * Add commands for moving between splits with a direction * Update keymaps * Change picker mapping * Add test and clean up some comments--- book/src/keymap.md | 18 +++-- helix-term/src/commands.rs | 20 +++++ helix-term/src/keymap.rs | 6 +- helix-term/src/ui/picker.rs | 2 +- helix-view/src/editor.rs | 18 ++++- helix-view/src/tree.rs | 191 +++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 236 insertions(+), 19 deletions(-) (limited to 'helix-term/src/ui') diff --git a/book/src/keymap.md b/book/src/keymap.md index 3dfc5dc4..644dc1c9 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -181,12 +181,16 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`). This layer is similar to vim keybindings as kakoune does not support window. -| Key | Description | Command | -| ----- | ------------- | ------- | -| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | -| `v`, `Ctrl-v` | Vertical right split | `vsplit` | -| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` | -| `q`, `Ctrl-q` | Close current window | `wclose` | +| Key | Description | Command | +| ----- | ------------- | ------- | +| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | +| `v`, `Ctrl-v` | Vertical right split | `vsplit` | +| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | +| `h`, `Ctrl-h` | Move to left split | `jump_view_left` | +| `j`, `Ctrl-j` | Move to split below | `jump_view_down` | +| `k`, `Ctrl-k` | Move to split above | `jump_view_up` | +| `l`, `Ctrl-l` | Move to right split | `jump_view_right` | +| `q`, `Ctrl-q` | Close current window | `wclose` | #### Space mode @@ -249,6 +253,6 @@ Keys to use within picker. Remapping currently not supported. | `Down`, `Ctrl-j`, `Ctrl-n` | Next entry | | `Ctrl-space` | Filter options | | `Enter` | Open selected | -| `Ctrl-h` | Open horizontally | +| `Ctrl-s` | Open horizontally | | `Ctrl-v` | Open vertically | | `Escape`, `Ctrl-c` | Close picker | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 272a9d9a..9d73ba6e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -301,6 +301,10 @@ impl Command { expand_selection, "Expand selection to parent syntax node", jump_forward, "Jump forward on jumplist", jump_backward, "Jump backward on jumplist", + jump_view_right, "Jump to the split to the right", + jump_view_left, "Jump to the split to the left", + jump_view_up, "Jump to the split above", + jump_view_down, "Jump to the split below", rotate_view, "Goto next window", hsplit, "Horizontal bottom split", vsplit, "Vertical right split", @@ -4373,6 +4377,22 @@ fn rotate_view(cx: &mut Context) { cx.editor.focus_next() } +fn jump_view_right(cx: &mut Context) { + cx.editor.focus_right() +} + +fn jump_view_left(cx: &mut Context) { + cx.editor.focus_left() +} + +fn jump_view_up(cx: &mut Context) { + cx.editor.focus_up() +} + +fn jump_view_down(cx: &mut Context) { + cx.editor.focus_down() +} + // split helper, clear it later fn split(cx: &mut Context, action: Action) { let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index cd4d3a32..f877387c 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -520,9 +520,13 @@ impl Default for Keymaps { "C-w" => { "Window" "C-w" | "w" => rotate_view, - "C-h" | "h" => hsplit, + "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-q" | "q" => wclose, + "C-h" | "h" => jump_view_left, + "C-j" | "j" => jump_view_down, + "C-k" | "k" => jump_view_up, + "C-l" | "l" => jump_view_right, }, // move under c diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 1f08cf13..7e257c0b 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -373,7 +373,7 @@ impl Component for Picker { return close_fn; } KeyEvent { - code: KeyCode::Char('h'), + code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, } => { if let Some(option) = self.selection() { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 52fca6d2..813c86fd 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -2,7 +2,7 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, graphics::{CursorKind, Rect}, theme::{self, Theme}, - tree::Tree, + tree::{self, Tree}, Document, DocumentId, View, ViewId, }; @@ -355,6 +355,22 @@ impl Editor { self.tree.focus_next(); } + pub fn focus_right(&mut self) { + self.tree.focus_direction(tree::Direction::Right); + } + + pub fn focus_left(&mut self) { + self.tree.focus_direction(tree::Direction::Left); + } + + pub fn focus_up(&mut self) { + self.tree.focus_direction(tree::Direction::Up); + } + + pub fn focus_down(&mut self) { + self.tree.focus_direction(tree::Direction::Down); + } + pub fn should_close(&self) -> bool { self.tree.is_empty() } diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index 576f64f0..064334b1 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -47,13 +47,21 @@ impl Node { // TODO: screen coord to container + container coordinate helpers -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Layout { Horizontal, Vertical, // could explore stacked/tabbed } +#[derive(Debug, Clone, Copy)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + #[derive(Debug)] pub struct Container { layout: Layout, @@ -150,7 +158,6 @@ impl Tree { } => container, _ => unreachable!(), }; - if container.layout == layout { // insert node after the current item if there is children already let pos = if container.children.is_empty() { @@ -393,6 +400,112 @@ impl Tree { Traverse::new(self) } + // Finds the split in the given direction if it exists + pub fn find_split_in_direction(&self, id: ViewId, direction: Direction) -> Option { + let parent = self.nodes[id].parent; + // Base case, we found the root of the tree + if parent == id { + return None; + } + // Parent must always be a container + let parent_container = match &self.nodes[parent].content { + Content::Container(container) => container, + Content::View(_) => unreachable!(), + }; + + match (direction, parent_container.layout) { + (Direction::Up, Layout::Vertical) + | (Direction::Left, Layout::Horizontal) + | (Direction::Right, Layout::Horizontal) + | (Direction::Down, Layout::Vertical) => { + // The desired direction of movement is not possible within + // the parent container so the search must continue closer to + // the root of the split tree. + self.find_split_in_direction(parent, direction) + } + (Direction::Up, Layout::Horizontal) + | (Direction::Down, Layout::Horizontal) + | (Direction::Left, Layout::Vertical) + | (Direction::Right, Layout::Vertical) => { + // It's possible to move in the desired direction within + // the parent container so an attempt is made to find the + // correct child. + match self.find_child(id, &parent_container.children, direction) { + // Child is found, search is ended + Some(id) => Some(id), + // A child is not found. This could be because of either two scenarios + // 1. Its not possible to move in the desired direction, and search should end + // 2. A layout like the following with focus at X and desired direction Right + // | _ | x | | + // | _ _ _ | | + // | _ _ _ | | + // The container containing X ends at X so no rightward movement is possible + // however there still exists another view/container to the right that hasn't + // been explored. Thus another search is done here in the parent container + // before concluding it's not possible to move in the desired direction. + None => self.find_split_in_direction(parent, direction), + } + } + } + } + + fn find_child(&self, id: ViewId, children: &[ViewId], direction: Direction) -> Option { + let mut child_id = match direction { + // index wise in the child list the Up and Left represents a -1 + // thus reversed iterator. + Direction::Up | Direction::Left => children + .iter() + .rev() + .skip_while(|i| **i != id) + .copied() + .nth(1)?, + // Down and Right => +1 index wise in the child list + Direction::Down | Direction::Right => { + children.iter().skip_while(|i| **i != id).copied().nth(1)? + } + }; + let (current_x, current_y) = match &self.nodes[self.focus].content { + Content::View(current_view) => (current_view.area.left(), current_view.area.top()), + Content::Container(_) => unreachable!(), + }; + + // If the child is a container the search finds the closest container child + // visually based on screen location. + while let Content::Container(container) = &self.nodes[child_id].content { + match (direction, container.layout) { + (_, Layout::Vertical) => { + // find closest split based on x because y is irrelevant + // in a vertical container (and already correct based on previous search) + child_id = *container.children.iter().min_by_key(|id| { + let x = match &self.nodes[**id].content { + Content::View(view) => view.inner_area().left(), + Content::Container(container) => container.area.left(), + }; + (current_x as i16 - x as i16).abs() + })?; + } + (_, Layout::Horizontal) => { + // find closest split based on y because x is irrelevant + // in a horizontal container (and already correct based on previous search) + child_id = *container.children.iter().min_by_key(|id| { + let y = match &self.nodes[**id].content { + Content::View(view) => view.inner_area().top(), + Content::Container(container) => container.area.top(), + }; + (current_y as i16 - y as i16).abs() + })?; + } + } + } + Some(child_id) + } + + pub fn focus_direction(&mut self, direction: Direction) { + if let Some(id) = self.find_split_in_direction(self.focus, direction) { + self.focus = id; + } + } + pub fn focus_next(&mut self) { // This function is very dumb, but that's because we don't store any parent links. // (we'd be able to go parent.next_sibling() recursively until we find something) @@ -420,13 +533,12 @@ impl Tree { // if found = container -> found = first child // } - let iter = self.traverse(); - - let mut iter = iter.skip_while(|&(key, _view)| key != self.focus); - iter.next(); // take the focused value - - if let Some((key, _)) = iter.next() { - self.focus = key; + let mut views = self + .traverse() + .skip_while(|&(id, _view)| id != self.focus) + .skip(1); // Skip focused value + if let Some((id, _)) = views.next() { + self.focus = id; } else { // extremely crude, take the first item again let (key, _) = self.traverse().next().unwrap(); @@ -472,3 +584,64 @@ impl<'a> Iterator for Traverse<'a> { } } } + +#[cfg(test)] +mod test { + use super::*; + use crate::DocumentId; + + #[test] + fn find_split_in_direction() { + let mut tree = Tree::new(Rect { + x: 0, + y: 0, + width: 180, + height: 80, + }); + let mut view = View::new(DocumentId::default()); + view.area = Rect::new(0, 0, 180, 80); + tree.insert(view); + + let l0 = tree.focus; + let view = View::new(DocumentId::default()); + tree.split(view, Layout::Vertical); + let r0 = tree.focus; + + tree.focus = l0; + let view = View::new(DocumentId::default()); + tree.split(view, Layout::Horizontal); + let l1 = tree.focus; + + tree.focus = l0; + let view = View::new(DocumentId::default()); + tree.split(view, Layout::Vertical); + let l2 = tree.focus; + + // Tree in test + // | L0 | L2 | | + // | L1 | R0 | + tree.focus = l2; + assert_eq!(Some(l0), tree.find_split_in_direction(l2, Direction::Left)); + assert_eq!(Some(l1), tree.find_split_in_direction(l2, Direction::Down)); + assert_eq!(Some(r0), tree.find_split_in_direction(l2, Direction::Right)); + assert_eq!(None, tree.find_split_in_direction(l2, Direction::Up)); + + tree.focus = l1; + assert_eq!(None, tree.find_split_in_direction(l1, Direction::Left)); + assert_eq!(None, tree.find_split_in_direction(l1, Direction::Down)); + assert_eq!(Some(r0), tree.find_split_in_direction(l1, Direction::Right)); + assert_eq!(Some(l0), tree.find_split_in_direction(l1, Direction::Up)); + + tree.focus = l0; + assert_eq!(None, tree.find_split_in_direction(l0, Direction::Left)); + assert_eq!(Some(l1), tree.find_split_in_direction(l0, Direction::Down)); + assert_eq!(Some(l2), tree.find_split_in_direction(l0, Direction::Right)); + assert_eq!(None, tree.find_split_in_direction(l0, Direction::Up)); + + tree.focus = r0; + assert_eq!(Some(l2), tree.find_split_in_direction(r0, Direction::Left)); + assert_eq!(None, tree.find_split_in_direction(r0, Direction::Down)); + assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right)); + assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up)); + } +} -- cgit v1.2.3-70-g09d2 From cee7ad781e5f6de249d728425a6283a26bb62dc3 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Sun, 24 Oct 2021 17:28:29 +0900 Subject: Mark a few functions as `const` --- helix-core/src/line_ending.rs | 6 +++--- helix-core/src/register.rs | 4 ++-- helix-term/src/ui/editor.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index 18ea5f9f..3541305c 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -20,7 +20,7 @@ pub enum LineEnding { impl LineEnding { #[inline] - pub fn len_chars(&self) -> usize { + pub const fn len_chars(&self) -> usize { match self { Self::Crlf => 2, _ => 1, @@ -28,7 +28,7 @@ impl LineEnding { } #[inline] - pub fn as_str(&self) -> &'static str { + pub const fn as_str(&self) -> &'static str { match self { Self::Crlf => "\u{000D}\u{000A}", Self::LF => "\u{000A}", @@ -42,7 +42,7 @@ impl LineEnding { } #[inline] - pub fn from_char(ch: char) -> Option { + pub const fn from_char(ch: char) -> Option { match ch { '\u{000A}' => Some(LineEnding::LF), '\u{000B}' => Some(LineEnding::VT), diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs index c3e6652e..c5444eb7 100644 --- a/helix-core/src/register.rs +++ b/helix-core/src/register.rs @@ -7,7 +7,7 @@ pub struct Register { } impl Register { - pub fn new(name: char) -> Self { + pub const fn new(name: char) -> Self { Self { name, values: Vec::new(), @@ -18,7 +18,7 @@ impl Register { Self { name, values } } - pub fn name(&self) -> char { + pub const fn name(&self) -> char { self.name } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 9234bb96..692696a6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1106,7 +1106,7 @@ fn canonicalize_key(key: &mut KeyEvent) { } #[inline] -fn abs_diff(a: usize, b: usize) -> usize { +const fn abs_diff(a: usize, b: usize) -> usize { if a > b { a - b } else { -- cgit v1.2.3-70-g09d2 From 3edca7854e66cbdb0c4baca25962a4f390fede55 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 25 Oct 2021 11:03:18 +0900 Subject: completion: fully revert state before apply & insertText common prefix --- helix-core/src/transaction.rs | 7 ++++ helix-term/src/ui/completion.rs | 71 +++++++++++++++++------------------------ helix-term/src/ui/editor.rs | 13 ++++++-- helix-view/src/document.rs | 24 +++++++++++++- 4 files changed, 71 insertions(+), 44 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 0e49fbe3..dfc18fbe 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -468,6 +468,13 @@ impl Transaction { } } + pub fn compose(mut self, other: Self) -> Self { + self.changes = self.changes.compose(other.changes); + // Other selection takes precedence + self.selection = other.selection; + self + } + pub fn with_selection(mut self, selection: Selection) -> Self { self.selection = Some(selection); self diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index c75b24f1..44879fcf 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface; use std::borrow::Cow; use helix_core::Transaction; -use helix_view::{graphics::Rect, Document, Editor, View}; +use helix_view::{graphics::Rect, Document, Editor}; use crate::commands; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; @@ -83,13 +83,13 @@ impl Completion { start_offset: usize, trigger_offset: usize, ) -> Self { - // let items: Vec = Vec::new(); let menu = Menu::new(items, move |editor: &mut Editor, item, event| { fn item_to_transaction( doc: &Document, - view: &View, item: &CompletionItem, offset_encoding: helix_lsp::OffsetEncoding, + start_offset: usize, + trigger_offset: usize, ) -> Transaction { if let Some(edit) = &item.text_edit { let edit = match edit { @@ -105,63 +105,52 @@ impl Completion { ) } else { let text = item.insert_text.as_ref().unwrap_or(&item.label); - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); + // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯ + // in these cases we need to check for a common prefix and remove it + let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset)); + let text = text.trim_start_matches::<&str>(&prefix); Transaction::change( doc.text(), - vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(), + vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(), ) } } + let (view, doc) = current!(editor); + + // if more text was entered, remove it + doc.restore(view.id); + match event { PromptEvent::Abort => {} PromptEvent::Update => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); + + // initialize a savepoint + doc.savepoint(); - let transaction = item_to_transaction(doc, view, item, offset_encoding); doc.apply(&transaction, view.id); } PromptEvent::Validate => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } - - let transaction = item_to_transaction(doc, view, item, offset_encoding); + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); doc.apply(&transaction, view.id); if let Some(additional_edits) = &item.additional_text_edits { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 692696a6..850fec0f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -13,7 +13,7 @@ use helix_core::{ syntax::{self, HighlightEvent}, unicode::segmentation::UnicodeSegmentation, unicode::width::UnicodeWidthStr, - LineEnding, Position, Range, Selection, + LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ document::Mode, @@ -721,7 +721,7 @@ impl EditorView { pub fn set_completion( &mut self, - editor: &Editor, + editor: &mut Editor, items: Vec, offset_encoding: helix_lsp::OffsetEncoding, start_offset: usize, @@ -736,6 +736,9 @@ impl EditorView { return; } + // Immediately initialize a savepoint + doc_mut!(editor).savepoint(); + // TODO : propagate required size on resize to completion too completion.required_size((size.width, size.height)); self.completion = Some(completion); @@ -945,6 +948,9 @@ impl Component for EditorView { if callback.is_some() { // assume close_fn self.completion = None; + // Clear any savepoints + let (_, doc) = current!(cxt.editor); + doc.savepoint = None; cxt.editor.clear_idle_timer(); // don't retrigger } } @@ -959,6 +965,9 @@ impl Component for EditorView { completion.update(&mut cxt); if completion.is_empty() { self.completion = None; + // Clear any savepoints + let (_, doc) = current!(cxt.editor); + doc.savepoint = None; cxt.editor.clear_idle_timer(); // don't retrigger } } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 8804681b..23c2dbc6 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -97,6 +97,9 @@ pub struct Document { // it back as it separated from the edits. We could split out the parts manually but that will // be more troublesome. history: Cell, + + pub savepoint: Option, + last_saved_revision: usize, version: i32, // should be usize? @@ -328,6 +331,7 @@ impl Document { text, selections: HashMap::default(), indent_style: DEFAULT_INDENT, + line_ending: DEFAULT_LINE_ENDING, mode: Mode::Normal, restore_cursor: false, syntax: None, @@ -337,9 +341,9 @@ impl Document { diagnostics: Vec::new(), version: 0, history: Cell::new(History::default()), + savepoint: None, last_saved_revision: 0, language_server: None, - line_ending: DEFAULT_LINE_ENDING, } } @@ -635,6 +639,14 @@ impl Document { if !transaction.changes().is_empty() { self.version += 1; + // generate revert to savepoint + if self.savepoint.is_some() { + take_with(&mut self.savepoint, |prev_revert| { + let revert = transaction.invert(&old_doc); + Some(revert.compose(prev_revert.unwrap())) + }); + } + // update tree-sitter syntax tree if let Some(syntax) = &mut self.syntax { // TODO: no unwrap @@ -724,6 +736,16 @@ impl Document { } } + pub fn savepoint(&mut self) { + self.savepoint = Some(Transaction::new(self.text())); + } + + pub fn restore(&mut self, view_id: ViewId) { + if let Some(revert) = self.savepoint.take() { + self.apply(&revert, view_id); + } + } + /// Undo modifications to the [`Document`] according to `uk`. pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) { let txns = self.history.get_mut().earlier(uk); -- cgit v1.2.3-70-g09d2 From acc5ac5e731ea978fdd8a0f6762f2cd5101780b2 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 25 Oct 2021 11:11:11 +0900 Subject: fix warning --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 850fec0f..97658c64 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -13,7 +13,7 @@ use helix_core::{ syntax::{self, HighlightEvent}, unicode::segmentation::UnicodeSegmentation, unicode::width::UnicodeWidthStr, - LineEnding, Position, Range, Selection, Transaction, + LineEnding, Position, Range, Selection, }; use helix_view::{ document::Mode, -- cgit v1.2.3-70-g09d2 From bca98b5bedfa6c9410384f26a2df6115874351bc Mon Sep 17 00:00:00 2001 From: CossonLeo Date: Tue, 26 Oct 2021 08:42:08 +0800 Subject: Add c-j c-k to menu keymap for move_up move_down (#908) --- helix-term/src/ui/menu.rs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 1130089d..dd163d34 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -214,6 +214,10 @@ impl Component for Menu { | KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { + code: KeyCode::Char('k'), + modifiers: KeyModifiers::CONTROL, } => { self.move_up(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); @@ -231,6 +235,10 @@ impl Component for Menu { | KeyEvent { code: KeyCode::Char('n'), modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { + code: KeyCode::Char('j'), + modifiers: KeyModifiers::CONTROL, } => { self.move_down(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); -- cgit v1.2.3-70-g09d2 From b142fd4080d99a7e4f39bb06207ded6771d47b20 Mon Sep 17 00:00:00 2001 From: CossonLeo Date: Tue, 26 Oct 2021 08:42:23 +0800 Subject: move_up will select last item, when no item selected (#907) --- helix-term/src/ui/menu.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index dd163d34..3c492d14 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -98,7 +98,8 @@ impl Menu { pub fn move_up(&mut self) { let len = self.matches.len(); - let pos = self.cursor.map_or(0, |i| (i + len.saturating_sub(1)) % len) % len; + let max_index = len.saturating_sub(1); + let pos = self.cursor.map_or(max_index, |i| (i + max_index) % len) % len; self.cursor = Some(pos); self.adjust_scroll(); } -- cgit v1.2.3-70-g09d2 From f331ba9df4d7d22a5e9599737da581feb630f748 Mon Sep 17 00:00:00 2001 From: CossonLeo Date: Tue, 26 Oct 2021 08:42:37 +0800 Subject: Clear competion items when start_offset > cursor (#906) --- helix-term/src/ui/completion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 44879fcf..a893e70b 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -199,7 +199,7 @@ impl Completion { .selection(view.id) .primary() .cursor(doc.text().slice(..)); - if self.start_offset <= cursor { + if self.trigger_offset <= cursor { let fragment = doc.text().slice(self.start_offset..cursor); let text = Cow::from(fragment); // TODO: logic is same as ui/picker -- cgit v1.2.3-70-g09d2 From 2505802d39f18f2f2dcfe8e00633f895c67beb76 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Tue, 26 Oct 2021 23:24:24 -0400 Subject: Improve statusline (#916) * Improve statusline * Change diagnostic count display to show counts of individual diagnostic types next to their corresponding gutter dots. * Add selection count to the statusline. * Do not display info or hint count in statusline * Reduce padding Co-authored-by: Blaž Hrastnik * Reduce padding Co-authored-by: Blaž Hrastnik * Use `Span::styled` * Reduce padding * Use `Style::patch` * Remove unnecessary `Cow` creation Co-authored-by: Blaž Hrastnik --- helix-term/src/ui/editor.rs | 97 ++++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 27 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 97658c64..bf316ee3 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -548,6 +548,8 @@ impl EditorView { theme: &Theme, is_focused: bool, ) { + use tui::text::{Span, Spans}; + //------------------------------- // Left side of the status line. //------------------------------- @@ -566,17 +568,17 @@ impl EditorView { }) .unwrap_or(""); - let style = if is_focused { + let base_style = if is_focused { theme.get("ui.statusline") } else { theme.get("ui.statusline.inactive") }; // statusline - surface.set_style(viewport.with_height(1), style); + surface.set_style(viewport.with_height(1), base_style); if is_focused { - surface.set_string(viewport.x + 1, viewport.y, mode, style); + surface.set_string(viewport.x + 1, viewport.y, mode, base_style); } - surface.set_string(viewport.x + 5, viewport.y, progress, style); + surface.set_string(viewport.x + 5, viewport.y, progress, base_style); if let Some(path) = doc.relative_path() { let path = path.to_string_lossy(); @@ -587,7 +589,7 @@ impl EditorView { viewport.y, title, viewport.width.saturating_sub(6) as usize, - style, + base_style, ); } @@ -595,8 +597,50 @@ impl EditorView { // Right side of the status line. //------------------------------- - // Compute the individual info strings. - let diag_count = format!("{}", doc.diagnostics().len()); + let mut right_side_text = Spans::default(); + + // Compute the individual info strings and add them to `right_side_text`. + + // Diagnostics + let diags = doc.diagnostics().iter().fold((0, 0), |mut counts, diag| { + use helix_core::diagnostic::Severity; + match diag.severity { + Some(Severity::Warning) => counts.0 += 1, + Some(Severity::Error) | None => counts.1 += 1, + _ => {} + } + counts + }); + let (warnings, errors) = diags; + let warning_style = theme.get("warning"); + let error_style = theme.get("error"); + for i in 0..2 { + let (count, style) = match i { + 0 => (warnings, warning_style), + 1 => (errors, error_style), + _ => unreachable!(), + }; + if count == 0 { + continue; + } + let style = base_style.patch(style); + right_side_text.0.push(Span::styled("●", style)); + right_side_text + .0 + .push(Span::styled(format!(" {} ", count), base_style)); + } + + // Selections + let sels_count = doc.selection(view.id).len(); + right_side_text.0.push(Span::styled( + format!( + " {} sel{} ", + sels_count, + if sels_count == 1 { "" } else { "s" } + ), + base_style, + )); + // let indent_info = match doc.indent_style { // IndentStyle::Tabs => "tabs", // IndentStyle::Spaces(1) => "spaces:1", @@ -609,29 +653,28 @@ impl EditorView { // IndentStyle::Spaces(8) => "spaces:8", // _ => "indent:ERROR", // }; - let position_info = { - let pos = coords_at_pos( - doc.text().slice(..), - doc.selection(view.id) - .primary() - .cursor(doc.text().slice(..)), - ); - format!("{}:{}", pos.row + 1, pos.col + 1) // convert to 1-indexing - }; - // Render them to the status line together. - let right_side_text = format!( - "{} {} ", - &diag_count[..diag_count.len().min(4)], - // indent_info, - position_info + // Position + let pos = coords_at_pos( + doc.text().slice(..), + doc.selection(view.id) + .primary() + .cursor(doc.text().slice(..)), ); - let text_len = right_side_text.len() as u16; - surface.set_string( - viewport.x + viewport.width.saturating_sub(text_len), + right_side_text.0.push(Span::styled( + format!(" {}:{} ", pos.row + 1, pos.col + 1), // Convert to 1-indexing. + base_style, + )); + + // Render to the statusline. + surface.set_spans( + viewport.x + + viewport + .width + .saturating_sub(right_side_text.width() as u16), viewport.y, - right_side_text, - style, + &right_side_text, + right_side_text.width() as u16, ); } -- cgit v1.2.3-70-g09d2 From 1066b081ddb06a5370371ca139dc8e9b992c1242 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 27 Oct 2021 18:23:17 +0900 Subject: fix: When cycling through prompt history, update event needs to trigger --- helix-term/src/ui/prompt.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 1d512ad2..853adfc2 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -501,6 +501,7 @@ impl Component for Prompt { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Backward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } KeyEvent { @@ -514,6 +515,7 @@ impl Component for Prompt { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Forward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } KeyEvent { -- cgit v1.2.3-70-g09d2 From 49f6c2623fbda5ff4be86e5e7d773bf900d9c75c Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Fri, 29 Oct 2021 11:00:18 +0800 Subject: Bump lsp-types to 0.91.0 (#932) --- Cargo.lock | 4 ++-- helix-lsp/Cargo.toml | 2 +- helix-lsp/src/client.rs | 7 +++--- helix-term/src/application.rs | 9 ++++---- helix-term/src/ui/completion.rs | 51 +++++++++++++++++++++-------------------- 5 files changed, 38 insertions(+), 35 deletions(-) (limited to 'helix-term/src/ui') diff --git a/Cargo.lock b/Cargo.lock index 45a8f5da..8af5c45c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.90.1" +version = "0.91.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3734ab1d7d157fc0c45110e06b587c31cd82bea2ccfd6b563cbff0aaeeb1d3" +checksum = "be7801b458592d0998af808d97f6a85a6057af3aaf2a2a5c3c677702bbeb4ed7" dependencies = [ "bitflags", "serde", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index f9910cc0..8cbff41d 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -19,7 +19,7 @@ futures-executor = "0.3" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } jsonrpc-core = { version = "18.0", default-features = false } # don't pull in all of futures log = "0.4" -lsp-types = { version = "0.90", features = ["proposed"] } +lsp-types = { version = "0.91", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 4068ae1f..b810feef 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -461,7 +461,7 @@ impl Client { }; let changes = match sync_capabilities { - lsp::TextDocumentSyncKind::Full => { + lsp::TextDocumentSyncKind::FULL => { vec![lsp::TextDocumentContentChangeEvent { // range = None -> whole document range: None, //Some(Range) @@ -469,10 +469,11 @@ impl Client { text: new_text.to_string(), }] } - lsp::TextDocumentSyncKind::Incremental => { + lsp::TextDocumentSyncKind::INCREMENTAL => { Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding) } - lsp::TextDocumentSyncKind::None => return None, + lsp::TextDocumentSyncKind::NONE => return None, + kind => unimplemented!("{:?}", kind), }; Some(self.notify::( diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 55b12c5a..6037148f 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -389,10 +389,11 @@ impl Application { message: diagnostic.message, severity: diagnostic.severity.map( |severity| match severity { - DiagnosticSeverity::Error => Error, - DiagnosticSeverity::Warning => Warning, - DiagnosticSeverity::Information => Info, - DiagnosticSeverity::Hint => Hint, + DiagnosticSeverity::ERROR => Error, + DiagnosticSeverity::WARNING => Warning, + DiagnosticSeverity::INFORMATION => Info, + DiagnosticSeverity::HINT => Hint, + severity => unimplemented!("{:?}", severity), }, ), // code diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index a893e70b..dcb2bfd8 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -30,31 +30,32 @@ impl menu::Item for CompletionItem { menu::Row::new(vec![ menu::Cell::from(self.label.as_str()), menu::Cell::from(match self.kind { - Some(lsp::CompletionItemKind::Text) => "text", - Some(lsp::CompletionItemKind::Method) => "method", - Some(lsp::CompletionItemKind::Function) => "function", - Some(lsp::CompletionItemKind::Constructor) => "constructor", - Some(lsp::CompletionItemKind::Field) => "field", - Some(lsp::CompletionItemKind::Variable) => "variable", - Some(lsp::CompletionItemKind::Class) => "class", - Some(lsp::CompletionItemKind::Interface) => "interface", - Some(lsp::CompletionItemKind::Module) => "module", - Some(lsp::CompletionItemKind::Property) => "property", - Some(lsp::CompletionItemKind::Unit) => "unit", - Some(lsp::CompletionItemKind::Value) => "value", - Some(lsp::CompletionItemKind::Enum) => "enum", - Some(lsp::CompletionItemKind::Keyword) => "keyword", - Some(lsp::CompletionItemKind::Snippet) => "snippet", - Some(lsp::CompletionItemKind::Color) => "color", - Some(lsp::CompletionItemKind::File) => "file", - Some(lsp::CompletionItemKind::Reference) => "reference", - Some(lsp::CompletionItemKind::Folder) => "folder", - Some(lsp::CompletionItemKind::EnumMember) => "enum_member", - Some(lsp::CompletionItemKind::Constant) => "constant", - Some(lsp::CompletionItemKind::Struct) => "struct", - Some(lsp::CompletionItemKind::Event) => "event", - Some(lsp::CompletionItemKind::Operator) => "operator", - Some(lsp::CompletionItemKind::TypeParameter) => "type_param", + Some(lsp::CompletionItemKind::TEXT) => "text", + Some(lsp::CompletionItemKind::METHOD) => "method", + Some(lsp::CompletionItemKind::FUNCTION) => "function", + Some(lsp::CompletionItemKind::CONSTRUCTOR) => "constructor", + Some(lsp::CompletionItemKind::FIELD) => "field", + Some(lsp::CompletionItemKind::VARIABLE) => "variable", + Some(lsp::CompletionItemKind::CLASS) => "class", + Some(lsp::CompletionItemKind::INTERFACE) => "interface", + Some(lsp::CompletionItemKind::MODULE) => "module", + Some(lsp::CompletionItemKind::PROPERTY) => "property", + Some(lsp::CompletionItemKind::UNIT) => "unit", + Some(lsp::CompletionItemKind::VALUE) => "value", + Some(lsp::CompletionItemKind::ENUM) => "enum", + Some(lsp::CompletionItemKind::KEYWORD) => "keyword", + Some(lsp::CompletionItemKind::SNIPPET) => "snippet", + Some(lsp::CompletionItemKind::COLOR) => "color", + Some(lsp::CompletionItemKind::FILE) => "file", + Some(lsp::CompletionItemKind::REFERENCE) => "reference", + Some(lsp::CompletionItemKind::FOLDER) => "folder", + Some(lsp::CompletionItemKind::ENUM_MEMBER) => "enum_member", + Some(lsp::CompletionItemKind::CONSTANT) => "constant", + Some(lsp::CompletionItemKind::STRUCT) => "struct", + Some(lsp::CompletionItemKind::EVENT) => "event", + Some(lsp::CompletionItemKind::OPERATOR) => "operator", + Some(lsp::CompletionItemKind::TYPE_PARAMETER) => "type_param", + Some(kind) => unimplemented!("{:?}", kind), None => "", }), // self.detail.as_deref().unwrap_or("") -- cgit v1.2.3-70-g09d2 From e5de103728b7a1338056f70524362930695d6c85 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 29 Oct 2021 16:48:25 +0900 Subject: Extract a clear_completion method --- helix-term/src/ui/editor.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index bf316ee3..c0d602c7 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -786,6 +786,14 @@ impl EditorView { completion.required_size((size.width, size.height)); self.completion = Some(completion); } + + pub fn clear_completion(&mut self, editor: &mut Editor) { + self.completion = None; + // Clear any savepoints + let (_, doc) = current!(editor); + doc.savepoint = None; + editor.clear_idle_timer(); // don't retrigger + } } impl EditorView { @@ -990,11 +998,7 @@ impl Component for EditorView { if callback.is_some() { // assume close_fn - self.completion = None; - // Clear any savepoints - let (_, doc) = current!(cxt.editor); - doc.savepoint = None; - cxt.editor.clear_idle_timer(); // don't retrigger + self.clear_completion(cxt.editor); } } } @@ -1007,11 +1011,7 @@ impl Component for EditorView { if let Some(completion) = &mut self.completion { completion.update(&mut cxt); if completion.is_empty() { - self.completion = None; - // Clear any savepoints - let (_, doc) = current!(cxt.editor); - doc.savepoint = None; - cxt.editor.clear_idle_timer(); // don't retrigger + self.clear_completion(cxt.editor); } } } -- cgit v1.2.3-70-g09d2 From 3eb829e2330fed5ad1c095f8bba44f62361b4943 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Wed, 3 Nov 2021 11:02:29 +0800 Subject: Ensure coords in screen depends on char width (#885) The issue affected files with lots of tabs at the start as well. Fix #840--- helix-core/src/lib.rs | 2 +- helix-core/src/movement.rs | 4 ++ helix-core/src/position.rs | 81 +++++++++++++++++++++++++++++++++++++---- helix-term/src/ui/completion.rs | 10 ++--- helix-view/src/view.rs | 8 ++-- 5 files changed, 87 insertions(+), 18 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 96f88ee4..d1720df0 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -194,7 +194,7 @@ pub use tendril::StrTendril as Tendril; pub use {regex, tree_sitter}; pub use graphemes::RopeGraphemes; -pub use position::{coords_at_pos, pos_at_coords, Position}; +pub use position::{coords_at_pos, pos_at_coords, visual_coords_at_pos, Position}; pub use selection::{Range, Selection}; pub use smallvec::SmallVec; pub use syntax::Syntax; diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 5d080545..9e85bd21 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -53,6 +53,10 @@ pub fn move_vertically( let pos = range.cursor(slice); // Compute the current position's 2d coordinates. + // TODO: switch this to use `visual_coords_at_pos` rather than + // `coords_at_pos` as this will cause a jerky movement when the visual + // position does not match, like moving from a line with tabs/CJK to + // a line without let Position { row, col } = coords_at_pos(slice, pos); let horiz = range.horiz.unwrap_or(col as u32); diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs index 08a8aed5..c6018ce6 100644 --- a/helix-core/src/position.rs +++ b/helix-core/src/position.rs @@ -2,6 +2,7 @@ use crate::{ chars::char_is_line_ending, graphemes::{ensure_grapheme_boundary_prev, RopeGraphemes}, line_ending::line_end_char_index, + unicode::width::UnicodeWidthChar, RopeSlice, }; @@ -54,11 +55,8 @@ impl From for tree_sitter::Point { } /// Convert a character index to (line, column) coordinates. /// -/// TODO: this should be split into two methods: one for visual -/// row/column, and one for "objective" row/column (possibly with -/// the column specified in `char`s). The former would be used -/// for cursor movement, and the latter would be used for e.g. the -/// row:column display in the status line. +/// column in `char` count which can be used for row:column display in +/// status line. See [`visual_coords_at_pos`] for a visual one. pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { let line = text.char_to_line(pos); @@ -69,6 +67,28 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { Position::new(line, col) } +/// Convert a character index to (line, column) coordinates visually. +/// +/// Takes \t, double-width characters (CJK) into account as well as text +/// not in the document in the future. +/// See [`coords_at_pos`] for an "objective" one. +pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Position { + let line = text.char_to_line(pos); + + let line_start = text.line_to_char(line); + let pos = ensure_grapheme_boundary_prev(text, pos); + let col = text + .slice(line_start..pos) + .chars() + .flat_map(|c| match c { + '\t' => Some(tab_width), + c => UnicodeWidthChar::width(c), + }) + .sum(); + + Position::new(line, col) +} + /// Convert (line, column) coordinates to a character index. /// /// If the `line` coordinate is beyond the end of the file, the EOF @@ -130,7 +150,6 @@ mod test { assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d // Test with wide characters. - // TODO: account for character width. let text = Rope::from("今日はいい\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -151,7 +170,6 @@ mod test { assert_eq!(coords_at_pos(slice, 9), (1, 0).into()); // Test with wide-character grapheme clusters. - // TODO: account for character width. let text = Rope::from("किमपि\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -161,7 +179,6 @@ mod test { assert_eq!(coords_at_pos(slice, 6), (1, 0).into()); // Test with tabs. - // Todo: account for tab stops. let text = Rope::from("\tHello\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -169,6 +186,54 @@ mod test { assert_eq!(coords_at_pos(slice, 2), (0, 2).into()); } + #[test] + fn test_visual_coords_at_pos() { + let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); // position on \n + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); // position on w + assert_eq!(visual_coords_at_pos(slice, 7, 8), (1, 1).into()); // position on o + assert_eq!(visual_coords_at_pos(slice, 10, 8), (1, 4).into()); // position on d + + // Test with wide characters. + let text = Rope::from("今日はいい\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 4).into()); + assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 6).into()); + assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 8).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 10).into()); + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); + + // Test with grapheme clusters. + let text = Rope::from("a̐éö̲\r\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 1).into()); + assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 7, 8), (0, 3).into()); + assert_eq!(visual_coords_at_pos(slice, 9, 8), (1, 0).into()); + + // Test with wide-character grapheme clusters. + // TODO: account for cluster. + let text = Rope::from("किमपि\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 3).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); + + // Test with tabs. + let text = Rope::from("\tHello\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 8).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 9).into()); + } + #[test] fn test_pos_at_coords() { let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index dcb2bfd8..dd782d29 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -264,12 +264,10 @@ impl Component for Completion { .language() .and_then(|scope| scope.strip_prefix("source.")) .unwrap_or(""); - let cursor_pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row - - view.offset.row) as u16; + let text = doc.text().slice(..); + let cursor_pos = doc.selection(view.id).primary().cursor(text); + let coords = helix_core::visual_coords_at_pos(text, cursor_pos, doc.tab_width()); + let cursor_pos = (coords.row - view.offset.row) as u16; let mut markdown_doc = match &option.documentation { Some(lsp::Documentation::String(contents)) | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 01f18c71..11f30155 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -2,10 +2,9 @@ use std::borrow::Cow; use crate::{graphics::Rect, Document, DocumentId, ViewId}; use helix_core::{ - coords_at_pos, graphemes::{grapheme_width, RopeGraphemes}, line_ending::line_end_char_index, - Position, RopeSlice, Selection, + visual_coords_at_pos, Position, RopeSlice, Selection, }; type Jump = (DocumentId, Selection); @@ -91,7 +90,10 @@ impl View { .selection(self.id) .primary() .cursor(doc.text().slice(..)); - let Position { col, row: line } = coords_at_pos(doc.text().slice(..), cursor); + + let Position { col, row: line } = + visual_coords_at_pos(doc.text().slice(..), cursor, doc.tab_width()); + let inner_area = self.inner_area(); let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1); -- cgit v1.2.3-70-g09d2 From 5b5d1b9dfff6b522559174f7f8e99aeb82c674a9 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Wed, 3 Nov 2021 23:24:05 -0400 Subject: Truncate the starts of file paths instead of the ends in picker (#951) * Truncate the starts of file paths in picker * Simplify the truncate implementation * Break loop at appropriate point * Fix alignment and ellipsis presence * Remove extraneous usage of `x_offset` Co-authored-by: Blaž Hrastnik --- helix-term/src/ui/picker.rs | 1 + helix-tui/src/buffer.rs | 78 ++++++++++++++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 22 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 7e257c0b..7fc6af0f 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -483,6 +483,7 @@ impl Component for Picker { text_style }, true, + true, ); } } diff --git a/helix-tui/src/buffer.rs b/helix-tui/src/buffer.rs index 377e3e39..f480bc2f 100644 --- a/helix-tui/src/buffer.rs +++ b/helix-tui/src/buffer.rs @@ -266,12 +266,14 @@ impl Buffer { where S: AsRef, { - self.set_string_truncated(x, y, string, width, style, false) + self.set_string_truncated(x, y, string, width, style, false, false) } /// Print at most the first `width` characters of a string if enough space is available - /// until the end of the line. If `markend` is true appends a `…` at the end of - /// truncated lines. + /// until the end of the line. If `ellipsis` is true appends a `…` at the end of + /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string + /// instead of the end. + #[allow(clippy::too_many_arguments)] pub fn set_string_truncated( &mut self, x: u16, @@ -280,6 +282,7 @@ impl Buffer { width: usize, style: Style, ellipsis: bool, + truncate_start: bool, ) -> (u16, u16) where S: AsRef, @@ -289,28 +292,59 @@ impl Buffer { let width = if ellipsis { width - 1 } else { width }; let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true); let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize)); - for s in graphemes { - let width = s.width(); - if width == 0 { - continue; + if !truncate_start { + for s in graphemes { + let width = s.width(); + if width == 0 { + continue; + } + // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we + // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32. + if width > max_offset.saturating_sub(x_offset) { + break; + } + + self.content[index].set_symbol(s); + self.content[index].set_style(style); + // Reset following cells if multi-width (they would be hidden by the grapheme), + for i in index + 1..index + width { + self.content[i].reset(); + } + index += width; + x_offset += width; } - // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we - // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32. - if width > max_offset.saturating_sub(x_offset) { - break; + if ellipsis && x_offset - (x as usize) < string.as_ref().width() { + self.content[index].set_symbol("…"); } - - self.content[index].set_symbol(s); - self.content[index].set_style(style); - // Reset following cells if multi-width (they would be hidden by the grapheme), - for i in index + 1..index + width { - self.content[i].reset(); + } else { + let mut start_index = self.index_of(x, y); + let mut index = self.index_of(max_offset as u16, y); + + let total_width = string.as_ref().width(); + let truncated = total_width > width; + if ellipsis && truncated { + self.content[start_index].set_symbol("…"); + start_index += 1; + } + if !truncated { + index -= width - total_width; + } + for s in graphemes.rev() { + let width = s.width(); + if width == 0 { + continue; + } + let start = index - width; + if start < start_index { + break; + } + self.content[start].set_symbol(s); + self.content[start].set_style(style); + for i in start + 1..index { + self.content[i].reset(); + } + index -= width; } - index += width; - x_offset += width; - } - if ellipsis && x_offset - (x as usize) < string.as_ref().width() { - self.content[index].set_symbol("…"); } (x_offset as u16, y) } -- cgit v1.2.3-70-g09d2 From 70d21a903fef3ec0787c453f369d95e5223a2656 Mon Sep 17 00:00:00 2001 From: diegodox Date: Thu, 4 Nov 2021 12:24:52 +0900 Subject: Prevent preview binary or large file (#939) * Prevent preview binary or large file (#847) * fix wrong method name * fix add use trait * update lock file * rename MAX_PREVIEW_SIZE from MAX_BYTE_PREVIEW * read small bytes to determine cotent type * [WIP] add preview struct to represent calcurated preveiw * Refactor content type detection - Remove unwraps - Reuse a single read buffer to avoid 1kb reallocations between previews * Refactor preview rendering so we don't construct docs when not necessary * Replace unwarap whit Preview::NotFound * Use index access to hide unwrap Co-authored-by: Blaž Hrastnik * fix Get and unwarp equivalent to referce of Index acess * better preview implementation * Rename Preview enum and vairant Co-authored-by: Gokul Soumya * fixup! Rename Preview enum and vairant * simplify long match * Center text, add docs, fix formatting, refactor Co-authored-by: Blaž Hrastnik Co-authored-by: Gokul Soumya --- Cargo.lock | 10 ++++ helix-term/Cargo.toml | 2 + helix-term/src/ui/picker.rs | 123 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 115 insertions(+), 20 deletions(-) (limited to 'helix-term/src/ui') diff --git a/Cargo.lock b/Cargo.lock index 0ce8ee8f..e036828a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "content_inspector" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" +dependencies = [ + "memchr", +] + [[package]] name = "crossbeam-utils" version = "0.8.5" @@ -415,6 +424,7 @@ version = "0.5.0" dependencies = [ "anyhow", "chrono", + "content_inspector", "crossterm", "fern", "futures-util", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 45b4eb2c..a0079feb 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -46,6 +46,8 @@ fuzzy-matcher = "0.3" ignore = "0.4" # markdown doc rendering pulldown-cmark = { version = "0.8", default-features = false } +# file type detection +content_inspector = "0.2.4" # config toml = "0.5" diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 7fc6af0f..291f1f85 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -12,7 +12,12 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; use tui::widgets::Widget; -use std::{borrow::Cow, collections::HashMap, path::PathBuf}; +use std::{ + borrow::Cow, + collections::HashMap, + io::Read, + path::{Path, PathBuf}, +}; use crate::ui::{Prompt, PromptEvent}; use helix_core::Position; @@ -23,18 +28,58 @@ use helix_view::{ }; pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80; +/// Biggest file size to preview in bytes +pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024; -/// File path and line number (used to align and highlight a line) +/// File path and range of lines (used to align and highlight lines) type FileLocation = (PathBuf, Option<(usize, usize)>); pub struct FilePicker { picker: Picker, /// Caches paths to documents - preview_cache: HashMap, + preview_cache: HashMap, + read_buffer: Vec, /// Given an item in the picker, return the file path and line number to display. file_fn: Box Option>, } +pub enum CachedPreview { + Document(Document), + Binary, + LargeFile, + NotFound, +} + +// We don't store this enum in the cache so as to avoid lifetime constraints +// from borrowing a document already opened in the editor. +pub enum Preview<'picker, 'editor> { + Cached(&'picker CachedPreview), + EditorDocument(&'editor Document), +} + +impl Preview<'_, '_> { + fn document(&self) -> Option<&Document> { + match self { + Preview::EditorDocument(doc) => Some(doc), + Preview::Cached(CachedPreview::Document(doc)) => Some(doc), + _ => None, + } + } + + /// Alternate text to show for the preview. + fn placeholder(&self) -> &str { + match *self { + Self::EditorDocument(_) => "", + Self::Cached(preview) => match preview { + CachedPreview::Document(_) => "", + CachedPreview::Binary => "", + CachedPreview::LargeFile => "", + CachedPreview::NotFound => "", + }, + } + } +} + impl FilePicker { pub fn new( options: Vec, @@ -45,6 +90,7 @@ impl FilePicker { Self { picker: Picker::new(false, options, format_fn, callback_fn), preview_cache: HashMap::new(), + read_buffer: Vec::with_capacity(1024), file_fn: Box::new(preview_fn), } } @@ -60,14 +106,45 @@ impl FilePicker { }) } - fn calculate_preview(&mut self, editor: &Editor) { - if let Some((path, _line)) = self.current_file(editor) { - if !self.preview_cache.contains_key(&path) && editor.document_by_path(&path).is_none() { - // TODO: enable syntax highlighting; blocked by async rendering - let doc = Document::open(&path, None, Some(&editor.theme), None).unwrap(); - self.preview_cache.insert(path, doc); - } + /// Get (cached) preview for a given path. If a document corresponding + /// to the path is already open in the editor, it is used instead. + fn get_preview<'picker, 'editor>( + &'picker mut self, + path: &Path, + editor: &'editor Editor, + ) -> Preview<'picker, 'editor> { + if let Some(doc) = editor.document_by_path(path) { + return Preview::EditorDocument(doc); + } + + if self.preview_cache.contains_key(path) { + return Preview::Cached(&self.preview_cache[path]); } + + let data = std::fs::File::open(path).and_then(|file| { + let metadata = file.metadata()?; + // Read up to 1kb to detect the content type + let n = file.take(1024).read_to_end(&mut self.read_buffer)?; + let content_type = content_inspector::inspect(&self.read_buffer[..n]); + self.read_buffer.clear(); + Ok((metadata, content_type)) + }); + let preview = data + .map( + |(metadata, content_type)| match (metadata.len(), content_type) { + (_, content_inspector::ContentType::BINARY) => CachedPreview::Binary, + (size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => CachedPreview::LargeFile, + _ => { + // TODO: enable syntax highlighting; blocked by async rendering + Document::open(path, None, Some(&editor.theme), None) + .map(CachedPreview::Document) + .unwrap_or(CachedPreview::NotFound) + } + }, + ) + .unwrap_or(CachedPreview::NotFound); + self.preview_cache.insert(path.to_owned(), preview); + Preview::Cached(&self.preview_cache[path]) } } @@ -79,12 +156,12 @@ impl Component for FilePicker { // |picker | | | // | | | | // +---------+ +---------+ - self.calculate_preview(cx.editor); let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW; let area = inner_rect(area); // -- Render the frame: // clear area let background = cx.editor.theme.get("ui.background"); + let text = cx.editor.theme.get("ui.text"); surface.clear_with(area, background); let picker_width = if render_preview { @@ -113,17 +190,23 @@ impl Component for FilePicker { horizontal: 1, }; let inner = inner.inner(&margin); - block.render(preview_area, surface); - if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, range)| { - cx.editor - .document_by_path(&path) - .or_else(|| self.preview_cache.get(&path)) - .zip(Some(range)) - }) { + if let Some((path, range)) = self.current_file(cx.editor) { + let preview = self.get_preview(&path, cx.editor); + let doc = match preview.document() { + Some(doc) => doc, + None => { + let alt_text = preview.placeholder(); + let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2; + let y = inner.y + inner.height / 2; + surface.set_stringn(x, y, alt_text, inner.width as usize, text); + return; + } + }; + // align to middle - let first_line = line + let first_line = range .map(|(start, end)| { let height = end.saturating_sub(start) + 1; let middle = start + (height.saturating_sub(1) / 2); @@ -150,7 +233,7 @@ impl Component for FilePicker { ); // highlight the line - if let Some((start, end)) = line { + if let Some((start, end)) = range { let offset = start.saturating_sub(first_line) as u16; surface.set_style( Rect::new( -- cgit v1.2.3-70-g09d2 From 39584cbccdb06b528220a13b643416f3fd5dc3c8 Mon Sep 17 00:00:00 2001 From: CossonLeo Date: Thu, 4 Nov 2021 11:26:01 +0800 Subject: Add c-s to pick word under doc cursor to prompt line & search completion (#831) * Add prompt shourtcut to book Add completions to search Add c-s to pick word under doc cursor to prompt line * limit 20 last items of search completion, update book * Update book/src/keymap.md Co-authored-by: Ivan Tham * limit search completions 200 Co-authored-by: Ivan Tham --- book/src/keymap.md | 22 ++++++++++++++++++++++ helix-term/src/commands.rs | 29 +++++++++++++++++++++++++++++ helix-term/src/ui/mod.rs | 3 ++- helix-term/src/ui/prompt.rs | 35 +++++++++++++++++++++++++++++++++-- 4 files changed, 86 insertions(+), 3 deletions(-) (limited to 'helix-term/src/ui') diff --git a/book/src/keymap.md b/book/src/keymap.md index a776ee70..4a6f80bb 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -258,3 +258,25 @@ Keys to use within picker. Remapping currently not supported. | `Ctrl-s` | Open horizontally | | `Ctrl-v` | Open vertically | | `Escape`, `Ctrl-c` | Close picker | + +# Prompt +Keys to use within prompt, Remapping currently not supported. +| Key | Description | +| ----- | ------------- | +| `Escape`, `Ctrl-c` | Close prompt | +| `Alt-b`, `Alt-Left` | Backward a word | +| `Ctrl-b`, `Left` | Backward a char | +| `Alt-f`, `Alt-Right` | Forward a word | +| `Ctrl-f`, `Right` | Forward a char | +| `Ctrl-e`, `End` | move prompt end | +| `Ctrl-a`, `Home` | move prompt start | +| `Ctrl-w` | delete previous word | +| `Ctrl-k` | delete to end of line | +| `backspace` | delete previous char | +| `Ctrl-s` | insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later | +| `Ctrl-p`, `Up` | select previous history | +| `Ctrl-n`, `Down` | select next history | +| `Tab` | slect next completion item | +| `BackTab` | slect previous completion item | +| `Enter` | Open selected | + diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index cc9106eb..6c073fb8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1087,6 +1087,7 @@ fn select_regex(cx: &mut Context) { cx, "select:".into(), Some(reg), + |_input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1109,6 +1110,7 @@ fn split_selection(cx: &mut Context) { cx, "split:".into(), Some(reg), + |_input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1172,6 +1174,15 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege }; } +fn search_completions(cx: &mut Context, reg: Option) -> Vec { + let mut items = reg + .and_then(|reg| cx.editor.registers.get(reg)) + .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect()); + items.sort_unstable(); + items.dedup(); + items.into_iter().cloned().collect() +} + // TODO: use one function for search vs extend fn search(cx: &mut Context) { let reg = cx.register.unwrap_or('/'); @@ -1182,11 +1193,19 @@ fn search(cx: &mut Context) { // HAXX: sadly we can't avoid allocating a single string for the whole buffer since we can't // feed chunks into the regex yet let contents = doc.text().slice(..).to_string(); + let completions = search_completions(cx, Some(reg)); let prompt = ui::regex_prompt( cx, "search:".into(), Some(reg), + move |input: &str| { + completions + .iter() + .filter(|comp| comp.starts_with(input)) + .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone()))) + .collect() + }, move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1246,10 +1265,19 @@ fn global_search(cx: &mut Context) { let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); let smart_case = cx.editor.config.smart_case; + + let completions = search_completions(cx, None); let prompt = ui::regex_prompt( cx, "global search:".into(), None, + move |input: &str| { + completions + .iter() + .filter(|comp| comp.starts_with(input)) + .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone()))) + .collect() + }, move |_view, _doc, regex, event| { if event != PromptEvent::Validate { return; @@ -4086,6 +4114,7 @@ fn keep_selections(cx: &mut Context) { cx, "keep:".into(), Some(reg), + |_input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 30a9ec6b..24eb7acd 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -29,6 +29,7 @@ pub fn regex_prompt( cx: &mut crate::commands::Context, prompt: std::borrow::Cow<'static, str>, history_register: Option, + completion_fn: impl FnMut(&str) -> Vec + 'static, fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static, ) -> Prompt { let (view, doc) = current!(cx.editor); @@ -38,7 +39,7 @@ pub fn regex_prompt( Prompt::new( prompt, history_register, - |_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate + completion_fn, move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| { match event { PromptEvent::Abort => { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 853adfc2..c999ba14 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -185,6 +185,11 @@ impl Prompt { self.exit_selection(); } + pub fn insert_str(&mut self, s: &str) { + self.line.insert_str(self.cursor, s); + self.cursor += s.len(); + } + pub fn move_cursor(&mut self, movement: Movement) { let pos = self.eval_movement(movement); self.cursor = pos @@ -473,6 +478,26 @@ impl Component for Prompt { self.delete_char_backwards(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } + KeyEvent { + code: KeyCode::Char('s'), + modifiers: KeyModifiers::CONTROL, + } => { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + use helix_core::textobject; + let range = textobject::textobject_word( + text, + doc.selection(view.id).primary(), + textobject::TextObject::Inside, + 1, + ); + let line = text.slice(range.from()..range.to()).to_string(); + if !line.is_empty() { + self.insert_str(line.as_str()); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); + } + } KeyEvent { code: KeyCode::Enter, .. @@ -520,11 +545,17 @@ impl Component for Prompt { } KeyEvent { code: KeyCode::Tab, .. - } => self.change_completion_selection(CompletionDirection::Forward), + } => { + self.change_completion_selection(CompletionDirection::Forward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update) + } KeyEvent { code: KeyCode::BackTab, .. - } => self.change_completion_selection(CompletionDirection::Backward), + } => { + self.change_completion_selection(CompletionDirection::Backward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update) + } KeyEvent { code: KeyCode::Char('q'), modifiers: KeyModifiers::CONTROL, -- cgit v1.2.3-70-g09d2 From e2560f427ef5e75155071e39da342628f5d5896a Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 4 Nov 2021 13:43:45 +0900 Subject: Replace documents SlotMap with BTreeMap --- helix-term/src/commands.rs | 6 +++--- helix-term/src/ui/editor.rs | 10 ++++----- helix-view/src/editor.rs | 51 ++++++++++++++++++++++++++------------------- helix-view/src/lib.rs | 4 +++- helix-view/src/macros.rs | 5 +++-- 5 files changed, 44 insertions(+), 32 deletions(-) (limited to 'helix-term/src/ui') diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6c073fb8..547a1d75 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1818,7 +1818,7 @@ mod cmd { let mut errors = String::new(); // save all documents - for (_, doc) in &mut cx.editor.documents { + for doc in &mut cx.editor.documents.values_mut() { if doc.path().is_none() { errors.push_str("cannot write a buffer without a filename\n"); continue; @@ -2512,7 +2512,7 @@ fn buffer_picker(cx: &mut Context) { cx.editor .documents .iter() - .map(|(id, doc)| (id, doc.path().cloned())) + .map(|(id, doc)| (*id, doc.path().cloned())) .collect(), move |(id, path): &(DocumentId, Option)| { let path = path.as_deref().map(helix_core::path::get_relative_path); @@ -2531,7 +2531,7 @@ fn buffer_picker(cx: &mut Context) { editor.switch(*id, Action::Replace); }, |editor, (id, path)| { - let doc = &editor.documents.get(*id)?; + let doc = &editor.documents.get(id)?; let &view_id = doc.selections().keys().next()?; let line = doc .selection(view_id) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index c0d602c7..0ffde47b 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -813,12 +813,12 @@ impl EditorView { let editor = &mut cxt.editor; let result = editor.tree.views().find_map(|(view, _focus)| { - view.pos_at_screen_coords(&editor.documents[view.doc], row, column) + view.pos_at_screen_coords(&editor.documents[&view.doc], row, column) .map(|pos| (pos, view.id)) }); if let Some((pos, view_id)) = result { - let doc = &mut editor.documents[editor.tree.get(view_id).doc]; + let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap(); if modifiers == crossterm::event::KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); @@ -870,7 +870,7 @@ impl EditorView { }; let result = cxt.editor.tree.views().find_map(|(view, _focus)| { - view.pos_at_screen_coords(&cxt.editor.documents[view.doc], row, column) + view.pos_at_screen_coords(&cxt.editor.documents[&view.doc], row, column) .map(|_| view.id) }); @@ -926,12 +926,12 @@ impl EditorView { } let result = editor.tree.views().find_map(|(view, _focus)| { - view.pos_at_screen_coords(&editor.documents[view.doc], row, column) + view.pos_at_screen_coords(&editor.documents[&view.doc], row, column) .map(|pos| (pos, view.id)) }); if let Some((pos, view_id)) = result { - let doc = &mut editor.documents[editor.tree.get(view_id).doc]; + let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap(); doc.set_selection(view_id, Selection::point(pos)); editor.tree.focus = view_id; commands::Command::paste_primary_clipboard_before.execute(cxt); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 21a64651..633e2541 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -8,6 +8,7 @@ use crate::{ use futures_util::future; use std::{ + collections::BTreeMap, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -15,8 +16,6 @@ use std::{ use tokio::time::{sleep, Duration, Instant, Sleep}; -use slotmap::SlotMap; - use anyhow::Error; pub use helix_core::diagnostic::Severity; @@ -108,7 +107,8 @@ impl std::fmt::Debug for Motion { #[derive(Debug)] pub struct Editor { pub tree: Tree, - pub documents: SlotMap, + pub next_document_id: usize, + pub documents: BTreeMap, pub count: Option, pub selected_register: Option, pub registers: Registers, @@ -149,7 +149,8 @@ impl Editor { Self { tree: Tree::new(area), - documents: SlotMap::with_key(), + next_document_id: 0, + documents: BTreeMap::new(), count: None, selected_register: None, theme: themes.default(), @@ -216,7 +217,7 @@ impl Editor { fn _refresh(&mut self) { for (view, _) in self.tree.views_mut() { - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; view.ensure_cursor_in_view(doc, self.config.scrolloff) } } @@ -225,7 +226,7 @@ impl Editor { use crate::tree::Layout; use helix_core::Selection; - if !self.documents.contains_key(id) { + if !self.documents.contains_key(&id) { log::error!("cannot switch to document that does not exist (anymore)"); return; } @@ -249,7 +250,7 @@ impl Editor { // Copy `doc.id` into a variable before calling `self.documents.remove`, which requires a mutable // borrow, invalidating direct access to `doc.id`. let id = doc.id; - self.documents.remove(id); + self.documents.remove(&id); } else { let jump = (view.doc, doc.selection(view.id).clone()); view.jumps.push(jump); @@ -281,14 +282,14 @@ impl Editor { let view = View::new(id); let view_id = self.tree.split(view, Layout::Horizontal); // initialize selection for view - let doc = &mut self.documents[id]; + let doc = self.documents.get_mut(&id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); } Action::VerticalSplit => { let view = View::new(id); let view_id = self.tree.split(view, Layout::Vertical); // initialize selection for view - let doc = &mut self.documents[id]; + let doc = self.documents.get_mut(&id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); } } @@ -297,9 +298,11 @@ impl Editor { } pub fn new_file(&mut self, action: Action) -> DocumentId { - let doc = Document::default(); - let id = self.documents.insert(doc); - self.documents[id].id = id; + let id = DocumentId(self.next_document_id); + self.next_document_id += 1; + let mut doc = Document::default(); + doc.id = id; + self.documents.insert(id, doc); self.switch(id, action); id } @@ -349,8 +352,10 @@ impl Editor { doc.set_language_server(Some(language_server)); } - let id = self.documents.insert(doc); - self.documents[id].id = id; + let id = DocumentId(self.next_document_id); + self.next_document_id += 1; + doc.id = id; + self.documents.insert(id, doc); id }; @@ -361,16 +366,20 @@ impl Editor { pub fn close(&mut self, id: ViewId, close_buffer: bool) { let view = self.tree.get(self.tree.focus); // remove selection - self.documents[view.doc].selections.remove(&id); + self.documents + .get_mut(&view.doc) + .unwrap() + .selections + .remove(&id); if close_buffer { // get around borrowck issues - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; if let Some(language_server) = doc.language_server() { tokio::spawn(language_server.text_document_did_close(doc.identifier())); } - self.documents.remove(view.doc); + self.documents.remove(&view.doc); } self.tree.remove(id); @@ -409,18 +418,18 @@ impl Editor { pub fn ensure_cursor_in_view(&mut self, id: ViewId) { let view = self.tree.get_mut(id); - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; view.ensure_cursor_in_view(doc, self.config.scrolloff) } #[inline] pub fn document(&self, id: DocumentId) -> Option<&Document> { - self.documents.get(id) + self.documents.get(&id) } #[inline] pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> { - self.documents.get_mut(id) + self.documents.get_mut(&id) } #[inline] @@ -445,7 +454,7 @@ impl Editor { pub fn cursor(&self) -> (Option, CursorKind) { let view = view!(self); - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; let cursor = doc .selection(view.id) .primary() diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index c37474d6..3e779356 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -12,8 +12,10 @@ pub mod theme; pub mod tree; pub mod view; +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct DocumentId(usize); + slotmap::new_key_type! { - pub struct DocumentId; pub struct ViewId; } diff --git a/helix-view/src/macros.rs b/helix-view/src/macros.rs index 0bebd02f..63d76a42 100644 --- a/helix-view/src/macros.rs +++ b/helix-view/src/macros.rs @@ -13,7 +13,8 @@ macro_rules! current { ( $( $editor:ident ).+ ) => {{ let view = $crate::view_mut!( $( $editor ).+ ); - let doc = &mut $( $editor ).+ .documents[view.doc]; + let id = view.doc; + let doc = $( $editor ).+ .documents.get_mut(&id).unwrap(); (view, doc) }}; } @@ -56,7 +57,7 @@ macro_rules! doc { macro_rules! current_ref { ( $( $editor:ident ).+ ) => {{ let view = $( $editor ).+ .tree.get($( $editor ).+ .tree.focus); - let doc = &$( $editor ).+ .documents[view.doc]; + let doc = &$( $editor ).+ .documents[&view.doc]; (view, doc) }}; } -- cgit v1.2.3-70-g09d2 From cfc82858679d264d178a0b072da26828e685de12 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 4 Nov 2021 22:25:08 -0400 Subject: Allow infoboxes to be disabled (#972) * Allow infoboxes to be disabled * Document `infoboxes` default value * Rename `infoboxes` to `auto_info` * Document `auto-info` * Fix incomplete rename--- book/src/configuration.md | 1 + helix-term/src/ui/editor.rs | 6 ++++-- helix-view/src/editor.rs | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) (limited to 'helix-term/src/ui') diff --git a/book/src/configuration.md b/book/src/configuration.md index 7d6ff28f..be25441f 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -22,6 +22,7 @@ To override global configuration parameters, create a `config.toml` file located | `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | +| `auto-info` | Whether to display infoboxes | `true` | ## LSP diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 0ffde47b..a7015577 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1086,8 +1086,10 @@ impl Component for EditorView { ); } - if let Some(ref mut info) = self.autoinfo { - info.render(area, surface, cx); + if cx.editor.config.auto_info { + if let Some(ref mut info) = self.autoinfo { + info.render(area, surface, cx); + } } let key_width = 15u16; // for showing pending keys diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 63a4ab29..6aa8b04d 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -58,6 +58,8 @@ pub struct Config { #[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")] pub idle_timeout: Duration, pub completion_trigger_len: u8, + /// Whether to display infoboxes. Defaults to true. + pub auto_info: bool, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -88,6 +90,7 @@ impl Default for Config { auto_completion: true, idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, + auto_info: true, } } } -- cgit v1.2.3-70-g09d2