Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/snippets/elaborate.rs')
| -rw-r--r-- | helix-core/src/snippets/elaborate.rs | 385 |
1 files changed, 0 insertions, 385 deletions
diff --git a/helix-core/src/snippets/elaborate.rs b/helix-core/src/snippets/elaborate.rs deleted file mode 100644 index b17c149f..00000000 --- a/helix-core/src/snippets/elaborate.rs +++ /dev/null @@ -1,385 +0,0 @@ -use std::mem::swap; -use std::ops::Index; -use std::sync::Arc; - -use anyhow::{anyhow, Result}; -use helix_stdx::rope::RopeSliceExt; -use helix_stdx::Range; -use regex_cursor::engines::meta::Builder as RegexBuilder; -use regex_cursor::engines::meta::Regex; -use regex_cursor::regex_automata::util::syntax::Config as RegexConfig; -use ropey::RopeSlice; - -use crate::case_conversion::to_lower_case_with; -use crate::case_conversion::to_upper_case_with; -use crate::case_conversion::{to_camel_case_with, to_pascal_case_with}; -use crate::snippets::parser::{self, CaseChange, FormatItem}; -use crate::snippets::{TabstopIdx, LAST_TABSTOP_IDX}; -use crate::Tendril; - -#[derive(Debug)] -pub struct Snippet { - elements: Vec<SnippetElement>, - tabstops: Vec<Tabstop>, -} - -impl Snippet { - pub fn parse(snippet: &str) -> Result<Self> { - let parsed_snippet = parser::parse(snippet) - .map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))?; - Ok(Snippet::new(parsed_snippet)) - } - - pub fn new(elements: Vec<parser::SnippetElement>) -> Snippet { - let mut res = Snippet { - elements: Vec::new(), - tabstops: Vec::new(), - }; - res.elements = res.elaborate(elements, None).into(); - res.fixup_tabstops(); - res.ensure_last_tabstop(); - res.renumber_tabstops(); - res - } - - pub fn elements(&self) -> &[SnippetElement] { - &self.elements - } - - pub fn tabstops(&self) -> impl Iterator<Item = &Tabstop> { - self.tabstops.iter() - } - - fn renumber_tabstops(&mut self) { - Self::renumber_tabstops_in(&self.tabstops, &mut self.elements); - for i in 0..self.tabstops.len() { - if let Some(parent) = self.tabstops[i].parent { - let parent = self - .tabstops - .binary_search_by_key(&parent, |tabstop| tabstop.idx) - .expect("all tabstops have been resolved"); - self.tabstops[i].parent = Some(TabstopIdx(parent)); - } - let tabstop = &mut self.tabstops[i]; - if let TabstopKind::Placeholder { default } = &tabstop.kind { - let mut default = default.clone(); - tabstop.kind = TabstopKind::Empty; - Self::renumber_tabstops_in(&self.tabstops, Arc::get_mut(&mut default).unwrap()); - self.tabstops[i].kind = TabstopKind::Placeholder { default }; - } - } - } - - fn renumber_tabstops_in(tabstops: &[Tabstop], elements: &mut [SnippetElement]) { - for elem in elements { - match elem { - SnippetElement::Tabstop { idx } => { - idx.0 = tabstops - .binary_search_by_key(&*idx, |tabstop| tabstop.idx) - .expect("all tabstops have been resolved") - } - SnippetElement::Variable { default, .. } => { - if let Some(default) = default { - Self::renumber_tabstops_in(tabstops, default); - } - } - SnippetElement::Text(_) => (), - } - } - } - - fn fixup_tabstops(&mut self) { - self.tabstops.sort_by_key(|tabstop| tabstop.idx); - self.tabstops.dedup_by(|tabstop1, tabstop2| { - if tabstop1.idx != tabstop2.idx { - return false; - } - // use the first non empty tabstop for all multicursor tabstops - if tabstop2.kind.is_empty() { - swap(tabstop2, tabstop1) - } - true - }) - } - - fn ensure_last_tabstop(&mut self) { - if matches!(self.tabstops.last(), Some(tabstop) if tabstop.idx == LAST_TABSTOP_IDX) { - return; - } - self.tabstops.push(Tabstop { - idx: LAST_TABSTOP_IDX, - parent: None, - kind: TabstopKind::Empty, - }); - self.elements.push(SnippetElement::Tabstop { - idx: LAST_TABSTOP_IDX, - }) - } - - fn elaborate( - &mut self, - default: Vec<parser::SnippetElement>, - parent: Option<TabstopIdx>, - ) -> Box<[SnippetElement]> { - default - .into_iter() - .map(|val| match val { - parser::SnippetElement::Tabstop { - tabstop, - transform: None, - } => SnippetElement::Tabstop { - idx: self.elaborate_placeholder(tabstop, parent, Vec::new()), - }, - parser::SnippetElement::Tabstop { - tabstop, - transform: Some(transform), - } => SnippetElement::Tabstop { - idx: self.elaborate_transform(tabstop, parent, transform), - }, - parser::SnippetElement::Placeholder { tabstop, value } => SnippetElement::Tabstop { - idx: self.elaborate_placeholder(tabstop, parent, value), - }, - parser::SnippetElement::Choice { tabstop, choices } => SnippetElement::Tabstop { - idx: self.elaborate_choice(tabstop, parent, choices), - }, - parser::SnippetElement::Variable { - name, - default, - transform, - } => SnippetElement::Variable { - name, - default: default.map(|default| self.elaborate(default, parent)), - // TODO: error for invalid transforms - transform: transform.and_then(Transform::new).map(Box::new), - }, - parser::SnippetElement::Text(text) => SnippetElement::Text(text), - }) - .collect() - } - - fn elaborate_choice( - &mut self, - idx: usize, - parent: Option<TabstopIdx>, - choices: Vec<Tendril>, - ) -> TabstopIdx { - let idx = TabstopIdx::elaborate(idx); - self.tabstops.push(Tabstop { - idx, - parent, - kind: TabstopKind::Choice { - choices: choices.into(), - }, - }); - idx - } - - fn elaborate_placeholder( - &mut self, - idx: usize, - parent: Option<TabstopIdx>, - mut default: Vec<parser::SnippetElement>, - ) -> TabstopIdx { - let idx = TabstopIdx::elaborate(idx); - if idx == LAST_TABSTOP_IDX && !default.is_empty() { - // Older versions of clangd for example may send a snippet like `${0:placeholder}` - // which is considered by VSCode to be a misuse of the `$0` tabstop. - log::warn!("Discarding placeholder text for the `$0` tabstop ({default:?}). \ - The `$0` tabstop signifies the final cursor position and should not include placeholder text."); - default.clear(); - } - let default = self.elaborate(default, Some(idx)); - self.tabstops.push(Tabstop { - idx, - parent, - kind: TabstopKind::Placeholder { - default: default.into(), - }, - }); - idx - } - - fn elaborate_transform( - &mut self, - idx: usize, - parent: Option<TabstopIdx>, - transform: parser::Transform, - ) -> TabstopIdx { - let idx = TabstopIdx::elaborate(idx); - if let Some(transform) = Transform::new(transform) { - self.tabstops.push(Tabstop { - idx, - parent, - kind: TabstopKind::Transform(Arc::new(transform)), - }) - } else { - // TODO: proper error - self.tabstops.push(Tabstop { - idx, - parent, - kind: TabstopKind::Empty, - }) - } - idx - } -} - -impl Index<TabstopIdx> for Snippet { - type Output = Tabstop; - fn index(&self, index: TabstopIdx) -> &Tabstop { - &self.tabstops[index.0] - } -} - -#[derive(Debug)] -pub enum SnippetElement { - Tabstop { - idx: TabstopIdx, - }, - Variable { - name: Tendril, - default: Option<Box<[SnippetElement]>>, - transform: Option<Box<Transform>>, - }, - Text(Tendril), -} - -#[derive(Debug)] -pub struct Tabstop { - idx: TabstopIdx, - pub parent: Option<TabstopIdx>, - pub kind: TabstopKind, -} - -#[derive(Debug)] -pub enum TabstopKind { - Choice { choices: Arc<[Tendril]> }, - Placeholder { default: Arc<[SnippetElement]> }, - Empty, - Transform(Arc<Transform>), -} - -impl TabstopKind { - pub fn is_empty(&self) -> bool { - matches!(self, TabstopKind::Empty) - } -} - -#[derive(Debug)] -pub struct Transform { - regex: Regex, - regex_str: Box<str>, - global: bool, - replacement: Box<[FormatItem]>, -} - -impl PartialEq for Transform { - fn eq(&self, other: &Self) -> bool { - self.replacement == other.replacement - && self.global == other.global - // doens't compare m and i setting but close enough - && self.regex_str == other.regex_str - } -} - -impl Transform { - fn new(transform: parser::Transform) -> Option<Transform> { - let mut config = RegexConfig::new(); - let mut global = false; - let mut invalid_config = false; - for c in transform.options.chars() { - match c { - 'i' => { - config = config.case_insensitive(true); - } - 'm' => { - config = config.multi_line(true); - } - 'g' => { - global = true; - } - // we ignore 'u' since we always want to - // do unicode aware matching - _ => invalid_config = true, - } - } - if invalid_config { - log::error!("invalid transform configuration characters {transform:?}"); - } - let regex = match RegexBuilder::new().syntax(config).build(&transform.regex) { - Ok(regex) => regex, - Err(err) => { - log::error!("invalid transform {err} {transform:?}"); - return None; - } - }; - Some(Transform { - regex, - regex_str: transform.regex.as_str().into(), - global, - replacement: transform.replacement.into(), - }) - } - - pub fn apply(&self, mut doc: RopeSlice<'_>, range: Range) -> Tendril { - let mut buf = Tendril::new(); - let it = self - .regex - .captures_iter(doc.regex_input_at(range)) - .enumerate(); - doc = doc.slice(range); - let mut last_match = 0; - for (_, cap) in it { - // unwrap on 0 is OK because captures only reports matches - let m = cap.get_group(0).unwrap(); - buf.extend(doc.byte_slice(last_match..m.start).chunks()); - last_match = m.end; - for fmt in &*self.replacement { - match *fmt { - FormatItem::Text(ref text) => { - buf.push_str(text); - } - FormatItem::Capture(i) => { - if let Some(cap) = cap.get_group(i) { - buf.extend(doc.byte_slice(cap.range()).chunks()); - } - } - FormatItem::CaseChange(i, change) => { - if let Some(cap) = cap.get_group(i).filter(|i| !i.is_empty()) { - let mut chars = doc.byte_slice(cap.range()).chars(); - match change { - CaseChange::Upcase => to_upper_case_with(chars, &mut buf), - CaseChange::Downcase => to_lower_case_with(chars, &mut buf), - CaseChange::Capitalize => { - let first_char = chars.next().unwrap(); - buf.extend(first_char.to_uppercase()); - buf.extend(chars); - } - CaseChange::PascalCase => to_pascal_case_with(chars, &mut buf), - CaseChange::CamelCase => to_camel_case_with(chars, &mut buf), - } - } - } - FormatItem::Conditional(i, ref if_, ref else_) => { - if cap.get_group(i).is_none_or(|mat| mat.is_empty()) { - buf.push_str(else_) - } else { - buf.push_str(if_) - } - } - } - } - if !self.global { - break; - } - } - buf.extend(doc.byte_slice(last_match..).chunks()); - buf - } -} - -impl TabstopIdx { - fn elaborate(idx: usize) -> Self { - TabstopIdx(idx.wrapping_sub(1)) - } -} |