completion now prefers completing variable names before trying comp specs
improved color picking for error messages
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use ariadne::Span;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, write_vars}
|
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, read_vars, write_vars}
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
@@ -102,15 +105,19 @@ fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: En
|
|||||||
return Err(ShErr::at(ShErrKind::ExecFail, blame, "push: missing array name".to_string()));
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "push: missing array name".to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
for (val, _) in argv {
|
for (val, span) in argv {
|
||||||
let push_val = val.clone();
|
let push_val = val.clone();
|
||||||
if let Err(e) = write_vars(|v| v.get_arr_mut(&name).map(|arr| match end {
|
write_vars(|v| {
|
||||||
End::Front => arr.push_front(push_val),
|
if let Ok(arr) = v.get_arr_mut(&name) {
|
||||||
End::Back => arr.push_back(push_val),
|
match end {
|
||||||
})) {
|
End::Front => arr.push_front(push_val),
|
||||||
state::set_status(1);
|
End::Back => arr.push_back(push_val),
|
||||||
return Err(e);
|
}
|
||||||
};
|
Ok(())
|
||||||
|
} else {
|
||||||
|
v.set_var(&name, VarKind::Arr(VecDeque::from([push_val])), VarFlags::NONE)
|
||||||
|
}
|
||||||
|
}).blame(span)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ use crate::{
|
|||||||
|
|
||||||
pub type ShResult<T> = Result<T, ShErr>;
|
pub type ShResult<T> = Result<T, ShErr>;
|
||||||
|
|
||||||
pub struct ColorRng;
|
pub struct ColorRng {
|
||||||
|
last_color: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
impl ColorRng {
|
impl ColorRng {
|
||||||
fn get_colors() -> &'static [Color] {
|
fn get_colors() -> &'static [Color] {
|
||||||
@@ -39,6 +41,16 @@ impl ColorRng {
|
|||||||
Color::Fixed(105), // medium purple
|
Color::Fixed(105), // medium purple
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn last_color(&mut self) -> Color {
|
||||||
|
if let Some(color) = self.last_color.take() {
|
||||||
|
return color;
|
||||||
|
} else {
|
||||||
|
let color = self.next().unwrap_or(Color::White);
|
||||||
|
self.last_color = Some(color);
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for ColorRng {
|
impl Iterator for ColorRng {
|
||||||
@@ -51,11 +63,19 @@ impl Iterator for ColorRng {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static COLOR_RNG: RefCell<ColorRng> = const { RefCell::new(ColorRng) };
|
static COLOR_RNG: RefCell<ColorRng> = const { RefCell::new(ColorRng { last_color: None }) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_color() -> Color {
|
pub fn next_color() -> Color {
|
||||||
COLOR_RNG.with(|rng| rng.borrow_mut().next().unwrap())
|
COLOR_RNG.with(|rng| {
|
||||||
|
let color = rng.borrow_mut().next().unwrap();
|
||||||
|
rng.borrow_mut().last_color = Some(color);
|
||||||
|
color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last_color() -> Color {
|
||||||
|
COLOR_RNG.with(|rng| rng.borrow_mut().last_color())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ShResultExt {
|
pub trait ShResultExt {
|
||||||
@@ -145,14 +165,14 @@ impl ShErr {
|
|||||||
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()] }
|
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()] }
|
||||||
}
|
}
|
||||||
pub fn at(kind: ShErrKind, span: Span, msg: impl Into<String>) -> Self {
|
pub fn at(kind: ShErrKind, span: Span, msg: impl Into<String>) -> Self {
|
||||||
let color = next_color();
|
let color = last_color(); // use last_color to ensure the same color is used for the label and the message given
|
||||||
let src = span.span_source().clone();
|
let src = span.span_source().clone();
|
||||||
let msg: String = msg.into();
|
let msg: String = msg.into();
|
||||||
Self::new(kind, span.clone())
|
Self::new(kind, span.clone())
|
||||||
.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||||
}
|
}
|
||||||
pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self {
|
pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self {
|
||||||
let color = next_color();
|
let color = last_color();
|
||||||
let src = span.span_source().clone();
|
let src = span.span_source().clone();
|
||||||
let msg: String = msg.into();
|
let msg: String = msg.into();
|
||||||
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||||
@@ -230,7 +250,7 @@ impl ShErr {
|
|||||||
}
|
}
|
||||||
pub fn print_error(&self) {
|
pub fn print_error(&self) {
|
||||||
let default = || {
|
let default = || {
|
||||||
eprintln!("{}", self.kind);
|
eprintln!("\n{}", self.kind);
|
||||||
for note in &self.notes {
|
for note in &self.notes {
|
||||||
eprintln!("note: {note}");
|
eprintln!("note: {note}");
|
||||||
}
|
}
|
||||||
@@ -245,6 +265,7 @@ impl ShErr {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| format!("Failed to fetch source '{}'", src.name()))
|
.ok_or_else(|| format!("Failed to fetch source '{}'", src.name()))
|
||||||
});
|
});
|
||||||
|
eprintln!();
|
||||||
if report.eprint(cache).is_err() {
|
if report.eprint(cache).is_err() {
|
||||||
default();
|
default();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn exec_subsh(&mut self, subsh: Node) -> ShResult<()> {
|
fn exec_subsh(&mut self, subsh: Node) -> ShResult<()> {
|
||||||
|
let blame = subsh.get_span().clone();
|
||||||
let NdRule::Command { assignments, argv } = subsh.class else {
|
let NdRule::Command { assignments, argv } = subsh.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -328,7 +329,7 @@ impl Dispatcher {
|
|||||||
let mut argv = match prepare_argv(argv) {
|
let mut argv = match prepare_argv(argv) {
|
||||||
Ok(argv) => argv,
|
Ok(argv) => argv,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
e.print_error();
|
e.try_blame(blame).print_error();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -376,7 +377,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
blame.rename(func_name.clone());
|
blame.rename(func_name.clone());
|
||||||
|
|
||||||
let argv = prepare_argv(argv)?;
|
let argv = prepare_argv(argv).try_blame(blame.clone())?;
|
||||||
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
||||||
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
||||||
func_body.body_mut().propagate_context(func_ctx);
|
func_body.body_mut().propagate_context(func_ctx);
|
||||||
@@ -833,6 +834,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
||||||
|
let blame = cmd.get_span().clone();
|
||||||
let context = cmd.context.clone();
|
let context = cmd.context.clone();
|
||||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -856,7 +858,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
self.io_stack.append_to_frame(cmd.redirs);
|
self.io_stack.append_to_frame(cmd.redirs);
|
||||||
|
|
||||||
let exec_args = ExecArgs::new(argv)?;
|
let exec_args = ExecArgs::new(argv).blame(blame)?;
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
let job = self.job_stack.curr_job_mut().unwrap();
|
let job = self.job_stack.curr_job_mut().unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use lex::{LexFlags, LexStream, Span, SpanSource, Tk, TkFlags, TkRule};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::{
|
libsh::{
|
||||||
error::{ShErr, ShErrKind, ShResult, next_color},
|
error::{ShErr, ShErrKind, ShResult, last_color, next_color},
|
||||||
utils::{NodeVecUtils, TkVecUtils},
|
utils::{NodeVecUtils, TkVecUtils},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -135,7 +135,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
|
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
|
||||||
let color = next_color();
|
let color = last_color();
|
||||||
let span = self.get_span().clone();
|
let span = self.get_span().clone();
|
||||||
(
|
(
|
||||||
span.clone().source().clone(),
|
span.clone().source().clone(),
|
||||||
@@ -1745,7 +1745,7 @@ pub fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult<File> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_err_full(reason: &str, blame: &Span, context: LabelCtx) -> ShErr {
|
fn parse_err_full(reason: &str, blame: &Span, context: LabelCtx) -> ShErr {
|
||||||
let color = next_color();
|
let color = last_color();
|
||||||
ShErr::new(ShErrKind::ParseErr, blame.clone())
|
ShErr::new(ShErrKind::ParseErr, blame.clone())
|
||||||
.with_label(
|
.with_label(
|
||||||
blame.span_source().clone(),
|
blame.span_source().clone(),
|
||||||
|
|||||||
@@ -433,6 +433,13 @@ impl CompSpec for BashCompSpec {
|
|||||||
if self.function.is_some() {
|
if self.function.is_some() {
|
||||||
candidates.extend(self.exec_comp_func(ctx)?);
|
candidates.extend(self.exec_comp_func(ctx)?);
|
||||||
}
|
}
|
||||||
|
candidates = candidates.into_iter()
|
||||||
|
.map(|c| {
|
||||||
|
let stripped = c.strip_prefix(&expanded).unwrap_or_default();
|
||||||
|
format!("{prefix}{stripped}")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
candidates.sort_by_key(|c| c.len()); // sort by length to prioritize shorter completions, ties are then sorted alphabetically
|
candidates.sort_by_key(|c| c.len()); // sort by length to prioritize shorter completions, ties are then sorted alphabetically
|
||||||
|
|
||||||
Ok(candidates)
|
Ok(candidates)
|
||||||
@@ -766,8 +773,25 @@ impl Completer {
|
|||||||
self.token_span = (cursor_pos, cursor_pos);
|
self.token_span = (cursor_pos, cursor_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try programmable completion first
|
// Use marker-based context detection for sub-token awareness (e.g. VAR_SUB
|
||||||
|
// inside a token). Run this before comp specs so variable completions take
|
||||||
|
// priority over programmable completion.
|
||||||
|
let (mut marker_ctx, token_start) = self.get_completion_context(&line, cursor_pos);
|
||||||
|
|
||||||
|
if marker_ctx.last() == Some(&markers::VAR_SUB) {
|
||||||
|
if let Some(cur) = ctx.words.get(ctx.cword) {
|
||||||
|
self.token_span.0 = token_start;
|
||||||
|
let mut span = cur.span.clone();
|
||||||
|
span.set_range(token_start..self.token_span.1);
|
||||||
|
let raw_tk = span.as_str();
|
||||||
|
let candidates = complete_vars(raw_tk);
|
||||||
|
if !candidates.is_empty() {
|
||||||
|
return Ok(CompResult::from_candidates(candidates));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try programmable completion
|
||||||
match self.try_comp_spec(&ctx)? {
|
match self.try_comp_spec(&ctx)? {
|
||||||
CompSpecResult::NoMatch { flags } => {
|
CompSpecResult::NoMatch { flags } => {
|
||||||
if flags.contains(CompOptFlags::DIRNAMES) {
|
if flags.contains(CompOptFlags::DIRNAMES) {
|
||||||
@@ -801,9 +825,6 @@ impl Completer {
|
|||||||
|
|
||||||
self.token_span = (cur_token.span.range().start, cur_token.span.range().end);
|
self.token_span = (cur_token.span.range().start, cur_token.span.range().end);
|
||||||
|
|
||||||
// Use marker-based context detection for sub-token awareness (e.g. VAR_SUB
|
|
||||||
// inside a token)
|
|
||||||
let (mut marker_ctx, token_start) = self.get_completion_context(&line, cursor_pos);
|
|
||||||
self.token_span.0 = token_start;
|
self.token_span.0 = token_start;
|
||||||
cur_token
|
cur_token
|
||||||
.span
|
.span
|
||||||
@@ -828,12 +849,9 @@ impl Completer {
|
|||||||
_ if self.dirs_only => complete_dirs(&expanded),
|
_ if self.dirs_only => complete_dirs(&expanded),
|
||||||
Some(markers::COMMAND) => complete_commands(&expanded),
|
Some(markers::COMMAND) => complete_commands(&expanded),
|
||||||
Some(markers::VAR_SUB) => {
|
Some(markers::VAR_SUB) => {
|
||||||
let var_candidates = complete_vars(&raw_tk);
|
// Variable completion already tried above and had no matches,
|
||||||
if var_candidates.is_empty() {
|
// fall through to filename completion
|
||||||
complete_filename(&expanded)
|
complete_filename(&expanded)
|
||||||
} else {
|
|
||||||
var_candidates
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(markers::ARG) => complete_filename(&expanded),
|
Some(markers::ARG) => complete_filename(&expanded),
|
||||||
_ => complete_filename(&expanded),
|
_ => complete_filename(&expanded),
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ impl ShedVi {
|
|||||||
|
|
||||||
match self.completer.complete(line, cursor_pos, direction) {
|
match self.completer.complete(line, cursor_pos, direction) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.writer.flush_write(&format!("\n{e}\n\n"))?;
|
e.print_error();
|
||||||
|
|
||||||
// Printing the error invalidates the layout
|
// Printing the error invalidates the layout
|
||||||
self.old_layout = None;
|
self.old_layout = None;
|
||||||
|
|||||||
@@ -780,7 +780,7 @@ impl AsFd for TermReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
pub prompt_end: Pos,
|
pub prompt_end: Pos,
|
||||||
pub cursor: Pos,
|
pub cursor: Pos,
|
||||||
|
|||||||
@@ -467,9 +467,7 @@ thread_local! {
|
|||||||
|
|
||||||
/// A shell function
|
/// A shell function
|
||||||
///
|
///
|
||||||
/// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers
|
/// Wraps the BraceGrp Node that forms the body of the function, and provides some helper methods to extract it from the parse tree
|
||||||
/// to. The Node must be stored with the ParsedSrc because the tokens of the
|
|
||||||
/// node contain an Arc<String> Which refers to the String held in ParsedSrc
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ShFunc(Node);
|
pub struct ShFunc(Node);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user