@@ -1,9 +1,7 @@
1use std::borrow::Cow;
1use std::borrow::Cow;
3use anyhow::{anyhow, Result};
3use anyhow::{anyhow, Result};
4−use helix_core::SmallVec;
4+use helix_core::{SmallVec, smallvec};
6−use crate::{util::lsp_pos_to_pos, OffsetEncoding};
8#[derive(Debug, PartialEq, Eq)]
6#[derive(Debug, PartialEq, Eq)]
9pub enum CaseChange {
7pub enum CaseChange {
@@ -34,7 +32,7 @@ pub enum SnippetElement<'a> {
35 Placeholder {
33 Placeholder {
36 tabstop: usize,
34 tabstop: usize,
37− value: Box<SnippetElement<'a>>,
35+ value: Vec<SnippetElement<'a>>,
40 tabstop: usize,
38 tabstop: usize,
@@ -57,141 +55,108 @@ pub fn parse(s: &str) -> Result<Snippet<'_>> {
57 parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
55 parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
60−pub fn into_transaction<'a>(
58+fn render_elements(
61− snippet: Snippet<'a>,
59+ snippet_elements: &[SnippetElement<'_>],
62− doc: &helix_core::Rope,
60+ insert: &mut String,
63− selection: &helix_core::Selection,
61+ offset: &mut usize,
64− edit: &lsp_types::TextEdit,
62+ tabstops: &mut Vec<(usize, (usize, usize))>,
65− line_ending: &str,
63+ newline_with_offset: &String,
66− offset_encoding: OffsetEncoding,
67 include_placeholer: bool,
64 include_placeholer: bool,
68−) -> helix_core::Transaction {
65+) {
69− use helix_core::{smallvec, Range, Transaction};
70 use SnippetElement::*;
66 use SnippetElement::*;
72− let text = doc.slice(..);
73− let primary_cursor = selection.primary().cursor(text);
75− let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
76− Some(start) => start as i128 - primary_cursor as i128,
77− None => return Transaction::new(doc),
79− let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
80− Some(end) => end as i128 - primary_cursor as i128,
81− None => return Transaction::new(doc),
84− let newline_with_offset = format!(
85− "{line_ending}{blank:width$}",
86− width = edit.range.start.character as usize,
91− let mut insert = String::new();
92− let mut tabstops: Vec<(usize, usize, usize)> = Vec::new();
94− for element in snippet.elements {
68+ for element in snippet_elements {
95 match element {
69 match element {
96− Text(text) => {
70+ &Text(text) => {
97 // small optimization to avoid calling replace when it's unnecessary
71 // small optimization to avoid calling replace when it's unnecessary
98 let text = if text.contains('\n') {
72 let text = if text.contains('\n') {
99− Cow::Owned(text.replace('\n', &newline_with_offset))
73+ Cow::Owned(text.replace('\n', newline_with_offset))
101 Cow::Borrowed(text)
75 Cow::Borrowed(text)
103− offset += text.chars().count();
77+ *offset += text.chars().count();
104 insert.push_str(&text);
78 insert.push_str(&text);
106− Variable {
80+ &Variable {
107− name: _name,
81+ name: _,
108− regex: None,
82+ regex: _,
109 r#default,
83 r#default,
111 // TODO: variables. For now, fall back to the default, which defaults to "".
85 // TODO: variables. For now, fall back to the default, which defaults to "".
112 let text = r#default.unwrap_or_default();
86 let text = r#default.unwrap_or_default();
113− offset += text.chars().count();
87+ *offset += text.chars().count();
114 insert.push_str(text);
88 insert.push_str(text);
116− Tabstop { tabstop } => {
90+ &Tabstop { tabstop } => {
117− tabstops.push((tabstop, offset, offset));
91+ tabstops.push((tabstop, (*offset, *offset)));
119− Placeholder { tabstop, value } => match value.as_ref() {
93+ Placeholder {
120− // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html
94+ tabstop,
121− // would make this a bit nicer
95+ value: inner_snippet_elements,
122− Text(text) => {
96+ } => {
123− if include_placeholer {
97+ let start_offset = *offset;
124− let len_chars = text.chars().count();
98+ if include_placeholer {
125− tabstops.push((tabstop, offset, offset + len_chars + 1));
99+ render_elements(
126− offset += len_chars;
100+ inner_snippet_elements,
127− insert.push_str(text);
101+ insert,
128− } else {
102+ offset,
129− tabstops.push((tabstop, offset, offset));
103+ tabstops,
130− }
104+ newline_with_offset,
131− }
105+ include_placeholer,
134− "Discarding snippet: generating a transaction for placeholder contents {:?} is unimplemented.",
137− return Transaction::new(doc);
108+ tabstops.push((*tabstop, (start_offset, *offset)));
140− other => {
113+ } => {
141− log::error!(
114+ // TODO: choices
142− "Discarding snippet: generating a transaction for {:?} is unimplemented.",
115+ tabstops.push((tabstop, (*offset, *offset)));
145− return Transaction::new(doc);
150− let transaction = Transaction::change_by_selection(doc, selection, |range| {
121+#[allow(clippy::type_complexity)] // only used one time
151− let cursor = range.cursor(text);
122+pub fn render(
152− (
123+ snippet: &Snippet<'_>,
153− (cursor as i128 + start_offset) as usize,
124+ newline_with_offset: String,
154− (cursor as i128 + end_offset) as usize,
125+ include_placeholer: bool,
155− Some(insert.clone().into()),
126+) -> (String, Vec<SmallVec<[(usize, usize); 1]>>) {
156− )
127+ let mut insert = String::new();
128+ let mut tabstops = Vec::new();
136+ &newline_with_offset,
159 // sort in ascending order (except for 0, which should always be the last one (per lsp doc))
140 // sort in ascending order (except for 0, which should always be the last one (per lsp doc))
160− tabstops.sort_unstable_by_key(|(n, _o1, _o2)| if *n == 0 { usize::MAX } else { *n });
141+ tabstops.sort_unstable_by_key(|(n, _)| if *n == 0 { usize::MAX } else { *n });
162 // merge tabstops with the same index (we take advantage of the fact that we just sorted them
143 // merge tabstops with the same index (we take advantage of the fact that we just sorted them
163 // above to simply look backwards)
144 // above to simply look backwards)
164 let mut ntabstops = Vec::<SmallVec<[(usize, usize); 1]>>::new();
145 let mut ntabstops = Vec::<SmallVec<[(usize, usize); 1]>>::new();
166 let mut prev = None;
147 let mut prev = None;
167− for (tabstop, o1, o2) in tabstops {
148+ for (tabstop, r) in tabstops {
168 if prev == Some(tabstop) {
149 if prev == Some(tabstop) {
169 let len_1 = ntabstops.len() - 1;
150 let len_1 = ntabstops.len() - 1;
170− ntabstops[len_1].push((o1, o2));
151+ ntabstops[len_1].push(r);
172 prev = Some(tabstop);
153 prev = Some(tabstop);
173− ntabstops.push(smallvec![(o1, o2)]);
154+ ntabstops.push(smallvec![r]);
178− if let Some(first) = ntabstops.first() {
159+ (insert, ntabstops)
179− let cursor_offset = insert.chars().count() as i128 - (end_offset - start_offset);
180− let mut extra_offset = start_offset;
181− transaction.with_selection(selection.clone().transform_iter(|range| {
182− let cursor = range.cursor(text);
183− let iter = first.iter().map(move |first| {
185− (cursor as i128 + first.0 as i128 + extra_offset) as usize,
186− (cursor as i128 + first.1 as i128 + extra_offset) as usize,
189− extra_offset += cursor_offset;
197mod parser {
162mod parser {
@@ -343,14 +308,15 @@ mod parser {
343 fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
308 fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
344 // TODO: why doesn't parse_as work?
309 // TODO: why doesn't parse_as work?
345 // let value = reparse_as(take_until(|c| c == '}'), anything());
310 // let value = reparse_as(take_until(|c| c == '}'), anything());
311+ // TODO: fix this to parse nested placeholders (take until terminates too early)
346 let value = filter_map(take_until(|c| c == '}'), |s| {
312 let value = filter_map(take_until(|c| c == '}'), |s| {
347− anything().parse(s).map(|parse_result| parse_result.1).ok()
313+ snippet().parse(s).map(|parse_result| parse_result.1).ok()
350 map(seq!("${", digit(), ":", value, "}"), |seq| {
316 map(seq!("${", digit(), ":", value, "}"), |seq| {
351 SnippetElement::Placeholder {
317 SnippetElement::Placeholder {
352 tabstop: seq.1,
318 tabstop: seq.1,
353− value: Box::new(seq.3),
319+ value: seq.3.elements,
@@ -430,7 +396,7 @@ mod parser {
430 Text("match("),
396 Text("match("),
431 Placeholder {
397 Placeholder {
432 tabstop: 1,
398 tabstop: 1,
433− value: Box::new(Text("Arg1")),
399+ value: vec!(Text("Arg1")),
435 Text(")")
401 Text(")")
@@ -447,12 +413,12 @@ mod parser {
447 Text("local "),
413 Text("local "),
448 Placeholder {
414 Placeholder {
449 tabstop: 1,
415 tabstop: 1,
450− value: Box::new(Text("var")),
416+ value: vec!(Text("var")),
452 Text(" = "),
418 Text(" = "),
453 Placeholder {
419 Placeholder {
454 tabstop: 1,
420 tabstop: 1,
455− value: Box::new(Text("value")),
421+ value: vec!(Text("value")),
@@ -460,6 +426,19 @@ mod parser {
430+ fn parse_tabstop_nested_in_placeholder() {
433+ elements: vec![Placeholder {
435+ value: vec!(Text("var, "), Tabstop { tabstop: 2 },),
438+ parse("${1:var, $2}")
464 fn parse_all() {
443 fn parse_all() {
465 assert_eq!(
444 assert_eq!(