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