Implemented the autocmd builtin, which allows you to register hooks for certain shell events.
This commit is contained in:
96
Cargo.lock
generated
96
Cargo.lock
generated
@@ -85,9 +85,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -114,9 +114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.55"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785"
|
||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -124,9 +124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.55"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61"
|
||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -148,9 +148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.7"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
@@ -250,21 +250,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -321,9 +309,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.46.1"
|
||||
version = "1.46.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248b42847813a1550dafd15296fd9748c651d0c32194559dbc05d804d54b21e8"
|
||||
checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4"
|
||||
dependencies = [
|
||||
"console",
|
||||
"once_cell",
|
||||
@@ -354,9 +342,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.20"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543"
|
||||
checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
@@ -367,9 +355,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.20"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5"
|
||||
checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -384,15 +372,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
version = "0.2.182"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -402,9 +390,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
@@ -476,18 +464,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
@@ -496,7 +484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.1",
|
||||
"getrandom",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
@@ -508,9 +496,9 @@ checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -520,9 +508,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -531,15 +519,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
@@ -641,9 +629,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -652,12 +640,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.24.0"
|
||||
version = "3.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -665,9 +653,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
|
||||
@@ -5,9 +5,17 @@ let
|
||||
boolToString = b:
|
||||
if b then "true" else "false";
|
||||
|
||||
mkFunctionDef = name: body: ''
|
||||
mkAutoCmd = cfg:
|
||||
map (hook: "autocmd ${hook} ${lib.optionalString (cfg.pattern != null) "-p \"${cfg.pattern}\""} '${cfg.command}'") cfg.hooks;
|
||||
|
||||
|
||||
mkFunctionDef = name: body:
|
||||
let
|
||||
indented = "\t" + lib.concatStringsSep "\n\t" (lib.splitString "\n" body);
|
||||
in
|
||||
''
|
||||
${name}() {
|
||||
${body}
|
||||
${indented}
|
||||
}'';
|
||||
|
||||
mkKeymapCmd = cfg: let
|
||||
@@ -58,6 +66,39 @@ in
|
||||
description = "Shell functions to set when shed starts";
|
||||
};
|
||||
|
||||
autocmds = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
options = {
|
||||
hooks = lib.mkOption {
|
||||
type = lib.types.addCheck (lib.types.listOf (lib.types.enum [
|
||||
"pre-cmd"
|
||||
"post-cmd"
|
||||
"pre-change-dir"
|
||||
"post-change-dir"
|
||||
"on-job-finish"
|
||||
"pre-prompt"
|
||||
"post-prompt"
|
||||
"pre-mode-change"
|
||||
"post-mode-change"
|
||||
])) (list: list != []);
|
||||
description = "The events that trigger this autocmd";
|
||||
};
|
||||
pattern = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "A regex pattern to use in the hook to determine whether it runs or not. What it's compared to differs by hook, for instance 'pre-change-dir' compares it to the new directory, pre-cmd compares it to the command, etc";
|
||||
};
|
||||
command = lib.mkOption {
|
||||
type = lib.types.addCheck lib.types.str (cmd: cmd != "");
|
||||
description = "The shell command to execute when the hook is triggered and the pattern (if provided) matches";
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
default = [];
|
||||
description = "Custom autocmds to set when shed starts";
|
||||
};
|
||||
|
||||
keymaps = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
options = {
|
||||
@@ -243,6 +284,7 @@ in
|
||||
completeLines = lib.concatLines (lib.mapAttrsToList mkCompleteCmd cfg.extraCompletion);
|
||||
keymapLines = lib.concatLines (map mkKeymapCmd cfg.keymaps);
|
||||
functionLines = lib.concatLines (lib.mapAttrsToList mkFunctionDef cfg.functions);
|
||||
autocmdLines = lib.concatLines (map mkAutoCmd cfg.autocmds);
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
home.packages = [ cfg.package ];
|
||||
@@ -269,6 +311,7 @@ in
|
||||
functionLines
|
||||
completeLines
|
||||
keymapLines
|
||||
autocmdLines
|
||||
])
|
||||
cfg.settings.extraPostConfig
|
||||
];
|
||||
|
||||
91
src/builtin/autocmd.rs
Normal file
91
src/builtin/autocmd.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, state::{self, AutoCmd, AutoCmdKind, write_logic}
|
||||
};
|
||||
|
||||
pub struct AutoCmdOpts {
|
||||
pattern: Option<Regex>,
|
||||
clear: bool
|
||||
}
|
||||
fn autocmd_optspec() -> [OptSpec;2] {
|
||||
[
|
||||
OptSpec {
|
||||
opt: Opt::Short('p'),
|
||||
takes_arg: true
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('c'),
|
||||
takes_arg: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_autocmd_opts(opts: &[Opt]) -> ShResult<AutoCmdOpts> {
|
||||
let mut autocmd_opts = AutoCmdOpts {
|
||||
pattern: None,
|
||||
clear: false
|
||||
};
|
||||
|
||||
let mut opts = opts.iter();
|
||||
while let Some(arg) = opts.next() {
|
||||
match arg {
|
||||
Opt::ShortWithArg('p', arg) => {
|
||||
autocmd_opts.pattern = Some(Regex::new(arg).map_err(|e| ShErr::simple(ShErrKind::ExecFail, format!("invalid regex for -p: {}", e)))?);
|
||||
}
|
||||
Opt::Short('c') => {
|
||||
autocmd_opts.clear = true;
|
||||
}
|
||||
_ => {
|
||||
return Err(ShErr::simple(ShErrKind::ExecFail, format!("unexpected option: {}", arg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(autocmd_opts)
|
||||
}
|
||||
|
||||
pub fn autocmd(node: Node) -> ShResult<()> {
|
||||
let span = node.get_span();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv,opts) = get_opts_from_tokens(argv, &autocmd_optspec()).promote_err(span.clone())?;
|
||||
let autocmd_opts = get_autocmd_opts(&opts).promote_err(span.clone())?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
let mut args = argv.iter();
|
||||
|
||||
let Some(autocmd_kind) = args.next() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, "expected an autocmd kind".to_string()));
|
||||
};
|
||||
|
||||
let Ok(autocmd_kind) = autocmd_kind.0.parse::<AutoCmdKind>() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, autocmd_kind.1.clone(), format!("invalid autocmd kind: {}", autocmd_kind.0)));
|
||||
};
|
||||
|
||||
if autocmd_opts.clear {
|
||||
write_logic(|l| l.clear_autocmds(autocmd_kind));
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(autocmd_cmd) = args.next() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, "expected an autocmd command".to_string()));
|
||||
};
|
||||
|
||||
let autocmd = AutoCmd {
|
||||
pattern: autocmd_opts.pattern,
|
||||
command: autocmd_cmd.0.clone(),
|
||||
};
|
||||
|
||||
write_logic(|l| l.insert_autocmd(autocmd_kind, autocmd));
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
@@ -44,7 +44,7 @@ pub fn cd(node: Node) -> ShResult<()> {
|
||||
.labeled(cd_span.clone(), format!("cd: Not a directory '{}'", new_dir.display().fg(next_color()))));
|
||||
}
|
||||
|
||||
if let Err(e) = env::set_current_dir(new_dir) {
|
||||
if let Err(e) = state::change_dir(new_dir) {
|
||||
return Err(ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||
.labeled(cd_span.clone(), format!("cd: Failed to change directory: '{}'", e.fg(Color::Red))));
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) = env::set_current_dir(target) {
|
||||
if let Err(e) = state::change_dir(target) {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to change directory: '{}'", e.fg(Color::Red)))
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
expand::expand_keymap, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, prelude::*, readline::{keys::KeyEvent, vimode::ModeReport}, state::{self, write_logic}
|
||||
expand::expand_keymap, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, prelude::*, readline::keys::KeyEvent, state::{self, write_logic}
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
|
||||
@@ -26,13 +26,14 @@ pub mod arrops;
|
||||
pub mod intro;
|
||||
pub mod getopts;
|
||||
pub mod keymap;
|
||||
pub mod autocmd;
|
||||
|
||||
pub const BUILTINS: [&str; 46] = [
|
||||
pub const BUILTINS: [&str; 47] = [
|
||||
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown",
|
||||
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
|
||||
"command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly",
|
||||
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate", "wait", "type",
|
||||
"getopts", "keymap", "read_key"
|
||||
"getopts", "keymap", "read_key", "autocmd"
|
||||
];
|
||||
|
||||
pub fn true_builtin() -> ShResult<()> {
|
||||
|
||||
@@ -4,7 +4,6 @@ use nix::{
|
||||
libc::{STDIN_FILENO, STDOUT_FILENO},
|
||||
unistd::{isatty, read, write},
|
||||
};
|
||||
use yansi::Paint;
|
||||
|
||||
use crate::{
|
||||
expand::expand_keymap, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, sys::TTY_FILENO}, parse::{NdRule, Node, execute::prepare_argv}, procio::borrow_fd, readline::term::{KeyReader, PollReader, RawModeGuard}, state::{self, VarFlags, VarKind, read_vars, write_vars}
|
||||
|
||||
@@ -2,9 +2,11 @@ use std::collections::VecDeque;
|
||||
|
||||
use ariadne::Span as AriadneSpan;
|
||||
|
||||
use crate::parse::execute::exec_input;
|
||||
use crate::parse::lex::{Span, Tk, TkRule};
|
||||
use crate::parse::{Node, Redir, RedirType};
|
||||
use crate::prelude::*;
|
||||
use crate::state::AutoCmd;
|
||||
|
||||
pub trait VecDequeExt<T> {
|
||||
fn to_vec(self) -> Vec<T>;
|
||||
@@ -22,6 +24,11 @@ pub trait TkVecUtils<Tk> {
|
||||
fn split_at_separators(&self) -> Vec<Vec<Tk>>;
|
||||
}
|
||||
|
||||
pub trait AutoCmdVecUtils {
|
||||
fn exec(&self);
|
||||
fn exec_with(&self, pattern: &str);
|
||||
}
|
||||
|
||||
pub trait RedirVecUtils<Redir> {
|
||||
/// Splits the vector of redirections into two vectors
|
||||
///
|
||||
@@ -33,6 +40,31 @@ pub trait NodeVecUtils<Node> {
|
||||
fn get_span(&self) -> Option<Span>;
|
||||
}
|
||||
|
||||
impl AutoCmdVecUtils for Vec<AutoCmd> {
|
||||
fn exec(&self) {
|
||||
for cmd in self {
|
||||
let AutoCmd { pattern: _, command } = cmd;
|
||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) {
|
||||
e.print_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn exec_with(&self, other_pattern: &str) {
|
||||
for cmd in self {
|
||||
let AutoCmd { pattern, command } = cmd;
|
||||
if let Some(pat) = pattern
|
||||
&& !pat.is_match(other_pattern) {
|
||||
log::trace!("autocmd pattern '{}' did not match '{}', skipping", pat, other_pattern);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) {
|
||||
e.print_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> VecDequeExt<T> for VecDeque<T> {
|
||||
fn to_vec(self) -> Vec<T> {
|
||||
self.into_iter().collect::<Vec<T>>()
|
||||
|
||||
13
src/main.rs
13
src/main.rs
@@ -31,12 +31,13 @@ use crate::builtin::keymap::KeyMapMatch;
|
||||
use crate::builtin::trap::TrapTarget;
|
||||
use crate::libsh::error::{self, ShErr, ShErrKind, ShResult};
|
||||
use crate::libsh::sys::TTY_FILENO;
|
||||
use crate::libsh::utils::AutoCmdVecUtils;
|
||||
use crate::parse::execute::exec_input;
|
||||
use crate::prelude::*;
|
||||
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
|
||||
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
||||
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||
use crate::state::{read_logic, source_rc, write_jobs, write_meta};
|
||||
use crate::state::{AutoCmdKind, read_logic, source_rc, write_jobs, write_meta};
|
||||
use clap::Parser;
|
||||
use state::{read_vars, write_vars};
|
||||
|
||||
@@ -357,9 +358,14 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
||||
// Process any available input
|
||||
match readline.process_input() {
|
||||
Ok(ReadlineEvent::Line(input)) => {
|
||||
let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd));
|
||||
let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd));
|
||||
|
||||
pre_exec.exec_with(&input);
|
||||
|
||||
let start = Instant::now();
|
||||
write_meta(|m| m.start_timer());
|
||||
if let Err(e) = RawModeGuard::with_cooked_mode(|| exec_input(input, None, true, Some("<stdin>".into()))) {
|
||||
if let Err(e) = RawModeGuard::with_cooked_mode(|| exec_input(input.clone(), None, true, Some("<stdin>".into()))) {
|
||||
match e.kind() {
|
||||
ShErrKind::CleanExit(code) => {
|
||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||
@@ -371,6 +377,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
||||
let command_run_time = start.elapsed();
|
||||
log::info!("Command executed in {:.2?}", command_run_time);
|
||||
write_meta(|m| m.stop_timer());
|
||||
|
||||
post_exec.exec_with(&input);
|
||||
|
||||
readline.fix_column()?;
|
||||
readline.writer.flush_write("\n\r")?;
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ use ariadne::Fmt;
|
||||
|
||||
use crate::{
|
||||
builtin::{
|
||||
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::{self, read_builtin}, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, autocmd::autocmd, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::{self, read_builtin}, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||
},
|
||||
expand::{expand_aliases, glob_to_regex},
|
||||
expand::{Expander, expand_aliases, expand_raw, glob_to_regex},
|
||||
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
|
||||
libsh::{
|
||||
error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
|
||||
@@ -19,7 +19,7 @@ use crate::{
|
||||
prelude::*,
|
||||
procio::{IoMode, IoStack},
|
||||
state::{
|
||||
self, ShFunc, VarFlags, VarKind, read_logic, read_shopts, write_jobs, write_logic, write_vars,
|
||||
self, ShFunc, VarFlags, VarKind, read_logic, read_shopts, write_jobs, write_logic, write_vars
|
||||
},
|
||||
};
|
||||
|
||||
@@ -429,10 +429,13 @@ impl Dispatcher {
|
||||
let block_patterns = block_pattern_raw.split('|');
|
||||
|
||||
for pattern in block_patterns {
|
||||
let pattern_regex = glob_to_regex(pattern, false);
|
||||
log::debug!("[case] testing input {:?} against pattern {:?} (regex: {:?})", pattern_raw, pattern, pattern_regex);
|
||||
log::debug!("[case] testing pattern {:?} against input {:?}", pattern, pattern_raw);
|
||||
let pattern_exp = Expander::from_raw(pattern)?.expand()?.join(" ");
|
||||
log::debug!("[case] expanded pattern: {:?}", pattern_exp);
|
||||
let pattern_regex = glob_to_regex(&pattern_exp, false);
|
||||
log::debug!("[case] testing input {:?} against pattern {:?} (regex: {:?})", pattern_raw, pattern_exp, pattern_regex);
|
||||
if pattern_regex.is_match(&pattern_raw) {
|
||||
log::debug!("[case] matched pattern {:?}", pattern);
|
||||
log::debug!("[case] matched pattern {:?}", pattern_exp);
|
||||
for node in &body {
|
||||
s.dispatch_node(node.clone())?;
|
||||
}
|
||||
@@ -828,6 +831,7 @@ impl Dispatcher {
|
||||
"getopts" => getopts(cmd),
|
||||
"keymap" => keymap::keymap(cmd),
|
||||
"read_key" => read::read_key(cmd),
|
||||
"autocmd" => autocmd(cmd),
|
||||
"true" | ":" => {
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::{
|
||||
};
|
||||
|
||||
use nix::sys::signal::Signal;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
builtin::complete::{CompFlags, CompOptFlags, CompOpts},
|
||||
@@ -823,13 +822,13 @@ impl Completer for FuzzyCompleter {
|
||||
}
|
||||
K(C::Tab, M::SHIFT) |
|
||||
K(C::Up, M::NONE) => {
|
||||
self.cursor.sub(1);
|
||||
self.cursor.wrap_sub(1);
|
||||
self.update_scroll_offset();
|
||||
Ok(CompResponse::Consumed)
|
||||
}
|
||||
K(C::Tab, M::NONE) |
|
||||
K(C::Down, M::NONE) => {
|
||||
self.cursor.add(1);
|
||||
self.cursor.wrap_add(1);
|
||||
self.update_scroll_offset();
|
||||
Ok(CompResponse::Consumed)
|
||||
}
|
||||
@@ -848,7 +847,7 @@ impl Completer for FuzzyCompleter {
|
||||
// soft wraps and re-wraps as a flat buffer.
|
||||
let total_cells = layout.rows as u32 * layout.cols as u32;
|
||||
let physical_rows = if new_cols > 0 {
|
||||
((total_cells + new_cols as u32 - 1) / new_cols as u32) as u16
|
||||
total_cells.div_ceil(new_cols as u32) as u16
|
||||
} else {
|
||||
layout.rows
|
||||
};
|
||||
@@ -864,8 +863,7 @@ impl Completer for FuzzyCompleter {
|
||||
// filling to t_cols). Compute how many extra rows that adds
|
||||
// between the prompt cursor and the fuzzy content.
|
||||
let gap_extra = if new_cols > 0 && layout.preceding_line_width > new_cols {
|
||||
let wrap_rows = ((layout.preceding_line_width as u32 + new_cols as u32 - 1)
|
||||
/ new_cols as u32) as u16;
|
||||
let wrap_rows = (layout.preceding_line_width as u32).div_ceil(new_cols as u32) as u16;
|
||||
let cursor_wrap_row = layout.preceding_cursor_col / new_cols;
|
||||
wrap_rows.saturating_sub(cursor_wrap_row + 1)
|
||||
} else {
|
||||
@@ -975,7 +973,7 @@ impl Completer for FuzzyCompleter {
|
||||
|
||||
let new_layout = FuzzyLayout {
|
||||
rows,
|
||||
cols: cols as u16,
|
||||
cols,
|
||||
cursor_col,
|
||||
preceding_line_width: self.prompt_line_width,
|
||||
preceding_cursor_col: self.prompt_cursor_col,
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
register::{RegisterContent, write_register},
|
||||
term::RawModeGuard,
|
||||
},
|
||||
state::{VarFlags, VarKind, read_shopts, read_vars, write_meta, write_vars},
|
||||
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
||||
};
|
||||
|
||||
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
||||
@@ -297,6 +297,20 @@ impl ClampedUsize {
|
||||
pub fn sub(&mut self, value: usize) {
|
||||
self.value = self.value.saturating_sub(value)
|
||||
}
|
||||
pub fn wrap_add(&mut self, value: usize) {
|
||||
self.value = self.ret_wrap_add(value);
|
||||
}
|
||||
pub fn wrap_sub(&mut self, value: usize) {
|
||||
self.value = self.ret_wrap_sub(value);
|
||||
}
|
||||
pub fn ret_wrap_add(&self, value: usize) -> usize {
|
||||
let max = self.upper_bound();
|
||||
(self.value + value) % (max + 1)
|
||||
}
|
||||
pub fn ret_wrap_sub(&self, value: usize) -> usize {
|
||||
let max = self.upper_bound();
|
||||
(self.value + (max + 1) - (value % (max + 1))) % (max + 1)
|
||||
}
|
||||
/// Add a value to the wrapped usize, return the result
|
||||
///
|
||||
/// Returns the result instead of mutating the inner value
|
||||
@@ -2774,7 +2788,7 @@ impl LineBuf {
|
||||
match content {
|
||||
RegisterContent::Span(ref text) => {
|
||||
let insert_idx = match anchor {
|
||||
Anchor::After => self.cursor.ret_add(1),
|
||||
Anchor::After => self.cursor.get().saturating_add(1).min(self.grapheme_indices().len()),
|
||||
Anchor::Before => self.cursor.get(),
|
||||
};
|
||||
self.insert_str_at(insert_idx, text);
|
||||
|
||||
@@ -10,12 +10,13 @@ use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVis
|
||||
use crate::builtin::keymap::{KeyMapFlags, KeyMapMatch};
|
||||
use crate::expand::expand_prompt;
|
||||
use crate::libsh::sys::TTY_FILENO;
|
||||
use crate::libsh::utils::AutoCmdVecUtils;
|
||||
use crate::parse::lex::{LexStream, QuoteState};
|
||||
use crate::{prelude::*, state};
|
||||
use crate::readline::complete::FuzzyCompleter;
|
||||
use crate::readline::term::{Pos, TermReader, calc_str_width};
|
||||
use crate::readline::vimode::ViEx;
|
||||
use crate::state::{ShellParam, read_logic, read_shopts, write_meta};
|
||||
use crate::state::{AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars};
|
||||
use crate::{
|
||||
libsh::error::ShResult,
|
||||
parse::lex::{self, LexFlags, Tk, TkFlags, TkRule},
|
||||
@@ -251,6 +252,8 @@ impl ShedVi {
|
||||
history: History::new()?,
|
||||
needs_redraw: true,
|
||||
};
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(new.mode.report_mode().to_string()), VarFlags::NONE))?;
|
||||
new.prompt.refresh()?;
|
||||
new.writer.flush_write("\n")?; // ensure we start on a new line, in case the previous command didn't end with a newline
|
||||
new.print_line(false)?;
|
||||
Ok(new)
|
||||
@@ -716,6 +719,9 @@ impl ShedVi {
|
||||
self.writer.clear_rows(layout)?;
|
||||
}
|
||||
|
||||
let pre_prompt = read_logic(|l| l.get_autocmds(AutoCmdKind::PrePrompt));
|
||||
pre_prompt.exec();
|
||||
|
||||
self
|
||||
.writer
|
||||
.redraw(self.prompt.get_ps1(), &line, &new_layout)?;
|
||||
@@ -786,15 +792,28 @@ impl ShedVi {
|
||||
|
||||
self.old_layout = Some(new_layout);
|
||||
self.needs_redraw = false;
|
||||
// Save physical cursor row so SIGWINCH can restore it
|
||||
|
||||
let post_prompt = read_logic(|l| l.get_autocmds(AutoCmdKind::PostPrompt));
|
||||
post_prompt.exec();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn swap_mode(&mut self, mode: &mut Box<dyn ViMode>) {
|
||||
std::mem::swap(&mut self.mode, mode);
|
||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok();
|
||||
self.prompt.refresh().ok();
|
||||
}
|
||||
|
||||
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
||||
let mut select_mode = None;
|
||||
let mut is_insert_mode = false;
|
||||
if cmd.is_mode_transition() {
|
||||
let count = cmd.verb_count();
|
||||
let pre_mode_change = read_logic(|l| l.get_autocmds(AutoCmdKind::PreModeChange));
|
||||
pre_mode_change.exec();
|
||||
|
||||
let mut mode: Box<dyn ViMode> = if let ModeReport::Ex = self.mode.report_mode() && cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) {
|
||||
if let Some(saved) = self.saved_mode.take() {
|
||||
saved
|
||||
@@ -823,8 +842,7 @@ impl ShedVi {
|
||||
.start_selecting(SelectMode::Char(SelectAnchor::End));
|
||||
}
|
||||
let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new());
|
||||
std::mem::swap(&mut mode, &mut self.mode);
|
||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||
self.swap_mode(&mut mode);
|
||||
|
||||
return self.editor.exec_cmd(cmd);
|
||||
}
|
||||
@@ -842,10 +860,12 @@ impl ShedVi {
|
||||
};
|
||||
|
||||
|
||||
std::mem::swap(&mut mode, &mut self.mode);
|
||||
self.swap_mode(&mut mode);
|
||||
|
||||
if self.mode.report_mode() == ModeReport::Ex {
|
||||
self.saved_mode = Some(mode);
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?;
|
||||
self.prompt.refresh()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -868,6 +888,13 @@ impl ShedVi {
|
||||
} else {
|
||||
self.editor.clear_insert_mode_start_pos();
|
||||
}
|
||||
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?;
|
||||
self.prompt.refresh()?;
|
||||
|
||||
let post_mode_change = read_logic(|l| l.get_autocmds(AutoCmdKind::PostModeChange));
|
||||
post_mode_change.exec();
|
||||
|
||||
return Ok(());
|
||||
} else if cmd.is_cmd_repeat() {
|
||||
let Some(replay) = self.repeat_action.clone() else {
|
||||
@@ -945,7 +972,7 @@ impl ShedVi {
|
||||
&& self.editor.select_range().is_none() {
|
||||
self.editor.stop_selecting();
|
||||
let mut mode: Box<dyn ViMode> = Box::new(ViNormal::new());
|
||||
std::mem::swap(&mut mode, &mut self.mode);
|
||||
self.swap_mode(&mut mode);
|
||||
}
|
||||
|
||||
if cmd.is_repeatable() {
|
||||
@@ -970,7 +997,7 @@ impl ShedVi {
|
||||
if self.mode.report_mode() == ModeReport::Visual && cmd.verb().is_some_and(|v| v.1.is_edit()) {
|
||||
self.editor.stop_selecting();
|
||||
let mut mode: Box<dyn ViMode> = Box::new(ViNormal::new());
|
||||
std::mem::swap(&mut mode, &mut self.mode);
|
||||
self.swap_mode(&mut mode);
|
||||
}
|
||||
|
||||
if self.mode.report_mode() != ModeReport::Visual && self.editor.select_range().is_some() {
|
||||
@@ -987,8 +1014,7 @@ impl ShedVi {
|
||||
} else {
|
||||
Box::new(ViNormal::new())
|
||||
};
|
||||
std::mem::swap(&mut mode, &mut self.mode);
|
||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||
self.swap_mode(&mut mode);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::libsh::error::ShResult;
|
||||
@@ -28,6 +30,19 @@ pub enum ModeReport {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Display for ModeReport {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ModeReport::Insert => write!(f, "INSERT"),
|
||||
ModeReport::Normal => write!(f, "NORMAL"),
|
||||
ModeReport::Ex => write!(f, "COMMAND"),
|
||||
ModeReport::Visual => write!(f, "VISUAL"),
|
||||
ModeReport::Replace => write!(f, "REPLACE"),
|
||||
ModeReport::Unknown => write!(f, "UNKNOWN"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CmdReplay {
|
||||
ModeReplay { cmds: Vec<ViCmd>, repeat: u16 },
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::execute::exec_input,
|
||||
prelude::*,
|
||||
state::{read_jobs, read_logic, write_jobs, write_meta},
|
||||
state::{AutoCmd, AutoCmdKind, read_jobs, read_logic, write_jobs, write_meta},
|
||||
};
|
||||
|
||||
static SIGNALS: AtomicU64 = AtomicU64::new(0);
|
||||
@@ -150,7 +150,7 @@ pub fn sig_setup(is_login: bool) {
|
||||
|
||||
|
||||
if is_login {
|
||||
setpgid(Pid::from_raw(0), Pid::from_raw(0));
|
||||
let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0));
|
||||
take_term().ok();
|
||||
}
|
||||
}
|
||||
@@ -313,6 +313,20 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
||||
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
||||
if let Some(job) = result {
|
||||
let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string();
|
||||
|
||||
let post_job_hooks = read_logic(|l| l.get_autocmds(AutoCmdKind::OnJobFinish));
|
||||
for cmd in post_job_hooks {
|
||||
let AutoCmd { pattern, command } = cmd;
|
||||
if let Some(pat) = pattern
|
||||
&& job.get_cmds().iter().all(|p| !pat.is_match(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) {
|
||||
e.print_error();
|
||||
}
|
||||
}
|
||||
|
||||
write_meta(|m| m.post_system_message(job_complete_msg))
|
||||
}
|
||||
}
|
||||
|
||||
155
src/state.rs
155
src/state.rs
@@ -9,11 +9,11 @@ use std::{
|
||||
};
|
||||
|
||||
use nix::unistd::{User, gethostname, getppid};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
builtin::{BUILTINS, keymap::{KeyMap, KeyMapFlags, KeyMapMatch}, map::MapNode, trap::TrapTarget}, exec_input, expand::expand_keymap, jobs::JobTab, libsh::{
|
||||
error::{ShErr, ShErrKind, ShResult},
|
||||
utils::VecDequeExt,
|
||||
error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt
|
||||
}, parse::{
|
||||
ConjunctNode, NdRule, Node, ParsedSrc,
|
||||
lex::{LexFlags, LexStream, Span, Tk},
|
||||
@@ -522,6 +522,62 @@ impl ShFunc {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum AutoCmdKind {
|
||||
PreCmd,
|
||||
PostCmd,
|
||||
PreChangeDir,
|
||||
PostChangeDir,
|
||||
OnJobFinish,
|
||||
PrePrompt,
|
||||
PostPrompt,
|
||||
PreModeChange,
|
||||
PostModeChange
|
||||
}
|
||||
|
||||
impl Display for AutoCmdKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::PreCmd => write!(f, "pre-cmd"),
|
||||
Self::PostCmd => write!(f, "post-cmd"),
|
||||
Self::PreChangeDir => write!(f, "pre-change-dir"),
|
||||
Self::PostChangeDir => write!(f, "post-change-dir"),
|
||||
Self::OnJobFinish => write!(f, "on-job-finish"),
|
||||
Self::PrePrompt => write!(f, "pre-prompt"),
|
||||
Self::PostPrompt => write!(f, "post-prompt"),
|
||||
Self::PreModeChange => write!(f, "pre-mode-change"),
|
||||
Self::PostModeChange => write!(f, "post-mode-change"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AutoCmdKind {
|
||||
type Err = ShErr;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"pre-cmd" => Ok(Self::PreCmd),
|
||||
"post-cmd" => Ok(Self::PostCmd),
|
||||
"pre-change-dir" => Ok(Self::PreChangeDir),
|
||||
"post-change-dir" => Ok(Self::PostChangeDir),
|
||||
"on-job-finish" => Ok(Self::OnJobFinish),
|
||||
"pre-prompt" => Ok(Self::PrePrompt),
|
||||
"post-prompt" => Ok(Self::PostPrompt),
|
||||
"pre-mode-change" => Ok(Self::PreModeChange),
|
||||
"post-mode-change" => Ok(Self::PostModeChange),
|
||||
_ => Err(ShErr::simple(
|
||||
ShErrKind::ParseErr,
|
||||
format!("Invalid autocmd kind: {}", s),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AutoCmd {
|
||||
pub pattern: Option<Regex>,
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
/// The logic table for the shell
|
||||
///
|
||||
/// Contains aliases and functions
|
||||
@@ -530,12 +586,34 @@ pub struct LogTab {
|
||||
functions: HashMap<String, ShFunc>,
|
||||
aliases: HashMap<String, ShAlias>,
|
||||
traps: HashMap<TrapTarget, String>,
|
||||
keymaps: Vec<KeyMap>
|
||||
keymaps: Vec<KeyMap>,
|
||||
autocmds: HashMap<AutoCmdKind, Vec<AutoCmd>>
|
||||
}
|
||||
|
||||
impl LogTab {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn autocmds(&self) -> &HashMap<AutoCmdKind, Vec<AutoCmd>> {
|
||||
&self.autocmds
|
||||
}
|
||||
pub fn autocmds_mut(&mut self) -> &mut HashMap<AutoCmdKind, Vec<AutoCmd>> {
|
||||
&mut self.autocmds
|
||||
}
|
||||
pub fn insert_autocmd(&mut self, kind: AutoCmdKind, cmd: AutoCmd) {
|
||||
self.autocmds.entry(kind).or_default().push(cmd);
|
||||
}
|
||||
pub fn get_autocmds(&self, kind: AutoCmdKind) -> Vec<AutoCmd> {
|
||||
self.autocmds.get(&kind).cloned().unwrap_or_default()
|
||||
}
|
||||
pub fn clear_autocmds(&mut self, kind: AutoCmdKind) {
|
||||
self.autocmds.remove(&kind);
|
||||
}
|
||||
pub fn keymaps(&self) -> &Vec<KeyMap> {
|
||||
&self.keymaps
|
||||
}
|
||||
pub fn keymaps_mut(&mut self) -> &mut Vec<KeyMap> {
|
||||
&mut self.keymaps
|
||||
}
|
||||
pub fn insert_keymap(&mut self, keymap: KeyMap) {
|
||||
let mut found_dup = false;
|
||||
@@ -797,6 +875,18 @@ impl Display for Var {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Var {
|
||||
fn from(value: String) -> Self {
|
||||
Self::new(VarKind::Str(value), VarFlags::NONE)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Var {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::new(VarKind::Str(value.into()), VarFlags::NONE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct VarTab {
|
||||
vars: HashMap<String, Var>,
|
||||
@@ -1496,6 +1586,65 @@ pub fn get_shopt(path: &str) -> String {
|
||||
read_shopts(|s| s.get(path)).unwrap().unwrap()
|
||||
}
|
||||
|
||||
pub fn with_vars<F,H,V,T>(vars: H, f: F) -> T
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
H: Into<HashMap<String,V>>,
|
||||
V: Into<Var> {
|
||||
|
||||
let snapshot = read_vars(|v| v.clone());
|
||||
let vars = vars.into();
|
||||
for (name, val) in vars {
|
||||
let val = val.into();
|
||||
write_vars(|v| v.set_var(&name, val.kind, val.flags).unwrap());
|
||||
}
|
||||
let _guard = scopeguard::guard(snapshot, |snap| {
|
||||
write_vars(|v| *v = snap);
|
||||
});
|
||||
f()
|
||||
}
|
||||
|
||||
pub fn change_dir<P: AsRef<Path>>(dir: P) -> ShResult<()> {
|
||||
let dir = dir.as_ref();
|
||||
let dir_raw = &dir.display().to_string();
|
||||
let pre_cd = read_logic(|l| l.get_autocmds(AutoCmdKind::PreChangeDir));
|
||||
let post_cd = read_logic(|l| l.get_autocmds(AutoCmdKind::PostChangeDir));
|
||||
|
||||
let current_dir = env::current_dir()?.display().to_string();
|
||||
with_vars([("_NEW_DIR".into(), dir_raw.as_str()), ("_OLD_DIR".into(), current_dir.as_str())], || {
|
||||
for cmd in pre_cd {
|
||||
let AutoCmd { command, pattern } = cmd;
|
||||
if let Some(pat) = pattern
|
||||
&& !pat.is_match(dir_raw) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd (pre-changedir)".to_string())) {
|
||||
e.print_error();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
env::set_current_dir(dir)?;
|
||||
|
||||
with_vars([("_NEW_DIR".into(), dir_raw.as_str()), ("_OLD_DIR".into(), current_dir.as_str())], || {
|
||||
for cmd in post_cd {
|
||||
let AutoCmd { command, pattern } = cmd;
|
||||
if let Some(pat) = pattern
|
||||
&& !pat.is_match(dir_raw) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd (post-changedir)".to_string())) {
|
||||
e.print_error();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_status() -> i32 {
|
||||
read_vars(|v| v.get_param(ShellParam::Status))
|
||||
.parse::<i32>()
|
||||
|
||||
Reference in New Issue
Block a user