Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/snippets/active.rs')
| -rw-r--r-- | helix-core/src/snippets/active.rs | 272 |
1 files changed, 0 insertions, 272 deletions
diff --git a/helix-core/src/snippets/active.rs b/helix-core/src/snippets/active.rs deleted file mode 100644 index 1c10b76d..00000000 --- a/helix-core/src/snippets/active.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::ops::{Index, IndexMut}; - -use foldhash::HashSet; -use helix_stdx::range::{is_exact_subset, is_subset}; -use helix_stdx::Range; -use ropey::Rope; - -use crate::movement::Direction; -use crate::snippets::render::{RenderedSnippet, Tabstop}; -use crate::snippets::TabstopIdx; -use crate::{Assoc, ChangeSet, Selection, Transaction}; - -pub struct ActiveSnippet { - ranges: Vec<Range>, - active_tabstops: HashSet<TabstopIdx>, - current_tabstop: TabstopIdx, - tabstops: Vec<Tabstop>, -} - -impl Index<TabstopIdx> for ActiveSnippet { - type Output = Tabstop; - fn index(&self, index: TabstopIdx) -> &Tabstop { - &self.tabstops[index.0] - } -} - -impl IndexMut<TabstopIdx> for ActiveSnippet { - fn index_mut(&mut self, index: TabstopIdx) -> &mut Tabstop { - &mut self.tabstops[index.0] - } -} - -impl ActiveSnippet { - pub fn new(snippet: RenderedSnippet) -> Option<Self> { - let snippet = Self { - ranges: snippet.ranges, - tabstops: snippet.tabstops, - active_tabstops: HashSet::default(), - current_tabstop: TabstopIdx(0), - }; - (snippet.tabstops.len() != 1).then_some(snippet) - } - - pub fn is_valid(&self, new_selection: &Selection) -> bool { - is_subset::<false>(self.ranges.iter().copied(), new_selection.range_bounds()) - } - - pub fn tabstops(&self) -> impl Iterator<Item = &Tabstop> { - self.tabstops.iter() - } - - pub fn delete_placeholder(&self, doc: &Rope) -> Transaction { - Transaction::delete( - doc, - self[self.current_tabstop] - .ranges - .iter() - .map(|range| (range.start, range.end)), - ) - } - - /// maps the active snippets through a `ChangeSet` updating all tabstop ranges - pub fn map(&mut self, changes: &ChangeSet) -> bool { - let positions_to_map = self.ranges.iter_mut().flat_map(|range| { - [ - (&mut range.start, Assoc::After), - (&mut range.end, Assoc::Before), - ] - }); - changes.update_positions(positions_to_map); - - for (i, tabstop) in self.tabstops.iter_mut().enumerate() { - if self.active_tabstops.contains(&TabstopIdx(i)) { - let positions_to_map = tabstop.ranges.iter_mut().flat_map(|range| { - let end_assoc = if range.start == range.end { - Assoc::Before - } else { - Assoc::After - }; - [ - (&mut range.start, Assoc::Before), - (&mut range.end, end_assoc), - ] - }); - changes.update_positions(positions_to_map); - } else { - let positions_to_map = tabstop.ranges.iter_mut().flat_map(|range| { - let end_assoc = if range.start == range.end { - Assoc::After - } else { - Assoc::Before - }; - [ - (&mut range.start, Assoc::After), - (&mut range.end, end_assoc), - ] - }); - changes.update_positions(positions_to_map); - } - let mut snippet_ranges = self.ranges.iter(); - let mut snippet_range = snippet_ranges.next().unwrap(); - let mut tabstop_i = 0; - let mut prev = Range { start: 0, end: 0 }; - let num_ranges = tabstop.ranges.len() / self.ranges.len(); - tabstop.ranges.retain_mut(|range| { - if tabstop_i == num_ranges { - snippet_range = snippet_ranges.next().unwrap(); - tabstop_i = 0; - } - tabstop_i += 1; - let retain = snippet_range.start <= snippet_range.end; - if retain { - range.start = range.start.max(snippet_range.start); - range.end = range.end.max(range.start).min(snippet_range.end); - // guaranteed by assoc - debug_assert!(prev.start <= range.start); - debug_assert!(range.start <= range.end); - if prev.end > range.start { - // not really sure what to do in this case. It shouldn't - // really occur in practice, the below just ensures - // our invariants hold - range.start = prev.end; - range.end = range.end.max(range.start) - } - prev = *range; - } - retain - }); - } - self.ranges.iter().all(|range| range.end <= range.start) - } - - pub fn next_tabstop(&mut self, current_selection: &Selection) -> (Selection, bool) { - let primary_idx = self.primary_idx(current_selection); - while self.current_tabstop.0 + 1 < self.tabstops.len() { - self.current_tabstop.0 += 1; - if self.activate_tabstop() { - let selection = self.tabstop_selection(primary_idx, Direction::Forward); - return (selection, self.current_tabstop.0 + 1 == self.tabstops.len()); - } - } - - ( - self.tabstop_selection(primary_idx, Direction::Forward), - true, - ) - } - - pub fn prev_tabstop(&mut self, current_selection: &Selection) -> Option<Selection> { - let primary_idx = self.primary_idx(current_selection); - while self.current_tabstop.0 != 0 { - self.current_tabstop.0 -= 1; - if self.activate_tabstop() { - return Some(self.tabstop_selection(primary_idx, Direction::Forward)); - } - } - None - } - // computes the primary idx adjusted for the number of cursors in the current tabstop - fn primary_idx(&self, current_selection: &Selection) -> usize { - let primary: Range = current_selection.primary().into(); - let res = self - .ranges - .iter() - .position(|&range| range.contains(primary)); - res.unwrap_or_else(|| { - unreachable!( - "active snippet must be valid {current_selection:?} {:?}", - self.ranges - ) - }) - } - - fn activate_tabstop(&mut self) -> bool { - let tabstop = &self[self.current_tabstop]; - if tabstop.has_placeholder() && tabstop.ranges.iter().all(|range| range.is_empty()) { - return false; - } - self.active_tabstops.clear(); - self.active_tabstops.insert(self.current_tabstop); - let mut parent = self[self.current_tabstop].parent; - while let Some(tabstop) = parent { - self.active_tabstops.insert(tabstop); - parent = self[tabstop].parent; - } - true - // TODO: if the user removes the selection(s) in one snippet (but - // there are still other cursors in other snippets) and jumps to the - // next tabstop the selection in that tabstop is restored (at the - // next tabstop). This could be annoying since its not possible to - // remove a snippet cursor until the snippet is complete. On the other - // hand it may be useful since the user may just have meant to edit - // a subselection (like with s) of the tabstops and so the selection - // removal was just temporary. Potentially this could have some sort of - // separate keymap - } - - pub fn tabstop_selection(&self, primary_idx: usize, direction: Direction) -> Selection { - let tabstop = &self[self.current_tabstop]; - tabstop.selection(direction, primary_idx, self.ranges.len()) - } - - pub fn insert_subsnippet(mut self, snippet: RenderedSnippet) -> Option<Self> { - if snippet.ranges.len() % self.ranges.len() != 0 - || !is_exact_subset(self.ranges.iter().copied(), snippet.ranges.iter().copied()) - { - log::warn!("number of subsnippets did not match, discarding outer snippet"); - return ActiveSnippet::new(snippet); - } - let mut cnt = 0; - let parent = self[self.current_tabstop].parent; - let tabstops = snippet.tabstops.into_iter().map(|mut tabstop| { - cnt += 1; - if let Some(parent) = &mut tabstop.parent { - parent.0 += self.current_tabstop.0; - } else { - tabstop.parent = parent; - } - tabstop - }); - self.tabstops - .splice(self.current_tabstop.0..=self.current_tabstop.0, tabstops); - self.activate_tabstop(); - Some(self) - } -} - -#[cfg(test)] -mod tests { - use std::iter::{self}; - - use ropey::Rope; - - use crate::snippets::{ActiveSnippet, Snippet, SnippetRenderCtx}; - use crate::{Selection, Transaction}; - - #[test] - fn fully_remove() { - let snippet = Snippet::parse("foo(${1:bar})$0").unwrap(); - let mut doc = Rope::from("bar.\n"); - let (transaction, _, snippet) = snippet.render( - &doc, - &Selection::point(4), - |_| (4, 4), - &mut SnippetRenderCtx::test_ctx(), - ); - assert!(transaction.apply(&mut doc)); - assert_eq!(doc, "bar.foo(bar)\n"); - let mut snippet = ActiveSnippet::new(snippet).unwrap(); - let edit = Transaction::change(&doc, iter::once((4, 12, None))); - assert!(edit.apply(&mut doc)); - snippet.map(edit.changes()); - assert!(!snippet.is_valid(&Selection::point(4))) - } - - #[test] - fn tabstop_zero_with_placeholder() { - // The `$0` tabstop should not have placeholder text. When we receive a snippet like this - // (from older versions of clangd for example) we should discard the placeholder text. - let snippet = Snippet::parse("sizeof(${0:expression-or-type})").unwrap(); - let mut doc = Rope::from("\n"); - let (transaction, _, snippet) = snippet.render( - &doc, - &Selection::point(0), - |_| (0, 0), - &mut SnippetRenderCtx::test_ctx(), - ); - assert!(transaction.apply(&mut doc)); - assert_eq!(doc, "sizeof()\n"); - assert!(ActiveSnippet::new(snippet).is_none()); - } -} |