WIP: apply snippets as transactions
This commit is contained in:
parent
5c4b93b4b4
commit
79ac545873
@ -1,10 +1,5 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Snippet<'a> {
|
||||
parts: Vec<SnippetElement<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum CaseChange {
|
||||
Upcase,
|
||||
@ -48,9 +43,88 @@ pub enum SnippetElement<'a> {
|
||||
Text(&'a str),
|
||||
}
|
||||
|
||||
pub fn parse<'a>(input: &'a str) -> Result<Snippet<'a>> {
|
||||
parser::parse(input)
|
||||
.map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Snippet<'a> {
|
||||
elements: Vec<SnippetElement<'a>>,
|
||||
}
|
||||
|
||||
pub fn parse<'a>(s: &'a str) -> Result<Snippet<'a>> {
|
||||
parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
|
||||
}
|
||||
|
||||
pub fn into_transaction<'a>(
|
||||
snippet: Snippet<'a>,
|
||||
text: &helix_core::Rope,
|
||||
trigger_offset: usize,
|
||||
) -> helix_core::Transaction {
|
||||
use helix_core::{smallvec, Range, Selection, Transaction};
|
||||
use SnippetElement::*;
|
||||
|
||||
let mut insert = String::new();
|
||||
let mut offset = trigger_offset;
|
||||
let mut tabstops: Vec<Range> = Vec::new();
|
||||
|
||||
for element in snippet.elements {
|
||||
match element {
|
||||
Text(text) => {
|
||||
offset += text.chars().count();
|
||||
insert.push_str(text)
|
||||
}
|
||||
Variable {
|
||||
name: _name,
|
||||
regex: None,
|
||||
r#default,
|
||||
} => {
|
||||
// TODO: variables. For now, fall back to the default, which defaults to "".
|
||||
let text = r#default.unwrap_or_default();
|
||||
offset += text.chars().count();
|
||||
insert.push_str(text);
|
||||
}
|
||||
Tabstop { .. } => {
|
||||
// TODO: tabstop indexing: 0 is final cursor position. 1,2,.. are positions.
|
||||
// TODO: merge tabstops with the same index
|
||||
tabstops.push(Range::point(offset));
|
||||
}
|
||||
Placeholder {
|
||||
tabstop: _tabstop,
|
||||
value,
|
||||
} => match value.as_ref() {
|
||||
// https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html
|
||||
// would make this a bit nicer
|
||||
Text(text) => {
|
||||
let len_chars = text.chars().count();
|
||||
tabstops.push(Range::new(offset, offset + len_chars));
|
||||
offset += len_chars;
|
||||
insert.push_str(text);
|
||||
}
|
||||
other => {
|
||||
log::error!(
|
||||
"Discarding snippet: generating a transaction for placeholder contents {:?} is unimplemented.",
|
||||
other
|
||||
);
|
||||
return Transaction::new(text);
|
||||
}
|
||||
},
|
||||
other => {
|
||||
log::error!(
|
||||
"Discarding snippet: generating a transaction for {:?} is unimplemented.",
|
||||
other
|
||||
);
|
||||
return Transaction::new(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let transaction = Transaction::change(
|
||||
text,
|
||||
std::iter::once((trigger_offset, trigger_offset, Some(insert.into()))),
|
||||
);
|
||||
|
||||
if let Some(first) = tabstops.first() {
|
||||
transaction.with_selection(Selection::new(smallvec![*first], 0))
|
||||
} else {
|
||||
transaction
|
||||
}
|
||||
}
|
||||
|
||||
mod parser {
|
||||
@ -253,7 +327,7 @@ fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
|
||||
}
|
||||
|
||||
fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
|
||||
map(one_or_more(anything()), |parts| Snippet { parts })
|
||||
map(one_or_more(anything()), |parts| Snippet { elements: parts })
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Result<Snippet, &str> {
|
||||
@ -274,7 +348,7 @@ fn empty_string_is_error() {
|
||||
fn parse_placeholders_in_function_call() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
parts: vec![
|
||||
elements: vec![
|
||||
Text("match("),
|
||||
Placeholder {
|
||||
tabstop: 1,
|
||||
@ -291,7 +365,7 @@ fn parse_placeholders_in_function_call() {
|
||||
fn parse_placeholders_in_statement() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
parts: vec![
|
||||
elements: vec![
|
||||
Text("local "),
|
||||
Placeholder {
|
||||
tabstop: 1,
|
||||
@ -312,7 +386,7 @@ fn parse_placeholders_in_statement() {
|
||||
fn parse_all() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
parts: vec![
|
||||
elements: vec![
|
||||
Text("hello "),
|
||||
Tabstop { tabstop: 1 },
|
||||
Tabstop { tabstop: 2 },
|
||||
@ -349,7 +423,7 @@ fn parse_all() {
|
||||
fn regex_capture_replace() {
|
||||
assert_eq!(
|
||||
Ok(Snippet {
|
||||
parts: vec![Variable {
|
||||
elements: vec![Variable {
|
||||
name: "TM_FILENAME",
|
||||
default: None,
|
||||
regex: Some(Regex {
|
||||
|
@ -109,32 +109,55 @@ fn item_to_transaction(
|
||||
start_offset: usize,
|
||||
trigger_offset: usize,
|
||||
) -> Transaction {
|
||||
let transaction = if let Some(edit) = &item.text_edit {
|
||||
let edit = match edit {
|
||||
lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
|
||||
lsp::CompletionTextEdit::InsertAndReplace(item) => {
|
||||
unimplemented!("completion: insert_and_replace {:?}", item)
|
||||
use helix_lsp::snippet;
|
||||
|
||||
match item {
|
||||
CompletionItem {
|
||||
text_edit: Some(edit),
|
||||
..
|
||||
} => {
|
||||
let edit = match edit {
|
||||
lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
|
||||
lsp::CompletionTextEdit::InsertAndReplace(item) => {
|
||||
unimplemented!("completion: insert_and_replace {:?}", item)
|
||||
}
|
||||
};
|
||||
|
||||
util::generate_transaction_from_edits(
|
||||
doc.text(),
|
||||
vec![edit],
|
||||
offset_encoding, // TODO: should probably transcode in Client
|
||||
)
|
||||
}
|
||||
CompletionItem {
|
||||
insert_text: Some(insert_text),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
..
|
||||
} => match snippet::parse(insert_text) {
|
||||
Ok(snippet) => {
|
||||
snippet::into_transaction(snippet, doc.text(), trigger_offset)
|
||||
}
|
||||
};
|
||||
|
||||
util::generate_transaction_from_edits(
|
||||
doc.text(),
|
||||
vec![edit],
|
||||
offset_encoding, // TODO: should probably transcode in Client
|
||||
)
|
||||
} else {
|
||||
let text = item.insert_text.as_ref().unwrap_or(&item.label);
|
||||
// Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
|
||||
// in these cases we need to check for a common prefix and remove it
|
||||
let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
|
||||
let text = text.trim_start_matches::<&str>(&prefix);
|
||||
Transaction::change(
|
||||
doc.text(),
|
||||
vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
|
||||
)
|
||||
};
|
||||
|
||||
transaction
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to parse snippet: {:?}, remaining output: {}",
|
||||
insert_text,
|
||||
err
|
||||
);
|
||||
Transaction::new(doc.text())
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let text = item.insert_text.as_ref().unwrap_or(&item.label);
|
||||
// Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
|
||||
// in these cases we need to check for a common prefix and remove it
|
||||
let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
|
||||
let text = text.trim_start_matches::<&str>(&prefix);
|
||||
Transaction::change(
|
||||
doc.text(),
|
||||
vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_changes(transaction: &Transaction, trigger_offset: usize) -> Vec<Change> {
|
||||
|
Loading…
Reference in New Issue
Block a user