Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/snippets/render.rs')
-rw-r--r--helix-core/src/snippets/render.rs355
1 files changed, 0 insertions, 355 deletions
diff --git a/helix-core/src/snippets/render.rs b/helix-core/src/snippets/render.rs
deleted file mode 100644
index e5a7d9bb..00000000
--- a/helix-core/src/snippets/render.rs
+++ /dev/null
@@ -1,355 +0,0 @@
-use std::borrow::Cow;
-use std::ops::{Index, IndexMut};
-use std::sync::Arc;
-
-use helix_stdx::Range;
-use ropey::{Rope, RopeSlice};
-use smallvec::SmallVec;
-
-use crate::indent::{normalize_indentation, IndentStyle};
-use crate::movement::Direction;
-use crate::snippets::elaborate;
-use crate::snippets::TabstopIdx;
-use crate::snippets::{Snippet, SnippetElement, Transform};
-use crate::{selection, Selection, Tendril, Transaction};
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum TabstopKind {
- Choice { choices: Arc<[Tendril]> },
- Placeholder,
- Empty,
- Transform(Arc<Transform>),
-}
-
-#[derive(Debug, PartialEq)]
-pub struct Tabstop {
- pub ranges: SmallVec<[Range; 1]>,
- pub parent: Option<TabstopIdx>,
- pub kind: TabstopKind,
-}
-
-impl Tabstop {
- pub fn has_placeholder(&self) -> bool {
- matches!(
- self.kind,
- TabstopKind::Choice { .. } | TabstopKind::Placeholder
- )
- }
-
- pub fn selection(
- &self,
- direction: Direction,
- primary_idx: usize,
- snippet_ranges: usize,
- ) -> Selection {
- Selection::new(
- self.ranges
- .iter()
- .map(|&range| {
- let mut range = selection::Range::new(range.start, range.end);
- if direction == Direction::Backward {
- range = range.flip()
- }
- range
- })
- .collect(),
- primary_idx * (self.ranges.len() / snippet_ranges),
- )
- }
-}
-
-#[derive(Debug, Default, PartialEq)]
-pub struct RenderedSnippet {
- pub tabstops: Vec<Tabstop>,
- pub ranges: Vec<Range>,
-}
-
-impl RenderedSnippet {
- pub fn first_selection(&self, direction: Direction, primary_idx: usize) -> Selection {
- self.tabstops[0].selection(direction, primary_idx, self.ranges.len())
- }
-}
-
-impl Index<TabstopIdx> for RenderedSnippet {
- type Output = Tabstop;
- fn index(&self, index: TabstopIdx) -> &Tabstop {
- &self.tabstops[index.0]
- }
-}
-
-impl IndexMut<TabstopIdx> for RenderedSnippet {
- fn index_mut(&mut self, index: TabstopIdx) -> &mut Tabstop {
- &mut self.tabstops[index.0]
- }
-}
-
-impl Snippet {
- pub fn prepare_render(&self) -> RenderedSnippet {
- let tabstops =
- self.tabstops()
- .map(|tabstop| Tabstop {
- ranges: SmallVec::new(),
- parent: tabstop.parent,
- kind: match &tabstop.kind {
- elaborate::TabstopKind::Choice { choices } => TabstopKind::Choice {
- choices: choices.clone(),
- },
- // start out as empty: the first non-empty placeholder will change this to
- // a placeholder automatically
- elaborate::TabstopKind::Empty
- | elaborate::TabstopKind::Placeholder { .. } => TabstopKind::Empty,
- elaborate::TabstopKind::Transform(transform) => {
- TabstopKind::Transform(transform.clone())
- }
- },
- })
- .collect();
- RenderedSnippet {
- tabstops,
- ranges: Vec::new(),
- }
- }
-
- pub fn render_at(
- &self,
- snippet: &mut RenderedSnippet,
- indent: RopeSlice<'_>,
- at_newline: bool,
- ctx: &mut SnippetRenderCtx,
- pos: usize,
- ) -> (Tendril, usize) {
- let mut ctx = SnippetRender {
- dst: snippet,
- src: self,
- indent,
- text: Tendril::new(),
- off: pos,
- ctx,
- at_newline,
- };
- ctx.render_elements(self.elements());
- let end = ctx.off;
- let text = ctx.text;
- snippet.ranges.push(Range { start: pos, end });
- (text, end - pos)
- }
-
- pub fn render(
- &self,
- doc: &Rope,
- selection: &Selection,
- change_range: impl FnMut(&selection::Range) -> (usize, usize),
- ctx: &mut SnippetRenderCtx,
- ) -> (Transaction, Selection, RenderedSnippet) {
- let mut snippet = self.prepare_render();
- let mut off = 0;
- let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
- doc,
- selection,
- change_range,
- |replacement_start, replacement_end| {
- let line_idx = doc.char_to_line(replacement_start);
- let line_start = doc.line_to_char(line_idx);
- let prefix = doc.slice(line_start..replacement_start);
- let indent_len = prefix.chars().take_while(|c| c.is_whitespace()).count();
- let indent = prefix.slice(..indent_len);
- let at_newline = indent_len == replacement_start - line_start;
-
- let (replacement, replacement_len) = self.render_at(
- &mut snippet,
- indent,
- at_newline,
- ctx,
- (replacement_start as i128 + off) as usize,
- );
- off +=
- replacement_start as i128 - replacement_end as i128 + replacement_len as i128;
-
- Some(replacement)
- },
- );
- (transaction, selection, snippet)
- }
-}
-
-pub type VariableResolver = dyn FnMut(&str) -> Option<Cow<str>>;
-pub struct SnippetRenderCtx {
- pub resolve_var: Box<VariableResolver>,
- pub tab_width: usize,
- pub indent_style: IndentStyle,
- pub line_ending: &'static str,
-}
-
-impl SnippetRenderCtx {
- #[cfg(test)]
- pub(super) fn test_ctx() -> SnippetRenderCtx {
- SnippetRenderCtx {
- resolve_var: Box::new(|_| None),
- tab_width: 4,
- indent_style: IndentStyle::Spaces(4),
- line_ending: "\n",
- }
- }
-}
-
-struct SnippetRender<'a> {
- ctx: &'a mut SnippetRenderCtx,
- dst: &'a mut RenderedSnippet,
- src: &'a Snippet,
- indent: RopeSlice<'a>,
- text: Tendril,
- off: usize,
- at_newline: bool,
-}
-
-impl SnippetRender<'_> {
- fn render_elements(&mut self, elements: &[SnippetElement]) {
- for element in elements {
- self.render_element(element)
- }
- }
-
- fn render_element(&mut self, element: &SnippetElement) {
- match *element {
- SnippetElement::Tabstop { idx } => self.render_tabstop(idx),
- SnippetElement::Variable {
- ref name,
- ref default,
- ref transform,
- } => {
- // TODO: allow resolve_var access to the doc and make it return rope slice
- // so we can access selections and other document content without allocating
- if let Some(val) = (self.ctx.resolve_var)(name) {
- if let Some(transform) = transform {
- self.push_multiline_str(&transform.apply(
- (&*val).into(),
- Range {
- start: 0,
- end: val.chars().count(),
- },
- ));
- } else {
- self.push_multiline_str(&val)
- }
- } else if let Some(default) = default {
- self.render_elements(default)
- }
- }
- SnippetElement::Text(ref text) => self.push_multiline_str(text),
- }
- }
-
- fn push_multiline_str(&mut self, text: &str) {
- let mut lines = text
- .split('\n')
- .map(|line| line.strip_suffix('\r').unwrap_or(line));
- let first_line = lines.next().unwrap();
- self.push_str(first_line, self.at_newline);
- for line in lines {
- self.push_newline();
- self.push_str(line, true);
- }
- }
-
- fn push_str(&mut self, mut text: &str, at_newline: bool) {
- if at_newline {
- let old_len = self.text.len();
- let old_indent_len = normalize_indentation(
- self.indent,
- text.into(),
- &mut self.text,
- self.ctx.indent_style,
- self.ctx.tab_width,
- );
- // this is ok because indentation can only be ascii chars (' ' and '\t')
- self.off += self.text.len() - old_len;
- text = &text[old_indent_len..];
- if text.is_empty() {
- self.at_newline = true;
- return;
- }
- }
- self.text.push_str(text);
- self.off += text.chars().count();
- }
-
- fn push_newline(&mut self) {
- self.off += self.ctx.line_ending.chars().count() + self.indent.len_chars();
- self.text.push_str(self.ctx.line_ending);
- self.text.extend(self.indent.chunks());
- }
-
- fn render_tabstop(&mut self, tabstop: TabstopIdx) {
- let start = self.off;
- let end = match &self.src[tabstop].kind {
- elaborate::TabstopKind::Placeholder { default } if !default.is_empty() => {
- self.render_elements(default);
- self.dst[tabstop].kind = TabstopKind::Placeholder;
- self.off
- }
- _ => start,
- };
- self.dst[tabstop].ranges.push(Range { start, end });
- }
-}
-
-#[cfg(test)]
-mod tests {
- use helix_stdx::Range;
-
- use crate::snippets::render::Tabstop;
- use crate::snippets::{Snippet, SnippetRenderCtx};
-
- use super::TabstopKind;
-
- fn assert_snippet(snippet: &str, expect: &str, tabstops: &[Tabstop]) {
- let snippet = Snippet::parse(snippet).unwrap();
- let mut rendered_snippet = snippet.prepare_render();
- let rendered_text = snippet
- .render_at(
- &mut rendered_snippet,
- "\t".into(),
- false,
- &mut SnippetRenderCtx::test_ctx(),
- 0,
- )
- .0;
- assert_eq!(rendered_text, expect);
- assert_eq!(&rendered_snippet.tabstops, tabstops);
- assert_eq!(
- rendered_snippet.ranges.last().unwrap().end,
- rendered_text.chars().count()
- );
- assert_eq!(rendered_snippet.ranges.last().unwrap().start, 0)
- }
-
- #[test]
- fn rust_macro() {
- assert_snippet(
- "macro_rules! ${1:name} {\n\t($3) => {\n\t\t$2\n\t};\n}",
- "macro_rules! name {\n\t () => {\n\t \n\t };\n\t}",
- &[
- Tabstop {
- ranges: vec![Range { start: 13, end: 17 }].into(),
- parent: None,
- kind: TabstopKind::Placeholder,
- },
- Tabstop {
- ranges: vec![Range { start: 42, end: 42 }].into(),
- parent: None,
- kind: TabstopKind::Empty,
- },
- Tabstop {
- ranges: vec![Range { start: 26, end: 26 }].into(),
- parent: None,
- kind: TabstopKind::Empty,
- },
- Tabstop {
- ranges: vec![Range { start: 53, end: 53 }].into(),
- parent: None,
- kind: TabstopKind::Empty,
- },
- ],
- );
- }
-}