From 3020077da8efbf914a9cb0a2cbb50362d339a39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 29 Sep 2020 01:00:35 +0900 Subject: [PATCH] Extend selection commands. --- Cargo.lock | 46 ++++++++++++++++++----------------- helix-core/Cargo.toml | 15 ++++++------ helix-core/src/lib.rs | 1 + helix-core/src/selection.rs | 29 ++++++++++++++++++++++ helix-core/src/syntax.rs | 6 ++--- helix-core/src/transaction.rs | 5 ++-- helix-syntax/Cargo.toml | 2 +- helix-syntax/src/lib.rs | 8 +++--- helix-term/Cargo.toml | 6 ++--- helix-term/src/editor.rs | 32 ++++++++++++++++++++++-- helix-view/Cargo.toml | 5 ++-- helix-view/src/commands.rs | 40 ++++++++++++++++++++++++++++++ helix-view/src/keymap.rs | 32 ++++++++++++++++++++++++ 13 files changed, 182 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index def2673ad..57f5fc17e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,9 +52,9 @@ checksum = "e1ba68f4276a778591e36a0c348a269888f3a177c8d2054969389e3b59611ff5" [[package]] name = "async-barrier" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06293698675eb72e1155867e5982f199d6b6c230dca35bc5ffd9852f470c22a" +checksum = "04b50fe84d0aea412a4e751cb01b9e26e2be533b2708607c2a2a739b192ee45a" dependencies = [ "async-mutex", "event-listener", @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "async-io" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c629684e697f58c0e99e5e2d84a840e3b336afbcfdbac7b44c3b1e222c2fd8" +checksum = "a9951f92a2b4f7793f8fc06a80bdb89b62c618c993497d4606474fb8c34941b5" dependencies = [ "concurrent-queue", "fastrand", @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3ad7fb4345397e57c19566844b0eba274b92e5d2d2791bb0664cc441697b95" +checksum = "76000290eb3c67dfe4e2bdf6b6155847f8e16fc844377a7bd0b5e97622656362" dependencies = [ "async-barrier", "async-mutex", @@ -127,9 +127,9 @@ dependencies = [ [[package]] name = "async-mutex" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66941c2577c4fa351e4ce5fdde8f86c69b88d623f3b955be1bc7362a23434632" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" dependencies = [ "event-listener", ] @@ -164,9 +164,9 @@ dependencies = [ [[package]] name = "async-rwlock" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806b1cc0828c2b1611ccbdd743fc0cc7af09009e62c95a0501c1e5da7b142a22" +checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c" dependencies = [ "async-mutex", "event-listener", @@ -174,18 +174,18 @@ dependencies = [ [[package]] name = "async-semaphore" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d2be5973230861689460806b8db059bbd8bcb507cabaa71646ae89f5b2f2ee" +checksum = "538c756e85eb6ffdefaec153804afb6da84b033e2e5ec3e9d459c34b4bf4d3f6" dependencies = [ "event-listener", ] [[package]] name = "async-task" -version = "4.0.1" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6725e96011a83fae25074a8734932e8d67763522839be7473dcfe8a0d6a378b1" +checksum = "8ab27c1aa62945039e44edaeee1dc23c74cc0c303dd5fe0fb462a184f1c3a518" [[package]] name = "atomic-waker" @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.4.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cd41440ae7e4734bbd42302f63eaba892afc93a3912dad84006247f0dedb0e" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "fastrand" @@ -340,9 +340,9 @@ checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" [[package]] name = "futures-lite" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b77e08e656f472d8ea84c472fa8b0a7a917883048e1cf2d4e34a323cd0aaf63" +checksum = "0db18c5f58083b54b0c416638ea73066722c2815c1e54dd8ba85ee3def593c3a" dependencies = [ "fastrand", "futures-core", @@ -387,6 +387,7 @@ dependencies = [ "anyhow", "helix-syntax", "once_cell", + "regex", "ropey", "smallvec", "tendril", @@ -425,6 +426,7 @@ dependencies = [ "anyhow", "crossterm", "helix-core", + "once_cell", "tui", ] @@ -774,9 +776,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df33680edb07e4fb76edcbdd9c7b849b96709fb878afcf0ada678d6bda167af" +checksum = "70ee7370fec3aecde3862a7d64c571048f70a7298daef1815e8fc68b9de54b5c" dependencies = [ "cc", "regex", @@ -784,9 +786,9 @@ dependencies = [ [[package]] name = "tui" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a977b0bb2e2033a6fef950f218f13622c3c34e59754b704ce3492dedab1dfe95" +checksum = "36626dee5ede9fd34015e9fb4fd7eedf3f3d05bdf1436aaef15b7d0a24233778" dependencies = [ "bitflags", "cassowary", diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 6a4b09e59..09d9c8297 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -9,12 +9,13 @@ edition = "2018" [dependencies] helix-syntax = { path = "../helix-syntax" } -ropey = "1.2.0" -anyhow = "1.0.31" -smallvec = "1.4.0" +ropey = "1.2" +anyhow = "1" +smallvec = "1.4" tendril = { git = "https://github.com/servo/tendril" } -unicode-segmentation = "1.6.0" -unicode-width = "0.1.7" +unicode-segmentation = "1.6" +unicode-width = "0.1" # slab = "0.4.2" -tree-sitter = "0.16.1" -once_cell = "1.4.1" +tree-sitter = "0.17" +once_cell = "1.4" +regex = "1" diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index e97c16beb..9705deaa7 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -8,6 +8,7 @@ mod transaction; pub use ropey::{Rope, RopeSlice}; + pub use tendril::StrTendril as Tendril; pub use position::Position; diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 80bb6a0c1..f934f74dd 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -60,6 +60,18 @@ pub fn overlaps(&self, other: &Self) -> bool { } } + pub fn contains(&self, pos: usize) -> bool { + if self.is_empty() { + return false; + } + + if self.anchor < self.head { + self.anchor <= pos && pos < self.head + } else { + self.head < pos && pos <= self.anchor + } + } + /// Map a range through a set of changes. Returns a new range representing the same position /// after the changes are applied. pub fn map(self, changes: &ChangeSet) -> Self { @@ -283,4 +295,21 @@ fn test_create_merges_adjacent_points() { assert_eq!(res, "8/10,10/12"); } + + #[test] + fn test_contains() { + let range = Range::new(10, 12); + + assert_eq!(range.contains(9), false); + assert_eq!(range.contains(10), true); + assert_eq!(range.contains(11), true); + assert_eq!(range.contains(12), false); + assert_eq!(range.contains(13), false); + + let range = Range::new(9, 6); + assert_eq!(range.contains(9), true); + assert_eq!(range.contains(7), true); + assert_eq!(range.contains(6), false); + } + } diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 26897ab3d..9b09bb582 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -48,7 +48,7 @@ pub fn highlight_config( if highlights_query.is_empty() { Ok(None) } else { - let language = get_language(&self.language_id); + let language = get_language(self.language_id); let mut config = HighlightConfiguration::new( language, &highlights_query, @@ -1456,7 +1456,7 @@ fn test_parser() { .map(String::from) .collect(); - let language = get_language(&LANG::Rust); + let language = get_language(LANG::Rust); let mut config = HighlightConfiguration::new( language, &std::fs::read_to_string( @@ -1478,7 +1478,7 @@ struct Stuff {} fn main() {} ", ); - let syntax = Syntax::new(LANG::Rust, &source, config); + let syntax = Syntax::new(&source, Arc::new(config)); let tree = syntax.root_layer.tree.unwrap(); let root = tree.root_node(); assert_eq!(root.kind(), "source_file"); diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 127cdaee5..278e071bc 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -1,4 +1,5 @@ use crate::{Range, Rope, Selection, State, Tendril}; +use std::convert::TryFrom; /// (from, to, replacement) pub type Change = (usize, usize, Option); @@ -112,7 +113,7 @@ pub fn compose(self, other: ChangeSet) -> Result { let (pos, _) = s.char_indices().nth(len - j).unwrap(); // calculate the difference let to_drop = s.len() - pos; - s.pop_back(to_drop as u32); + s.pop_back(u32::try_from(to_drop).unwrap()); head_a = Some(Insert(s)); head_b = changes_b.next(); } @@ -136,7 +137,7 @@ pub fn compose(self, other: ChangeSet) -> Result { let (pos, _) = s.char_indices().nth(j).unwrap(); // calculate the difference let to_drop = s.len() - pos; - s.pop_back(to_drop as u32); + s.pop_back(u32::try_from(to_drop).unwrap()); head_a = Some(Insert(s)); head_b = changes_b.next(); } diff --git a/helix-syntax/Cargo.toml b/helix-syntax/Cargo.toml index ef8ed8631..61a9b5b05 100644 --- a/helix-syntax/Cargo.toml +++ b/helix-syntax/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tree-sitter = "0.16" +tree-sitter = "0.17" enum-iterator = "0.6" [build-dependencies] diff --git a/helix-syntax/src/lib.rs b/helix-syntax/src/lib.rs index 60472fddc..61d63273e 100644 --- a/helix-syntax/src/lib.rs +++ b/helix-syntax/src/lib.rs @@ -13,7 +13,7 @@ macro_rules! mk_extern { #[macro_export] macro_rules! mk_enum { ( $( $camel:ident ),* ) => { - #[derive(Clone, Debug, IntoEnumIterator, PartialEq)] + #[derive(Clone, Copy, Debug, IntoEnumIterator, PartialEq)] pub enum LANG { $( $camel, @@ -25,7 +25,8 @@ pub enum LANG { #[macro_export] macro_rules! mk_get_language { ( $( ($camel:ident, $name:ident) ),* ) => { - pub fn get_language(lang: &LANG) -> Language { + #[must_use] + pub fn get_language(lang: LANG) -> Language { unsafe { match lang { $( @@ -40,7 +41,8 @@ pub fn get_language(lang: &LANG) -> Language { #[macro_export] macro_rules! mk_get_language_name { ( $( $camel:ident ),* ) => { - pub fn get_language_name(lang: &LANG) -> &'static str { + #[must_use] + pub const fn get_language_name(lang: LANG) -> &'static str { match lang { $( LANG::$camel => stringify!($camel), diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index d32fc0b9b..ecbbe7d26 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -15,9 +15,9 @@ helix-core = { path = "../helix-core" } helix-view = { path = "../helix-view", features = ["term"]} anyhow = "1" -argh = "0.1.3" +argh = "0.1" smol = "1" -num_cpus = "1.13.0" -tui = { version = "0.10.0", default-features = false, features = ["crossterm"] } +num_cpus = "1.13" +tui = { version = "0.11", default-features = false, features = ["crossterm"] } crossterm = { version = "0.17", features = ["event-stream"] } diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs index 24e62306d..131c285ea 100644 --- a/helix-term/src/editor.rs +++ b/helix-term/src/editor.rs @@ -1,8 +1,9 @@ use crate::Args; -use helix_core::{state::coords_at_pos, state::Mode, syntax::HighlightEvent, State}; +use helix_core::{state::coords_at_pos, state::Mode, syntax::HighlightEvent, Range, State}; use helix_view::{commands, keymap, View}; use std::{ + borrow::Cow, io::{self, stdout, Write}, path::PathBuf, time::Duration, @@ -120,6 +121,16 @@ fn render(&mut self) { let mut visual_x = 0; let mut line = 0u16; + let visible_selections: Vec = view + .state + .selection() + .ranges() + .iter() + // TODO: limit selection to one in viewport + .filter(|range| !range.is_empty()) // && range.overlaps(&Range::new(start, end + 1)) + .copied() + .collect(); + 'outer: for event in highlights { match event.unwrap() { HighlightEvent::HighlightStart(span) => { @@ -149,6 +160,8 @@ fn render(&mut self) { // way if only the selection/cursor changes we can copy from cache // and paint the new cursor. + let mut char_index = start; + // iterate over range char by char for grapheme in RopeGraphemes::new(&text) { // TODO: track current char_index @@ -164,8 +177,21 @@ fn render(&mut self) { } else { // Cow will prevent allocations if span contained in a single slice // which should really be the majority case - let grapheme = std::borrow::Cow::from(grapheme); + let grapheme = Cow::from(grapheme); let width = grapheme_width(&grapheme) as u16; + + let style = if visible_selections + .iter() + .any(|range| range.contains(char_index)) + { + // cedar + style.clone().bg(Color::Rgb(128, 47, 0)) + } else { + style + }; + + // TODO: paint cursor heads except primary + self.surface.set_string( offset + visual_x, line, @@ -176,6 +202,8 @@ fn render(&mut self) { visual_x += width; } // if grapheme == "\t" + + char_index += 1; } } } diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 379576259..0d8874748 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -10,9 +10,10 @@ edition = "2018" term = ["tui", "crossterm"] [dependencies] -anyhow = "1.0.32" +anyhow = "1" helix-core = { path = "../helix-core" } # Conversion traits -tui = { version = "0.10.0", default-features = false, features = ["crossterm"], optional = true} +tui = { version = "0.11", default-features = false, features = ["crossterm"], optional = true} crossterm = { version = "0.17", features = ["event-stream"], optional = true} +once_cell = "1.4" diff --git a/helix-view/src/commands.rs b/helix-view/src/commands.rs index 42390c239..d868a17c9 100644 --- a/helix-view/src/commands.rs +++ b/helix-view/src/commands.rs @@ -115,6 +115,46 @@ pub fn move_next_word_end(view: &mut View, count: usize) { // avoid select by default by having a visual mode switch that makes movements into selects +pub fn extend_char_left(view: &mut View, count: usize) { + // TODO: use a transaction + let selection = view + .state + .extend_selection(Direction::Backward, Granularity::Character, count); + view.state.selection = selection; +} + +pub fn extend_char_right(view: &mut View, count: usize) { + // TODO: use a transaction + view.state.selection = + view.state + .extend_selection(Direction::Forward, Granularity::Character, count); +} + +pub fn extend_line_up(view: &mut View, count: usize) { + // TODO: use a transaction + view.state.selection = + view.state + .extend_selection(Direction::Backward, Granularity::Line, count); +} + +pub fn extend_line_down(view: &mut View, count: usize) { + // TODO: use a transaction + view.state.selection = + view.state + .extend_selection(Direction::Forward, Granularity::Line, count); +} + +pub fn delete_selection(view: &mut View, _count: usize) { + let transaction = + Transaction::change_by_selection(&view.state, |range| (range.from(), range.to(), None)); + transaction.apply(&mut view.state); +} + +pub fn change_selection(view: &mut View, count: usize) { + delete_selection(view, count); + insert_mode(view, count); +} + // insert mode: // first we calculate the correct cursors/selections // then we just append at each cursor diff --git a/helix-view/src/keymap.rs b/helix-view/src/keymap.rs index 5d9b66534..750f1a5fe 100644 --- a/helix-view/src/keymap.rs +++ b/helix-view/src/keymap.rs @@ -7,7 +7,9 @@ // normal = { // q = record_macro // w = (next) word +// W = next WORD // e = end of word +// E = end of WORD // r = // t = 'till char // y = yank @@ -55,6 +57,12 @@ // & = align cursor // ? = extend to next given regex match (alt = to prev) // +// in kakoune these are alt-h alt-l / gh gl +// select from curs to begin end / move curs to begin end +// 0 = start of line +// ^ = start of line (first non blank char) +// $ = end of line +// // z = save selections // Z = restore selections // x = select line @@ -110,6 +118,22 @@ pub fn default() -> Keymaps { code: KeyCode::Char('l'), modifiers: Modifiers::NONE }] => commands::move_char_right as Command, + vec![Key { + code: KeyCode::Char('H'), + modifiers: Modifiers::SHIFT + }] => commands::extend_char_left as Command, + vec![Key { + code: KeyCode::Char('J'), + modifiers: Modifiers::SHIFT + }] => commands::extend_line_down as Command, + vec![Key { + code: KeyCode::Char('K'), + modifiers: Modifiers::SHIFT + }] => commands::extend_line_up as Command, + vec![Key { + code: KeyCode::Char('L'), + modifiers: Modifiers::SHIFT + }] => commands::extend_char_right as Command, vec![Key { code: KeyCode::Char('w'), modifiers: Modifiers::NONE @@ -142,6 +166,14 @@ pub fn default() -> Keymaps { code: KeyCode::Char('o'), modifiers: Modifiers::NONE }] => commands::open_below as Command, + vec![Key { + code: KeyCode::Char('d'), + modifiers: Modifiers::NONE + }] => commands::delete_selection as Command, + vec![Key { + code: KeyCode::Char('c'), + modifiers: Modifiers::NONE + }] => commands::change_selection as Command, vec![Key { code: KeyCode::Esc, modifiers: Modifiers::NONE