From cc5fba63c5ab4f422a54891d35890ee12d5e646c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy <mmstickman@gmail.com> Date: Sun, 1 Oct 2017 10:01:11 -0400 Subject: [PATCH] Implement PROMPT Function Support To use a function to generate a prompt, simply create a function whose name is PROMPT. --- manual/src/ch02-00-features.md | 66 ----------------------------- manual/src/ch03-00-miscellaneous.md | 1 + manual/src/ch03-07-prompt_fn.md | 13 ++++++ src/shell/binary.rs | 62 +++++++++++++++++++++++---- 4 files changed, 68 insertions(+), 74 deletions(-) create mode 100644 manual/src/ch03-07-prompt_fn.md diff --git a/manual/src/ch02-00-features.md b/manual/src/ch02-00-features.md index 42b7df3c..6b110878 100644 --- a/manual/src/ch02-00-features.md +++ b/manual/src/ch02-00-features.md @@ -1,71 +1,5 @@ # Features -Below is an overview of features that Ion has either already implemented, or aims to implement in -the future. If you have any ideas for features that Ion hasn't considered, you are welcome to -open an issue on the project's GitHub. - -- Misc - - [x] Implicit `cd` - - [x] XDG App Dirs -- Variables - - [x] String Variables - - [x] Array Variables - - [x] Aliases - - [ ] Associative Arrays -- Shell Expansions - - Variable Expansions - - [x] String Expansions (**$string**, **${string}**) - - [x] Array Expansions (**@array**, **@{array}**) - - Process Expansions - - [x] String Process Expansions (**$(command args...)**) - - [x] Array Process Expansions (**@(command args...)**) - - Brace Expansions (**abc{1,2,3}def{1,2,3}ghi**) - - [x] Brace Ranges - - [x] Nested Braces - - [x] Permutated Braces - - [x] Arithmetic Expansions (**$((5 * 10 / 3.5))**) - - Method Expansions - - [x] String Methods (**$method(args...)**) - - [x] Array Methods (**@method(args...)**) - - [x] Inline Methods (Expressions Within Methods) -- Slicing Syntax - - [x] String Variable Slicing (**$string[5..10]**) - - [x] Array Variable Slicing (**@array[5..10]**) - - [x] Array Slicing (**[one two three][2]**) - - [x] String Process Slicing (**$(command args...)[15..]**) - - [x] Array Process Slicing (**@(command args...)[1]**) - - [x] Exclusive Ranges (**N..N**) - - [x] Inclusive Ranges (**N...N**) -- Control Flow - - [x] For Loops - - [x] While Loops - - [x] If Statements - - [x] Match Statements (Incomplete) - - [ ] Match All Statements - - [ ] For Match Loop - - [ ] Foreach Loops -- Functions - - [x] Optionally-Typed Function Parameters - - [x] Descriptions - - [ ] Local Scopes & Dynamic Variables - - [ ] Piping / Redirecting Functions - - [ ] Backgrounding Functions - - [ ] Execute Function When Variable Changes -- Builtin Commands - - [ ] Piping / Redirecting Builtins - - [ ] Implemented All Builtins - - [ ] Completed Help Documentation -- [x] Script Executions -- [x] Signal Handling -- Job Control - - [x] `bg`, `fg`, `jobs`, etc. - - [x] Send jobs to background with **&** -- Plugins Support - - [ ] Builtin Plugins - - [ ] Prompt Plugins - - [ ] Syntax Plugins - - ## Miscellanious Features Small features that don't belong in any specific category. diff --git a/manual/src/ch03-00-miscellaneous.md b/manual/src/ch03-00-miscellaneous.md index d461dd8d..b7e14a9f 100644 --- a/manual/src/ch03-00-miscellaneous.md +++ b/manual/src/ch03-00-miscellaneous.md @@ -8,3 +8,4 @@ These are features of Ion that don't belong to any specific category: - [Multi-line Arguments](ch03-04-multiargs.html) - [Multi-line Comments](ch03-05-multicomments.html) - [General Tips](ch03-06-general.html) +- [Prompt Function](ch03-07-prompt_fn.html) diff --git a/manual/src/ch03-07-prompt_fn.md b/manual/src/ch03-07-prompt_fn.md new file mode 100644 index 00000000..79605d6f --- /dev/null +++ b/manual/src/ch03-07-prompt_fn.md @@ -0,0 +1,13 @@ +# Prompt Function + +The prompt may optionally be generated from a function, instead of a string. Take note, however, +that prompts generated from functions aren't as efficient, due to the need to perform a fork and +capture the output of the fork to use as the prompt. To use a function for generating the prompt, +simply create a function whose name is **PROMPT**, and the output of that command will be used as +the prompt. Below is an example: + +``` +fn PROMPT + echo -n "${PWD}# " +end +``` diff --git a/src/shell/binary.rs b/src/shell/binary.rs index 4af818e1..361ef8ac 100644 --- a/src/shell/binary.rs +++ b/src/shell/binary.rs @@ -12,11 +12,13 @@ use smallstring::SmallString; use smallvec::SmallVec; use std::env; use std::fs::File; -use std::io::{self, ErrorKind, Write}; +use std::io::{self, ErrorKind, Read, Write}; use std::iter::{self, FromIterator}; use std::mem; +use std::os::unix::io::FromRawFd; use std::path::{Path, PathBuf}; use std::process; +use std::process::exit; use sys; use types::*; @@ -36,28 +38,68 @@ pub(crate) trait Binary { /// rendering, controlling, and getting input from the prompt. fn readln(&mut self) -> Option<String>; /// Generates the prompt that will be used by Liner. - fn prompt(&self) -> String; + fn prompt(&mut self) -> String; /// Display version information and exit fn display_version(&self); + // Executes the PROMPT function, if it exists, and returns the output. + fn prompt_fn(&mut self) -> Option<String>; } impl<'a> Binary for Shell<'a> { - fn prompt(&self) -> String { + fn prompt(&mut self) -> String { if self.flow_control.level == 0 { - let prompt_var = self.variables.get_var_or_empty("PROMPT"); - expand_string(&prompt_var, self, false).join(" ") + let rprompt = match self.prompt_fn() { + Some(prompt) => prompt, + None => self.variables.get_var_or_empty("PROMPT"), + }; + expand_string(&rprompt, self, false).join(" ") } else { " ".repeat(self.flow_control.level as usize) } } + fn prompt_fn(&mut self) -> Option<String> { + let function = match self.functions.get("PROMPT") { + Some(func) => func.clone(), + None => return None, + }; + + let (read_fd, write_fd) = match sys::pipe2(0) { + Ok(fds) => fds, + Err(why) => { + eprintln!("ion: unable to create pipe: {}", why); + return None; + } + }; + + match unsafe { sys::fork() } { + Ok(0) => { + let _ = sys::dup2(write_fd, sys::STDOUT_FILENO); + let _ = sys::close(read_fd); + let _ = sys::close(write_fd); + let _ = function.execute(self, &["ion"]); + exit(0); + } + Ok(_) => { + let _ = sys::close(write_fd); + let mut child_stdout = unsafe { File::from_raw_fd(read_fd) }; + let mut output = String::new(); + let _ = child_stdout.read_to_string(&mut output); + Some(output) + } + Err(why) => { + let _ = sys::close(read_fd); + let _ = sys::close(write_fd); + eprintln!("ion: fork error: {}", why); + None + } + } + } + fn readln(&mut self) -> Option<String> { { let vars_ptr = &self.variables as *const Variables; let dirs_ptr = &self.directory_stack as *const DirectoryStack; - let funcs = &self.functions; - let vars = &self.variables; - let builtins = self.builtins; // Collects the current list of values from history for completion. let history = &self.context.as_ref().unwrap().history.buffers.iter() @@ -68,6 +110,10 @@ impl<'a> Binary for Shell<'a> { loop { let prompt = self.prompt(); + let funcs = &self.functions; + let vars = &self.variables; + let builtins = self.builtins; + let line = self.context.as_mut().unwrap().read_line( prompt, &mut move |Event { -- GitLab