Compare commits

..

No commits in common. "main" and "ab11cf6f742b4251d92f2ac430cde2fbac9a3672" have entirely different histories.

3 changed files with 75 additions and 370 deletions

View File

@ -3,11 +3,7 @@
use super::{ Error, Result, exec, };
use std::{
io::Write,
collections::HashMap,
iter::FromIterator
};
use std::{collections::HashMap, io::Write, iter::FromIterator};
/// An environment used to [evaluate expressions](mod@super::eval) or
/// [execute programs](mod@super::exec).
@ -17,35 +13,23 @@ pub trait Env {
fn args (&self) -> Args;
/// Bind a value to a name.
fn bind <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>;
fn bind (&mut self, name: &str, value: &str);
/// Get a bound value from the env.
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>;
fn get (&self, name: &str) -> Option<String>;
/// Get the current working directory.
fn working_dir (&self) -> Result<std::path::PathBuf>;
/// Change the working directory.
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>;
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()>;
/// Write to stdout.
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString;
fn stdout (&mut self, data: &str) -> Result<()>;
/// Search the `PATH` variable for the `query`. The default implementation
/// is derived from [`Env::get`].
fn search <S> (&self, query: S) -> Option<exec::Program>
where
S: AsRef<str>
{
fn search (&self, query: &str) -> Option<exec::Program> {
self.get("PATH")?
.split(':')
.filter_map(|path| {
@ -56,7 +40,7 @@ pub trait Env {
})
.map(|file| file.path())
.find_map(|path| {
if path.file_name()? == query.as_ref() {
if path.file_name()? == query {
Some (path)
} else {
None
@ -80,14 +64,13 @@ pub trait Env {
/// Create a new environment that inherits this environments'
/// behavior, except the args of this env are overwritten with
/// `args`.
fn set_args <A> (self, args: A) -> SetArgs<Self>
fn set_args (self, args: Args) -> SetArgs<Self>
where
Self: Sized,
A: IntoIterator<Item = String>,
Self: Sized
{
SetArgs {
parent: self,
args: Args::from_iter(args),
args,
}
}
@ -105,24 +88,15 @@ impl<E: Env> Env for SetArgs<E> {
self.parent.working_dir()
}
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
self.parent.set_working_dir(path)
}
fn search <S> (&self, query: S) -> Option<exec::Program>
where
S: AsRef<str>
{
fn search (&self, query: &str) -> Option<exec::Program> {
self.parent.search(query)
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
fn stdout (&mut self, data: &str) -> Result<()> {
self.parent.stdout(data)
}
@ -130,18 +104,11 @@ impl<E: Env> Env for SetArgs<E> {
self.args.clone()
}
fn bind <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
fn bind (&mut self, name: &str, value: &str) {
self.parent.bind(name, value);
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
fn get (&self, name: &str) -> Option<String> {
self.parent.get(name)
}
@ -161,24 +128,15 @@ impl<E: Env> Env for Scope<'_, E> {
self.parent.working_dir()
}
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
self.parent.set_working_dir(path)
}
fn search <S> (&self, query: S) -> Option<exec::Program>
where
S: AsRef<str>
{
fn search (&self, query: &str) -> Option<exec::Program> {
self.parent.search(query)
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
fn stdout (&mut self, data: &str) -> Result<()> {
self.parent.stdout(data)
}
@ -186,23 +144,13 @@ impl<E: Env> Env for Scope<'_, E> {
self.parent.args()
}
fn bind <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
self.bindings.insert(
name.as_ref().to_string(),
value.as_ref().to_string()
);
fn bind (&mut self, name: &str, value: &str) {
self.bindings.insert(name.to_string(), value.to_string());
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
fn get (&self, name: &str) -> Option<String> {
self.bindings
.get(name.as_ref())
.get(name)
.cloned()
.or_else(|| {
self.parent.get(name)
@ -212,20 +160,15 @@ impl<E: Env> Env for Scope<'_, E> {
/// An iterator of arguments.
#[derive(Default, Clone)]
pub struct Args {
args: Vec<String>,
cur: usize
}
pub struct Args (Vec<String>);
impl<S: ToString> FromIterator<S> for Args {
fn from_iter <T: IntoIterator<Item = S>> (iter: T) -> Args {
Args {
args: iter
.into_iter()
Args (
iter.into_iter()
.map(|s| s.to_string())
.collect(),
cur: 0
}
.collect()
)
}
}
@ -233,12 +176,11 @@ impl Iterator for Args {
type Item = String;
fn next (&mut self) -> Option<Self::Item> {
let Args { args, cur } = self;
let val = args.get(*cur)?;
*cur += 1;
Some (val.clone())
if self.0.len() > 0 {
Some (self.0.remove(0))
} else {
None
}
}
}
@ -264,29 +206,16 @@ impl Env for Pure {
std::iter::empty::<String>().collect()
}
fn bind <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
self.bindings.insert(
name.as_ref().to_string(),
value.as_ref().to_string()
);
fn bind (&mut self, name: &str, value: &str) {
self.bindings.insert(name.to_string(), value.to_string());
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
self.bindings.get(name.as_ref()).cloned()
fn get (&self, name: &str) -> Option<String> {
self.bindings.get(name).cloned()
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
self.buf += &data.to_string();
fn stdout (&mut self, data: &str) -> Result<()> {
self.buf += data;
Ok (())
}
@ -299,12 +228,7 @@ impl Env for Pure {
}
}
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
let path = path.as_ref();
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
if path.is_dir() && path.exists() {
Ok (self.cwd = path.to_owned())
} else {
@ -323,19 +247,12 @@ impl Env for Inherit {
std::env::args().skip(1).collect()
}
fn bind <N, V> (&mut self, name: N, value: V)
where
N: AsRef<str>,
V: AsRef<str>,
{
std::env::set_var(name.as_ref(), value.as_ref());
fn bind (&mut self, name: &str, value: &str) {
std::env::set_var(name, value);
}
fn get <N> (&self, name: N) -> Option<String>
where
N: AsRef<str>
{
std::env::var(name.as_ref()).ok()
fn get (&self, name: &str) -> Option<String> {
std::env::var(name).ok()
}
fn working_dir (&self) -> Result<std::path::PathBuf> {
@ -343,25 +260,13 @@ impl Env for Inherit {
.map_err(Error::Io)
}
fn set_working_dir <P> (&mut self, path: P) -> Result<()>
where
P: AsRef<std::path::Path>
{
let path = path.as_ref();
fn set_working_dir (&mut self, path: &std::path::Path) -> Result<()> {
std::env::set_current_dir(path)
.map_err(Error::Io)
}
fn stdout <R> (&mut self, data: R) -> Result<()>
where
R: ToString
{
std::io::stdout().write(
data.to_string()
.as_bytes()
)?;
fn stdout (&mut self, data: &str) -> Result<()> {
std::io::stdout().write(data.as_bytes())?;
Ok (())
}
}

190
src/env/history.rs vendored
View File

@ -1,191 +1 @@
//! History control.
/// An entry in the [`History`] buffer.
#[derive(Debug)]
pub struct Entry {
raw: String,
}
impl Entry {
/// Returns `None` if the string does not match the given
/// fuzzy pattern. Returns `Some(score)` which can be used
/// to rank the results depending on how well they match.
pub fn fuzzy_match (&self, s: &str) -> Option<usize> {
let mut i = 0;
for c in s.chars() {
i = self.raw[i..].find(c)?;
}
Some (0)
}
/// Returns `true` if the entry contains the substring `s`.
pub fn exact_match (&self, s: &str) -> bool {
self.raw.contains(s)
}
}
/// A buffer that keeps track of the shell history.
#[derive(Default)]
pub struct History {
buf: Vec<Entry>,
cfg: Config,
cur: usize,
}
impl History {
/// Create a new empty history buffer with the given configuration.
///
/// To create one with the default configuration, you can use [`History::default`].
pub fn new (cfg: Config) -> History {
History {
buf: Vec::new(),
cur: 0,
cfg,
}
}
/// Move the cursor backwards in time.
pub fn prev (&mut self) -> &mut Self {
self.back(1)
}
/// Move the cursor forwards in time.
pub fn next (&mut self) -> &mut Self {
self.skip(1)
}
/// Time travel backwards.
fn back (&mut self, n: usize) -> &mut Self {
let History { buf, cur, .. } = self;
*cur = if *cur + n >= buf.len() { buf.len() - 1 } else { *cur + n };
self
}
/// Time travel into the "future".
fn skip (&mut self, n: usize) -> &mut Self {
let History { cur, .. } = self;
*cur = if *cur > n { *cur - n } else { 0 };
self
}
/// Append the given entry to the history buffer.
pub fn append (&mut self, entry: impl Into<Entry>) -> &mut Self {
let entry = entry.into();
self.buf.push(entry);
self
}
/// Remove the last entry of the buffer, if one exists.
pub fn pop (&mut self) -> Option<Entry> {
self.buf.pop()
}
/// Search the command history.
pub fn search <'s: 'x, 'x> (&'x self, s: &'s str) -> impl Iterator<Item = &'x Entry> {
self.buf.iter()
.rev()
.skip(self.cur)
.filter(move |Entry { raw }| {
raw.contains(s)
})
}
/// Search the command history using the fuzzy algorithm of
/// [`fuzzy_match`](Entry::fuzzy_match). The matching entries are
/// returned in the order in which they are entered in the buffer,
/// reversed. The fuzzy matching score is ignored.
pub fn fuzzy_search <'s: 'x, 'x> (&'x self, s: &'s str) -> impl Iterator<Item = &'x Entry> {
self.buf.iter()
.rev()
.skip(self.cur)
.filter(move |e| {
e.fuzzy_match(&s).is_some()
})
}
}
impl<S: ToString> From<S> for Entry {
fn from (text: S) -> Entry {
Entry { raw: text.to_string() }
}
}
/// Configuration for the [`History`] buffer.
#[derive(Clone)]
pub struct Config {
ignore: Vec<String>,
ignore_dups: bool,
limit: usize,
}
impl Config {
/// Create a new config.
///
/// This is an alias for [`Config::default`].
pub fn new () -> Config {
Config::default()
}
/// Configure the history to ignore consecutive exactly equal
/// history entries.
pub fn ignore_dups (mut self) -> Self {
self.ignore_dups = true;
self
}
/// Set the history size limit.
pub fn limit (mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Create a [`History`] buffer which uses this configuration.
pub fn into_history (self) -> History {
History::new(self)
}
}
impl Default for Config {
fn default () -> Self {
Config {
ignore: Vec::new(),
ignore_dups: false,
limit: 10000,
}
}
}
#[test]
fn fuzzy_search () {
let mut hist = History::default();
hist.append("z /etc/nixos")
.append("exa /home/riley")
.append("bat /home/riley/default.nix")
.append("exa /etc")
.append("nv ~/.config/sway/config")
.append("exa /etc/nixos")
.append("nv /etc/nixos/configuration.nix");
assert!(hist.fuzzy_search("cosw").count() == 1);
assert!(hist.fuzzy_search("conf").count() == 2);
assert!(hist.fuzzy_search("nix").count() == 4);
// Matches `nv /etc/nixos/configuration.nix` and
// `nv ~/.config/sway/config`, in that order.
let mut iter = hist.fuzzy_search("cfg");
assert!(iter.next().unwrap().raw == "nv /etc/nixos/configuration.nix");
assert!(iter.next().unwrap().raw == "nv ~/.config/sway/config");
}
#[test]
fn fuzzy_match () {
Entry::from("exa /home/riley").fuzzy_match("exa").unwrap();
}

View File

@ -15,6 +15,18 @@ pub trait Exec {
}
impl<F> Exec for F
where
F: Fn (&mut dyn Env) -> Result<()>
{
fn exec <E> (&self, env: &mut E) -> Result<()>
where
E: Env
{
self (env)
}
}
/// A shell command.
pub enum Command {
Program (Program),
@ -118,30 +130,20 @@ pub struct NotFound;
/// ```
///
/// [`FromStr`]: std::str::FromStr
pub struct Builtin (BuiltinName);
enum BuiltinName {
Disown,
Type,
Exit,
Pwd,
Fg,
Bg,
Cd,
}
pub struct Builtin (fn (&mut dyn Env) -> Result<()>);
impl std::str::FromStr for Builtin {
type Err = NotFound;
fn from_str (s: &str) -> Result<Self, Self::Err> {
let ret = Builtin (match s {
"disown" => BuiltinName::Disown,
"type" => BuiltinName::Type,
"exit" => BuiltinName::Exit,
"pwd" => BuiltinName::Pwd,
"fg" => BuiltinName::Fg,
"bg" => BuiltinName::Bg,
"cd" => BuiltinName::Cd,
"disown" => builtin::disown,
"type" => builtin::r#type,
"exit" => builtin::exit,
"pwd" => builtin::pwd,
"fg" => builtin::fg,
"bg" => builtin::bg,
"cd" => builtin::cd,
_ => return Err (NotFound)
});
@ -154,19 +156,7 @@ impl Exec for Builtin {
where
E: Env
{
use BuiltinName::*;
let f = match self.0 {
Disown => builtin::disown,
Type => builtin::r#type,
Exit => builtin::exit,
Pwd => builtin::pwd,
Fg => builtin::fg,
Bg => builtin::bg,
Cd => builtin::cd,
};
f (env)
self.0.exec(env)
}
}
@ -177,20 +167,20 @@ pub mod builtin {
use super::{ Env, Result, };
/// Change the current working directory.
pub fn cd (env: &mut impl Env) -> Result<()> {
pub fn cd (env: &mut dyn Env) -> Result<()> {
let arg = env.args().next().unwrap();
env.set_working_dir(arg)
env.set_working_dir(arg.as_ref())
}
/// Print the current working directory.
pub fn pwd (env: &mut impl Env) -> Result<()> {
pub fn pwd (env: &mut dyn Env) -> Result<()> {
let cwd = env.working_dir()?;
env.stdout(format!("{}", cwd.display()).as_str());
Ok (())
}
/// Stop the program. Optionally takes an exit code.
pub fn exit (env: &mut impl Env) -> Result<()> {
pub fn exit (env: &mut dyn Env) -> Result<()> {
if let Some (code) = env.args().next().and_then(|s| s.parse::<i32>().ok()) {
std::process::exit(code)
} else {
@ -199,7 +189,7 @@ pub mod builtin {
}
/// Display information about the type of a command.
pub fn r#type (env: &mut impl Env) -> Result<()> {
pub fn r#type (env: &mut dyn Env) -> Result<()> {
use crate::exec::Builtin;
for arg in env.args() {
@ -220,19 +210,19 @@ pub mod builtin {
/// Send a job to the background. See also [`Job::bg`].
///
/// [`Job::bg`]: crate::job::Job::bg
pub fn bg (_: &mut impl Env) -> Result<()> {
pub fn bg (_: &mut dyn Env) -> Result<()> {
todo!()
}
/// Bring a job to the foreground. See also [`Job::fg`].
///
/// [`Job::fg`]: crate::job::Job::fg
pub fn fg (_: &mut impl Env) -> Result<()> {
pub fn fg (_: &mut dyn Env) -> Result<()> {
todo!()
}
/// Disown all background jobs.
pub fn disown (_: &mut impl Env) -> Result<()> {
pub fn disown (_: &mut dyn Env) -> Result<()> {
todo!()
}