parinfer.rs
Branches > main > 61242ea > files / nvim / pack / plugins / opt / parinfer-rust / src / parinfer.rs
use crate::changes;
use crate::types::*;
use std::borrow::Cow;
use std::collections::HashMap;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
// {{{1 Constants / Predicates
fn match_paren(paren: &str) -> Option<&'static str> {
match paren {
"{" => Some("}"),
"}" => Some("{"),
"[" => Some("]"),
"]" => Some("["),
"(" => Some(")"),
")" => Some("("),
_ => None,
}
}
#[cfg(test)]
#[test]
fn match_paren_works() {
assert_eq!(match_paren("}"), Some("{"));
assert_eq!(match_paren("x"), None);
}
// {{{1 Options Structure
struct TransformedChange {
old_end_x: Column,
new_end_x: Column,
lookup_line_no: LineNumber,
lookup_x: Column,
}
pub fn chomp_cr(text: &str) -> &str {
if text.ends_with('\r') {
&text[0..text.len() - 1]
} else {
text
}
}
fn split_lines(text: &str) -> Vec<&str> {
text.split('\n').map(chomp_cr).collect()
}
fn transform_change<'a>(change: &'a Change) -> TransformedChange {
let new_lines: Vec<&'a str> = change.new_text.split('\n').map(chomp_cr).collect();
let old_lines: Vec<&'a str> = change.old_text.split('\n').map(chomp_cr).collect();
// single line case:
// (defn foo| [])
// ^ newEndX, newEndLineNo
// +++
// multi line case:
// (defn foo
// ++++
// "docstring."
// ++++++++++++++++
// |[])
// ++^ newEndX, newEndLineNo
let last_old_line_len = UnicodeWidthStr::width(old_lines[old_lines.len() - 1]);
let last_new_line_len = UnicodeWidthStr::width(new_lines[new_lines.len() - 1]);
let old_end_x = (if old_lines.len() == 1 { change.x } else { 0 }) + last_old_line_len;
let new_end_x = (if new_lines.len() == 1 { change.x } else { 0 }) + last_new_line_len;
let new_end_line_no = change.line_no + (new_lines.len() - 1);
TransformedChange {
old_end_x,
new_end_x,
lookup_line_no: new_end_line_no,
lookup_x: new_end_x,
}
}
fn transform_changes(changes: &Vec<Change>) -> HashMap<(LineNumber, Column), TransformedChange> {
let mut lines: HashMap<(LineNumber, Column), TransformedChange> = HashMap::new();
for change in changes {
let transformed_change = transform_change(change);
lines.insert(
(
transformed_change.lookup_line_no,
transformed_change.lookup_x,
),
transformed_change,
);
}
lines
}
// {{{1 State Structure (was Result)
#[derive(Debug)]
struct ParenTrailClamped<'a> {
start_x: Option<Column>,
end_x: Option<Column>,
openers: Vec<Paren<'a>>,
}
#[derive(Debug)]
struct InternalParenTrail<'a> {
line_no: Option<LineNumber>,
start_x: Option<Column>,
end_x: Option<Column>,
openers: Vec<Paren<'a>>,
clamped: ParenTrailClamped<'a>,
}
#[derive(PartialEq, Eq)]
pub enum Mode {
Indent,
Paren,
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum TrackingArgTabStop {
NotSearching,
Space,
Arg,
}
#[derive(PartialEq, Eq)]
enum Now {
Normal,
Escaping,
Escaped,
}
impl State<'_> {
fn is_escaping(&self) -> bool {
matches!(self.escape, Now::Escaping)
}
fn is_escaped(&self) -> bool {
matches!(self.escape, Now::Escaped)
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum In<'a> {
Code,
Comment,
String {
delim: &'a str,
},
LispReaderSyntax,
LispBlockCommentPre {
depth: usize,
},
LispBlockComment {
depth: usize,
},
LispBlockCommentPost {
depth: usize,
},
GuileBlockComment,
GuileBlockCommentPost,
JanetLongStringPre {
open_delim_len: usize,
},
JanetLongString {
open_delim_len: usize,
close_delim_len: usize,
},
HyBracketStringPre,
HyBracketString,
HyBracketStringPost,
}
impl State<'_> {
fn is_in_code(&self) -> bool {
matches!(self.context, In::Code | In::LispReaderSyntax)
}
fn is_in_comment(&self) -> bool {
matches!(self.context, In::Comment)
}
fn is_in_stringish(&self) -> bool {
matches!(
self.context,
In::String { .. }
| In::LispBlockCommentPre { .. }
| In::LispBlockComment { .. }
| In::LispBlockCommentPost { .. }
| In::GuileBlockComment
| In::GuileBlockCommentPost
| In::JanetLongStringPre { .. }
| In::JanetLongString { .. }
| In::HyBracketStringPre {..}
| In::HyBracketString {..}
| In::HyBracketStringPost {..}
)
}
}
struct State<'a> {
mode: Mode,
smart: bool,
orig_text: &'a str,
orig_cursor_x: Option<Column>,
orig_cursor_line: Option<LineNumber>,
input_lines: Vec<&'a str>,
input_line_no: LineNumber,
input_x: Column,
lines: Vec<Cow<'a, str>>,
line_no: LineNumber,
ch: &'a str,
x: Column,
indent_x: Option<Column>,
paren_stack: Vec<Paren<'a>>,
tab_stops: Vec<TabStop<'a>>,
paren_trail: InternalParenTrail<'a>,
paren_trails: Vec<ParenTrail>,
return_parens: bool,
parens: Vec<Paren<'a>>,
cursor_x: Option<Column>,
cursor_line: Option<LineNumber>,
prev_cursor_x: Option<Column>,
prev_cursor_line: Option<Column>,
selection_start_line: Option<LineNumber>,
changes: HashMap<(LineNumber, Column), TransformedChange>,
context: In<'a>,
comment_x: Option<Column>,
escape: Now,
lisp_vline_symbols_enabled: bool,
lisp_reader_syntax_enabled: bool,
lisp_block_comments_enabled: bool,
guile_block_comments_enabled: bool,
scheme_sexp_comments_enabled: bool,
janet_long_strings_enabled: bool,
hy_bracket_strings_enabled: bool,
quote_danger: bool,
tracking_indent: bool,
skip_char: bool,
success: bool,
partial_result: bool,
force_balance: bool,
comment_char: String,
string_delimiters: Vec<String>,
max_indent: Option<Column>,
indent_delta: i64,
tracking_arg_tab_stop: TrackingArgTabStop,
hy_bracket_tag: Vec<&'a str>,
hy_bracket_tag_remaining: Vec<&'a str>,
error: Option<Error>,
error_pos_cache: HashMap<ErrorName, Error>,
}
fn initial_paren_trail<'a>() -> InternalParenTrail<'a> {
InternalParenTrail {
line_no: None,
start_x: None,
end_x: None,
openers: vec![],
clamped: ParenTrailClamped {
start_x: None,
end_x: None,
openers: vec![],
},
}
}
fn get_initial_result<'a>(text: &'a str, options: &Options, mode: Mode, smart: bool) -> State<'a> {
let lisp_reader_syntax_enabled = [
options.lisp_block_comments,
options.guile_block_comments,
options.scheme_sexp_comments,
options.hy_bracket_strings,
]
.iter()
.any(|is_true| *is_true);
State {
mode,
smart,
orig_text: text,
orig_cursor_x: options.cursor_x,
orig_cursor_line: options.cursor_line,
input_lines: split_lines(text),
input_line_no: 0,
input_x: 0,
lines: vec![],
line_no: usize::MAX,
ch: &text[0..0],
x: 0,
indent_x: None,
paren_stack: vec![],
tab_stops: vec![],
paren_trail: initial_paren_trail(),
paren_trails: vec![],
return_parens: false,
parens: vec![],
cursor_x: options.cursor_x,
cursor_line: options.cursor_line,
prev_cursor_x: options.prev_cursor_x,
prev_cursor_line: options.prev_cursor_line,
selection_start_line: None,
changes: transform_changes(&options.changes),
context: In::Code,
comment_x: None,
escape: Now::Normal,
lisp_vline_symbols_enabled: options.lisp_vline_symbols,
lisp_reader_syntax_enabled,
lisp_block_comments_enabled: options.lisp_block_comments,
guile_block_comments_enabled: options.guile_block_comments,
scheme_sexp_comments_enabled: options.scheme_sexp_comments,
janet_long_strings_enabled: options.janet_long_strings,
hy_bracket_strings_enabled: options.hy_bracket_strings,
quote_danger: false,
tracking_indent: false,
skip_char: false,
success: false,
partial_result: false,
force_balance: false,
comment_char: options.comment_char.to_string(),
string_delimiters: options.string_delimiters.clone(),
max_indent: None,
indent_delta: 0,
tracking_arg_tab_stop: TrackingArgTabStop::NotSearching,
hy_bracket_tag: vec![],
hy_bracket_tag_remaining: vec![],
error: None,
error_pos_cache: HashMap::new(),
}
}
// {{{1 Possible Errors
pub type Result<T> = std::result::Result<T, Error>;
fn error_message(error: ErrorName) -> &'static str {
match error {
ErrorName::QuoteDanger => "Quotes must balanced inside comment blocks.",
ErrorName::EolBackslash => "Line cannot end in a hanging backslash.",
ErrorName::UnclosedQuote => "String is missing a closing quote.",
ErrorName::UnclosedParen => "Unclosed open-paren.",
ErrorName::UnmatchedCloseParen => "Unmatched close-paren.",
ErrorName::UnmatchedOpenParen => "Unmatched open-paren.",
ErrorName::LeadingCloseParen => "Line cannot lead with a close-paren.",
ErrorName::Utf8EncodingError => "UTF8 encoded incorrectly.",
ErrorName::JsonEncodingError => "JSON encoded incorrectly.",
ErrorName::Panic => "Internal error (please report!)",
ErrorName::Restart => "Restart requested (you shouldn't see this).",
}
}
fn cache_error_pos(result: &mut State, name: ErrorName) {
let error = Error {
name,
message: String::new(),
line_no: result.line_no,
x: result.x,
input_line_no: result.input_line_no,
input_x: result.input_x,
};
result.error_pos_cache.insert(name, error);
}
fn error(result: &mut State, name: ErrorName) -> Result<()> {
let (line_no, x) = match (result.partial_result, result.error_pos_cache.get(&name)) {
(true, Some(cache)) => (cache.line_no, cache.x),
(false, Some(cache)) => (cache.input_line_no, cache.input_x),
(true, None) => (result.line_no, result.x),
(false, None) => (result.input_line_no, result.input_x),
};
let mut e = Error {
name,
line_no,
x,
message: String::from(error_message(name)),
input_line_no: result.input_line_no,
input_x: result.input_x,
};
if name == ErrorName::UnclosedParen {
if let Some(opener) = peek(&result.paren_stack, 0) {
e.line_no = if result.partial_result {
opener.line_no
} else {
opener.input_line_no
};
e.x = if result.partial_result {
opener.x
} else {
opener.input_x
};
}
}
Err(e)
}
// {{{1 String Operations
fn column_byte_index(s: &str, x: usize) -> usize {
s.grapheme_indices(true)
.scan(0, |column, (idx, ch)| {
let start_column = *column;
*column += UnicodeWidthStr::width(ch);
Some((start_column, (idx, ch)))
})
.filter_map(|(n, (idx, _))| if n == x { Some(idx) } else { None })
.next()
.unwrap_or(s.len())
}
#[cfg(test)]
#[test]
fn column_byte_index_works() {
assert_eq!(column_byte_index("abc", 1), 1);
assert_eq!(column_byte_index("abc", 3), 3);
assert_eq!(column_byte_index("åbc", 3), 4);
assert_eq!(column_byte_index("åbc", 1), 2);
assert_eq!(column_byte_index("wo", 4), 6);
assert_eq!(column_byte_index("wo", 2), 3);
assert_eq!(column_byte_index("wo", 0), 0);
}
fn replace_within_string(orig: &str, start: usize, end: usize, replace: &str) -> String {
let start_i = column_byte_index(orig, start);
let end_i = column_byte_index(orig, end);
String::from(&orig[0..start_i]) + replace + &orig[end_i..]
}
#[cfg(test)]
#[test]
fn replace_within_string_works() {
assert_eq!(replace_within_string("aaa", 0, 2, ""), "a");
assert_eq!(replace_within_string("aaa", 0, 1, "b"), "baa");
assert_eq!(replace_within_string("aaa", 0, 2, "b"), "ba");
assert_eq!(replace_within_string("ééé", 0, 2, ""), "é");
assert_eq!(replace_within_string("ééé", 0, 1, "b"), "béé");
assert_eq!(replace_within_string("ééé", 1, 2, "b"), "ébé");
assert_eq!(replace_within_string("ééé", 0, 2, "b"), "bé");
assert_eq!(replace_within_string("ééé", 3, 3, "b"), "éééb");
}
fn repeat_string(text: &str, n: usize) -> String {
String::from(text).repeat(n)
}
#[cfg(test)]
#[test]
fn repeat_string_works() {
assert_eq!(repeat_string("a", 2), "aa");
assert_eq!(repeat_string("aa", 3), "aaaaaa");
assert_eq!(repeat_string("aa", 0), "");
assert_eq!(repeat_string("", 0), "");
assert_eq!(repeat_string("", 5), "");
}
fn get_line_ending(text: &str) -> &'static str {
if text.chars().any(|ch| ch == '\r') {
"\r\n"
} else {
"\n"
}
}
#[cfg(test)]
#[test]
fn get_line_ending_works() {
assert_eq!(get_line_ending("foo\nbar"), "\n");
assert_eq!(get_line_ending("foo\r\nbar"), "\r\n");
}
// {{{1 Line operations
fn is_cursor_affected(result: &State<'_>, start: Column, end: Column) -> bool {
match result.cursor_x {
Some(x) if x == start && x == end => x == 0,
Some(x) => x >= end,
None => false,
}
}
fn shift_cursor_on_edit(
result: &mut State<'_>,
line_no: LineNumber,
start: Column,
end: Column,
replace: &str,
) {
let old_length = end - start;
let new_length = UnicodeWidthStr::width(replace);
let dx = new_length as Delta - old_length as Delta;
if let (Some(cursor_x), Some(cursor_line)) = (result.cursor_x, result.cursor_line) {
if dx != 0 && cursor_line == line_no && is_cursor_affected(result, start, end) {
result.cursor_x = Some(((cursor_x as Delta) + dx) as usize);
}
}
}
fn replace_within_line(
result: &mut State<'_>,
line_no: LineNumber,
start: Column,
end: Column,
replace: &str,
) {
let line = result.lines[line_no].clone();
let new_line = replace_within_string(&line, start, end, replace);
result.lines[line_no] = Cow::from(new_line);
shift_cursor_on_edit(result, line_no, start, end, replace);
}
fn insert_within_line(result: &mut State<'_>, line_no: LineNumber, idx: Column, insert: &str) {
replace_within_line(result, line_no, idx, idx, insert);
}
fn init_line(result: &mut State<'_>) {
result.x = 0;
result.line_no = usize::wrapping_add(result.line_no, 1);
// reset line-specific state
result.indent_x = None;
result.comment_x = None;
result.indent_delta = 0;
result
.error_pos_cache
.remove(&ErrorName::UnmatchedCloseParen);
result
.error_pos_cache
.remove(&ErrorName::UnmatchedOpenParen);
result.error_pos_cache.remove(&ErrorName::LeadingCloseParen);
result.tracking_arg_tab_stop = TrackingArgTabStop::NotSearching;
result.tracking_indent = !result.is_in_stringish();
}
fn commit_char<'a>(result: &mut State<'a>, orig_ch: &'a str) {
let ch = result.ch;
let ch_width = UnicodeWidthStr::width(ch);
if orig_ch != ch {
let line_no = result.line_no;
let x = result.x;
let orig_ch_width = UnicodeWidthStr::width(orig_ch);
replace_within_line(result, line_no, x, x + orig_ch_width, ch);
result.indent_delta -= orig_ch_width as Delta - ch_width as Delta;
}
result.x += ch_width;
}
// {{{1 Misc Utils
fn clamp<T: Clone + Ord>(val: T, min_n: Option<T>, max_n: Option<T>) -> T {
if let Some(low) = min_n {
if low >= val {
return low;
}
}
if let Some(high) = max_n {
if high <= val {
return high;
}
}
val
}
#[cfg(test)]
#[test]
fn clamp_works() {
assert_eq!(clamp(1, Some(3), Some(5)), 3);
assert_eq!(clamp(9, Some(3), Some(5)), 5);
assert_eq!(clamp(1, Some(3), None), 3);
assert_eq!(clamp(5, Some(3), None), 5);
assert_eq!(clamp(1, None, Some(5)), 1);
assert_eq!(clamp(9, None, Some(5)), 5);
assert_eq!(clamp(1, None, None), 1);
}
fn peek<T>(array: &[T], i: usize) -> Option<&T> {
if i >= array.len() {
None
} else {
Some(&array[array.len() - 1 - i])
}
}
#[cfg(test)]
#[test]
fn peek_works() {
assert_eq!(peek(&['a'], 0), Some(&'a'));
assert_eq!(peek(&['a'], 1), None);
assert_eq!(peek(&['a', 'b', 'c'], 0), Some(&'c'));
assert_eq!(peek(&['a', 'b', 'c'], 1), Some(&'b'));
assert_eq!(peek(&['a', 'b', 'c'], 5), None);
let empty: Vec<char> = vec![];
assert_eq!(peek(&empty, 0), None);
assert_eq!(peek(&empty, 1), None);
}
// {{{1 Questions about characters
fn is_close_paren(paren: &str) -> bool {
matches!(paren, "}" | "]" | ")")
}
fn is_valid_close_paren<'a>(paren_stack: &Vec<Paren<'a>>, ch: &'a str) -> bool {
if paren_stack.is_empty() {
return false;
}
if let Some(paren) = peek(paren_stack, 0) {
if let Some(close) = match_paren(ch) {
if paren.ch == close {
return true;
}
}
}
false
}
fn is_whitespace(result: &State<'_>) -> bool {
!result.is_escaped() && (result.ch == " " || result.ch == " ")
}
fn is_closable(result: &State<'_>) -> bool {
let ch = result.ch;
let closer = is_close_paren(ch) && !result.is_escaped();
result.is_in_code() && !is_whitespace(result) && !ch.is_empty() && !closer
}
// {{{1 Advanced operations on characters
fn check_cursor_holding(result: &State<'_>) -> Result<bool> {
let opener = peek(&result.paren_stack, 0).unwrap();
let hold_min_x = peek(&result.paren_stack, 1).map(|p| p.x + 1).unwrap_or(0);
let hold_max_x = opener.x;
let holding = result.cursor_line == Some(opener.line_no)
&& result.cursor_x.map(|x| hold_min_x <= x).unwrap_or(false)
&& result.cursor_x.map(|x| x <= hold_max_x).unwrap_or(false);
let should_check_prev = result.changes.is_empty() && result.prev_cursor_line.is_some();
if should_check_prev {
let prev_holding = result.prev_cursor_line == Some(opener.line_no)
&& result
.prev_cursor_x
.map(|x| hold_min_x <= x)
.unwrap_or(false)
&& result
.prev_cursor_x
.map(|x| x <= hold_max_x)
.unwrap_or(false);
if prev_holding && !holding {
return Err(Error {
name: ErrorName::Restart,
x: 0,
input_line_no: 0,
input_x: 0,
line_no: 0,
message: String::new(),
});
}
}
Ok(holding)
}
fn track_arg_tab_stop(result: &mut State<'_>, state: TrackingArgTabStop) {
if state == TrackingArgTabStop::Space {
if result.is_in_code() && is_whitespace(result) {
result.tracking_arg_tab_stop = TrackingArgTabStop::Arg;
}
} else if state == TrackingArgTabStop::Arg && !is_whitespace(result) {
let opener = result.paren_stack.last_mut().unwrap();
opener.arg_x = Some(result.x);
result.tracking_arg_tab_stop = TrackingArgTabStop::NotSearching;
}
}
// {{{1 Literal character events
fn in_code_on_open_paren(result: &mut State<'_>) {
let opener = Paren {
input_line_no: result.input_line_no,
input_x: result.input_x,
line_no: result.line_no,
x: result.x,
ch: result.ch,
indent_delta: result.indent_delta,
max_child_indent: None,
arg_x: None,
closer: None,
children: vec![],
};
if result.return_parens {
if let Some(parent) = result.paren_stack.last_mut() {
parent.children.push(opener.clone());
} else {
result.parens.push(opener.clone());
}
}
result.paren_stack.push(opener);
result.tracking_arg_tab_stop = TrackingArgTabStop::Space;
}
fn in_code_on_matched_close_paren(result: &mut State<'_>) -> Result<()> {
let mut opener = (*peek(&result.paren_stack, 0).unwrap()).clone();
if result.return_parens {
set_closer(&mut opener);
}
result.paren_trail.end_x = Some(result.x + 1);
result.paren_trail.openers.push(opener);
if result.mode == Mode::Indent && result.smart && check_cursor_holding(result)? {
let orig_start_x = result.paren_trail.start_x;
let orig_end_x = result.paren_trail.end_x;
let orig_openers = result.paren_trail.openers.clone();
let x = result.x;
let line_no = result.line_no;
reset_paren_trail(result, line_no, x + 1);
result.paren_trail.clamped = ParenTrailClamped {
start_x: orig_start_x,
end_x: orig_end_x,
openers: orig_openers,
};
}
result.paren_stack.pop();
result.tracking_arg_tab_stop = TrackingArgTabStop::NotSearching;
Ok(())
}
fn in_code_on_unmatched_close_paren(result: &mut State<'_>) -> Result<()> {
match result.mode {
Mode::Paren => {
let in_leading_paren_trail = result.paren_trail.line_no == Some(result.line_no)
&& result.paren_trail.start_x == result.indent_x;
let can_remove = result.smart && in_leading_paren_trail;
if !can_remove {
error(result, ErrorName::UnmatchedCloseParen)?;
}
}
Mode::Indent => {
if !result
.error_pos_cache
.contains_key(&ErrorName::UnmatchedCloseParen)
{
cache_error_pos(result, ErrorName::UnmatchedCloseParen);
if peek(&result.paren_stack, 0).is_some() {
cache_error_pos(result, ErrorName::UnmatchedOpenParen);
let opener = peek(&result.paren_stack, 0).unwrap();
if let Some(err) = result
.error_pos_cache
.get_mut(&ErrorName::UnmatchedOpenParen)
{
err.input_line_no = opener.input_line_no;
err.input_x = opener.input_x;
}
}
}
}
}
result.ch = "";
Ok(())
}
fn on_newline(result: &mut State<'_>) {
if result.is_in_comment() {
result.context = In::Code;
}
result.ch = "";
}
fn in_comment_on_quote(result: &mut State<'_>) {
result.quote_danger = !result.quote_danger;
if result.quote_danger {
cache_error_pos(result, ErrorName::QuoteDanger);
}
}
fn on_backslash(result: &mut State<'_>) {
result.escape = Now::Escaping;
}
fn after_backslash(result: &mut State<'_>) -> Result<()> {
result.escape = Now::Escaped;
if result.ch == "\n" && result.is_in_code() {
return error(result, ErrorName::EolBackslash);
}
Ok(())
}
// {{{1 Character dispatch
fn on_context(result: &mut State<'_>) -> Result<()> {
match (result.context, result.ch) {
(In::Code, ch) if ch == result.comment_char => {
result.context = In::Comment;
result.comment_x = Some(result.x);
result.tracking_arg_tab_stop = TrackingArgTabStop::NotSearching;
},
(In::Code, ch) if result.string_delimiters.contains(&ch.to_string()) => {
result.context = In::String { delim: result.ch };
cache_error_pos(result, ErrorName::UnclosedQuote);
},
(In::Code, "(" | "[" | "{") => in_code_on_open_paren(result),
(In::Code, ")" | "]" | "}") if is_valid_close_paren(&result.paren_stack, result.ch) => {
in_code_on_matched_close_paren(result)?;
},
(In::Code, ")" | "]" | "}") => {
in_code_on_unmatched_close_paren(result)?;
},
(In::Code, "|") if result.lisp_vline_symbols_enabled => {
result.context = In::String { delim: result.ch };
cache_error_pos(result, ErrorName::UnclosedQuote);
},
(In::Code, "#") if result.lisp_reader_syntax_enabled => {
result.context = In::LispReaderSyntax;
},
(In::Code, "`") if result.janet_long_strings_enabled => {
result.context = In::JanetLongStringPre { open_delim_len: 1 };
cache_error_pos(result, ErrorName::UnclosedQuote);
},
(In::Code, "\t") => { result.ch = " "; },
(In::Code, _) => (),
(In::Comment, ch) if result.string_delimiters.contains(&ch.to_string()) => in_comment_on_quote(result),
(In::Comment, "|") if result.lisp_vline_symbols_enabled => in_comment_on_quote(result),
(In::Comment, "`") if result.janet_long_strings_enabled => in_comment_on_quote(result),
(In::Comment, _) => (),
(In::String { delim }, ch) if delim == ch => { result.context = In::Code; },
(In::String { .. }, _) => (),
(In::LispReaderSyntax, "|") if result.lisp_block_comments_enabled => {
result.context = In::LispBlockComment { depth: 1 };
},
(In::LispReaderSyntax, "!") if result.guile_block_comments_enabled => { result.context = In::GuileBlockComment; },
(In::LispReaderSyntax, ";") if result.scheme_sexp_comments_enabled => { result.context = In::Code; },
(In::LispReaderSyntax, "[") if result.hy_bracket_strings_enabled => {
result.hy_bracket_tag.clear();
result.context = In::HyBracketStringPre;
},
(In::LispReaderSyntax, _) => {
// Backtrack!
result.context = In::Code;
on_context(result)?
},
(In::LispBlockCommentPre { depth }, "|") => {
result.context = In::LispBlockComment { depth: depth + 1 };
},
(In::LispBlockCommentPre { depth }, _) => { result.context = In::LispBlockComment { depth }; },
(In::LispBlockComment { depth }, "#") => { result.context = In::LispBlockCommentPre { depth }; },
(In::LispBlockComment { depth }, "|") => { result.context = In::LispBlockCommentPost { depth }; },
(In::LispBlockComment { .. }, _) => (),
(In::LispBlockCommentPost { depth: 1 }, "#") => { result.context = In::Code; },
(In::LispBlockCommentPost { depth }, "#") => {
result.context = In::LispBlockComment { depth: depth - 1 };
},
(In::LispBlockCommentPost { depth }, _) => {
result.context = In::LispBlockComment { depth };
},
(In::GuileBlockComment, "!") => { result.context = In::GuileBlockCommentPost; },
(In::GuileBlockComment, _) => (),
(In::GuileBlockCommentPost, "#") => { result.context = In::Code },
(In::GuileBlockCommentPost, _) => {
result.context = In::GuileBlockComment;
},
(In::JanetLongStringPre { open_delim_len }, "`") => {
result.context = In::JanetLongStringPre { open_delim_len: open_delim_len + 1 };
},
(In::JanetLongStringPre { open_delim_len }, _) => {
result.context = In::JanetLongString { open_delim_len, close_delim_len: 0 };
},
(In::JanetLongString { open_delim_len, close_delim_len }, "`") if open_delim_len == close_delim_len + 1 => {
result.context = In::Code;
},
(In::JanetLongString { open_delim_len, close_delim_len }, "`") => {
result.context = In::JanetLongString { open_delim_len, close_delim_len: close_delim_len + 1 };
},
(In::JanetLongString { open_delim_len, .. }, _) => {
result.context = In::JanetLongString { open_delim_len, close_delim_len: 0 };
},
(In::HyBracketStringPre, "[") => { result.context = In::HyBracketString; },
(In::HyBracketStringPre, _) => { result.hy_bracket_tag.push(result.ch); },
(In::HyBracketString, "]") => {
result.hy_bracket_tag_remaining = result.hy_bracket_tag.clone();
result.hy_bracket_tag_remaining.reverse();
result.context = In::HyBracketStringPost;
},
(In::HyBracketString, _) => (),
(In::HyBracketStringPost, "]") if result.hy_bracket_tag_remaining.is_empty() => {
result.context = In::Code;
},
(In::HyBracketStringPost, "]") => {
result.hy_bracket_tag_remaining = result.hy_bracket_tag.clone();
result.hy_bracket_tag_remaining.reverse();
},
(In::HyBracketStringPost, ch) if Some(&ch) == result.hy_bracket_tag_remaining.last() => {
result.hy_bracket_tag_remaining.pop();
},
(In::HyBracketStringPost, _) => { result.context = In::HyBracketString; },
}
Ok(())
}
fn on_char(result: &mut State<'_>) -> Result<()> {
let mut ch = result.ch;
if result.is_escaped() {
result.escape = Now::Normal;
}
if result.is_escaping() {
after_backslash(result)?;
} else if ch == "\\" {
on_backslash(result);
} else if ch == "\n" {
on_newline(result);
} else {
on_context(result)?;
}
ch = result.ch;
if is_closable(result) {
let line_no = result.line_no;
let x = result.x;
reset_paren_trail(result, line_no, x + UnicodeWidthStr::width(ch));
}
let state = result.tracking_arg_tab_stop;
if state != TrackingArgTabStop::NotSearching {
track_arg_tab_stop(result, state);
}
Ok(())
}
// {{{1 Cursor functions
fn is_cursor_left_of(
cursor_x: Option<Column>,
cursor_line: Option<LineNumber>,
x: Option<Column>,
line_no: LineNumber,
) -> bool {
if let (Some(x), Some(cursor_x)) = (x, cursor_x) {
cursor_line == Some(line_no) && cursor_x <= x // inclusive since (cursorX = x) implies (x-1 < cursor < x)
} else {
false
}
}
fn is_cursor_right_of(
cursor_x: Option<Column>,
cursor_line: Option<LineNumber>,
x: Option<Column>,
line_no: LineNumber,
) -> bool {
if let (Some(x), Some(cursor_x)) = (x, cursor_x) {
cursor_line == Some(line_no) && cursor_x > x
} else {
false
}
}
fn is_cursor_in_comment(
result: &State<'_>,
cursor_x: Option<Column>,
cursor_line: Option<LineNumber>,
) -> bool {
is_cursor_right_of(cursor_x, cursor_line, result.comment_x, result.line_no)
}
fn handle_change_delta(result: &mut State<'_>) {
if !result.changes.is_empty() && (result.smart || result.mode == Mode::Paren) {
if let Some(change) = result.changes.get(&(result.input_line_no, result.input_x)) {
result.indent_delta += change.new_end_x as Delta - change.old_end_x as Delta;
}
}
}
// {{{1 Paren Trail functions
fn reset_paren_trail(result: &mut State<'_>, line_no: LineNumber, x: Column) {
result.paren_trail.line_no = Some(line_no);
result.paren_trail.start_x = Some(x);
result.paren_trail.end_x = Some(x);
result.paren_trail.openers = vec![];
result.paren_trail.clamped.start_x = None;
result.paren_trail.clamped.end_x = None;
result.paren_trail.clamped.openers = vec![];
}
fn is_cursor_clamping_paren_trail(
result: &State<'_>,
cursor_x: Option<Column>,
cursor_line: Option<LineNumber>,
) -> bool {
is_cursor_right_of(
cursor_x,
cursor_line,
result.paren_trail.start_x,
result.line_no,
) && !is_cursor_in_comment(result, cursor_x, cursor_line)
}
// INDENT MODE: allow the cursor to clamp the paren trail
fn clamp_paren_trail_to_cursor(result: &mut State<'_>) {
let clamping = is_cursor_clamping_paren_trail(result, result.cursor_x, result.cursor_line);
if clamping {
let start_x = result.paren_trail.start_x.unwrap();
let end_x = result.paren_trail.end_x.unwrap();
let new_start_x = std::cmp::max(start_x, result.cursor_x.unwrap());
let new_end_x = std::cmp::max(end_x, result.cursor_x.unwrap());
let line = &result.lines[result.line_no];
let mut remove_count = 0;
for (x, ch) in line.graphemes(true).scan(0, |column, ch| {
let start_column = *column;
*column += UnicodeWidthStr::width(ch);
Some((start_column, ch))
}) {
if x < start_x || x >= new_start_x {
continue;
}
if is_close_paren(ch) {
remove_count += 1;
}
}
let openers = result.paren_trail.openers.clone();
result.paren_trail.openers = openers[remove_count..].to_vec();
result.paren_trail.start_x = Some(new_start_x);
result.paren_trail.end_x = Some(new_end_x);
result.paren_trail.clamped.openers = openers[..remove_count].to_vec();
result.paren_trail.clamped.start_x = Some(start_x);
result.paren_trail.clamped.end_x = Some(end_x);
}
}
fn pop_paren_trail(result: &mut State<'_>) {
let start_x = result.paren_trail.start_x;
let end_x = result.paren_trail.end_x;
if start_x == end_x {
return;
}
while let Some(paren) = result.paren_trail.openers.pop() {
result.paren_stack.push(paren);
}
}
#[allow(clippy::if_same_then_else)]
fn get_parent_opener_index(result: &mut State<'_>, indent_x: usize) -> usize {
for i in 0..result.paren_stack.len() {
let opener = peek(&result.paren_stack, i).unwrap().clone();
let opener_index = result.paren_stack.len() - i - 1;
let curr_outside = opener.x < indent_x;
let prev_indent_x = indent_x as Delta - result.indent_delta;
let prev_outside = opener.x as Delta - opener.indent_delta < prev_indent_x;
let mut is_parent = false;
if prev_outside && curr_outside {
is_parent = true;
} else if !prev_outside && !curr_outside {
is_parent = false;
} else if prev_outside && !curr_outside {
// POSSIBLE FRAGMENTATION
// (foo --\
// +--- FRAGMENT `(foo bar)` => `(foo) bar`
// bar) --/
// 1. PREVENT FRAGMENTATION
// ```in
// (foo
// ++
// bar
// ```
// ```out
// (foo
// bar
// ```
if result.indent_delta == 0 {
is_parent = true;
}
// 2. ALLOW FRAGMENTATION
// ```in
// (foo
// bar
// --
// ```
// ```out
// (foo)
// bar
// ```
else if opener.indent_delta == 0 {
is_parent = false;
} else {
// TODO: identify legitimate cases where both are nonzero
// allow the fragmentation by default
is_parent = false;
// TODO: should we throw to exit instead? either of:
// 1. give up, just `throw error(...)`
// 2. fallback to paren mode to preserve structure
}
} else if !prev_outside && curr_outside {
// POSSIBLE ADOPTION
// (foo) --\
// +--- ADOPT `(foo) bar` => `(foo bar)`
// bar --/
{
let next_opener = peek(&result.paren_stack, i + 1);
// 1. DISALLOW ADOPTION
// ```in
// (foo
// --
// (bar)
// --
// baz)
// ```
// ```out
// (foo
// (bar)
// baz)
// ```
// OR
// ```in
// (foo
// --
// (bar)
// -
// baz)
// ```
// ```out
// (foo
// (bar)
// baz)
// ```
if next_opener
.map(|no| no.indent_delta <= opener.indent_delta)
.unwrap_or(false)
{
// we can only disallow adoption if nextOpener.indentDelta will actually
// prevent the indentX from being in the opener's threshold.
is_parent =
indent_x as Delta + next_opener.unwrap().indent_delta > opener.x as Delta;
}
// 2. ALLOW ADOPTION
// ```in
// (foo
// (bar)
// --
// baz)
// ```
// ```out
// (foo
// (bar
// baz))
// ```
// OR
// ```in
// (foo
// -
// (bar)
// --
// baz)
// ```
// ```out
// (foo
// (bar)
// baz)
// ```
else if next_opener
.map(|no| no.indent_delta > opener.indent_delta)
.unwrap_or(false)
{
is_parent = true;
}
// 3. ALLOW ADOPTION
// ```in
// (foo)
// --
// bar
// ```
// ```out
// (foo
// bar)
// ```
// OR
// ```in
// (foo)
// bar
// ++
// ```
// ```out
// (foo
// bar
// ```
// OR
// ```in
// (foo)
// +
// bar
// ++
// ```
// ```out
// (foo
// bar)
// ```
else if result.indent_delta > opener.indent_delta {
is_parent = true;
}
}
if is_parent {
// if new parent
// Clear `indentDelta` since it is reserved for previous child lines only.
result.paren_stack[opener_index].indent_delta = 0;
}
}
if is_parent {
return i;
}
}
result.paren_stack.len()
}
// INDENT MODE: correct paren trail from indentation
fn correct_paren_trail(result: &mut State<'_>, indent_x: usize) {
let mut parens = String::new();
let index = get_parent_opener_index(result, indent_x);
for _ in 0..index {
let mut opener = result.paren_stack.pop().unwrap();
let close_ch = match_paren(opener.ch).unwrap();
if result.return_parens {
opener.closer = Some(Closer { trail: None });
}
result.paren_trail.openers.push(opener);
parens.push_str(close_ch);
}
if let Some(line_no) = result.paren_trail.line_no {
let start_x = result.paren_trail.start_x.unwrap();
let end_x = result.paren_trail.end_x.unwrap();
replace_within_line(result, line_no, start_x, end_x, &parens[..]);
result.paren_trail.end_x = result.paren_trail.start_x.map(|x| x + parens.len());
remember_paren_trail(result);
}
}
fn clean_paren_trail(result: &mut State<'_>) {
let start_x = result.paren_trail.start_x;
let end_x = result.paren_trail.end_x;
if start_x == end_x || Some(result.line_no) != result.paren_trail.line_no {
return;
}
let start_x = start_x.unwrap();
let end_x = end_x.unwrap();
let mut new_trail = String::new();
let mut space_count = 0;
for (x, ch) in result.lines[result.line_no]
.graphemes(true)
.scan(0, |column, ch| {
let start_column = *column;
*column += UnicodeWidthStr::width(ch);
Some((start_column, ch))
})
{
if x < start_x || x >= end_x {
continue;
}
if is_close_paren(ch) {
new_trail.push_str(ch);
} else {
space_count += 1;
}
}
if space_count > 0 {
let line_no = result.line_no;
replace_within_line(result, line_no, start_x, end_x, &new_trail[..]);
result.paren_trail.end_x = result.paren_trail.end_x.map(|x| x - space_count);
}
}
fn set_closer(opener: &mut Paren<'_>) {
opener.closer = Some(Closer { trail: None })
}
fn append_paren_trail(result: &mut State<'_>) {
let mut opener = result.paren_stack.pop().unwrap().clone();
let close_ch = match_paren(opener.ch).unwrap();
if result.return_parens {
set_closer(&mut opener);
}
set_max_indent(result, &opener);
let line_no = result.paren_trail.line_no.unwrap();
let end_x = result.paren_trail.end_x.unwrap();
insert_within_line(result, line_no, end_x, close_ch);
result.paren_trail.end_x = result.paren_trail.end_x.map(|x| x + 1);
result.paren_trail.openers.push(opener);
update_remembered_paren_trail(result);
}
fn invalidate_paren_trail(result: &mut State<'_>) {
result.paren_trail = initial_paren_trail();
}
fn check_unmatched_outside_paren_trail(result: &mut State<'_>) -> Result<()> {
let mut do_error = false;
if let Some(cache) = result.error_pos_cache.get(&ErrorName::UnmatchedCloseParen) {
if result
.paren_trail
.start_x
.map(|x| cache.x < x)
.unwrap_or(false)
{
do_error = true;
}
}
if do_error {
error(result, ErrorName::UnmatchedCloseParen)?;
}
Ok(())
}
fn set_max_indent<'a>(result: &mut State<'a>, opener: &Paren<'a>) {
if let Some(parent) = result.paren_stack.last_mut() {
parent.max_child_indent = Some(opener.x);
} else {
result.max_indent = Some(opener.x);
}
}
fn remember_paren_trail(result: &mut State<'_>) {
if !result.paren_trail.clamped.openers.is_empty() || !result.paren_trail.openers.is_empty() {
let is_clamped = result.paren_trail.clamped.start_x.is_some();
let short_trail = ParenTrail {
line_no: result.paren_trail.line_no.unwrap(),
start_x: if is_clamped {
result.paren_trail.clamped.start_x
} else {
result.paren_trail.start_x
}
.unwrap(),
end_x: if is_clamped {
result.paren_trail.clamped.end_x
} else {
result.paren_trail.end_x
}
.unwrap(),
};
result.paren_trails.push(short_trail.clone());
if result.return_parens {
for opener in result.paren_trail.openers.iter_mut() {
opener.closer.as_mut().unwrap().trail = Some(short_trail.clone());
}
}
}
}
fn update_remembered_paren_trail(result: &mut State<'_>) {
if result.paren_trails.is_empty()
|| Some(result.paren_trails[result.paren_trails.len() - 1].line_no)
!= result.paren_trail.line_no
{
remember_paren_trail(result);
} else {
let n = result.paren_trails.len() - 1;
let trail = result.paren_trails.get_mut(n).unwrap();
trail.end_x = result.paren_trail.end_x.unwrap();
if result.return_parens {
if let Some(opener) = result.paren_trail.openers.last_mut() {
opener.closer.as_mut().unwrap().trail = Some(trail.clone());
}
}
}
}
fn finish_new_paren_trail(result: &mut State<'_>) {
if result.is_in_stringish() {
invalidate_paren_trail(result);
} else if result.mode == Mode::Indent {
clamp_paren_trail_to_cursor(result);
pop_paren_trail(result);
} else if result.mode == Mode::Paren {
if let Some(paren) = peek(&result.paren_trail.openers, 0).cloned() {
set_max_indent(result, &paren);
}
if Some(result.line_no) != result.cursor_line {
clean_paren_trail(result);
}
remember_paren_trail(result);
}
}
// {{{1 Indentation functions
fn add_indent(result: &mut State<'_>, delta: Delta) {
let orig_indent = result.x;
let new_indent = (orig_indent as Delta + delta) as Column;
let indent_str = repeat_string(" ", new_indent);
let line_no = result.line_no;
replace_within_line(result, line_no, 0, orig_indent, &indent_str);
result.x = new_indent;
result.indent_x = Some(new_indent);
result.indent_delta += delta;
}
fn should_add_opener_indent<'a>(result: &State<'a>, opener: &Paren<'a>) -> bool {
// Don't add opener.indent_delta if the user already added it.
// (happens when multiple lines are indented together)
opener.indent_delta != result.indent_delta
}
fn correct_indent(result: &mut State<'_>) {
let orig_indent = result.x as Delta;
let mut new_indent = orig_indent as Delta;
let mut min_indent = 0;
let mut max_indent = result.max_indent.map(|x| x as Delta);
if let Some(opener) = peek(&result.paren_stack, 0) {
min_indent = opener.x + 1;
max_indent = opener.max_child_indent.map(|x| x as Delta);
if should_add_opener_indent(result, opener) {
new_indent += opener.indent_delta;
}
}
new_indent = clamp(new_indent, Some(min_indent as Delta), max_indent);
if new_indent != orig_indent {
add_indent(result, new_indent - orig_indent);
}
}
fn on_indent(result: &mut State<'_>) -> Result<()> {
result.indent_x = Some(result.x);
result.tracking_indent = false;
if result.quote_danger {
error(result, ErrorName::QuoteDanger)?;
}
match result.mode {
Mode::Indent => {
let x = result.x;
correct_paren_trail(result, x);
let to_add = match peek(&result.paren_stack, 0) {
Some(opener) if should_add_opener_indent(result, opener) => {
Some(opener.indent_delta)
}
_ => None,
};
if let Some(adjust) = to_add {
add_indent(result, adjust);
}
}
Mode::Paren => correct_indent(result),
}
Ok(())
}
fn check_leading_close_paren(result: &mut State<'_>) -> Result<()> {
if result
.error_pos_cache
.contains_key(&ErrorName::LeadingCloseParen)
&& result.paren_trail.line_no == Some(result.line_no)
{
error(result, ErrorName::LeadingCloseParen)?;
}
Ok(())
}
fn on_leading_close_paren(result: &mut State<'_>) -> Result<()> {
match result.mode {
Mode::Indent => {
if !result.force_balance {
if result.smart {
error(result, ErrorName::Restart)?;
}
if !result
.error_pos_cache
.contains_key(&ErrorName::LeadingCloseParen)
{
cache_error_pos(result, ErrorName::LeadingCloseParen);
}
}
result.skip_char = true;
}
Mode::Paren => {
if !is_valid_close_paren(&result.paren_stack, result.ch) {
if result.smart {
result.skip_char = true;
} else {
error(result, ErrorName::UnmatchedCloseParen)?;
}
} else if is_cursor_left_of(
result.cursor_x,
result.cursor_line,
Some(result.x),
result.line_no,
) {
let line_no = result.line_no;
let x = result.x;
reset_paren_trail(result, line_no, x);
on_indent(result)?;
} else {
append_paren_trail(result);
result.skip_char = true;
}
}
}
Ok(())
}
fn on_comment_line(result: &mut State<'_>) {
let paren_trail_length = result.paren_trail.openers.len();
// restore the openers matching the previous paren trail
if let Mode::Paren = result.mode {
for j in 0..paren_trail_length {
if let Some(opener) = peek(&result.paren_trail.openers, j) {
result.paren_stack.push(opener.clone());
}
}
};
let x = result.x;
let i = get_parent_opener_index(result, x);
let mut indent_to_add: Delta = 0;
if let Some(opener) = peek(&result.paren_stack, i) {
// shift the comment line based on the parent open paren
if should_add_opener_indent(result, opener) {
indent_to_add = opener.indent_delta;
}
// TODO: store some information here if we need to place close-parens after comment lines
}
if indent_to_add != 0 {
add_indent(result, indent_to_add);
}
// repop the openers matching the previous paren trail
if let Mode::Paren = result.mode {
for _ in 0..paren_trail_length {
result.paren_stack.pop();
}
}
}
fn check_indent(result: &mut State<'_>) -> Result<()> {
if is_close_paren(result.ch) {
on_leading_close_paren(result)?;
} else if result.ch == result.comment_char {
// comments don't count as indentation points
on_comment_line(result);
result.tracking_indent = false;
} else if result.ch != "\n" && result.ch != " " && result.ch != "\t" {
on_indent(result)?;
}
Ok(())
}
fn make_tab_stop<'a>(opener: &Paren<'a>) -> TabStop<'a> {
TabStop {
ch: opener.ch,
x: opener.x,
line_no: opener.line_no,
arg_x: opener.arg_x,
}
}
fn get_tab_stop_line(result: &State<'_>) -> Option<LineNumber> {
result.selection_start_line.or(result.cursor_line)
}
fn set_tab_stops(result: &mut State<'_>) {
if get_tab_stop_line(result) != Some(result.line_no) {
return;
}
result.tab_stops = result.paren_stack.iter().map(make_tab_stop).collect();
if result.mode == Mode::Paren {
let paren_trail_tabs: Vec<_> = result
.paren_trail
.openers
.iter()
.rev()
.map(make_tab_stop)
.collect();
result.tab_stops.extend(paren_trail_tabs);
}
// remove argX if it falls to the right of the next stop
for i in 1..result.tab_stops.len() {
let x = result.tab_stops[i].x;
if let Some(prev_arg_x) = result.tab_stops[i - 1].arg_x {
if prev_arg_x >= x {
result.tab_stops[i - 1].arg_x = None;
}
}
}
}
// {{{1 High-level processing functions
fn process_char<'a>(result: &mut State<'a>, ch: &'a str) -> Result<()> {
let orig_ch = ch;
result.ch = ch;
result.skip_char = false;
handle_change_delta(result);
if result.tracking_indent {
check_indent(result)?;
}
if result.skip_char {
result.ch = "";
} else {
on_char(result)?;
}
commit_char(result, orig_ch);
Ok(())
}
fn process_line(result: &mut State<'_>, line_no: usize) -> Result<()> {
init_line(result);
result.lines.push(Cow::from(result.input_lines[line_no]));
set_tab_stops(result);
for (x, ch) in result.input_lines[line_no]
.graphemes(true)
.scan(0, |column, ch| {
let start_column = *column;
*column += UnicodeWidthStr::width(ch);
Some((start_column, ch))
})
{
result.input_x = x;
process_char(result, ch)?;
}
process_char(result, "\n")?;
if !result.force_balance {
check_unmatched_outside_paren_trail(result)?;
check_leading_close_paren(result)?;
}
if Some(result.line_no) == result.paren_trail.line_no {
finish_new_paren_trail(result);
}
Ok(())
}
fn finalize_result(result: &mut State<'_>) -> Result<()> {
if result.quote_danger {
error(result, ErrorName::QuoteDanger)?;
}
if result.is_in_stringish() {
error(result, ErrorName::UnclosedQuote)?;
}
if !result.paren_stack.is_empty() && result.mode == Mode::Paren {
error(result, ErrorName::UnclosedParen)?;
}
if result.mode == Mode::Indent {
init_line(result);
on_indent(result)?;
}
result.success = true;
Ok(())
}
fn process_error(result: &mut State<'_>, e: Error) {
result.success = false;
result.error = Some(e);
}
fn process_text<'a>(text: &'a str, options: &Options, mode: Mode, smart: bool) -> State<'a> {
let mut result = get_initial_result(text, options, mode, smart);
let mut process_result: Result<()> = Ok(());
for i in 0..result.input_lines.len() {
result.input_line_no = i;
process_result = process_line(&mut result, i);
if process_result.is_err() {
break;
}
}
if process_result.is_ok() {
process_result = finalize_result(&mut result);
}
match process_result {
Err(Error {
name: ErrorName::Restart,
..
}) => process_text(text, options, Mode::Paren, smart),
Err(e) => {
process_error(&mut result, e);
result
}
_ => result,
}
}
// {{{1 Public API
fn public_result(result: State<'_>) -> Answer<'_> {
let line_ending = get_line_ending(result.orig_text);
if result.success {
Answer {
text: Cow::from(result.lines.join(line_ending)),
cursor_x: result.cursor_x,
cursor_line: result.cursor_line,
success: true,
tab_stops: result.tab_stops,
paren_trails: result.paren_trails,
parens: result.parens,
error: None,
}
} else {
Answer {
text: if result.partial_result {
Cow::from(result.lines.join(line_ending))
} else {
Cow::from(result.orig_text)
},
cursor_x: if result.partial_result {
result.cursor_x
} else {
result.orig_cursor_x
},
cursor_line: if result.partial_result {
result.cursor_line
} else {
result.orig_cursor_line
},
paren_trails: result.paren_trails,
success: false,
tab_stops: result.tab_stops,
error: result.error,
parens: result.parens,
}
}
}
pub fn indent_mode<'a>(text: &'a str, options: &Options) -> Answer<'a> {
public_result(process_text(text, options, Mode::Indent, false))
}
pub fn paren_mode<'a>(text: &'a str, options: &Options) -> Answer<'a> {
public_result(process_text(text, options, Mode::Paren, false))
}
pub fn smart_mode<'a>(text: &'a str, options: &Options) -> Answer<'a> {
let smart = options.selection_start_line.is_none();
public_result(process_text(text, options, Mode::Indent, smart))
}
pub fn process(request: &Request) -> Answer {
let mut options = request.options.clone();
if let Some(ref prev_text) = request.options.prev_text {
options.changes = changes::compute_text_changes(prev_text, &request.text);
}
if request.mode == "paren" {
paren_mode(&request.text, &options)
} else if request.mode == "indent" {
indent_mode(&request.text, &options)
} else if request.mode == "smart" {
smart_mode(&request.text, &options)
} else {
Answer::from(Error {
message: String::from("Bad value specified for `mode`"),
..Error::default()
})
}
}
// This is like the process function above, but uses a reference counted version of Request
#[allow(dead_code)]
pub fn rc_process(request: &SharedRequest) -> Answer<'_> {
let mut options = request.options.clone();
if let Some(ref prev_text) = request.options.prev_text {
options.changes = changes::compute_text_changes(prev_text, &request.text);
}
if request.mode == "paren" {
paren_mode(&request.text, &options)
} else if request.mode == "indent" {
indent_mode(&request.text, &options)
} else if request.mode == "smart" {
smart_mode(&request.text, &options)
} else {
Answer::from(Error {
message: String::from("Bad value specified for `mode`"),
..Error::default()
})
}
}