Implemented -o opt for complete/compgen builtins
Completion candidates now come with a space by default, unless it's a directory
This commit is contained in:
@@ -3,7 +3,7 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
|
|||||||
|
|
||||||
use crate::{builtin::setup_builtin, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, procio::{IoStack, borrow_fd}, readline::complete::{BashCompSpec, CompContext, CompSpec}, state::{self, read_meta, write_meta}};
|
use crate::{builtin::setup_builtin, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, procio::{IoStack, borrow_fd}, readline::complete::{BashCompSpec, CompContext, CompSpec}, state::{self, read_meta, write_meta}};
|
||||||
|
|
||||||
pub const COMPGEN_OPTS: [OptSpec;7] = [
|
pub const COMPGEN_OPTS: [OptSpec;8] = [
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('F'),
|
opt: Opt::Short('F'),
|
||||||
takes_arg: true
|
takes_arg: true
|
||||||
@@ -31,10 +31,14 @@ pub const COMPGEN_OPTS: [OptSpec;7] = [
|
|||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('v'),
|
opt: Opt::Short('v'),
|
||||||
takes_arg: false
|
takes_arg: false
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('o'),
|
||||||
|
takes_arg: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const COMP_OPTS: [OptSpec;10] = [
|
pub const COMP_OPTS: [OptSpec;11] = [
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('F'),
|
opt: Opt::Short('F'),
|
||||||
takes_arg: true
|
takes_arg: true
|
||||||
@@ -74,6 +78,10 @@ pub const COMP_OPTS: [OptSpec;10] = [
|
|||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('v'),
|
opt: Opt::Short('v'),
|
||||||
takes_arg: false
|
takes_arg: false
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('o'),
|
||||||
|
takes_arg: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -88,6 +96,12 @@ bitflags! {
|
|||||||
const PRINT = 0b0000100000;
|
const PRINT = 0b0000100000;
|
||||||
const REMOVE = 0b0001000000;
|
const REMOVE = 0b0001000000;
|
||||||
}
|
}
|
||||||
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct CompOptFlags: u32 {
|
||||||
|
const DEFAULT = 0b0000000001;
|
||||||
|
const DIRNAMES = 0b0000000010;
|
||||||
|
const NOSPACE = 0b0000000100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
@@ -95,7 +109,8 @@ pub struct CompOpts {
|
|||||||
pub func: Option<String>,
|
pub func: Option<String>,
|
||||||
pub wordlist: Option<Vec<String>>,
|
pub wordlist: Option<Vec<String>>,
|
||||||
pub action: Option<String>,
|
pub action: Option<String>,
|
||||||
pub flags: CompFlags
|
pub flags: CompFlags,
|
||||||
|
pub opt_flags: CompOptFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
@@ -229,6 +244,20 @@ pub fn get_comp_opts(opts: Vec<Opt>) -> ShResult<CompOpts> {
|
|||||||
Opt::ShortWithArg('A',action) => {
|
Opt::ShortWithArg('A',action) => {
|
||||||
comp_opts.action = Some(action);
|
comp_opts.action = Some(action);
|
||||||
}
|
}
|
||||||
|
Opt::ShortWithArg('o', opt_flag) => {
|
||||||
|
match opt_flag.as_str() {
|
||||||
|
"default" => comp_opts.opt_flags |= CompOptFlags::DEFAULT,
|
||||||
|
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
|
||||||
|
"nospace" => comp_opts.opt_flags |= CompOptFlags::NOSPACE,
|
||||||
|
_ => {
|
||||||
|
return Err(ShErr::full(
|
||||||
|
ShErrKind::InvalidOpt,
|
||||||
|
format!("complete: invalid option: {}", opt_flag),
|
||||||
|
Default::default()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Opt::Short('r') => comp_opts.flags |= CompFlags::REMOVE,
|
Opt::Short('r') => comp_opts.flags |= CompFlags::REMOVE,
|
||||||
Opt::Short('p') => comp_opts.flags |= CompFlags::PRINT,
|
Opt::Short('p') => comp_opts.flags |= CompFlags::PRINT,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::term::{Style, Styled},
|
getopt::Opt, libsh::term::{Style, Styled}, parse::lex::Span, prelude::*
|
||||||
parse::lex::Span,
|
|
||||||
prelude::*,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ShResult<T> = Result<T, ShErr>;
|
pub type ShResult<T> = Result<T, ShErr>;
|
||||||
@@ -395,6 +393,7 @@ impl From<Errno> for ShErr {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ShErrKind {
|
pub enum ShErrKind {
|
||||||
IoErr(io::ErrorKind),
|
IoErr(io::ErrorKind),
|
||||||
|
InvalidOpt,
|
||||||
SyntaxErr,
|
SyntaxErr,
|
||||||
ParseErr,
|
ParseErr,
|
||||||
InternalErr,
|
InternalErr,
|
||||||
@@ -421,6 +420,7 @@ impl Display for ShErrKind {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let output = match self {
|
let output = match self {
|
||||||
Self::IoErr(e) => &format!("I/O Error: {e}"),
|
Self::IoErr(e) => &format!("I/O Error: {e}"),
|
||||||
|
Self::InvalidOpt => &format!("Invalid option"),
|
||||||
Self::SyntaxErr => "Syntax Error",
|
Self::SyntaxErr => "Syntax Error",
|
||||||
Self::ParseErr => "Parse Error",
|
Self::ParseErr => "Parse Error",
|
||||||
Self::InternalErr => "Internal Error",
|
Self::InternalErr => "Internal Error",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ impl Display for ShedLogLevel {
|
|||||||
|
|
||||||
pub fn log_level() -> ShedLogLevel {
|
pub fn log_level() -> ShedLogLevel {
|
||||||
use ShedLogLevel::*;
|
use ShedLogLevel::*;
|
||||||
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
let level = std::env::var("SHED_LOG_LEVEL").unwrap_or_default();
|
||||||
match level.to_lowercase().as_str() {
|
match level.to_lowercase().as_str() {
|
||||||
"error" => ERROR,
|
"error" => ERROR,
|
||||||
"warn" => WARN,
|
"warn" => WARN,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ fn kickstart_lazy_evals() {
|
|||||||
fn setup_panic_handler() {
|
fn setup_panic_handler() {
|
||||||
let default_panic_hook = std::panic::take_hook();
|
let default_panic_hook = std::panic::take_hook();
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = state::FERN.try_with(|shed| {
|
let _ = state::SHED.try_with(|shed| {
|
||||||
if let Ok(mut jobs) = shed.jobs.try_borrow_mut() {
|
if let Ok(mut jobs) = shed.jobs.try_borrow_mut() {
|
||||||
jobs.hang_up();
|
jobs.hang_up();
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::ClearReadline => {
|
ShErrKind::ClearReadline => {
|
||||||
// Ctrl+C - clear current input and show new prompt
|
// Ctrl+C - clear current input and show new prompt
|
||||||
readline.reset();
|
readline.reset(false)?;
|
||||||
}
|
}
|
||||||
ShErrKind::CleanExit(code) => {
|
ShErrKind::CleanExit(code) => {
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
@@ -269,7 +269,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
readline.writer.flush_write("\n")?;
|
readline.writer.flush_write("\n")?;
|
||||||
|
|
||||||
// Reset for next command with fresh prompt
|
// Reset for next command with fresh prompt
|
||||||
readline.reset();
|
readline.reset(true)?;
|
||||||
let real_end = start.elapsed();
|
let real_end = start.elapsed();
|
||||||
log::info!("Total round trip time: {:.2?}", real_end);
|
log::info!("Total round trip time: {:.2?}", real_end);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::{collections::HashSet, env, fmt::Debug, os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc};
|
use std::{collections::HashSet, env, fmt::Debug, os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{BUILTINS, complete::{CompFlags, CompOpts}},
|
builtin::{BUILTINS, complete::{CompFlags, CompOptFlags, CompOpts}},
|
||||||
libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils},
|
libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils},
|
||||||
parse::{execute::{VarCtxGuard, exec_input}, lex::{self, LexFlags, Tk, TkFlags, TkRule}},
|
parse::{execute::{VarCtxGuard, exec_input}, lex::{self, LexFlags, Tk, TkFlags, TkRule, ends_with_unescaped}},
|
||||||
readline::{
|
readline::{
|
||||||
Marker, annotate_input, annotate_input_recursive, get_insertions,
|
Marker, annotate_input, annotate_input_recursive, get_insertions,
|
||||||
markers::{self, is_marker},
|
markers::{self, is_marker},
|
||||||
@@ -167,6 +167,12 @@ fn complete_filename(start: &str) -> Vec<String> {
|
|||||||
candidates
|
candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum CompSpecResult {
|
||||||
|
NoSpec, // No compspec registered
|
||||||
|
NoMatch { flags: CompOptFlags }, // Compspec found but no candidates matched, returns behavior flags
|
||||||
|
Match(CompResult) // Compspec found and candidates returned
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default,Debug,Clone)]
|
#[derive(Default,Debug,Clone)]
|
||||||
pub struct BashCompSpec {
|
pub struct BashCompSpec {
|
||||||
/// -F: The name of a function to generate the possible completions.
|
/// -F: The name of a function to generate the possible completions.
|
||||||
@@ -186,6 +192,7 @@ pub struct BashCompSpec {
|
|||||||
/// -A signal: complete signal names
|
/// -A signal: complete signal names
|
||||||
pub signals: bool,
|
pub signals: bool,
|
||||||
|
|
||||||
|
pub flags: CompOptFlags,
|
||||||
/// The original command
|
/// The original command
|
||||||
pub source: String
|
pub source: String
|
||||||
}
|
}
|
||||||
@@ -231,7 +238,7 @@ impl BashCompSpec {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn from_comp_opts(opts: CompOpts) -> Self {
|
pub fn from_comp_opts(opts: CompOpts) -> Self {
|
||||||
let CompOpts { func, wordlist, action: _, flags } = opts;
|
let CompOpts { func, wordlist, action: _, flags, opt_flags } = opts;
|
||||||
Self {
|
Self {
|
||||||
function: func,
|
function: func,
|
||||||
wordlist,
|
wordlist,
|
||||||
@@ -240,6 +247,7 @@ impl BashCompSpec {
|
|||||||
commands: flags.contains(CompFlags::CMDS),
|
commands: flags.contains(CompFlags::CMDS),
|
||||||
users: flags.contains(CompFlags::USERS),
|
users: flags.contains(CompFlags::USERS),
|
||||||
vars: flags.contains(CompFlags::VARS),
|
vars: flags.contains(CompFlags::VARS),
|
||||||
|
flags: opt_flags,
|
||||||
signals: false, // TODO: implement signal completion
|
signals: false, // TODO: implement signal completion
|
||||||
source: String::new()
|
source: String::new()
|
||||||
}
|
}
|
||||||
@@ -320,11 +328,18 @@ impl CompSpec for BashCompSpec {
|
|||||||
fn source(&self) -> &str {
|
fn source(&self) -> &str {
|
||||||
&self.source
|
&self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_flags(&self) -> CompOptFlags {
|
||||||
|
self.flags
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CompSpec: Debug + CloneCompSpec {
|
pub trait CompSpec: Debug + CloneCompSpec {
|
||||||
fn complete(&self, ctx: &CompContext) -> ShResult<Vec<String>>;
|
fn complete(&self, ctx: &CompContext) -> ShResult<Vec<String>>;
|
||||||
fn source(&self) -> &str;
|
fn source(&self) -> &str;
|
||||||
|
fn get_flags(&self) -> CompOptFlags {
|
||||||
|
CompOptFlags::empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CloneCompSpec {
|
pub trait CloneCompSpec {
|
||||||
@@ -376,23 +391,20 @@ impl CompResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default,Debug,Clone)]
|
||||||
pub struct Completer {
|
pub struct Completer {
|
||||||
pub candidates: Vec<String>,
|
pub candidates: Vec<String>,
|
||||||
pub selected_idx: usize,
|
pub selected_idx: usize,
|
||||||
pub original_input: String,
|
pub original_input: String,
|
||||||
pub token_span: (usize, usize),
|
pub token_span: (usize, usize),
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
|
pub dirs_only: bool,
|
||||||
|
pub no_space: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer {
|
impl Completer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self::default()
|
||||||
candidates: vec![],
|
|
||||||
selected_idx: 0,
|
|
||||||
original_input: String::new(),
|
|
||||||
token_span: (0, 0),
|
|
||||||
active: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn slice_line(line: &str, cursor_pos: usize) -> (&str, &str) {
|
pub fn slice_line(line: &str, cursor_pos: usize) -> (&str, &str) {
|
||||||
@@ -487,11 +499,29 @@ impl Completer {
|
|||||||
self.get_completed_line()
|
self.get_completed_line()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_spaces(&mut self) {
|
||||||
|
if !self.no_space {
|
||||||
|
self.candidates = std::mem::take(&mut self.candidates)
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| {
|
||||||
|
if !ends_with_unescaped(&c, "/") // directory
|
||||||
|
&& !ends_with_unescaped(&c, "=") // '='-type arg
|
||||||
|
&& !ends_with_unescaped(&c, " ") { // already has a space
|
||||||
|
format!("{} ", c)
|
||||||
|
} else {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_completion(&mut self, line: String, cursor_pos: usize) -> ShResult<Option<String>> {
|
pub fn start_completion(&mut self, line: String, cursor_pos: usize) -> ShResult<Option<String>> {
|
||||||
let result = self.get_candidates(line.clone(), cursor_pos)?;
|
let result = self.get_candidates(line.clone(), cursor_pos)?;
|
||||||
match result {
|
match result {
|
||||||
CompResult::Many { candidates } => {
|
CompResult::Many { candidates } => {
|
||||||
self.candidates = candidates.clone();
|
self.candidates = candidates.clone();
|
||||||
|
self.add_spaces();
|
||||||
self.selected_idx = 0;
|
self.selected_idx = 0;
|
||||||
self.original_input = line;
|
self.original_input = line;
|
||||||
self.active = true;
|
self.active = true;
|
||||||
@@ -500,6 +530,7 @@ impl Completer {
|
|||||||
}
|
}
|
||||||
CompResult::Single { result } => {
|
CompResult::Single { result } => {
|
||||||
self.candidates = vec![result.clone()];
|
self.candidates = vec![result.clone()];
|
||||||
|
self.add_spaces();
|
||||||
self.selected_idx = 0;
|
self.selected_idx = 0;
|
||||||
self.original_input = line;
|
self.original_input = line;
|
||||||
self.active = false;
|
self.active = false;
|
||||||
@@ -578,20 +609,20 @@ impl Completer {
|
|||||||
Ok(ctx)
|
Ok(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_comp_spec(&self, ctx: &CompContext) -> ShResult<CompResult> {
|
pub fn try_comp_spec(&self, ctx: &CompContext) -> ShResult<CompSpecResult> {
|
||||||
let Some(cmd) = ctx.cmd() else {
|
let Some(cmd) = ctx.cmd() else {
|
||||||
return Ok(CompResult::NoMatch);
|
return Ok(CompSpecResult::NoSpec);
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(spec) = read_meta(|m| m.get_comp_spec(cmd)) else {
|
let Some(spec) = read_meta(|m| m.get_comp_spec(cmd)) else {
|
||||||
return Ok(CompResult::NoMatch);
|
return Ok(CompSpecResult::NoSpec);
|
||||||
};
|
};
|
||||||
|
|
||||||
let candidates = spec.complete(ctx)?;
|
let candidates = spec.complete(ctx)?;
|
||||||
if candidates.is_empty() {
|
if candidates.is_empty() {
|
||||||
Ok(CompResult::NoMatch)
|
Ok(CompSpecResult::NoMatch { flags: spec.get_flags() })
|
||||||
} else {
|
} else {
|
||||||
Ok(CompResult::from_candidates(candidates))
|
Ok(CompSpecResult::Match(CompResult::from_candidates(candidates)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,10 +641,26 @@ impl Completer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try programmable completion first
|
// Try programmable completion first
|
||||||
let res = self.try_comp_spec(&ctx)?;
|
|
||||||
if !matches!(res, CompResult::NoMatch) {
|
match self.try_comp_spec(&ctx)? {
|
||||||
return Ok(res);
|
CompSpecResult::NoMatch { flags } => {
|
||||||
}
|
if flags.contains(CompOptFlags::DIRNAMES) {
|
||||||
|
self.dirs_only = true;
|
||||||
|
} else if flags.contains(CompOptFlags::DEFAULT) {
|
||||||
|
/* fall through */
|
||||||
|
} else {
|
||||||
|
return Ok(CompResult::NoMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.contains(CompOptFlags::NOSPACE) {
|
||||||
|
self.no_space = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompSpecResult::Match(comp_result) => {
|
||||||
|
return Ok(comp_result);
|
||||||
|
}
|
||||||
|
CompSpecResult::NoSpec => { /* carry on */ }
|
||||||
|
}
|
||||||
|
|
||||||
// Get the current token from CompContext
|
// Get the current token from CompContext
|
||||||
let Some(mut cur_token) = ctx.words.get(ctx.cword).cloned() else {
|
let Some(mut cur_token) = ctx.words.get(ctx.cword).cloned() else {
|
||||||
@@ -648,6 +695,7 @@ impl Completer {
|
|||||||
|
|
||||||
let last_marker = marker_ctx.last().copied();
|
let last_marker = marker_ctx.last().copied();
|
||||||
let mut candidates = match marker_ctx.pop() {
|
let mut candidates = match marker_ctx.pop() {
|
||||||
|
_ 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);
|
let var_candidates = complete_vars(&raw_tk);
|
||||||
@@ -682,9 +730,3 @@ impl Completer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Completer {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -292,19 +292,19 @@ impl Highlighter {
|
|||||||
fn is_valid(command: &str) -> bool {
|
fn is_valid(command: &str) -> bool {
|
||||||
let cmd_path = Path::new(&command);
|
let cmd_path = Path::new(&command);
|
||||||
|
|
||||||
if cmd_path.is_absolute() {
|
if cmd_path.is_dir() && read_shopts(|o| o.core.autocd) {
|
||||||
// the user has given us an absolute path
|
// this is a directory and autocd is enabled
|
||||||
if cmd_path.is_dir() && read_shopts(|o| o.core.autocd) {
|
return true;
|
||||||
// this is a directory and autocd is enabled
|
}
|
||||||
true
|
|
||||||
} else {
|
if cmd_path.is_absolute() {
|
||||||
let Ok(meta) = cmd_path.metadata() else {
|
// the user has given us an absolute path
|
||||||
return false;
|
let Ok(meta) = cmd_path.metadata() else {
|
||||||
};
|
return false;
|
||||||
// this is a file that is executable by someone
|
};
|
||||||
meta.permissions().mode() & 0o111 != 0
|
// this is a file that is executable by someone
|
||||||
}
|
meta.permissions().mode() & 0o111 != 0
|
||||||
} else {
|
} else {
|
||||||
read_meta(|m| m.cached_cmds().get(command).is_some())
|
read_meta(|m| m.cached_cmds().get(command).is_some())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ impl History {
|
|||||||
pub fn new() -> ShResult<Self> {
|
pub fn new() -> ShResult<Self> {
|
||||||
let ignore_dups = crate::state::read_shopts(|s| s.core.hist_ignore_dupes);
|
let ignore_dups = crate::state::read_shopts(|s| s.core.hist_ignore_dupes);
|
||||||
let max_hist = crate::state::read_shopts(|s| s.core.max_hist);
|
let max_hist = crate::state::read_shopts(|s| s.core.max_hist);
|
||||||
let path = PathBuf::from(env::var("FERNHIST").unwrap_or({
|
let path = PathBuf::from(env::var("SHEDHIST").unwrap_or({
|
||||||
let home = env::var("HOME").unwrap();
|
let home = env::var("HOME").unwrap();
|
||||||
format!("{home}/.shed_history")
|
format!("{home}/.shed_history")
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -771,6 +771,14 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Some(self.line_bounds(line_no))
|
Some(self.line_bounds(line_no))
|
||||||
}
|
}
|
||||||
|
pub fn this_line_exclusive(&mut self) -> (usize, usize) {
|
||||||
|
let line_no = self.cursor_line_number();
|
||||||
|
let (start, mut end) = self.line_bounds(line_no);
|
||||||
|
if self.read_grapheme_before(end).is_some_and(|gr| gr == "\n") {
|
||||||
|
end = end.saturating_sub(1);
|
||||||
|
}
|
||||||
|
(start, end)
|
||||||
|
}
|
||||||
pub fn this_line(&mut self) -> (usize, usize) {
|
pub fn this_line(&mut self) -> (usize, usize) {
|
||||||
let line_no = self.cursor_line_number();
|
let line_no = self.cursor_line_number();
|
||||||
self.line_bounds(line_no)
|
self.line_bounds(line_no)
|
||||||
@@ -781,6 +789,9 @@ impl LineBuf {
|
|||||||
pub fn end_of_line(&mut self) -> usize {
|
pub fn end_of_line(&mut self) -> usize {
|
||||||
self.this_line().1
|
self.this_line().1
|
||||||
}
|
}
|
||||||
|
pub fn end_of_line_exclusive(&mut self) -> usize {
|
||||||
|
self.this_line_exclusive().1
|
||||||
|
}
|
||||||
pub fn select_lines_up(&mut self, n: usize) -> Option<(usize, usize)> {
|
pub fn select_lines_up(&mut self, n: usize) -> Option<(usize, usize)> {
|
||||||
if self.start_of_line() == 0 {
|
if self.start_of_line() == 0 {
|
||||||
return None;
|
return None;
|
||||||
@@ -1932,7 +1943,7 @@ impl LineBuf {
|
|||||||
for tk in tokens {
|
for tk in tokens {
|
||||||
if tk.flags.contains(TkFlags::KEYWORD) {
|
if tk.flags.contains(TkFlags::KEYWORD) {
|
||||||
match tk.as_str() {
|
match tk.as_str() {
|
||||||
"then" | "do" => level += 1,
|
"then" | "do" | "in" => level += 1,
|
||||||
"done" | "fi" | "esac" => level = level.saturating_sub(1),
|
"done" | "fi" | "esac" => level = level.saturating_sub(1),
|
||||||
_ => { /* Continue */ }
|
_ => { /* Continue */ }
|
||||||
}
|
}
|
||||||
@@ -2476,7 +2487,7 @@ impl LineBuf {
|
|||||||
log::debug!("self.grapheme_indices().len(): {}", self.grapheme_indices().len());
|
log::debug!("self.grapheme_indices().len(): {}", self.grapheme_indices().len());
|
||||||
|
|
||||||
let mut do_indent = false;
|
let mut do_indent = false;
|
||||||
if verb == Verb::Change && (start,end) == self.this_line() {
|
if verb == Verb::Change && (start,end) == self.this_line_exclusive() {
|
||||||
do_indent = read_shopts(|o| o.prompt.auto_indent);
|
do_indent = read_shopts(|o| o.prompt.auto_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -257,14 +257,19 @@ impl ShedVi {
|
|||||||
|
|
||||||
|
|
||||||
/// Reset readline state for a new prompt
|
/// Reset readline state for a new prompt
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self, full_redraw: bool) -> ShResult<()> {
|
||||||
|
// Clear old display before resetting state — old_layout must survive
|
||||||
|
// so print_line can call clear_rows with the full multi-line layout
|
||||||
self.prompt = Prompt::new();
|
self.prompt = Prompt::new();
|
||||||
self.editor = Default::default();
|
self.editor = Default::default();
|
||||||
self.mode = Box::new(ViInsert::new());
|
self.mode = Box::new(ViInsert::new());
|
||||||
self.old_layout = None;
|
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
|
if full_redraw {
|
||||||
|
self.old_layout = None;
|
||||||
|
}
|
||||||
self.history.pending = None;
|
self.history.pending = None;
|
||||||
self.history.reset();
|
self.history.reset();
|
||||||
|
self.print_line(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prompt(&self) -> &Prompt {
|
pub fn prompt(&self) -> &Prompt {
|
||||||
@@ -276,6 +281,9 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn should_submit(&mut self) -> ShResult<bool> {
|
fn should_submit(&mut self) -> ShResult<bool> {
|
||||||
|
if self.mode.report_mode() == ModeReport::Normal {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
let input = Arc::new(self.editor.buffer.clone());
|
let input = Arc::new(self.editor.buffer.clone());
|
||||||
self.editor.calc_indent_level();
|
self.editor.calc_indent_level();
|
||||||
let lex_result1 = LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>();
|
let lex_result1 = LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>();
|
||||||
@@ -562,6 +570,7 @@ impl ShedVi {
|
|||||||
self.writer.flush_write(&self.mode.cursor_style())?;
|
self.writer.flush_write(&self.mode.cursor_style())?;
|
||||||
|
|
||||||
self.old_layout = Some(new_layout);
|
self.old_layout = Some(new_layout);
|
||||||
|
self.needs_redraw = false;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -675,7 +675,6 @@ impl ViNormal {
|
|||||||
// Double inputs
|
// Double inputs
|
||||||
('?', Some(VerbCmd(_, Verb::Rot13)))
|
('?', Some(VerbCmd(_, Verb::Rot13)))
|
||||||
| ('d', Some(VerbCmd(_, Verb::Delete)))
|
| ('d', Some(VerbCmd(_, Verb::Delete)))
|
||||||
| ('c', Some(VerbCmd(_, Verb::Change)))
|
|
||||||
| ('y', Some(VerbCmd(_, Verb::Yank)))
|
| ('y', Some(VerbCmd(_, Verb::Yank)))
|
||||||
| ('=', Some(VerbCmd(_, Verb::Equalize)))
|
| ('=', Some(VerbCmd(_, Verb::Equalize)))
|
||||||
| ('u', Some(VerbCmd(_, Verb::ToLower)))
|
| ('u', Some(VerbCmd(_, Verb::ToLower)))
|
||||||
@@ -685,6 +684,9 @@ impl ViNormal {
|
|||||||
| ('<', Some(VerbCmd(_, Verb::Dedent))) => {
|
| ('<', Some(VerbCmd(_, Verb::Dedent))) => {
|
||||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineInclusive));
|
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineInclusive));
|
||||||
}
|
}
|
||||||
|
('c', Some(VerbCmd(_, Verb::Change))) => {
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineExclusive));
|
||||||
|
}
|
||||||
('W', Some(VerbCmd(_, Verb::Change))) => {
|
('W', Some(VerbCmd(_, Verb::Change))) => {
|
||||||
// Same with 'W'
|
// Same with 'W'
|
||||||
break 'motion_parse Some(MotionCmd(
|
break 'motion_parse Some(MotionCmd(
|
||||||
|
|||||||
28
src/state.rs
28
src/state.rs
@@ -378,7 +378,7 @@ impl ScopeStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
pub static FERN: Shed = Shed::new();
|
pub static SHED: Shed = Shed::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A shell function
|
/// A shell function
|
||||||
@@ -751,8 +751,8 @@ impl VarTab {
|
|||||||
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||||
env::set_var("HOME", home.clone());
|
env::set_var("HOME", home.clone());
|
||||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||||
env::set_var("FERN_HIST", format!("{}/.shedhist", home));
|
env::set_var("SHED_HIST", format!("{}/.shedhist", home));
|
||||||
env::set_var("FERN_RC", format!("{}/.shedrc", home));
|
env::set_var("SHED_RC", format!("{}/.shedrc", home));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn init_sh_argv(&mut self) {
|
pub fn init_sh_argv(&mut self) {
|
||||||
@@ -1131,22 +1131,22 @@ impl MetaTab {
|
|||||||
|
|
||||||
/// Read from the job table
|
/// Read from the job table
|
||||||
pub fn read_jobs<T, F: FnOnce(&JobTab) -> T>(f: F) -> T {
|
pub fn read_jobs<T, F: FnOnce(&JobTab) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&shed.jobs.borrow()))
|
SHED.with(|shed| f(&shed.jobs.borrow()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the job table
|
/// Write to the job table
|
||||||
pub fn write_jobs<T, F: FnOnce(&mut JobTab) -> T>(f: F) -> T {
|
pub fn write_jobs<T, F: FnOnce(&mut JobTab) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&mut shed.jobs.borrow_mut()))
|
SHED.with(|shed| f(&mut shed.jobs.borrow_mut()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the var scope stack
|
/// Read from the var scope stack
|
||||||
pub fn read_vars<T, F: FnOnce(&ScopeStack) -> T>(f: F) -> T {
|
pub fn read_vars<T, F: FnOnce(&ScopeStack) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&shed.var_scopes.borrow()))
|
SHED.with(|shed| f(&shed.var_scopes.borrow()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the variable table
|
/// Write to the variable table
|
||||||
pub fn write_vars<T, F: FnOnce(&mut ScopeStack) -> T>(f: F) -> T {
|
pub fn write_vars<T, F: FnOnce(&mut ScopeStack) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&mut shed.var_scopes.borrow_mut()))
|
SHED.with(|shed| f(&mut shed.var_scopes.borrow_mut()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse `arr[idx]` into (name, raw_index_expr). Pure parsing, no expansion.
|
/// Parse `arr[idx]` into (name, raw_index_expr). Pure parsing, no expansion.
|
||||||
@@ -1211,30 +1211,30 @@ pub fn expand_arr_index(idx_raw: &str) -> ShResult<ArrIndex> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T {
|
pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&shed.meta.borrow()))
|
SHED.with(|shed| f(&shed.meta.borrow()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the meta table
|
/// Write to the meta table
|
||||||
pub fn write_meta<T, F: FnOnce(&mut MetaTab) -> T>(f: F) -> T {
|
pub fn write_meta<T, F: FnOnce(&mut MetaTab) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&mut shed.meta.borrow_mut()))
|
SHED.with(|shed| f(&mut shed.meta.borrow_mut()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the logic table
|
/// Read from the logic table
|
||||||
pub fn read_logic<T, F: FnOnce(&LogTab) -> T>(f: F) -> T {
|
pub fn read_logic<T, F: FnOnce(&LogTab) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&shed.logic.borrow()))
|
SHED.with(|shed| f(&shed.logic.borrow()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the logic table
|
/// Write to the logic table
|
||||||
pub fn write_logic<T, F: FnOnce(&mut LogTab) -> T>(f: F) -> T {
|
pub fn write_logic<T, F: FnOnce(&mut LogTab) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&mut shed.logic.borrow_mut()))
|
SHED.with(|shed| f(&mut shed.logic.borrow_mut()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_shopts<T, F: FnOnce(&ShOpts) -> T>(f: F) -> T {
|
pub fn read_shopts<T, F: FnOnce(&ShOpts) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&shed.shopts.borrow()))
|
SHED.with(|shed| f(&shed.shopts.borrow()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_shopts<T, F: FnOnce(&mut ShOpts) -> T>(f: F) -> T {
|
pub fn write_shopts<T, F: FnOnce(&mut ShOpts) -> T>(f: F) -> T {
|
||||||
FERN.with(|shed| f(&mut shed.shopts.borrow_mut()))
|
SHED.with(|shed| f(&mut shed.shopts.borrow_mut()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn descend_scope(argv: Option<Vec<String>>) {
|
pub fn descend_scope(argv: Option<Vec<String>>) {
|
||||||
@@ -1261,7 +1261,7 @@ pub fn set_status(code: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_rc() -> ShResult<()> {
|
pub fn source_rc() -> ShResult<()> {
|
||||||
let path = if let Ok(path) = env::var("FERN_RC") {
|
let path = if let Ok(path) = env::var("SHED_RC") {
|
||||||
PathBuf::from(&path)
|
PathBuf::from(&path)
|
||||||
} else {
|
} else {
|
||||||
let home = env::var("HOME").unwrap();
|
let home = env::var("HOME").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user