aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlaž Hrastnik2021-01-22 08:13:14 +0000
committerBlaž Hrastnik2021-01-22 08:13:14 +0000
commit2bea5db7bdb3ad3fa029df830d824cd5c26a153f (patch)
treee45016157d4c3db7a56c5935f3e33843bc0daf87
parenta702af0aeb4562cbcdf8e2bc4008b8ce95da9a56 (diff)
commands: Implement select_on_matches.
-rw-r--r--helix-core/src/selection.rs29
-rw-r--r--helix-term/src/commands.rs50
-rw-r--r--helix-term/src/keymap.rs19
-rw-r--r--helix-term/src/ui/mod.rs41
4 files changed, 98 insertions, 41 deletions
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 9413fead..55514864 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -258,6 +258,35 @@ impl Selection {
// TODO: checkSelection -> check if valid for doc length
+pub fn select_on_matches(
+ text: &RopeSlice,
+ selections: &Selection,
+ regex: &crate::regex::Regex,
+) -> Selection {
+ let mut result = SmallVec::with_capacity(selections.ranges().len());
+
+ for sel in selections.ranges() {
+ // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
+ let fragment = sel.fragment(&text);
+
+ let mut sel_start = sel.from();
+ let sel_end = sel.to();
+
+ let mut start_byte = text.char_to_byte(sel_start);
+
+ for mat in regex.find_iter(&fragment) {
+ // TODO: retain range direction
+
+ let start = text.byte_to_char(start_byte + mat.start());
+ let end = text.byte_to_char(start_byte + mat.end());
+ result.push(Range::new(start, end - 1));
+ }
+ }
+
+ // TODO: figure out a new primary index
+ Selection::new(result, 0)
+}
+
// TODO: support to split on capture #N instead of whole match
pub fn split_on_matches(
text: &RopeSlice,
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 4a461239..ff8704d8 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -282,6 +282,19 @@ pub fn extend_line_down(cx: &mut Context) {
doc.set_selection(selection);
}
+pub fn select_regex(cx: &mut Context) {
+ let prompt = ui::regex_prompt(cx, "select:".to_string(), |doc, regex| {
+ let text = &doc.text().slice(..);
+ let selection = selection::select_on_matches(text, doc.selection(), &regex);
+ doc.set_selection(selection);
+ });
+ cx.callback = Some(Box::new(
+ move |compositor: &mut Compositor, editor: &mut Editor| {
+ compositor.push(Box::new(prompt));
+ },
+ ));
+}
+
pub fn split_selection(cx: &mut Context) {
// TODO: this needs to store initial selection state, revert on esc, confirm on enter
// needs to also call the callback function per input change, not just final time.
@@ -298,38 +311,11 @@ pub fn split_selection(cx: &mut Context) {
let snapshot = cx.doc().state.clone();
- let prompt = Prompt::new(
- "split:".to_string(),
- |input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate
- move |editor: &mut Editor, input: &str, event: PromptEvent| {
- match event {
- PromptEvent::Abort => {
- // revert state
- let doc = &mut editor.view_mut().doc;
- doc.state = snapshot.clone();
- }
- PromptEvent::Validate => {
- //
- }
- PromptEvent::Update => {
- match Regex::new(input) {
- Ok(regex) => {
- let doc = &mut editor.view_mut().doc;
-
- // revert state to what it was before the last update
- doc.state = snapshot.clone();
-
- let text = &doc.text().slice(..);
- let selection =
- selection::split_on_matches(text, doc.selection(), &regex);
- doc.set_selection(selection);
- }
- Err(_err) => (), // TODO: mark command line as error
- }
- }
- }
- },
- );
+ let prompt = ui::regex_prompt(cx, "split:".to_string(), |doc, regex| {
+ let text = &doc.text().slice(..);
+ let selection = selection::split_on_matches(text, doc.selection(), &regex);
+ doc.set_selection(selection);
+ });
cx.callback = Some(Box::new(
move |compositor: &mut Compositor, editor: &mut Editor| {
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 440a3efb..a27abf09 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -121,14 +121,14 @@ macro_rules! ctrl {
};
}
-// macro_rules! alt {
-// ($ch:expr) => {
-// Key {
-// code: KeyCode::Char($ch),
-// modifiers: Modifiers::ALT,
-// }
-// };
-// }
+macro_rules! alt {
+ ($ch:expr) => {
+ Key {
+ code: KeyCode::Char($ch),
+ modifiers: Modifiers::ALT,
+ }
+ };
+}
pub fn default() -> Keymaps {
hashmap!(
@@ -159,7 +159,8 @@ pub fn default() -> Keymaps {
vec![key!('o')] => commands::open_below,
vec![key!('d')] => commands::delete_selection,
vec![key!('c')] => commands::change_selection,
- vec![key!('s')] => commands::split_selection_on_newline,
+ vec![key!('s')] => commands::select_regex,
+ vec![alt!('s')] => commands::split_selection_on_newline,
vec![shift!('S')] => commands::split_selection,
vec![key!(';')] => commands::collapse_selection,
// TODO should be alt(;)
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 5ef967b0..bd40b249 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -9,12 +9,53 @@ pub use prompt::{Prompt, PromptEvent};
pub use tui::layout::Rect;
pub use tui::style::{Color, Modifier, Style};
+use helix_core::regex::Regex;
+use helix_view::{Document, Editor};
+
// TODO: temp
#[inline(always)]
pub fn text_color() -> Style {
Style::default().fg(Color::Rgb(219, 191, 239)) // lilac
}
+pub fn regex_prompt(
+ cx: &mut crate::commands::Context,
+ prompt: String,
+ fun: impl Fn(&mut Document, Regex) + 'static,
+) -> Prompt {
+ let snapshot = cx.doc().state.clone();
+
+ Prompt::new(
+ prompt,
+ |input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate
+ move |editor: &mut Editor, input: &str, event: PromptEvent| {
+ match event {
+ PromptEvent::Abort => {
+ // revert state
+ let doc = &mut editor.view_mut().doc;
+ doc.state = snapshot.clone();
+ }
+ PromptEvent::Validate => {
+ //
+ }
+ PromptEvent::Update => {
+ match Regex::new(input) {
+ Ok(regex) => {
+ let doc = &mut editor.view_mut().doc;
+
+ // revert state to what it was before the last update
+ doc.state = snapshot.clone();
+
+ fun(doc, regex);
+ }
+ Err(_err) => (), // TODO: mark command line as error
+ }
+ }
+ }
+ },
+ )
+}
+
use std::path::{Path, PathBuf};
pub fn file_picker(root: &str, ex: &'static smol::Executor) -> Picker<PathBuf> {
use ignore::Walk;