Split selection on regex, fix InputEdit generation.
This commit is contained in:
parent
3020077da8
commit
36e7e2133f
6 changed files with 146 additions and 23 deletions
|
@ -2,7 +2,7 @@
|
|||
pub mod graphemes;
|
||||
pub mod macros;
|
||||
mod position;
|
||||
mod selection;
|
||||
pub mod selection;
|
||||
pub mod state;
|
||||
pub mod syntax;
|
||||
mod transaction;
|
||||
|
@ -11,6 +11,9 @@ pub use ropey::{Rope, RopeSlice};
|
|||
|
||||
pub use tendril::StrTendril as Tendril;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use {regex, tree_sitter};
|
||||
|
||||
pub use position::Position;
|
||||
pub use selection::Range;
|
||||
pub use selection::Selection;
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
//! single selection range.
|
||||
//!
|
||||
//! All positioning is done via `char` offsets into the buffer.
|
||||
use crate::{Assoc, ChangeSet};
|
||||
use crate::{Assoc, ChangeSet, Rope, RopeSlice};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[inline]
|
||||
fn abs_difference(x: usize, y: usize) -> usize {
|
||||
|
@ -22,7 +23,7 @@ pub struct Range {
|
|||
pub anchor: usize,
|
||||
/// The head of the range, moved when extending.
|
||||
pub head: usize,
|
||||
}
|
||||
} // TODO: might be cheaper to store normalized as from/to and an inverted flag
|
||||
|
||||
impl Range {
|
||||
pub fn new(anchor: usize, head: usize) -> Self {
|
||||
|
@ -106,6 +107,11 @@ impl Range {
|
|||
}
|
||||
|
||||
// groupAt
|
||||
|
||||
#[inline]
|
||||
pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> {
|
||||
Cow::from(text.slice(self.from()..self.to()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A selection consists of one or more selection ranges.
|
||||
|
@ -239,10 +245,50 @@ impl Selection {
|
|||
self.primary_index,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn fragments<'a>(&'a self, text: &'a RopeSlice) -> impl Iterator<Item = Cow<str>> + 'a {
|
||||
self.ranges.iter().map(move |range| range.fragment(text))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: checkSelection -> check if valid for doc length
|
||||
|
||||
// TODO: support to split on capture #N instead of whole match
|
||||
pub fn split_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);
|
||||
|
||||
let mut start = sel_start;
|
||||
|
||||
for mat in regex.find_iter(&fragment) {
|
||||
// TODO: retain range direction
|
||||
|
||||
let end = text.byte_to_char(start_byte + mat.start());
|
||||
result.push(Range::new(start, end - 1));
|
||||
start = text.byte_to_char(start_byte + mat.end());
|
||||
}
|
||||
|
||||
if start <= sel_end {
|
||||
result.push(Range::new(start, sel_end));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: figure out a new primary index
|
||||
Selection::new(result, 0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -312,4 +358,30 @@ mod test {
|
|||
assert_eq!(range.contains(6), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_on_matches() {
|
||||
use crate::regex::Regex;
|
||||
|
||||
let text = Rope::from("abcd efg wrs xyz 123 456");
|
||||
|
||||
let selections = Selection::new(smallvec![Range::new(0, 8), Range::new(10, 19),], 0);
|
||||
|
||||
let result = split_on_matches(&text.slice(..), &selections, &Regex::new(r"\s+").unwrap());
|
||||
|
||||
assert_eq!(
|
||||
result.ranges(),
|
||||
&[
|
||||
Range::new(0, 4),
|
||||
Range::new(5, 8),
|
||||
Range::new(10, 12),
|
||||
Range::new(15, 18),
|
||||
Range::new(19, 19),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result.fragments(&text.slice(..)).collect::<Vec<_>>(),
|
||||
&["abcd", "efg", "rs", "xyz", ""]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,9 +188,19 @@ impl Syntax {
|
|||
syntax
|
||||
}
|
||||
|
||||
pub fn update(&mut self, source: &Rope, changeset: &ChangeSet) -> Result<(), Error> {
|
||||
self.root_layer
|
||||
.update(&mut self.parser, &self.config, source, changeset)
|
||||
pub fn update(
|
||||
&mut self,
|
||||
old_source: &Rope,
|
||||
source: &Rope,
|
||||
changeset: &ChangeSet,
|
||||
) -> Result<(), Error> {
|
||||
self.root_layer.update(
|
||||
&mut self.parser,
|
||||
&self.config,
|
||||
old_source,
|
||||
source,
|
||||
changeset,
|
||||
)
|
||||
|
||||
// TODO: deal with injections and update them too
|
||||
}
|
||||
|
@ -385,7 +395,7 @@ impl LanguageLayer {
|
|||
}
|
||||
|
||||
pub(crate) fn generate_edits(
|
||||
text: &RopeSlice,
|
||||
old_text: &RopeSlice,
|
||||
changeset: &ChangeSet,
|
||||
) -> Vec<tree_sitter::InputEdit> {
|
||||
use Operation::*;
|
||||
|
@ -399,7 +409,7 @@ impl LanguageLayer {
|
|||
// TODO; this is a lot easier with Change instead of Operation.
|
||||
|
||||
fn point_at_pos(text: &RopeSlice, pos: usize) -> (usize, Point) {
|
||||
let byte = text.char_to_byte(pos);
|
||||
let byte = text.char_to_byte(pos); // <- attempted to index past end
|
||||
let line = text.char_to_line(pos);
|
||||
let line_start_byte = text.line_to_byte(line);
|
||||
let col = byte - line_start_byte;
|
||||
|
@ -437,8 +447,8 @@ impl LanguageLayer {
|
|||
new_pos += len;
|
||||
}
|
||||
Delete(_) => {
|
||||
let (start_byte, start_position) = point_at_pos(&text, old_pos);
|
||||
let (old_end_byte, old_end_position) = point_at_pos(&text, old_end);
|
||||
let (start_byte, start_position) = point_at_pos(&old_text, old_pos);
|
||||
let (old_end_byte, old_end_position) = point_at_pos(&old_text, old_end);
|
||||
|
||||
// TODO: Position also needs to be byte based...
|
||||
// let byte = char_to_byte(old_pos)
|
||||
|
@ -475,7 +485,7 @@ impl LanguageLayer {
|
|||
};
|
||||
}
|
||||
Insert(s) => {
|
||||
let (start_byte, start_position) = point_at_pos(&text, old_pos);
|
||||
let (start_byte, start_position) = point_at_pos(&old_text, old_pos);
|
||||
|
||||
let ins = s.chars().count();
|
||||
|
||||
|
@ -501,6 +511,7 @@ impl LanguageLayer {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
config: &HighlightConfiguration,
|
||||
old_source: &Rope,
|
||||
source: &Rope,
|
||||
changeset: &ChangeSet,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -508,7 +519,7 @@ impl LanguageLayer {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let edits = Self::generate_edits(&source.slice(..), changeset);
|
||||
let edits = Self::generate_edits(&old_source.slice(..), changeset);
|
||||
|
||||
// Notify the tree about all the changes
|
||||
for edit in edits {
|
||||
|
@ -1530,4 +1541,23 @@ fn test_input_edits() {
|
|||
}
|
||||
]
|
||||
);
|
||||
|
||||
// Testing with the official example from tree-sitter
|
||||
let mut state = State::new("fn test() {}".into());
|
||||
let transaction = Transaction::change(&state, vec![(8, 8, Some("a: u32".into()))].into_iter());
|
||||
let edits = LanguageLayer::generate_edits(&state.doc.slice(..), &transaction.changes);
|
||||
transaction.apply(&mut state);
|
||||
|
||||
assert_eq!(state.doc(), "fn test(a: u32) {}");
|
||||
assert_eq!(
|
||||
edits,
|
||||
&[InputEdit {
|
||||
start_byte: 8,
|
||||
old_end_byte: 8,
|
||||
new_end_byte: 14,
|
||||
start_position: Point { row: 0, column: 8 },
|
||||
old_end_position: Point { row: 0, column: 8 },
|
||||
new_end_position: Point { row: 0, column: 14 }
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -326,9 +326,20 @@ impl Transaction {
|
|||
|
||||
/// Returns true if applied successfully.
|
||||
pub fn apply(&self, state: &mut State) -> bool {
|
||||
// apply changes to the document
|
||||
if !self.changes.apply(&mut state.doc) {
|
||||
return false;
|
||||
if !self.changes.is_empty() {
|
||||
// TODO: also avoid mapping the selection if not necessary
|
||||
|
||||
let old_doc = state.doc().clone();
|
||||
|
||||
// apply changes to the document
|
||||
if !self.changes.apply(&mut state.doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(syntax) = &mut state.syntax {
|
||||
// TODO: no unwrap
|
||||
syntax.update(&old_doc, &state.doc, &self.changes).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// update the selection: either take the selection specified in the transaction, or map the
|
||||
|
@ -338,14 +349,6 @@ impl Transaction {
|
|||
.clone()
|
||||
.unwrap_or_else(|| state.selection.clone().map(&self.changes));
|
||||
|
||||
// TODO: no unwrap
|
||||
state
|
||||
.syntax
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.update(&state.doc, &self.changes)
|
||||
.unwrap();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use helix_core::{
|
||||
graphemes,
|
||||
regex::Regex,
|
||||
selection,
|
||||
state::{Direction, Granularity, Mode, State},
|
||||
Range, Selection, Tendril, Transaction,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::view::View;
|
||||
|
||||
|
@ -144,6 +147,14 @@ pub fn extend_line_down(view: &mut View, count: usize) {
|
|||
.extend_selection(Direction::Forward, Granularity::Line, count);
|
||||
}
|
||||
|
||||
pub fn split_selection_on_newline(view: &mut View, _count: usize) {
|
||||
let text = &view.state.doc.slice(..);
|
||||
// only compile the regex once
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\n").unwrap());
|
||||
// TODO: use a transaction
|
||||
view.state.selection = selection::split_on_matches(text, view.state.selection(), ®EX)
|
||||
}
|
||||
|
||||
pub fn delete_selection(view: &mut View, _count: usize) {
|
||||
let transaction =
|
||||
Transaction::change_by_selection(&view.state, |range| (range.from(), range.to(), None));
|
||||
|
|
|
@ -174,6 +174,10 @@ pub fn default() -> Keymaps {
|
|||
code: KeyCode::Char('c'),
|
||||
modifiers: Modifiers::NONE
|
||||
}] => commands::change_selection as Command,
|
||||
vec![Key {
|
||||
code: KeyCode::Char('s'),
|
||||
modifiers: Modifiers::NONE
|
||||
}] => commands::split_selection_on_newline as Command,
|
||||
vec![Key {
|
||||
code: KeyCode::Esc,
|
||||
modifiers: Modifiers::NONE
|
||||
|
|
Loading…
Add table
Reference in a new issue