use super::super::command_checker::CheckerEnv;
use super::super::highlight::{ColorSpan, HighlightStyle};
use super::ctx::ScanCtx;
use super::expansion;
use super::helpers::is_operator_char;
use super::helpers::is_redirect_start;
use super::state::ScanMode;
use super::word;
pub(super) fn scan_normal(ctx: &mut ScanCtx<'_>, env: &CheckerEnv<'_>, pos: usize) -> usize {
if pos >= ctx.input.len() {
return pos;
}
let ch = ctx.input[pos];
if ch.is_ascii_whitespace() {
ctx.state.word_start = true;
return pos + 1;
}
if ch == '#' && ctx.state.word_start {
ctx.state.push_mode(ScanMode::Comment { start: pos });
return pos;
}
if is_operator_char(ch) {
let start = pos;
let mut end = pos + 1;
if ch == '|' && end < ctx.input.len() && ctx.input[end] == '|' {
end += 1; } else if ch == '&' && end < ctx.input.len() && ctx.input[end] == '&' {
end += 1; } else if ch == ';' && end < ctx.input.len() && ctx.input[end] == ';' {
end += 1; }
ctx.spans.push(ColorSpan {
start,
end,
style: HighlightStyle::Operator,
});
ctx.state.command_position = true;
ctx.state.word_start = true;
return end;
}
if is_redirect_start(ch) {
let start = pos;
let mut end = pos + 1;
if ch == '>' && end < ctx.input.len() {
match ctx.input[end] {
'>' | '|' | '&' => end += 1,
_ => {}
}
} else if ch == '<' && end < ctx.input.len() {
match ctx.input[end] {
'<' | '&' | '>' => end += 1,
_ => {}
}
if end == start + 2
&& ctx.input[start + 1] == '<'
&& end < ctx.input.len()
&& ctx.input[end] == '-'
{
end += 1;
}
}
ctx.spans.push(ColorSpan {
start,
end,
style: HighlightStyle::Redirect,
});
ctx.state.command_position = false;
ctx.state.word_start = true;
return end;
}
if ch == '(' {
ctx.spans.push(ColorSpan {
start: pos,
end: pos + 1,
style: HighlightStyle::Operator,
});
ctx.state.command_position = true;
ctx.state.word_start = true;
return pos + 1;
}
if ch == ')' {
let stack_len = ctx.state.mode_stack.len();
if stack_len >= 2
&& let ScanMode::CommandSub { start } = ctx.state.mode_stack[stack_len - 2]
{
ctx.state.pop_mode(); ctx.spans.push(ColorSpan {
start,
end: pos + 1,
style: HighlightStyle::CommandSub,
});
ctx.state.pop_mode(); ctx.state.word_start = false;
ctx.state.command_position = false;
return pos + 1;
}
ctx.spans.push(ColorSpan {
start: pos,
end: pos + 1,
style: HighlightStyle::Operator,
});
ctx.state.command_position = false;
ctx.state.word_start = true;
return pos + 1;
}
if ch == '\'' {
ctx.state.push_mode(ScanMode::SingleQuote { start: pos });
ctx.state.word_start = false;
ctx.state.command_position = false;
return pos + 1; }
if ch == '"' {
ctx.state.push_mode(ScanMode::DoubleQuote { start: pos });
ctx.state.word_start = false;
ctx.state.command_position = false;
return pos + 1;
}
if ch == '`' {
let stack_len = ctx.state.mode_stack.len();
if stack_len >= 2
&& let ScanMode::Backtick { start } = ctx.state.mode_stack[stack_len - 2]
{
ctx.state.pop_mode(); ctx.spans.push(ColorSpan {
start,
end: pos + 1,
style: HighlightStyle::CommandSub,
});
ctx.state.pop_mode(); ctx.state.word_start = false;
ctx.state.command_position = false;
return pos + 1;
}
ctx.state.push_mode(ScanMode::Backtick { start: pos });
ctx.state.push_mode(ScanMode::Normal);
ctx.state.word_start = true;
ctx.state.command_position = true;
return pos + 1;
}
if ch == '$' {
return expansion::scan_dollar(ctx, env, pos);
}
if ch == '~' && ctx.state.word_start {
ctx.spans.push(ColorSpan {
start: pos,
end: pos + 1,
style: HighlightStyle::Tilde,
});
ctx.state.word_start = false;
return pos + 1;
}
word::scan_word(ctx, env, pos)
}