Compare commits
2 Commits
307386ffc6
...
3a5b56fcd3
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a5b56fcd3 | |||
| 09024728f6 |
@@ -31,7 +31,6 @@ use nix::unistd::read;
|
|||||||
use crate::builtin::keymap::KeyMapMatch;
|
use crate::builtin::keymap::KeyMapMatch;
|
||||||
use crate::builtin::trap::TrapTarget;
|
use crate::builtin::trap::TrapTarget;
|
||||||
use crate::libsh::error::{self, ShErr, ShErrKind, ShResult};
|
use crate::libsh::error::{self, ShErr, ShErrKind, ShResult};
|
||||||
use crate::libsh::guards::scope_guard;
|
|
||||||
use crate::libsh::sys::TTY_FILENO;
|
use crate::libsh::sys::TTY_FILENO;
|
||||||
use crate::libsh::utils::AutoCmdVecUtils;
|
use crate::libsh::utils::AutoCmdVecUtils;
|
||||||
use crate::parse::execute::{exec_dash_c, exec_input};
|
use crate::parse::execute::{exec_dash_c, exec_input};
|
||||||
|
|||||||
@@ -803,7 +803,7 @@ impl Dispatcher {
|
|||||||
self.job_stack.new_job();
|
self.job_stack.new_job();
|
||||||
if cmds.len() == 1 {
|
if cmds.len() == 1 {
|
||||||
self.fg_job = !is_bg && self.interactive;
|
self.fg_job = !is_bg && self.interactive;
|
||||||
let mut cmd = cmds.into_iter().next().unwrap();
|
let cmd = cmds.into_iter().next().unwrap();
|
||||||
if is_bg && !matches!(cmd.class, NdRule::Command { .. }) {
|
if is_bg && !matches!(cmd.class, NdRule::Command { .. }) {
|
||||||
self.run_fork(
|
self.run_fork(
|
||||||
&cmd.get_command().map(|t| t.to_string()).unwrap_or_default(),
|
&cmd.get_command().map(|t| t.to_string()).unwrap_or_default(),
|
||||||
|
|||||||
@@ -217,6 +217,32 @@ impl Tk {
|
|||||||
};
|
};
|
||||||
self.span.as_str().trim() == ";;"
|
self.span.as_str().trim() == ";;"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_opener(&self) -> bool {
|
||||||
|
OPENERS.contains(&self.as_str()) ||
|
||||||
|
matches!(self.class, TkRule::BraceGrpStart) ||
|
||||||
|
matches!(self.class, TkRule::CasePattern)
|
||||||
|
}
|
||||||
|
pub fn is_closer(&self) -> bool {
|
||||||
|
matches!(self.as_str(), "fi" | "done" | "esac") ||
|
||||||
|
self.has_double_semi() ||
|
||||||
|
matches!(self.class, TkRule::BraceGrpEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_closer_for(&self, other: &Tk) -> bool {
|
||||||
|
if (matches!(other.class, TkRule::BraceGrpStart) && matches!(self.class, TkRule::BraceGrpEnd))
|
||||||
|
|| (matches!(other.class, TkRule::CasePattern) && self.has_double_semi()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match other.as_str() {
|
||||||
|
"for" |
|
||||||
|
"while" |
|
||||||
|
"until" => matches!(self.as_str(), "done"),
|
||||||
|
"if" => matches!(self.as_str(), "fi"),
|
||||||
|
"case" => matches!(self.as_str(), "esac"),
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Tk {
|
impl Display for Tk {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fmt::{Debug, Write},
|
fmt::{Debug, Write},
|
||||||
path::{Path, PathBuf},
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
libsh::{error::ShResult, guards::var_ctx_guard},
|
libsh::{error::ShResult, guards::var_ctx_guard},
|
||||||
parse::{
|
parse::{
|
||||||
execute::exec_input,
|
execute::exec_input,
|
||||||
lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule},
|
lex::{LexFlags, LexStream, QuoteState, Tk, TkRule},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
readline::{
|
readline::{
|
||||||
@@ -350,6 +350,73 @@ impl ClampedUsize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)]
|
||||||
|
pub struct IndentCtx {
|
||||||
|
depth: usize,
|
||||||
|
ctx: Vec<Tk>,
|
||||||
|
in_escaped_line: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndentCtx {
|
||||||
|
pub fn new() -> Self { Self::default() }
|
||||||
|
|
||||||
|
pub fn depth(&self) -> usize {
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ctx(&self) -> &[Tk] {
|
||||||
|
&self.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn descend(&mut self, tk: Tk) {
|
||||||
|
self.ctx.push(tk);
|
||||||
|
self.depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ascend(&mut self) {
|
||||||
|
self.depth = self.depth.saturating_sub(1);
|
||||||
|
self.ctx.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
std::mem::take(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_tk(&mut self, tk: Tk) {
|
||||||
|
if tk.is_opener() {
|
||||||
|
self.descend(tk);
|
||||||
|
} else if self.ctx.last().is_some_and(|t| tk.is_closer_for(t)) {
|
||||||
|
self.ascend();
|
||||||
|
} else if matches!(tk.class, TkRule::Sep) && self.in_escaped_line {
|
||||||
|
self.in_escaped_line = false;
|
||||||
|
self.depth = self.depth.saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate(&mut self, input: &str) -> usize {
|
||||||
|
self.depth = 0;
|
||||||
|
self.ctx.clear();
|
||||||
|
self.in_escaped_line = false;
|
||||||
|
|
||||||
|
let input_arc = Arc::new(input.to_string());
|
||||||
|
let Ok(tokens) = LexStream::new(input_arc, LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<Tk>>>() else {
|
||||||
|
log::error!("Lexing failed during depth calculation: {:?}", input);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
for tk in tokens {
|
||||||
|
self.check_tk(tk);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.ends_with("\\\n") {
|
||||||
|
self.in_escaped_line = true;
|
||||||
|
self.depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct LineBuf {
|
pub struct LineBuf {
|
||||||
pub buffer: String,
|
pub buffer: String,
|
||||||
@@ -363,7 +430,7 @@ pub struct LineBuf {
|
|||||||
|
|
||||||
pub insert_mode_start_pos: Option<usize>,
|
pub insert_mode_start_pos: Option<usize>,
|
||||||
pub saved_col: Option<usize>,
|
pub saved_col: Option<usize>,
|
||||||
pub auto_indent_level: usize,
|
pub indent_ctx: IndentCtx,
|
||||||
|
|
||||||
pub undo_stack: Vec<Edit>,
|
pub undo_stack: Vec<Edit>,
|
||||||
pub redo_stack: Vec<Edit>,
|
pub redo_stack: Vec<Edit>,
|
||||||
@@ -617,6 +684,17 @@ impl LineBuf {
|
|||||||
pub fn read_slice_to_cursor(&self) -> Option<&str> {
|
pub fn read_slice_to_cursor(&self) -> Option<&str> {
|
||||||
self.read_slice_to(self.cursor.get())
|
self.read_slice_to(self.cursor.get())
|
||||||
}
|
}
|
||||||
|
pub fn cursor_is_escaped(&mut self) -> bool {
|
||||||
|
let Some(to_cursor) = self.slice_to_cursor() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// count the number of backslashes
|
||||||
|
let delta = to_cursor.len() - to_cursor.trim_end_matches('\\').len();
|
||||||
|
|
||||||
|
// an even number of backslashes means each one is escaped
|
||||||
|
delta % 2 != 0
|
||||||
|
}
|
||||||
pub fn slice_to_cursor_inclusive(&mut self) -> Option<&str> {
|
pub fn slice_to_cursor_inclusive(&mut self) -> Option<&str> {
|
||||||
self.slice_to(self.cursor.ret_add(1))
|
self.slice_to(self.cursor.ret_add(1))
|
||||||
}
|
}
|
||||||
@@ -829,7 +907,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Some(self.line_bounds(line_no))
|
Some(self.line_bounds(line_no))
|
||||||
}
|
}
|
||||||
pub fn word_at(&mut self, pos: usize, word: Word) -> (usize, usize) {
|
pub fn word_at(&mut self, _pos: usize, word: Word) -> (usize, usize) {
|
||||||
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
||||||
self.cursor.get()
|
self.cursor.get()
|
||||||
} else {
|
} else {
|
||||||
@@ -2031,51 +2109,13 @@ impl LineBuf {
|
|||||||
let end = start + (new.len().max(gr.len()));
|
let end = start + (new.len().max(gr.len()));
|
||||||
self.buffer.replace_range(start..end, new);
|
self.buffer.replace_range(start..end, new);
|
||||||
}
|
}
|
||||||
pub fn calc_indent_level(&mut self) {
|
pub fn calc_indent_level(&mut self) -> usize {
|
||||||
// FIXME: This implementation is extremely naive but it kind of sort of works for now
|
|
||||||
// Need to re-implement it and write tests
|
|
||||||
let to_cursor = self
|
let to_cursor = self
|
||||||
.slice_to_cursor()
|
.slice_to_cursor()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.unwrap_or(self.buffer.clone());
|
.unwrap_or(self.buffer.clone());
|
||||||
|
|
||||||
let mut level: usize = 0;
|
self.indent_ctx.calculate(&to_cursor)
|
||||||
|
|
||||||
if to_cursor.ends_with("\\\n") {
|
|
||||||
level += 1; // Line continuation, so we need to add an extra level
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = Arc::new(to_cursor);
|
|
||||||
let Ok(tokens) = LexStream::new(input, LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<Tk>>>()
|
|
||||||
else {
|
|
||||||
log::error!("Failed to lex buffer for indent calculation");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let mut last_keyword: Option<String> = None;
|
|
||||||
for tk in tokens {
|
|
||||||
if tk.flags.contains(TkFlags::KEYWORD) {
|
|
||||||
match tk.as_str() {
|
|
||||||
"in" => {
|
|
||||||
if last_keyword.as_deref() == Some("case") {
|
|
||||||
level += 1;
|
|
||||||
} else {
|
|
||||||
// 'in' is also used in for loops, but we already increment level on 'do' for those
|
|
||||||
// so we just skip it here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"then" | "do" => level += 1,
|
|
||||||
"done" | "fi" | "esac" => level = level.saturating_sub(1),
|
|
||||||
_ => { /* Continue */ }
|
|
||||||
}
|
|
||||||
last_keyword = Some(tk.to_string());
|
|
||||||
} else if tk.class == TkRule::BraceGrpStart {
|
|
||||||
level += 1;
|
|
||||||
} else if tk.class == TkRule::BraceGrpEnd {
|
|
||||||
level = level.saturating_sub(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.auto_indent_level = level;
|
|
||||||
}
|
}
|
||||||
pub fn eval_motion(&mut self, verb: Option<&Verb>, motion: MotionCmd) -> MotionKind {
|
pub fn eval_motion(&mut self, verb: Option<&Verb>, motion: MotionCmd) -> MotionKind {
|
||||||
let buffer = self.buffer.clone();
|
let buffer = self.buffer.clone();
|
||||||
@@ -2652,8 +2692,8 @@ impl LineBuf {
|
|||||||
register.write_to_register(register_content);
|
register.write_to_register(register_content);
|
||||||
self.cursor.set(start);
|
self.cursor.set(start);
|
||||||
if do_indent {
|
if do_indent {
|
||||||
self.calc_indent_level();
|
let depth = self.calc_indent_level();
|
||||||
let tabs = (0..self.auto_indent_level).map(|_| '\t');
|
let tabs = (0..depth).map(|_| '\t');
|
||||||
for tab in tabs {
|
for tab in tabs {
|
||||||
self.insert_at_cursor(tab);
|
self.insert_at_cursor(tab);
|
||||||
self.cursor.add(1);
|
self.cursor.add(1);
|
||||||
@@ -2888,17 +2928,29 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
end = end.saturating_sub(1);
|
end = end.saturating_sub(1);
|
||||||
let mut last_was_whitespace = false;
|
let mut last_was_whitespace = false;
|
||||||
for i in start..end {
|
let mut last_was_escape = false;
|
||||||
|
let mut i = start;
|
||||||
|
while i < end {
|
||||||
let Some(gr) = self.grapheme_at(i) else {
|
let Some(gr) = self.grapheme_at(i) else {
|
||||||
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if gr == "\n" {
|
if gr == "\n" {
|
||||||
if last_was_whitespace {
|
if last_was_whitespace {
|
||||||
self.remove(i);
|
self.remove(i);
|
||||||
|
end -= 1;
|
||||||
} else {
|
} else {
|
||||||
self.force_replace_at(i, " ");
|
self.force_replace_at(i, " ");
|
||||||
}
|
}
|
||||||
|
if last_was_escape {
|
||||||
|
// if we are here, then we just joined an escaped newline
|
||||||
|
// semantically, echo foo\\nbar == echo foo bar
|
||||||
|
// so a joined line should remove the escape.
|
||||||
|
self.remove(i - 1);
|
||||||
|
end -= 1;
|
||||||
|
}
|
||||||
last_was_whitespace = false;
|
last_was_whitespace = false;
|
||||||
|
last_was_escape = false;
|
||||||
let strip_pos = if self.grapheme_at(i) == Some(" ") {
|
let strip_pos = if self.grapheme_at(i) == Some(" ") {
|
||||||
i + 1
|
i + 1
|
||||||
} else {
|
} else {
|
||||||
@@ -2906,26 +2958,39 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
while self.grapheme_at(strip_pos) == Some("\t") {
|
while self.grapheme_at(strip_pos) == Some("\t") {
|
||||||
self.remove(strip_pos);
|
self.remove(strip_pos);
|
||||||
|
end -= 1;
|
||||||
}
|
}
|
||||||
self.cursor.set(i);
|
self.cursor.set(i);
|
||||||
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
|
} else if gr == "\\" {
|
||||||
|
if last_was_whitespace && last_was_escape {
|
||||||
|
// if we are here, then the pattern of the last three chars was this:
|
||||||
|
// ' \\', a space and two backslashes.
|
||||||
|
// This means the "last" was an escaped backslash, not whitespace.
|
||||||
|
last_was_whitespace = false;
|
||||||
}
|
}
|
||||||
|
last_was_escape = !last_was_escape;
|
||||||
|
} else {
|
||||||
last_was_whitespace = is_whitespace(gr);
|
last_was_whitespace = is_whitespace(gr);
|
||||||
|
last_was_escape = false;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn verb_insert_char(&mut self, ch: char) {
|
fn verb_insert_char(&mut self, ch: char) {
|
||||||
self.insert_at_cursor(ch);
|
self.insert_at_cursor(ch);
|
||||||
self.cursor.add(1);
|
self.cursor.add(1);
|
||||||
let before = self.auto_indent_level;
|
let before_escaped = self.indent_ctx.in_escaped_line;
|
||||||
if read_shopts(|o| o.prompt.auto_indent)
|
let before = self.indent_ctx.depth();
|
||||||
&& let Some(line_content) = self.this_line_content()
|
if read_shopts(|o| o.prompt.auto_indent) {
|
||||||
{
|
let after = self.calc_indent_level();
|
||||||
match line_content.trim() {
|
// Only dedent if the depth decrease came from a closer, not from
|
||||||
"esac" | "done" | "fi" | "}" => {
|
// a line continuation bonus going away
|
||||||
self.calc_indent_level();
|
if after < before
|
||||||
if self.auto_indent_level < before {
|
&& !(before_escaped && !self.indent_ctx.in_escaped_line) {
|
||||||
let delta = before - self.auto_indent_level;
|
let delta = before - after;
|
||||||
let line_start = self.start_of_line();
|
let line_start = self.start_of_line();
|
||||||
for _ in 0..delta {
|
for _ in 0..delta {
|
||||||
if self.grapheme_at(line_start).is_some_and(|gr| gr == "\t") {
|
if self.grapheme_at(line_start).is_some_and(|gr| gr == "\t") {
|
||||||
@@ -2937,15 +3002,13 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn verb_insert(&mut self, string: String) {
|
fn verb_insert(&mut self, string: String) {
|
||||||
self.insert_str_at_cursor(&string);
|
self.insert_str_at_cursor(&string);
|
||||||
let graphemes = string.graphemes(true).count();
|
let graphemes = string.graphemes(true).count();
|
||||||
self.cursor.add(graphemes);
|
self.cursor.add(graphemes);
|
||||||
}
|
}
|
||||||
|
#[allow(clippy::unnecessary_to_owned)]
|
||||||
fn verb_indent(&mut self, motion: MotionKind) -> ShResult<()> {
|
fn verb_indent(&mut self, motion: MotionKind) -> ShResult<()> {
|
||||||
let Some((start, end)) = self.range_from_motion(&motion) else {
|
let Some((start, end)) = self.range_from_motion(&motion) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -2975,6 +3038,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
#[allow(clippy::unnecessary_to_owned)]
|
||||||
fn verb_dedent(&mut self, motion: MotionKind) -> ShResult<()> {
|
fn verb_dedent(&mut self, motion: MotionKind) -> ShResult<()> {
|
||||||
let Some((start, mut end)) = self.range_from_motion(&motion) else {
|
let Some((start, mut end)) = self.range_from_motion(&motion) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -3017,8 +3081,8 @@ impl LineBuf {
|
|||||||
Anchor::After => {
|
Anchor::After => {
|
||||||
self.push('\n');
|
self.push('\n');
|
||||||
if auto_indent {
|
if auto_indent {
|
||||||
self.calc_indent_level();
|
let depth = self.calc_indent_level();
|
||||||
for _ in 0..self.auto_indent_level {
|
for _ in 0..depth {
|
||||||
self.push('\t');
|
self.push('\t');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3027,8 +3091,8 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Anchor::Before => {
|
Anchor::Before => {
|
||||||
if auto_indent {
|
if auto_indent {
|
||||||
self.calc_indent_level();
|
let depth = self.calc_indent_level();
|
||||||
for _ in 0..self.auto_indent_level {
|
for _ in 0..depth {
|
||||||
self.insert_at(0, '\t');
|
self.insert_at(0, '\t');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3055,8 +3119,8 @@ impl LineBuf {
|
|||||||
self.insert_at_cursor('\n');
|
self.insert_at_cursor('\n');
|
||||||
self.cursor.add(1);
|
self.cursor.add(1);
|
||||||
if auto_indent {
|
if auto_indent {
|
||||||
self.calc_indent_level();
|
let depth = self.calc_indent_level();
|
||||||
for _ in 0..self.auto_indent_level {
|
for _ in 0..depth {
|
||||||
self.insert_at_cursor('\t');
|
self.insert_at_cursor('\t');
|
||||||
self.cursor.add(1);
|
self.cursor.add(1);
|
||||||
}
|
}
|
||||||
@@ -3198,7 +3262,6 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[allow(clippy::unnecessary_to_owned)]
|
|
||||||
pub fn exec_verb(
|
pub fn exec_verb(
|
||||||
&mut self,
|
&mut self,
|
||||||
verb: Verb,
|
verb: Verb,
|
||||||
@@ -3285,10 +3348,10 @@ impl LineBuf {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Let's evaluate the motion now
|
* Let's evaluate the motion now
|
||||||
* If we got some weird command like 'dvw' we will have to simulate a visual
|
* If we got some weird command like 'dvw' we will
|
||||||
* selection to get the range If motion is None, we will try to use
|
* have to simulate a visual selection to get the range
|
||||||
* self.select_range If self.select_range is None, we will use
|
* If motion is None, we will try to use self.select_range
|
||||||
* MotionKind::Null
|
* If self.select_range is None, we will use MotionKind::Null
|
||||||
*/
|
*/
|
||||||
let motion_eval =
|
let motion_eval =
|
||||||
if flags.intersects(CmdFlags::VISUAL | CmdFlags::VISUAL_LINE | CmdFlags::VISUAL_BLOCK) {
|
if flags.intersects(CmdFlags::VISUAL | CmdFlags::VISUAL_LINE | CmdFlags::VISUAL_BLOCK) {
|
||||||
|
|||||||
@@ -253,7 +253,6 @@ pub struct ShedVi {
|
|||||||
pub repeat_action: Option<CmdReplay>,
|
pub repeat_action: Option<CmdReplay>,
|
||||||
pub repeat_motion: Option<MotionCmd>,
|
pub repeat_motion: Option<MotionCmd>,
|
||||||
pub editor: LineBuf,
|
pub editor: LineBuf,
|
||||||
pub next_is_escaped: bool,
|
|
||||||
|
|
||||||
pub old_layout: Option<Layout>,
|
pub old_layout: Option<Layout>,
|
||||||
pub history: History,
|
pub history: History,
|
||||||
@@ -271,7 +270,6 @@ impl ShedVi {
|
|||||||
completer: Box::new(FuzzyCompleter::default()),
|
completer: Box::new(FuzzyCompleter::default()),
|
||||||
highlighter: Highlighter::new(),
|
highlighter: Highlighter::new(),
|
||||||
mode: Box::new(ViInsert::new()),
|
mode: Box::new(ViInsert::new()),
|
||||||
next_is_escaped: false,
|
|
||||||
saved_mode: None,
|
saved_mode: None,
|
||||||
pending_keymap: Vec::new(),
|
pending_keymap: Vec::new(),
|
||||||
old_layout: None,
|
old_layout: None,
|
||||||
@@ -303,7 +301,6 @@ impl ShedVi {
|
|||||||
completer: Box::new(FuzzyCompleter::default()),
|
completer: Box::new(FuzzyCompleter::default()),
|
||||||
highlighter: Highlighter::new(),
|
highlighter: Highlighter::new(),
|
||||||
mode: Box::new(ViInsert::new()),
|
mode: Box::new(ViInsert::new()),
|
||||||
next_is_escaped: false,
|
|
||||||
saved_mode: None,
|
saved_mode: None,
|
||||||
pending_keymap: Vec::new(),
|
pending_keymap: Vec::new(),
|
||||||
old_layout: None,
|
old_layout: None,
|
||||||
@@ -417,7 +414,7 @@ impl ShedVi {
|
|||||||
LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>();
|
LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>();
|
||||||
let lex_result2 =
|
let lex_result2 =
|
||||||
LexStream::new(Arc::clone(&input), LexFlags::empty()).collect::<ShResult<Vec<_>>>();
|
LexStream::new(Arc::clone(&input), LexFlags::empty()).collect::<ShResult<Vec<_>>>();
|
||||||
let is_top_level = self.editor.auto_indent_level == 0;
|
let is_top_level = self.editor.indent_ctx.ctx().is_empty();
|
||||||
|
|
||||||
let is_complete = match (lex_result1.is_err(), lex_result2.is_err()) {
|
let is_complete = match (lex_result1.is_err(), lex_result2.is_err()) {
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
@@ -808,14 +805,6 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key
|
|
||||||
&& !self.next_is_escaped
|
|
||||||
{
|
|
||||||
self.next_is_escaped = true;
|
|
||||||
} else {
|
|
||||||
self.next_is_escaped = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(cmd) = self.mode.handle_key_fallible(key) else {
|
let Ok(cmd) = self.mode.handle_key_fallible(key) else {
|
||||||
// it's an ex mode error
|
// it's an ex mode error
|
||||||
self.mode = Box::new(ViNormal::new()) as Box<dyn ViMode>;
|
self.mode = Box::new(ViNormal::new()) as Box<dyn ViMode>;
|
||||||
@@ -834,8 +823,7 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cmd.is_submit_action()
|
if cmd.is_submit_action()
|
||||||
&& !self.next_is_escaped
|
&& !self.editor.cursor_is_escaped()
|
||||||
&& !self.editor.buffer.ends_with('\\')
|
|
||||||
&& (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete))
|
&& (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete))
|
||||||
{
|
{
|
||||||
if self.editor.attempt_history_expansion(&self.history) {
|
if self.editor.attempt_history_expansion(&self.history) {
|
||||||
|
|||||||
@@ -233,3 +233,30 @@ vi_test! {
|
|||||||
vi_indent_cursor_pos : "echo foo" => ">>" => "\techo foo", 1;
|
vi_indent_cursor_pos : "echo foo" => ">>" => "\techo foo", 1;
|
||||||
vi_join_indent_lines : "echo foo\n\t\techo bar" => "J" => "echo foo echo bar", 8
|
vi_join_indent_lines : "echo foo\n\t\techo bar" => "J" => "echo foo echo bar", 8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vi_auto_indent() {
|
||||||
|
let (mut vi, _g) = test_vi("");
|
||||||
|
|
||||||
|
// Type each line and press Enter separately so auto-indent triggers
|
||||||
|
let lines = [
|
||||||
|
"func() {",
|
||||||
|
"case foo in",
|
||||||
|
"bar)",
|
||||||
|
"while true; do",
|
||||||
|
"echo foo \\\rbar \\\rbiz \\\rbazz\rbreak\rdone\r;;\resac\r}"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (i,line) in lines.iter().enumerate() {
|
||||||
|
vi.feed_bytes(line.as_bytes());
|
||||||
|
if i != lines.len() - 1 {
|
||||||
|
vi.feed_bytes(b"\r");
|
||||||
|
}
|
||||||
|
vi.process_input().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vi.editor.as_str(),
|
||||||
|
"func() {\n\tcase foo in\n\t\tbar)\n\t\t\twhile true; do\n\t\t\t\techo foo \\\n\t\t\t\t\tbar \\\n\t\t\t\t\tbiz \\\n\t\t\t\t\tbazz\n\t\t\t\tbreak\n\t\t\tdone\n\t\t;;\n\tesac\n}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ impl TestGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pty_slave(&self) -> BorrowedFd {
|
pub fn pty_slave(&self) -> BorrowedFd<'_> {
|
||||||
unsafe { BorrowedFd::borrow_raw(self.pty_slave.as_raw_fd()) }
|
unsafe { BorrowedFd::borrow_raw(self.pty_slave.as_raw_fd()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ impl crate::parse::Node {
|
|||||||
if offender.is_none()
|
if offender.is_none()
|
||||||
&& expected_rule
|
&& expected_rule
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |e| *e != s.class.as_nd_kind())
|
.is_none_or(|e| *e != s.class.as_nd_kind())
|
||||||
{
|
{
|
||||||
offender = Some((s.class.as_nd_kind(), expected_rule));
|
offender = Some((s.class.as_nd_kind(), expected_rule));
|
||||||
} else if offender.is_none() {
|
} else if offender.is_none() {
|
||||||
|
|||||||
Reference in New Issue
Block a user