Repository: cantino/mcfly (language: rust)

Issue:
Zsh
Awesome!! Any plan on making it available for zsh?


Discussion / hints:
Thanks @Linusero! I don't use zsh, but I think that'd be a pretty simple change. I designed it to be as agnostic as possible. I think you'd just need to port mcfly.bash. Interested in sending a PR?
Sure, I will try once I have some time, not so confident about my programming skills though.
Thanks! I'm happy to collaborate and answer questions. 
I created a similar-ish plugin for ZSH (https://github.com/jimeh/zsh-peco-history) for searching history with [`peco`](https://github.com/peco/peco). But I'm curious how mcfly would compare.

So I'd be happy to try and help out with ZSH integration.

@cantino since mcfly doesn't quite seem to work like peco or fzf, would you mind writing up some more detailed documentation about how the `mcfly` executable itself works, and how you're supposed to interact with it. That would be a great help :)
Seems someone is already working on this now: #8.
`peco` looks great! Good to know about.

The `mcfly` executable does a few things, listable with `mcfly help`. As an integration point, it expects the following:


* `$HISTFILE` to point to the shell's default history file to be imported (when there's no history in mcfly's sqlite3 DB), cleaned if you use the F2 command in the UI, and appended to if you provide the `--append-to-histfile` option when running `mcfly add`.
* `$MCFLY_HISTORY`, the shell history file to read from when adding or searching. This is set to a temporary file by `mcfly.bash`. `mcfly` uses this file both to save commands to the DB (by taking the last line as the most recent command) and to figure out what you're searching for if you hit `ctrl-r` after having typed a bit (again by taking the last line after stripping off the leading `#mcfly:`.)
* `$PWD` the current directory

Given these environment variables, `mcfly.bash` adds lines to the db with: `mcfly add --exit \$__last_exit --append-to-histfile`. `mcfly` uses the exit status provided on the command line and figures out the command based on the last line of `$MCFLY_HISTORY`. It also appends to `$HISTFILE` in order to provide backwards compatibility because ` --append-to-histfile` was provided.

When searching, `mcfly.bash` uses the `bind` command in `mcfly.bash` to comment out the current line being typed and to execute `mcfly search` which brings up the search UI, prefilling it with the last line of `$MCFLY_HISTORY`, if it contains `#mcfly: something`.

Please see [mcfly.bash](https://github.com/cantino/mcfly/blob/master/mcfly.bash) for more comments on this flow.
I'd like to work on the Rust part of the zsh history parser. I have some questions before actually implement it.

1. Should we rename (and refactor) the bash-related parts, such as `bash_history.rs` and `from_bash_history`?

2. How to detect "zsh mode"? Here're some options:
   1. Detect by `$SHELL`
   2. Detect by `$HISTFILE`
   3. Heuristics on file content
   The "fancy" format is enabled only when `EXTENDED_HISTORY` is set. Also, it's possible to rename `.zhistory`. I can't think of a reliable and elegant method.

3. Where to implement the detection code of "zsh mode"? Depending on the mechanism, sometimes it's better to add a flag to "mcfly add" and let the script do the remaining work. Sometimes everything should be done in the Rust part.
@cantino Thanks for the detailed explanation :)

So mcfly uses it's own separate internal SQLite database, which is added to each time the prompt command runs, and thats the database that's actually searched through, rather than the contents of any history file on disk right?

And it sounds like mcfly uses the `$MCFLY_HISTORY` file on disk due to limitations in bash. Please correct me if I'm wrong.

ZSH is a bit more flexible than Bash, and I think you can completely skip having mcfly touching any history files on disk. My [peco plugin](https://github.com/jimeh/zsh-peco-history/blob/b207ec8971d38667719a6f66ef02f8d6b18a1c36/zsh-peco-history.zsh#L36-L37) for example feeds all history into peco via STDIN, which peco then filters, and when you pick an entry inside of peco, all peco itself does is print that to STDOUT, which zsh captures, and puts as the text on your prompt.

Obviously populating the history from scratch on each invocation of ctrl-r would mean the current path is not available. But it should be possible to run a custom command to append to mcfly on each prompt render, without having to worry about mcfly also writing to ZSH's own history file. And any text typed into the prompt when you press ctrl-r is available from ZSH and can be passed to mcfly as a CLI argument, so no need to read it from disk.

Knowing this, I think I'll be able to put together a ZSH plugin for mcfly similar to my zsh-peco-historyf plugin. But a couple of changes might be needed to take full advantage of niceties offered by ZSH:

- Allow passing in the initial search query to the `search` command via the CLI, and maybe add a specific flag for reading it from the `$MCLFY_HISTORY` file for use by the bash plugin.
- Make `MCFLY_HISTORY` an optional environment variable, as I believe it's not needed for ZSH as we can capture and pass in the initial search query as a CLI argument.
- Make `HISTFILE` an optional environment variable, and maybe rename it to `BASH_HISTFILE`, so a separate `ZSH_HISTFILE` env var can be used for importing ZSH history. And only import history if the relevant env var is set.

Also, I have a question:

- What is `MCFLY_SESSION_ID`, and why is it needed?
@hghwng, I think adding a flag to `mcfly add` is the right approach. Let's not be too smart in our detection in the Rust code. The shell script can tell us.

I think we could use a trait to provide different parsers.
@jimeh 

> So mcfly uses it's own separate internal SQLite database, which is added to each time the prompt command runs, and thats the database that's actually searched through, rather than the contents of any history file on disk right?

Yup!

> And it sounds like mcfly uses the $MCFLY_HISTORY file on disk due to limitations in bash. Please correct me if I'm wrong.

Yes, it's how we figure out the fully escaped text of the most recent command, and also how we figure out what you're searching for if you type some characters before hitting `ctrl-r`. This avoids bad things if you type backticks or other active characters in the line you're searching for.

> ZSH is a bit more flexible than Bash, and I think you can completely skip having mcfly touching any history files on disk. My peco plugin for example feeds all history into peco via STDIN, which peco then filters, and when you pick an entry inside of peco, all peco itself does is print that to STDOUT, which zsh captures, and puts as the text on your prompt.

Nice. Maybe we can encapsulate the weird Bash handling into a trait.

...

> Allow passing in the initial search query to the search command via the CLI, and maybe add a specific flag for reading it from the $MCLFY_HISTORY file for use by the bash plugin.

You can do this now. Both search and add take a `--command` option to pass it in. But be careful of escaping shell characters.  https://github.com/cantino/mcfly/blob/master/src/settings.rs

> What is MCFLY_SESSION_ID, and why is it needed?

This is how we can identify the context of commands in a terminal window so that we're not conflating commands across sessions.
@Linusero @cantino @jimeh 

Hi, I would like to contribute to this idea. I was trying mcfly and just got to know that it does not work with zsh. Can I contribute in any way to this?
@HarshitKaushik You can fork my branch (or just copy the `mcfly.zsh` file from it) and make a new pull request. It seems zsh's history file format is slightly different, I'm not sure what other fixes to the application may be required.
@cantino, I'm more than happy with contributing with a Zsh script.

I'm confused about the need of the `MCFLY_HISTORY` file. Is it required to write all commands to it, even if directly passing them as a parameter to `mcfly`? (see function below)

A simple Zsh function that would be executed after each command can be:
```
_mcfly_precmd() {
  local exit_status=${?} # Exit status of last command.
  local last_command=${history[$((HISTCMD - 1))]}
  mcfly add --exit ${exit_status} ${last_command}
}
autoload -Uz add-zsh-hook && add-zsh-hook precmd _mcfly_precmd
```

We can read the last command directly from the `history` array, so no need to read/write from any file, not even worry about the `.zsh_history` file, which Zsh takes care of independently. Another good thing about this approach is that is also nicely handles multi-line commands, which would be more complex to read from a file.

So another question I have is how does `mcfly` handle multi-line commands? Like
```
% print 'Line 1
Line 2'
```
@ericbn As long as you can pass the command to mcfly using the `--command` option and escape it properly, I don't *think* `mcfly` needs to read from `MCFLY_HISTORY`.
@ericbn Has there been any progress? Is there something I can help?
I've been a bit too busy the past couple of months, but I still intend to take a proper stab at this based on my [zsh plugin](https://github.com/jimeh/zsh-peco-history) for [`peco`](https://github.com/peco/peco) at some point in the next few weeks.

Mcfly looks very promising, and I'm very keen to try it out properly within my setup :)
I would really love to see zsh support fro mcfly...
It sounds very interesting and I like to use the ZSH very much. Is there already an update for the project?
Perhaps there are already successes with https://github.com/cantino/mcfly/pull/8?
Oh man, I just installed this and only after did I notice that it doesn't support zsh! I am also looking forward for zsh support!

Fix patch:
diff --git a/.gitignore b/.gitignore
index 4d87f277..16434917 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
 /target
 **/*.rs.bk
 update.sh
+.zsh_history
+.zshrc
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 49eb88e5..570ea1e3 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,3 +1,5 @@
+0.4.0 - Jun 28, 2020
+ - Zsh support!
 0.3.6 - Dec 15, 2019
  - Optional VI-style keybindings from @JamJar00
 0.3.5 - Aug 30, 2019
diff --git a/Cargo.lock b/Cargo.lock
index d50b7ed9..bea8ae3b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,176 +1,192 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 [[package]]
-name = "ansi_term"
-version = "0.11.0"
+name = "aho-corasick"
+version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "argon2rs"
-version = "0.2.5"
+name = "ansi_term"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "arrayvec"
-version = "0.4.7"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
 name = "atty"
-version = "0.2.10"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hermit-abi 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "backtrace"
-version = "0.3.9"
+name = "autocfg"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
-name = "backtrace-sys"
-version = "0.1.24"
+name = "base64"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
 name = "bitflags"
-version = "1.0.3"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "blake2-rfc"
-version = "0.2.18"
+name = "blake2b_simd"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "bstr"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "cc"
-version = "1.0.18"
+version = "1.0.52"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "cfg-if"
-version = "0.1.5"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "clap"
-version = "2.32.0"
+version = "2.33.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "cloudabi"
-version = "0.0.3"
+name = "constant_time_eq"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
-name = "constant_time_eq"
-version = "0.1.3"
+name = "crossbeam-utils"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "csv"
-version = "1.0.1"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "csv-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.75 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "csv-core"
-version = "0.1.4"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "dirs"
-version = "1.0.4"
+version = "2.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "failure"
-version = "0.1.2"
+name = "dirs-sys"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "failure_derive"
-version = "0.1.2"
+name = "getrandom"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "fuchsia-zircon"
-version = "0.3.3"
+name = "hermit-abi"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "fuchsia-zircon-sys"
-version = "0.3.3"
+name = "itoa"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "libc"
-version = "0.2.42"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -178,104 +194,101 @@ name = "libsqlite3-sys"
 version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "vcpkg 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "linked-hash-map"
-version = "0.4.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "lru-cache"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mcfly"
-version = "0.3.6"
+version = "0.4.0"
 dependencies = [
- "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "csv 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "relative-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rusqlite 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "shellexpand 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "memchr"
-version = "2.0.1"
+version = "2.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
-name = "nodrop"
-version = "0.1.12"
+name = "numtoa"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.11"
+version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "proc-macro2"
-version = "0.4.20"
+name = "ppv-lite86"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
-name = "quote"
-version = "0.6.8"
+name = "rand"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "rand"
-version = "0.4.3"
+name = "rand_chacha"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "rand"
-version = "0.5.5"
+name = "rand_core"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "rand_core"
-version = "0.2.1"
+name = "rand_hc"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "redox_syscall"
-version = "0.1.40"
+version = "0.1.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -283,23 +296,46 @@ name = "redox_termios"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "redox_users"
-version = "0.2.0"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "1.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "relative-path"
-version = "0.4.0"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -307,114 +343,110 @@ name = "rusqlite"
 version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libsqlite3-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "rustc-demangle"
-version = "0.1.9"
+name = "rust-argon2"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
-name = "scoped_threadpool"
-version = "0.1.9"
+name = "ryu"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde"
-version = "1.0.75"
+version = "1.0.106"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "shellexpand"
-version = "1.0.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "strsim"
-version = "0.7.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "syn"
-version = "0.14.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "synstructure"
-version = "0.9.0"
+name = "termion"
+version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "termion"
-version = "1.5.1"
+name = "textwrap"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "textwrap"
-version = "0.10.0"
+name = "thread_local"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "time"
-version = "0.1.40"
+version = "0.1.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.2.1"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.5"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "unicode-xid"
-version = "0.1.0"
+name = "vcpkg"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "vcpkg"
-version = "0.2.4"
+name = "vec_map"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "vec_map"
-version = "0.8.1"
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "winapi"
-version = "0.3.5"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -432,58 +464,64 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [metadata]
+"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
-"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
-"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
-"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1"
-"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
-"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
-"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
-"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
-"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
-"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
-"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
-"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
-"checksum csv 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bbb6d75aae072248e381715437855a69595e5a97d3abbf748fe7a95e65d77fa"
-"checksum csv-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dd8e6d86f7ba48b4276ef1317edc8cc36167546d8972feb4a2b5fec0b374105"
-"checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a"
-"checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9"
-"checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426"
-"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
-"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1"
+"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
+"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
+"checksum bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41"
+"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+"checksum cc 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)" = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d"
+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
+"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
+"checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279"
+"checksum csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
+"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
+"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
+"checksum hermit-abi 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4"
+"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+"checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
 "checksum libsqlite3-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "742b695cbfb89e549dca6960a55e6802f67d352e33e97859ee46dee835211b0f"
-"checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939"
-"checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21"
-"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
-"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
-"checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f"
-"checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee"
-"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5"
-"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
-"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"
-"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2"
-"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
+"checksum linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
+"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
+"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
+"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
-"checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26"
-"checksum relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7790c7f1cc73d831d28dc5a7deb316a006e7848e6a7f467cdb10a0a9e0fb1c"
+"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
+"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
+"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
+"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
+"checksum relative-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bedde000f40f2921ce439ea165c9c53fd629bfa115140c72e22aceacb4a21954"
 "checksum rusqlite 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39bae767eb27866f5c0be918635ae54af705bc09db11be2c43a3c6b361cf3462"
-"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
-"checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
-"checksum serde 1.0.75 (registry+https://github.com/rust-lang/crates.io-index)" = "22d340507cea0b7e6632900a176101fea959c7065d93ba555072da90aaaafc87"
-"checksum shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de7a5b5a9142fd278a10e0209b021a1b85849352e6951f4f914735c976737564"
-"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
-"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
-"checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7"
-"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
-"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
-"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
-"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
-"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
-"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
-"checksum vcpkg 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cbe533e138811704c0e3cbde65a818b35d3240409b4346256c5ede403e082474"
-"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
-"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
+"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
+"checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
+"checksum serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
+"checksum shellexpand 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2b22262a9aaf9464d356f656fea420634f78c881c5eebd5ef5e66d8b9bc603"
+"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+"checksum termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
+"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
+"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
+"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
 "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
index 9b989bd0..96097bdf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,9 +1,9 @@
 [package]
 name = "mcfly"
-version = "0.3.6"
+version = "0.4.0"
 authors = ["Andrew Cantino <cantino@users.noreply.github.com>"]
 edition = "2018"
-description = "McFly replaces your default ctrl-r Bash history search with an intelligent search engine that takes into account your working directory and the context of recently executed commands. McFly's suggestions are prioritized in real time with a small neural network."
+description = "McFly replaces your default ctrl-r shell history search with an intelligent search engine that takes into account your working directory and the context of recently executed commands. McFly's suggestions are prioritized in real time with a small neural network."
 license = "MIT"
 repository = "https://github.com/cantino/mcfly"
 exclude = [
@@ -16,16 +16,20 @@ exclude = [
 [profile.release]
 lto = true
 
+[profile.dev]
+debug = true
+
 [dependencies]
-clap = "2.32.0"
+clap = "2.33"
 csv = "1"
-dirs = "1.0"
+dirs = "2.0"
 libc = "0.2"
-rand = "0.5.5"
-relative-path = "0.4.0"
-shellexpand = "1.0.0"
-termion = "1.5.1"
-unicode-segmentation = "1.2.1"
+rand = "0.7"
+regex = "1"
+relative-path = "1.0"
+shellexpand = "2.0"
+termion = "1.5.5"
+unicode-segmentation = "1.6"
 
 [dependencies.rusqlite]
 version = "0.15.0"
diff --git a/README.md b/README.md
index 70eb0eb1..694c4878 100644
--- a/README.md
+++ b/README.md
@@ -5,17 +5,17 @@
 
 <img src="/docs/screenshot.png" alt="screenshot" width="400">
 
-McFly replaces your default `ctrl-r` Bash history search with an intelligent search engine that takes into account
+McFly replaces your default `ctrl-r` shell history search with an intelligent search engine that takes into account
 your working directory and the context of recently executed commands. McFly's suggestions are prioritized
 in real time with a small neural network.
  
-TL;DR: an upgraded `ctrl-r` for Bash whose history results make sense for what you're working on right now.
+TL;DR: an upgraded `ctrl-r` where history results make sense for what you're working on right now.
 
 ## Features
 
 * Rebinds `ctrl-r` to bring up a full-screen reverse history search prioritized with a small neural network.
 * Augments your shell history to track command exit status, timestamp, and execution directory in a SQLite database.
-* Maintains your normal Bash history file as well so that you can stop using McFly whenever you want.
+* Maintains your normal shell history file as well so that you can stop using McFly whenever you want.
 * Unicode support throughout.
 * Includes a simple action to scrub any history item from the McFly database and your shell history files.
 * Designed to be extensible for other shells in the future.
@@ -47,13 +47,22 @@ When suggesting a command, McFly takes into consideration:
     ```bash
     brew install mcfly
     ```
-1. Add the following to the end of your `~/.bashrc` file:
+1. Add the following to the end of your `~/.bashrc` or `~/.zshrc` file, respectively:
+
+    Bash:
     ```bash
     if [[ -r "$(brew --prefix)/opt/mcfly/mcfly.bash" ]]; then
       source "$(brew --prefix)/opt/mcfly/mcfly.bash"
     fi
     ```
-1. Run `. ~/.bashrc` or restart your terminal emulator.
+
+    Zsh:
+    ```bash
+    if [[ -r "$(brew --prefix)/opt/mcfly/mcfly.zsh" ]]; then
+      source "$(brew --prefix)/opt/mcfly/mcfly.zsh"
+    fi
+    ```
+1. Run `. ~/.bashrc` / `. ~/.zshrc` or restart your terminal emulator.
 
 #### Uninstalling with Homebrew
 
@@ -65,20 +74,29 @@ When suggesting a command, McFly takes into consideration:
     ```bash
     brew untap cantino/mcfly
     ```
-1. Remove the lines you added to `~/.bashrc`.
+1. Remove the lines you added to `~/.bashrc` / `~/.zshrc`.
 
 ### Installing manually from GitHub
 
 1. Download the [latest release from GitHub](https://github.com/cantino/mcfly/releases).
-1. Install to a location in your `$PATH`. (For example, you could create a directory at `~/bin`, copy `mcfly` to this location, and add `export PATH="$PATH:$HOME/bin"` to your `.bashrc`.)
-1. Copy `mcfly.bash` to a known location.
-1. Add the following to the end of your `~/.bashrc` file:
+1. Install to a location in your `$PATH`. (For example, you could create a directory at `~/bin`, copy `mcfly` to this location, and add `export PATH="$PATH:$HOME/bin"` to your `.bashrc` / `.zshrc`.)
+1. Copy `mcfly.bash` or `mcfly.zsh` to a known location.
+1. Add the following to the end of your `~/.bashrc` or `~/.zshrc` file, respectively:
+
+    Bash:
     ```bash
     if [[ -r /path/to/mcfly.bash ]]; then
       source /path/to/mcfly.bash
     fi
     ```
-1. Run `. ~/.bashrc` or restart your terminal emulator.
+
+    Zsh:
+    ```bash
+    if [[ -r /path/to/mcfly.zsh ]]; then
+      source /path/to/mcfly.zsh
+    fi
+    ```
+1. Run `. ~/.bashrc` / `. ~/.zshrc` or restart your terminal emulator.
 
 ### Install manually from source
 
@@ -86,13 +104,22 @@ When suggesting a command, McFly takes into consideration:
 1. Run `git clone https://github.com/cantino/mcfly` and `cd mcfly`
 1. Run `cargo install --path .`
 1. Ensure `~/.cargo/bin` is in your `$PATH`.
-1. Add the following to the end of your `~/.bashrc` file:
+1. Add the following to the end of your `~/.bashrc` or `~/.zshrc` file, respectively:
+
+    Bash:
     ```bash
     if [[ -r /path/to/mcfly.bash ]]; then
       source /path/to/mcfly.bash
     fi
     ```
-1. Run `. ~/.bashrc` or restart your terminal emulator.
+
+    Zsh:
+    ```bash
+    if [[ -r /path/to/mcfly.zsh ]]; then
+      source /path/to/mcfly.zsh
+    fi
+    ```
+1. Run `. ~/.bashrc` / `. ~/.zshrc` or restart your terminal emulator.
 
 ## iTerm2
 
@@ -101,7 +128,7 @@ To avoid McFly's UI messing up your scrollback history in iTerm2, make sure this
 <img src="/docs/iterm2.jpeg" alt="iterm2 UI instructions">
 
 ## Settings
-A number of settings can be set via environment variables. To set a setting you should add the following snippets to your `~/.bash_profile`.
+A number of settings can be set via environment variables. To set a setting you should add the following snippets to your `~/.bashrc` / `~/.zshrc`.
 
 ### Light Mode
 To swap the color scheme for use in a light terminal, set the environment variable `MCFLY_LIGHT`.
diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh
index 2c6f5dfd..f811726b 100644
--- a/ci/before_deploy.sh
+++ b/ci/before_deploy.sh
@@ -23,6 +23,7 @@ main() {
 
     cp target/$TARGET/release/mcfly $stage/
     cp mcfly.bash $stage/
+    cp mcfly.zsh $stage/
 
     cd $stage
     tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
diff --git a/dev.bash b/dev.bash
index f7b262fc..6a75f165 100755
--- a/dev.bash
+++ b/dev.bash
@@ -1,10 +1,14 @@
 #!/bin/bash
 # Build mcfly and run a dev environment bash for local mcfly testing
 
-set -e
 this_dir=$(cd `dirname "$0"`; pwd)
 
+rm -f target/debug/mcfly
+rm -rf target/debug/deps/mcfly-*
 cargo build
-PATH="$PATH:$this_dir/target/debug" \
-MCFLY_DEBUG=1 \
-    exec /bin/bash --init-file "$this_dir/mcfly.bash" -i
+# For some reason, to get line numbers in backtraces, we have to run the binary directly.
+HISTFILE=$HOME/.bash_history \
+  MCFLY_PATH=$(find target/debug/deps/mcfly-* -maxdepth 1 -type f | grep -v '\.d') \
+  RUST_BACKTRACE=full \
+  MCFLY_DEBUG=1 \
+  exec /bin/bash --init-file "$this_dir/mcfly.bash" -i
diff --git a/dev.zsh b/dev.zsh
new file mode 100755
index 00000000..e3299f3c
--- /dev/null
+++ b/dev.zsh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# Build mcfly and run a dev environment zsh for local mcfly testing
+
+this_dir=$(cd `dirname "$0"`; pwd)
+
+# Setup for local testing.
+touch $this_dir/.zsh_history
+
+# Needed so that the test instance of zsh sources the local mcfly.zsh file on startup.
+echo "source ./mcfly.zsh" > $this_dir/.zshrc
+
+rm -f target/debug/mcfly
+rm -rf target/debug/deps/mcfly-*
+cargo build
+# For some reason, to get line numbers in backtraces, we have to run the binary directly.
+HISTFILE=$HOME/.zsh_history \
+  MCFLY_PATH=$(find target/debug/deps/mcfly-* -maxdepth 1 -type f | grep -v '\.d') \
+  RUST_BACKTRACE=full \
+  MCFLY_DEBUG=1 \
+  ZDOTDIR="$this_dir" \
+  /bin/zsh -i
diff --git a/mcfly.bash b/mcfly.bash
index 52da3654..2c27ced3 100644
--- a/mcfly.bash
+++ b/mcfly.bash
@@ -19,6 +19,9 @@ fi
 # MCFLY_SESSION_ID is used by McFly internally to keep track of the commands from a particular terminal session.
 export MCFLY_SESSION_ID=$(dd if=/dev/urandom bs=256 count=1 2> /dev/null | env LC_ALL=C tr -dc 'a-zA-Z0-9' | head -c 24)
 
+# Find the binary
+MCFLY_PATH=${MCFLY_PATH:-$(which mcfly)}
+
 # Ignore commands with a leading space
 export HISTCONTROL="ignorespace"
 
@@ -40,7 +43,7 @@ function mcfly_prompt_command {
   # * append commands to $HISTFILE, (~/.bash_history by default)
   #   for backwards compatibility and to load in new terminal sessions;
   # * find the text of the last command in $MCFLY_HISTORY and save it to the database.
-  mcfly add --exit ${exit_code} --append-to-histfile
+  $MCFLY_PATH add --exit ${exit_code} --append-to-histfile
   # Clear the in-memory history and reload it from $MCFLY_HISTORY
   # (to remove instances of '#mcfly: ' from the local session history).
   history -cr ${MCFLY_HISTORY}
diff --git a/mcfly.zsh b/mcfly.zsh
new file mode 100644
index 00000000..77e4b920
--- /dev/null
+++ b/mcfly.zsh
@@ -0,0 +1,86 @@
+#!/bin/zsh
+
+# Ensure stdin is a tty
+[[ -t 0 ]] || return
+
+# Avoid loading this file more than once
+if [[ "$__MCFLY_LOADED" == "loaded" ]]; then
+  return 0
+fi
+__MCFLY_LOADED="loaded"
+
+emulate -L zsh
+
+# Ensure HISTFILE exists.
+export HISTFILE="${HISTFILE:-$HOME/.zsh_history}"
+if [[ ! -r "${HISTFILE}" ]]; then
+  echo "McFly: ${HISTFILE} does not exist or is not readable. Please fix this or set HISTFILE to something else before using McFly."
+  return 1
+fi
+
+# MCFLY_SESSION_ID is used by McFly internally to keep track of the commands from a particular terminal session.
+export MCFLY_SESSION_ID=$(dd if=/dev/urandom bs=256 count=1 2> /dev/null | env LC_ALL=C tr -dc 'a-zA-Z0-9' | head -c 24)
+
+# Find the binary
+MCFLY_PATH=${MCFLY_PATH:-$(which mcfly)}
+
+# Required for commented out mcfly search commands to work.
+setopt interactive_comments   # allow comments in interactive shells (like Bash does)
+
+# McFly's temporary, per-session history file.
+if [[ ! -f "${MCFLY_HISTORY}" ]]; then
+  export MCFLY_HISTORY=$(mktemp -t mcfly.XXXXXXXX)
+fi
+
+# Setup a function to be used by $PROMPT_COMMAND.
+function mcfly_prompt_command {
+  local exit_code=$? # Record exit status of previous command.
+
+  # Populate McFly's temporary, per-session history file from recent commands in the shell's primary HISTFILE.
+  if [[ ! -f "${MCFLY_HISTORY}" ]]; then
+    export MCFLY_HISTORY=$(mktemp -t mcfly.XXXXXXXX)
+    tail -n100 "${HISTFILE}" >| ${MCFLY_HISTORY}
+  fi
+
+  # Write history to $MCFLY_HISTORY.
+  fc -W "${MCFLY_HISTORY}"
+
+  # Run mcfly with the saved code. It fill find the text of the last command in $MCFLY_HISTORY and save it to the database.
+  [ -n "$MCFLY_DEBUG" ] && echo "mcfly.zsh: Run mcfly add --exit ${exit_code}"
+  $MCFLY_PATH add --exit ${exit_code}
+  return ${exit_code} # Restore the original exit code by returning it.
+}
+precmd_functions+=(mcfly_prompt_command)
+
+# Cleanup $MCFLY_HISTORY tmp files on exit.
+exit_logger() {
+  [ -n "$MCFLY_DEBUG" ] && echo "mcfly.zsh: Exiting and removing $MCFLY_HISTORY"
+  rm -f $MCFLY_HISTORY
+}
+zshexit_functions+=(exit_logger)
+
+# If this is an interactive shell, take ownership of ctrl-r.
+if [[ $- =~ .*i.* ]]; then
+  mcfly-history-widget() {
+    () {
+      echoti rmkx
+      exec </dev/tty
+      local mcfly_output=$(mktemp -t mcfly.output.XXXXXXXX)
+      $MCFLY_PATH search -o "${mcfly_output}" "${LBUFFER}"
+      local mode=$(sed -n 1p $mcfly_output)
+      local selected=$(sed 1d $mcfly_output)
+      rm -f $mcfly_output
+      echoti smkx
+      if [[ -n $selected ]]; then
+        RBUFFER=""
+        LBUFFER="${selected}"
+      fi
+      if [[ "${mode}" == "run" ]]; then
+        zle accept-line
+      fi
+      zle redisplay
+    }
+  }
+  zle -N mcfly-history-widget
+  bindkey '^R' mcfly-history-widget
+fi
diff --git a/pkg/brew/mcfly.rb b/pkg/brew/mcfly.rb
index 2757ad14..cf3f168c 100644
--- a/pkg/brew/mcfly.rb
+++ b/pkg/brew/mcfly.rb
@@ -21,16 +21,23 @@ class Mcfly < Formula
 
   def install
     prefix.install "mcfly.bash"
+    prefix.install "mcfly.zsh"
     bin.install "mcfly"
   end
 
   def caveats
     <<~EOS
-      ONE MORE STEP! Edit ~/.bashrc and add the following at the end:
+      ONE MORE STEP!
 
-      if [ -r $(brew --prefix)/opt/mcfly/mcfly.bash ]; then
-        . $(brew --prefix)/opt/mcfly/mcfly.bash
-      fi
+      If you use Bash, edit ~/.bashrc and add the following at the end:
+        if [ -r $(brew --prefix)/opt/mcfly/mcfly.bash ]; then
+          . $(brew --prefix)/opt/mcfly/mcfly.bash
+        fi
+
+      If you use Zsh, edit ~/.zshrc and add the following at the end:
+        if [ -r $(brew --prefix)/opt/mcfly/mcfly.zsh ]; then
+          . $(brew --prefix)/opt/mcfly/mcfly.zsh
+        fi
     EOS
   end
 end
diff --git a/src/bash_history.rs b/src/bash_history.rs
deleted file mode 100644
index b066bde3..00000000
--- a/src/bash_history.rs
+++ /dev/null
@@ -1,120 +0,0 @@
-use std::env;
-use std::fs;
-use std::fs::OpenOptions;
-use std::io::Write;
-use std::path::PathBuf;
-use std::fs::File;
-use std::io::Read;
-
-fn read_ignoring_utf_errors(path: &PathBuf) -> String {
-    let mut f = File::open(path).unwrap_or_else(|_| panic!("McFly error: {:?} file not found", &path));
-    let mut buffer = Vec::new();
-    f.read_to_end(&mut buffer).unwrap_or_else(|_| panic!("McFly error: Unable to read from {:?}", &path));
-    String::from_utf8_lossy(&buffer).to_string()
-}
-
-#[allow(clippy::if_same_then_else)]
-fn has_leading_timestamp(line: &str) -> bool {
-    let mut matched_chars = 0;
-
-    for (index, c) in line.chars().enumerate() {
-        if index == 0 && c == '#' {
-            matched_chars += 1;
-        } else if index > 0 && index < 11 && (c.is_digit(10))  {
-            matched_chars += 1;
-        } else if index > 11 {
-            break;
-        }
-    }
-
-    matched_chars == 11
-}
-
-pub fn bash_history_file_path() -> PathBuf {
-    let path =
-        PathBuf::from(env::var("HISTFILE").unwrap_or_else(|err| panic!(format!("McFly error: Please ensure HISTFILE is set for your shell ({})", err))));
-    fs::canonicalize(&path).unwrap_or_else(|err| panic!(format!("McFly error: The contents of $HISTFILE appear invalid ({})", err)))
-}
-
-pub fn full_history(path: &PathBuf) -> Vec<String> {
-    let bash_history_contents = read_ignoring_utf_errors(&path);
-
-    bash_history_contents
-        .split('\n')
-        .filter(|line| !has_leading_timestamp(line) && !line.is_empty())
-        .map(String::from)
-        .collect::<Vec<String>>()
-}
-
-pub fn last_history_line(path: &PathBuf) -> Option<String> {
-    // Could switch to https://github.com/mikeycgto/rev_lines
-    full_history(path).last().map(|s| s.trim().to_string())
-}
-
-pub fn delete_last_history_entry_if_search(path: &PathBuf) {
-    let bash_history_contents = read_ignoring_utf_errors(&path);
-
-    let mut lines = bash_history_contents
-        .split('\n')
-        .map(String::from)
-        .collect::<Vec<String>>();
-
-    if !lines.is_empty() && lines[lines.len() - 1].is_empty() {
-        lines.pop();
-    }
-
-    if lines.is_empty() || !lines[lines.len() - 1].starts_with("#mcfly:") {
-        return; // Abort if empty or the last line isn't a comment.
-    }
-
-    lines.pop();
-
-    if !lines.is_empty() && has_leading_timestamp(&lines[lines.len() - 1]) {
-        lines.pop();
-    }
-
-    lines.push(String::from("")); // New line at end of file expected by bash.
-
-    fs::write(&path, lines.join("\n")).unwrap_or_else(|_| panic!("McFly error: Unable to update {:?}", &path));
-}
-
-pub fn delete_lines(path: &PathBuf, command: &str) {
-    let history_contents = read_ignoring_utf_errors(&path);
-
-    let lines = history_contents
-        .split('\n')
-        .map(String::from)
-        .filter(|cmd| !cmd.eq(command))
-        .collect::<Vec<String>>();
-
-    fs::write(&path, lines.join("\n")).unwrap_or_else(|_| panic!("McFly error: Unable to update {:?}", &path));
-}
-
-pub fn append_history_entry(command: &str, path: &PathBuf) {
-    let mut file = OpenOptions::new()
-        .write(true)
-        .append(true)
-        .open(path)
-        .unwrap_or_else(|err| panic!(format!("McFly error: please make sure HISTFILE exists ({})", err)));
-
-    if let Err(e) = writeln!(file, "{}", command) {
-        eprintln!("Couldn't append to file {:?}: {}", &path, e);
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::has_leading_timestamp;
-
-    #[test]
-    fn has_leading_timestamp_works() {
-        assert_eq!(false, has_leading_timestamp("abc"));
-        assert_eq!(false, has_leading_timestamp("#abc"));
-        assert_eq!(false, has_leading_timestamp("#123456"));
-        assert_eq!(true, has_leading_timestamp("#1234567890"));
-        assert_eq!(false, has_leading_timestamp("#123456789"));
-        assert_eq!(false, has_leading_timestamp("# 1234567890"));
-        assert_eq!(false, has_leading_timestamp("1234567890"));
-        assert_eq!(false, has_leading_timestamp("hello 1234567890"));
-    }
-}
diff --git a/src/command_input.rs b/src/command_input.rs
index de354bc2..9058d36a 100644
--- a/src/command_input.rs
+++ b/src/command_input.rs
@@ -224,7 +224,8 @@ impl CommandInput {
             return 0;
         }
 
-        let mut word_boundaries = self.command
+        let mut word_boundaries = self
+            .command
             .split_word_bound_indices()
             .map(|(i, _)| i)
             .collect::<Vec<usize>>();
@@ -271,7 +272,8 @@ impl CommandInput {
 
         let grapheme_indices = command_copy.grapheme_indices(true);
 
-        let mut word_boundaries = self.command
+        let mut word_boundaries = self
+            .command
             .split_word_bound_indices()
             .map(|(i, _)| i)
             .collect::<Vec<usize>>();
diff --git a/src/fake_typer.rs b/src/fake_typer.rs
index 94ce168f..5962da38 100644
--- a/src/fake_typer.rs
+++ b/src/fake_typer.rs
@@ -1,5 +1,3 @@
-use libc;
-
 // Should we be using https://docs.rs/libc/0.2.44/libc/fn.ioctl.html instead?
 extern "C" {
     pub fn ioctl(fd: i8, request: u32, arg: *const u8) -> i8;
diff --git a/src/history/db_extensions.rs b/src/history/db_extensions.rs
index fc377240..13726ac2 100644
--- a/src/history/db_extensions.rs
+++ b/src/history/db_extensions.rs
@@ -1,7 +1,6 @@
-use rusqlite;
-use rusqlite::Connection;
 use crate::history::history::Features;
 use crate::network::Network;
+use rusqlite::Connection;
 
 pub fn add_db_functions(db: &Connection) {
     let network = Network::default();
@@ -31,5 +30,11 @@ pub fn add_db_functions(db: &Connection) {
         };
 
         Ok(network.output(&features))
-    }).unwrap_or_else(|err| panic!(format!("McFly error: Successful create_scalar_function ({})", err)));
+    })
+    .unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Successful create_scalar_function ({})",
+            err
+        ))
+    });
 }
diff --git a/src/history/history.rs b/src/history/history.rs
index 02241d39..b3d16ca2 100644
--- a/src/history/history.rs
+++ b/src/history/history.rs
@@ -1,17 +1,17 @@
 #![allow(clippy::module_inception)]
-use crate::bash_history;
+use crate::shell_history;
 use rusqlite::{Connection, MappedRows, Row, NO_PARAMS};
-use std::{fmt, fs, io};
 use std::io::Write;
 use std::path::PathBuf;
+use std::{fmt, fs, io};
 //use std::time::Instant;
 use crate::history::{db_extensions, schema};
-use crate::simplified_command::SimplifiedCommand;
-use crate::path_update_helpers;
-use std::time::{Instant, SystemTime, UNIX_EPOCH};
 use crate::network::Network;
+use crate::path_update_helpers;
 use crate::settings::Settings;
+use crate::simplified_command::SimplifiedCommand;
 use rusqlite::types::ToSql;
+use std::time::{Instant, SystemTime, UNIX_EPOCH};
 
 #[derive(Debug, Clone, Default)]
 pub struct Features {
@@ -59,7 +59,15 @@ pub struct History {
     pub network: Network,
 }
 
-const IGNORED_COMMANDS: [&str; 7] = ["pwd", "ls", "cd", "cd ..", "clear", "history", "mcfly search"];
+const IGNORED_COMMANDS: [&str; 7] = [
+    "pwd",
+    "ls",
+    "cd",
+    "cd ..",
+    "clear",
+    "history",
+    "mcfly search",
+];
 
 impl History {
     pub fn load() -> History {
@@ -128,13 +136,9 @@ impl History {
                                       ]).unwrap_or_else(|err| panic!(format!("McFly error: Insert into commands to work ({})", err)));
     }
 
-    fn determine_if_selected_from_ui(
-        &self,
-        command: &str,
-        session_id: &str,
-        dir: &str,
-    ) -> bool {
-        let rows_affected = self.connection
+    fn determine_if_selected_from_ui(&self, command: &str, session_id: &str, dir: &str) -> bool {
+        let rows_affected = self
+            .connection
             .execute_named(
                 "DELETE FROM selected_commands \
                  WHERE cmd = :cmd \
@@ -146,7 +150,12 @@ impl History {
                     (":dir", &dir.to_owned()),
                 ],
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: DELETE from selected_commands to work ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: DELETE from selected_commands to work ({})",
+                    err
+                ))
+            });
 
         // Delete any other pending selected commands for this session -- they must have been aborted or edited.
         self.connection
@@ -154,7 +163,12 @@ impl History {
                 "DELETE FROM selected_commands WHERE session_id = :session_id",
                 &[(":session_id", &session_id.to_owned())],
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: DELETE from selected_commands to work ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: DELETE from selected_commands to work ({})",
+                    err
+                ))
+            });
 
         rows_affected > 0
     }
@@ -171,9 +185,8 @@ impl History {
     // Update historical paths in our database if a directory has been renamed or moved.
     pub fn possibly_update_paths(&self, command: &str, exit_code: Option<i32>) {
         let successful = exit_code.is_none() || exit_code.unwrap() == 0;
-        let is_move = |c:&str| c.to_lowercase().starts_with("mv ")
-            && !c.contains('*')
-            && !c.contains('?');
+        let is_move =
+            |c: &str| c.to_lowercase().starts_with("mv ") && !c.contains('*') && !c.contains('?');
         if successful && is_move(command) {
             let parts = path_update_helpers::parse_mv_command(command);
             if parts.len() == 2 {
@@ -189,10 +202,15 @@ impl History {
                             // It was probably a file.
                             return;
                         }
-                        let maybe_moved_directory = PathBuf::from(&normalized_to).join(utf8_basename);
+                        let maybe_moved_directory =
+                            PathBuf::from(&normalized_to).join(utf8_basename);
                         if maybe_moved_directory.exists() {
                             if maybe_moved_directory.is_dir() {
-                                self.update_paths(&normalized_from, maybe_moved_directory.to_str().unwrap(), false);
+                                self.update_paths(
+                                    &normalized_from,
+                                    maybe_moved_directory.to_str().unwrap(),
+                                    false,
+                                );
                             } else {
                                 // The source must have been a file, so ignore it.
                             }
@@ -224,44 +242,103 @@ impl History {
                            FROM contextual_commands
                            WHERE cmd LIKE (:like)
                            ORDER BY rank DESC LIMIT :limit";
-        let mut statement = self.connection.prepare(query).unwrap_or_else(|err| panic!(format!("McFly error: Prepare to work ({})", err)));
+        let mut statement = self
+            .connection
+            .prepare(query)
+            .unwrap_or_else(|err| panic!(format!("McFly error: Prepare to work ({})", err)));
         let command_iter = statement
-            .query_map_named(
-                &[(":like", &like_query), (":limit", &num)],
-                |row| Command {
-                    id: row.get_checked(0).unwrap_or_else(|err| panic!(format!("McFly error: id to be readable ({})", err))),
-                    cmd: row.get_checked(1).unwrap_or_else(|err| panic!(format!("McFly error: cmd to be readable ({})", err))),
-                    cmd_tpl: row.get_checked(2).unwrap_or_else(|err| panic!(format!("McFly error: cmd_tpl to be readable ({})", err))),
-                    session_id: row.get_checked(3).unwrap_or_else(|err| panic!(format!("McFly error: session_id to be readable ({})", err))),
-                    when_run: row.get_checked(4).unwrap_or_else(|err| panic!(format!("McFly error: when_run to be readable ({})", err))),
-                    exit_code: row.get_checked(5).unwrap_or_else(|err| panic!(format!("McFly error: exit_code to be readable ({})", err))),
-                    selected: row.get_checked(6).unwrap_or_else(|err| panic!(format!("McFly error: selected to be readable ({})", err))),
-                    dir: row.get_checked(7).unwrap_or_else(|err| panic!(format!("McFly error: dir to be readable ({})", err))),
-                    rank: row.get_checked(8).unwrap_or_else(|err| panic!(format!("McFly error: rank to be readable ({})", err))),
-                    features: Features {
-                        age_factor: row.get_checked(9).unwrap_or_else(|err| panic!(format!("McFly error: age_factor to be readable ({})", err))),
-                        length_factor: row.get_checked(10).unwrap_or_else(|err| panic!(format!("McFly error: length_factor to be readable ({})", err))),
-                        exit_factor: row.get_checked(11).unwrap_or_else(|err| panic!(format!("McFly error: exit_factor to be readable ({})", err))),
-                        recent_failure_factor: row.get_checked(12)
-                            .unwrap_or_else(|err| panic!(format!("McFly error: recent_failure_factor to be readable ({})", err))),
-                        selected_dir_factor: row.get_checked(13)
-                            .unwrap_or_else(|err| panic!(format!("McFly error: selected_dir_factor to be readable ({})", err))),
-                        dir_factor: row.get_checked(14).unwrap_or_else(|err| panic!(format!("McFly error: dir_factor to be readable ({})", err))),
-                        overlap_factor: row.get_checked(15).unwrap_or_else(|err| panic!(format!("McFly error: overlap_factor to be readable ({})", err))),
-                        immediate_overlap_factor: row.get_checked(16)
-                            .unwrap_or_else(|err| panic!(format!("McFly error: immediate_overlap_factor to be readable ({})", err))),
-                        selected_occurrences_factor: row.get_checked(17)
-                            .unwrap_or_else(|err| panic!(format!("McFly error: selected_occurrences_factor to be readable ({})", err))),
-                        occurrences_factor: row.get_checked(18)
-                            .unwrap_or_else(|err| panic!(format!("McFly error: occurrences_factor to be readable ({})", err))),
-                    },
+            .query_map_named(&[(":like", &like_query), (":limit", &num)], |row| Command {
+                id: row.get_checked(0).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: id to be readable ({})", err))
+                }),
+                cmd: row.get_checked(1).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: cmd to be readable ({})", err))
+                }),
+                cmd_tpl: row.get_checked(2).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: cmd_tpl to be readable ({})", err))
+                }),
+                session_id: row.get_checked(3).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: session_id to be readable ({})", err))
+                }),
+                when_run: row.get_checked(4).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: when_run to be readable ({})", err))
+                }),
+                exit_code: row.get_checked(5).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: exit_code to be readable ({})", err))
+                }),
+                selected: row.get_checked(6).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: selected to be readable ({})", err))
+                }),
+                dir: row.get_checked(7).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: dir to be readable ({})", err))
+                }),
+                rank: row.get_checked(8).unwrap_or_else(|err| {
+                    panic!(format!("McFly error: rank to be readable ({})", err))
+                }),
+                features: Features {
+                    age_factor: row.get_checked(9).unwrap_or_else(|err| {
+                        panic!(format!("McFly error: age_factor to be readable ({})", err))
+                    }),
+                    length_factor: row.get_checked(10).unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: length_factor to be readable ({})",
+                            err
+                        ))
+                    }),
+                    exit_factor: row.get_checked(11).unwrap_or_else(|err| {
+                        panic!(format!("McFly error: exit_factor to be readable ({})", err))
+                    }),
+                    recent_failure_factor: row.get_checked(12).unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: recent_failure_factor to be readable ({})",
+                            err
+                        ))
+                    }),
+                    selected_dir_factor: row.get_checked(13).unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: selected_dir_factor to be readable ({})",
+                            err
+                        ))
+                    }),
+                    dir_factor: row.get_checked(14).unwrap_or_else(|err| {
+                        panic!(format!("McFly error: dir_factor to be readable ({})", err))
+                    }),
+                    overlap_factor: row.get_checked(15).unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: overlap_factor to be readable ({})",
+                            err
+                        ))
+                    }),
+                    immediate_overlap_factor: row.get_checked(16).unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: immediate_overlap_factor to be readable ({})",
+                            err
+                        ))
+                    }),
+                    selected_occurrences_factor: row.get_checked(17).unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: selected_occurrences_factor to be readable ({})",
+                            err
+                        ))
+                    }),
+                    occurrences_factor: row.get_checked(18).unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: occurrences_factor to be readable ({})",
+                            err
+                        ))
+                    }),
                 },
-            )
+            })
             .unwrap_or_else(|err| panic!(format!("McFly error: Query Map to work ({})", err)));
 
         let mut names = Vec::new();
         for command in command_iter {
-            names.push(command.unwrap_or_else(|err| panic!(format!("McFly error: Unable to load command from DB ({})", err))));
+            names.push(command.unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Unable to load command from DB ({})",
+                    err
+                ))
+            }));
         }
 
         names
@@ -287,9 +364,15 @@ impl History {
 
         self.connection
             .execute("DROP TABLE IF EXISTS temp.contextual_commands;", NO_PARAMS)
-            .unwrap_or_else(|err| panic!(format!("McFly error: Removal of temp table to work ({})", err)));
-
-        let (mut when_run_min, when_run_max): (f64, f64) = self.connection
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Removal of temp table to work ({})",
+                    err
+                ))
+            });
+
+        let (mut when_run_min, when_run_max): (f64, f64) = self
+            .connection
             .query_row(
                 "SELECT MIN(when_run), MAX(when_run) FROM commands",
                 NO_PARAMS,
@@ -301,7 +384,8 @@ impl History {
             when_run_min -= 60.0 * 60.0;
         }
 
-        let max_occurrences: f64 = self.connection
+        let max_occurrences: f64 = self
+            .connection
             .query_row(
                 "SELECT COUNT(*) AS c FROM commands GROUP BY cmd ORDER BY c DESC LIMIT 1",
                 NO_PARAMS,
@@ -313,7 +397,8 @@ impl History {
             .query_row("SELECT COUNT(*) AS c FROM commands WHERE selected = 1 GROUP BY cmd ORDER BY c DESC LIMIT 1", NO_PARAMS,
                        |row| row.get(0)).unwrap_or(1.0);
 
-        let max_length: f64 = self.connection
+        let max_length: f64 = self
+            .connection
             .query_row("SELECT MAX(LENGTH(cmd)) FROM commands", NO_PARAMS, |row| {
                 row.get(0)
             })
@@ -388,19 +473,35 @@ impl History {
                                     selected_occurrences_factor, occurrences_factor);",
                 NO_PARAMS,
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: Ranking of temp table to work ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Ranking of temp table to work ({})",
+                    err
+                ))
+            });
 
         self.connection
             .execute(
                 "CREATE INDEX temp.MyIndex ON contextual_commands(id);",
                 NO_PARAMS,
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: Creation of index on temp table to work ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Creation of index on temp table to work ({})",
+                    err
+                ))
+            });
 
         // println!("Seconds: {}", (beginning_of_execution.elapsed().as_secs() as f64) + (beginning_of_execution.elapsed().subsec_nanos() as f64 / 1000_000_000.0));
     }
 
-    pub fn commands(&self, session_id: &Option<String>, num: i16, offset: u16, random: bool) -> Vec<Command> {
+    pub fn commands(
+        &self,
+        session_id: &Option<String>,
+        num: i16,
+        offset: u16,
+        random: bool,
+    ) -> Vec<Command> {
         let order = if random { "RANDOM()" } else { "id" };
         let query = if session_id.is_none() {
             format!("SELECT id, cmd, cmd_tpl, session_id, when_run, exit_code, selected, dir FROM commands ORDER BY {} DESC LIMIT :limit OFFSET :offset", order)
@@ -409,16 +510,16 @@ impl History {
         };
 
         if session_id.is_none() {
-            self.run_query(&query, &[
-                (":limit", &num),
-                (":offset", &offset),
-            ])
+            self.run_query(&query, &[(":limit", &num), (":offset", &offset)])
         } else {
-            self.run_query(&query, &[
-                (":session_id", &session_id.to_owned().unwrap()),
-                (":limit", &num),
-                (":offset", &offset),
-            ])
+            self.run_query(
+                &query,
+                &[
+                    (":session_id", &session_id.to_owned().unwrap()),
+                    (":limit", &num),
+                    (":offset", &offset),
+                ],
+            )
         }
     }
 
@@ -437,9 +538,9 @@ impl History {
             ..Command::default()
         };
 
-        let command_iter: MappedRows<_> = statement.
-            query_map_named(params, closure).
-            unwrap_or_else(|err| panic!(format!("McFly error: Query Map to work ({})", err)));
+        let command_iter: MappedRows<_> = statement
+            .query_map_named(params, closure)
+            .unwrap_or_else(|err| panic!(format!("McFly error: Query Map to work ({})", err)));
 
         let mut vec = Vec::new();
         for result in command_iter {
@@ -452,9 +553,7 @@ impl History {
     }
 
     pub fn last_command(&self, session_id: &Option<String>) -> Option<Command> {
-        self.commands(session_id, 1, 0, false)
-            .get(0)
-            .cloned()
+        self.commands(session_id, 1, 0, false).get(0).cloned()
     }
 
     pub fn last_command_templates(
@@ -475,14 +574,24 @@ impl History {
                 "DELETE FROM selected_commands WHERE cmd = :command",
                 &[(":command", &command)],
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: DELETE from selected_commands to work ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: DELETE from selected_commands to work ({})",
+                    err
+                ))
+            });
 
         self.connection
             .execute_named(
                 "DELETE FROM commands WHERE cmd = :command",
                 &[(":command", &command)],
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: DELETE from commands to work ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: DELETE from commands to work ({})",
+                    err
+                ))
+            });
     }
 
     pub fn update_paths(&self, old_path: &str, new_path: &str, print_output: bool) {
@@ -500,22 +609,31 @@ impl History {
                 "UPDATE commands SET old_dir = :new_dir || SUBSTR(old_dir, :length) WHERE old_dir = :exact OR old_dir LIKE (:like)"
             ).unwrap();
 
-            let affected = dir_update_statement.execute_named(&[
-                (":like", &like_query),
-                (":exact", &normalized_old_path),
-                (":new_dir", &normalized_new_path),
-                (":length", &(normalized_old_path.chars().count() as u32 + 1)),
-            ]).unwrap_or_else(|err| panic!(format!("McFly error: dir UPDATE to work ({})", err)));
-
-            old_dir_update_statement.execute_named(&[
-                (":like", &like_query),
-                (":exact", &normalized_old_path),
-                (":new_dir", &normalized_new_path),
-                (":length", &(normalized_old_path.chars().count() as u32 + 1)),
-            ]).unwrap_or_else(|err| panic!(format!("McFly error: old_dir UPDATE to work ({})", err)));
+            let affected = dir_update_statement
+                .execute_named(&[
+                    (":like", &like_query),
+                    (":exact", &normalized_old_path),
+                    (":new_dir", &normalized_new_path),
+                    (":length", &(normalized_old_path.chars().count() as u32 + 1)),
+                ])
+                .unwrap_or_else(|err| panic!(format!("McFly error: dir UPDATE to work ({})", err)));
+
+            old_dir_update_statement
+                .execute_named(&[
+                    (":like", &like_query),
+                    (":exact", &normalized_old_path),
+                    (":new_dir", &normalized_new_path),
+                    (":length", &(normalized_old_path.chars().count() as u32 + 1)),
+                ])
+                .unwrap_or_else(|err| {
+                    panic!(format!("McFly error: old_dir UPDATE to work ({})", err))
+                });
 
             if print_output {
-                println!("McFly: Command database paths renamed from {} to {} (affected {} commands)", normalized_old_path, normalized_new_path, affected);
+                println!(
+                    "McFly: Command database paths renamed from {} to {} (affected {} commands)",
+                    normalized_old_path, normalized_new_path, affected
+                );
             }
         } else if print_output {
             println!("McFly: Not updating paths due to invalid options.");
@@ -524,24 +642,26 @@ impl History {
 
     fn from_bash_history() -> History {
         print!(
-            "McFly: Importing Bash history for the first time. This may take a minute or two..."
+            "McFly: Importing shell history for the first time. This may take a minute or two..."
         );
-        io::stdout().flush().unwrap_or_else(|err| panic!(format!("McFly error: STDOUT flush should work ({})", err)));
+        io::stdout().flush().unwrap_or_else(|err| {
+            panic!(format!("McFly error: STDOUT flush should work ({})", err))
+        });
 
         // Load this first to make sure it works before we create the DB.
-        let bash_history = bash_history::full_history(&bash_history::bash_history_file_path());
+        let bash_history = shell_history::full_history(&shell_history::history_file_path());
 
         // Make ~/.mcfly
         fs::create_dir_all(Settings::storage_dir_path())
             .unwrap_or_else(|_| panic!("Unable to create {:?}", Settings::storage_dir_path()));
 
         // Make ~/.mcfly/history.db
-        let connection = Connection::open(Settings::mcfly_db_path()).unwrap_or_else(|_|
+        let connection = Connection::open(Settings::mcfly_db_path()).unwrap_or_else(|_| {
             panic!(
                 "Unable to create history DB at {:?}",
                 Settings::mcfly_db_path()
             )
-        );
+        });
         db_extensions::add_db_functions(&connection);
 
         connection.execute_batch(
@@ -590,7 +710,9 @@ impl History {
                                 (":exit_code", &0),
                                 (":selected", &0),
                             ])
-                            .unwrap_or_else(|err| panic!(format!("McFly error: Insert to work ({})", err)));
+                            .unwrap_or_else(|err| {
+                                panic!(format!("McFly error: Insert to work ({})", err))
+                            });
                     }
                 }
             }
@@ -607,7 +729,12 @@ impl History {
     }
 
     fn from_db_path(path: PathBuf) -> History {
-        let connection = Connection::open(path).unwrap_or_else(|err| panic!(format!("McFly error: Unable to open history database ({})", err)));
+        let connection = Connection::open(path).unwrap_or_else(|err| {
+            panic!(format!(
+                "McFly error: Unable to open history database ({})",
+                err
+            ))
+        });
         db_extensions::add_db_functions(&connection);
         History {
             connection,
diff --git a/src/history/schema.rs b/src/history/schema.rs
index 3d2d6b20..b6fa2a9f 100644
--- a/src/history/schema.rs
+++ b/src/history/schema.rs
@@ -27,7 +27,9 @@ pub fn migrate(connection: &Connection) {
             "McFly: Upgrading McFly DB to version {}, please wait...",
             CURRENT_SCHEMA_VERSION
         );
-        io::stdout().flush().unwrap_or_else(|err| panic!(format!("McFly error: STDOUT flush should work ({})", err)));
+        io::stdout().flush().unwrap_or_else(|err| {
+            panic!(format!("McFly error: STDOUT flush should work ({})", err))
+        });
     }
 
     if current_version < 1 {
@@ -35,11 +37,18 @@ pub fn migrate(connection: &Connection) {
             .execute_batch(
                 "ALTER TABLE commands ADD COLUMN cmd_tpl TEXT; UPDATE commands SET cmd_tpl = '';",
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: Unable to add cmd_tpl to commands ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Unable to add cmd_tpl to commands ({})",
+                    err
+                ))
+            });
 
         let mut statement = connection
             .prepare("UPDATE commands SET cmd_tpl = :cmd_tpl WHERE id = :id")
-            .unwrap_or_else(|err| panic!(format!("McFly error: Unable to prepare update ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!("McFly error: Unable to prepare update ({})", err))
+            });
 
         for (id, cmd) in cmd_strings(connection) {
             let simplified_command = SimplifiedCommand::new(cmd.as_str(), true);
@@ -56,12 +65,18 @@ pub fn migrate(connection: &Connection) {
                  UPDATE commands SET session_id = 'UNKNOWN'; \
                  CREATE INDEX command_session_id ON commands (session_id);",
             )
-            .unwrap_or_else(|err| panic!(format!("McFly error: Unable to add session_id to commands ({})", err)));
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Unable to add session_id to commands ({})",
+                    err
+                ))
+            });
     }
 
     if current_version < 3 {
-        connection.execute_batch(
-            "CREATE TABLE selected_commands( \
+        connection
+            .execute_batch(
+                "CREATE TABLE selected_commands( \
               id INTEGER PRIMARY KEY AUTOINCREMENT, \
               cmd TEXT NOT NULL, \
               session_id TEXT NOT NULL, \
@@ -70,8 +85,14 @@ pub fn migrate(connection: &Connection) {
             CREATE INDEX selected_command_session_cmds ON selected_commands (session_id, cmd); \
             \
             ALTER TABLE commands ADD COLUMN selected INTEGER; \
-            UPDATE commands SET selected = 0;")
-            .unwrap_or_else(|err| panic!(format!("McFly error: Unable to add selected_commands ({})", err)));
+            UPDATE commands SET selected = 0;",
+            )
+            .unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Unable to add selected_commands ({})",
+                    err
+                ))
+            });
     }
 
     if current_version < CURRENT_SCHEMA_VERSION {
@@ -90,7 +111,12 @@ fn make_schema_versions_table(connection: &Connection) {
 
              CREATE UNIQUE INDEX IF NOT EXISTS schema_versions_index ON schema_versions (version);",
         )
-        .unwrap_or_else(|err| panic!(format!("McFly error: Unable to create schema_versions db table ({})", err)));
+        .unwrap_or_else(|err| {
+            panic!(format!(
+                "McFly error: Unable to create schema_versions db table ({})",
+                err
+            ))
+        });
 }
 
 fn write_current_schema_version(connection: &Connection) {
@@ -98,9 +124,12 @@ fn write_current_schema_version(connection: &Connection) {
         "INSERT INTO schema_versions (version, when_run) VALUES ({}, strftime('%s','now'))",
         CURRENT_SCHEMA_VERSION
     );
-    connection
-        .execute_batch(&insert)
-        .unwrap_or_else(|err| panic!(format!("McFly error: Unable to update schema_versions ({})", err)));
+    connection.execute_batch(&insert).unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Unable to update schema_versions ({})",
+            err
+        ))
+    });
 }
 
 fn cmd_strings(connection: &Connection) -> Vec<(i64, String)> {
diff --git a/src/history_cleaner.rs b/src/history_cleaner.rs
index ffa72e0b..84315ab3 100644
--- a/src/history_cleaner.rs
+++ b/src/history_cleaner.rs
@@ -1,6 +1,6 @@
-use crate::bash_history;
 use crate::history::History;
 use crate::settings::Settings;
+use crate::shell_history;
 use std::env;
 use std::fs;
 use std::path::PathBuf;
@@ -14,16 +14,24 @@ pub fn clean(settings: &Settings, history: &History, command: &str) {
     clean_temporary_files(&settings.mcfly_history, command);
 
     // Clean up HISTFILE.
-    let histfile =
-        PathBuf::from(env::var("HISTFILE").unwrap_or_else(|err| panic!(format!("McFly error: Please ensure that HISTFILE is set ({})", err))));
-    bash_history::delete_lines(&histfile, command);
+    let histfile = PathBuf::from(env::var("HISTFILE").unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Please ensure that HISTFILE is set ({})",
+            err
+        ))
+    }));
+    shell_history::delete_lines(&histfile, command);
 }
 
 fn clean_temporary_files(mcfly_history: &PathBuf, command: &str) {
     let path = mcfly_history.as_path();
     if let Some(directory) = path.parent() {
-        let expanded_path =
-            fs::canonicalize(directory).unwrap_or_else(|err| panic!(format!("McFly error: The contents of $MCFLY_HISTORY appear invalid ({})", err)));
+        let expanded_path = fs::canonicalize(directory).unwrap_or_else(|err| {
+            panic!(format!(
+                "McFly error: The contents of $MCFLY_HISTORY appear invalid ({})",
+                err
+            ))
+        });
         let paths = fs::read_dir(&expanded_path).unwrap();
 
         for path in paths {
@@ -31,7 +39,7 @@ fn clean_temporary_files(mcfly_history: &PathBuf, command: &str) {
                 if let Some(file_name) = entry.path().file_name() {
                     if let Some(valid_unicode_str) = file_name.to_str() {
                         if valid_unicode_str.starts_with("mcfly.") {
-                            bash_history::delete_lines(&entry.path(), command);
+                            shell_history::delete_lines(&entry.path(), command);
                         }
                     }
                 }
diff --git a/src/interface.rs b/src/interface.rs
index ab891072..aded6f06 100644
--- a/src/interface.rs
+++ b/src/interface.rs
@@ -4,8 +4,8 @@ use crate::history::History;
 use crate::fixed_length_grapheme_string::FixedLengthGraphemeString;
 use crate::history::Command;
 use crate::history_cleaner;
-use crate::settings::Settings;
 use crate::settings::KeyScheme;
+use crate::settings::Settings;
 use std::io::{stdin, stdout, Write};
 use termion::color;
 use termion::event::Key;
@@ -23,7 +23,7 @@ pub struct Interface<'a> {
     debug: bool,
     run: bool,
     menu_mode: MenuMode,
-    in_vim_insert_mode: bool
+    in_vim_insert_mode: bool,
 }
 
 pub struct SelectionResult {
@@ -47,10 +47,12 @@ impl MenuMode {
         match *self {
             MenuMode::Normal => match interface.settings.key_scheme {
                 KeyScheme::Emacs => "McFly | ESC - Exit | ⏎ - Run | TAB - Edit | F2 - Delete",
-                KeyScheme::Vim => if interface.in_vim_insert_mode {
-                    "McFly (Vim) | ESC - Exit | ⏎ - Run | TAB - Edit | F2 - Delete        -- INSERT --"
-                } else {
-                    "McFly (Vim) | ESC - Exit | ⏎ - Run | TAB - Edit | F2 - Delete"
+                KeyScheme::Vim => {
+                    if interface.in_vim_insert_mode {
+                        "McFly (Vim) | ESC - Exit | ⏎ - Run | TAB - Edit | F2 - Delete        -- INSERT --"
+                    } else {
+                        "McFly (Vim) | ESC - Exit | ⏎ - Run | TAB - Edit | F2 - Delete"
+                    }
                 }
             },
             MenuMode::ConfirmDelete => "Delete selected command from the history? (Y/N)",
@@ -80,7 +82,7 @@ impl<'a> Interface<'a> {
             debug: settings.debug,
             run: false,
             menu_mode: MenuMode::Normal,
-            in_vim_insert_mode: false
+            in_vim_insert_mode: false,
         }
     }
 
@@ -131,7 +133,8 @@ impl<'a> Interface<'a> {
             text = self.menu_mode.text(self),
             reset_bg = color::Bg(color::Reset).to_string(),
             width = width as usize
-        ).unwrap();
+        )
+        .unwrap();
         screen.flush().unwrap();
     }
 
@@ -147,13 +150,15 @@ impl<'a> Interface<'a> {
             cursor::Goto(1, PROMPT_LINE_INDEX),
             clear::CurrentLine,
             self.input
-        ).unwrap();
+        )
+        .unwrap();
         write!(
             screen,
             "{}{}",
             cursor::Goto(self.input.cursor as u16 + 3, PROMPT_LINE_INDEX),
             cursor::Show
-        ).unwrap();
+        )
+        .unwrap();
         screen.flush().unwrap();
     }
 
@@ -163,7 +168,8 @@ impl<'a> Interface<'a> {
             "{}{}",
             cursor::Hide,
             cursor::Goto(0, RESULTS_TOP_INDEX + self.settings.results + 1)
-        ).unwrap();
+        )
+        .unwrap();
         screen.flush().unwrap();
     }
 
@@ -174,7 +180,8 @@ impl<'a> Interface<'a> {
             cursor::Hide,
             cursor::Goto(1, RESULTS_TOP_INDEX),
             clear::All
-        ).unwrap();
+        )
+        .unwrap();
         let (width, _height): (u16, u16) = terminal_size().unwrap();
 
         if !self.matches.is_empty() && self.selection > self.matches.len() - 1 {
@@ -222,7 +229,8 @@ impl<'a> Interface<'a> {
                     fg,
                     self.debug
                 )
-            ).unwrap();
+            )
+            .unwrap();
 
             write!(screen, "{}", color::Bg(color::Reset)).unwrap();
             write!(screen, "{}", color::Fg(color::Reset)).unwrap();
@@ -238,7 +246,8 @@ impl<'a> Interface<'a> {
             cursor::Goto(1, 2),
             clear::CurrentLine,
             s.into()
-        ).unwrap();
+        )
+        .unwrap();
         screen.flush().unwrap();
     }
 
@@ -263,7 +272,9 @@ impl<'a> Interface<'a> {
 
     fn confirm(&mut self, confirmation: bool) {
         if confirmation {
-            if let MenuMode::ConfirmDelete = self.menu_mode { self.delete_selection() }
+            if let MenuMode::ConfirmDelete = self.menu_mode {
+                self.delete_selection()
+            }
         }
         self.menu_mode = MenuMode::Normal;
     }
@@ -281,7 +292,8 @@ impl<'a> Interface<'a> {
 
     fn refresh_matches(&mut self) {
         self.selection = 0;
-        self.matches = self.history
+        self.matches = self
+            .history
             .find_matches(&self.input.command, self.settings.results as i16);
     }
 
@@ -321,7 +333,7 @@ impl<'a> Interface<'a> {
             } else {
                 let early_out = match self.settings.key_scheme {
                     KeyScheme::Emacs => self.select_with_emacs_key_scheme(c.unwrap()),
-                    KeyScheme::Vim => self.select_with_vim_key_scheme(c.unwrap())
+                    KeyScheme::Vim => self.select_with_vim_key_scheme(c.unwrap()),
                 };
 
                 if early_out {
@@ -426,10 +438,7 @@ impl<'a> Interface<'a> {
                     self.accept_selection();
                     return true;
                 }
-                Key::Ctrl('c')
-                | Key::Ctrl('g')
-                | Key::Ctrl('z')
-                | Key::Ctrl('r') => {
+                Key::Ctrl('c') | Key::Ctrl('g') | Key::Ctrl('z') | Key::Ctrl('r') => {
                     self.run = false;
                     self.input.clear();
                     return true;
@@ -437,7 +446,9 @@ impl<'a> Interface<'a> {
                 Key::Left => self.input.move_cursor(Move::Backward),
                 Key::Right => self.input.move_cursor(Move::Forward),
                 Key::Up | Key::PageUp | Key::Ctrl('u') => self.move_selection(MoveSelection::Up),
-                Key::Down | Key::PageDown | Key::Ctrl('d') => self.move_selection(MoveSelection::Down),
+                Key::Down | Key::PageDown | Key::Ctrl('d') => {
+                    self.move_selection(MoveSelection::Down)
+                }
                 Key::Esc => self.in_vim_insert_mode = false,
                 Key::Backspace => {
                     self.input.delete(Move::Backward);
@@ -562,13 +573,28 @@ impl<'a> Interface<'a> {
             out.push_grapheme_str(format!("age: {:.*} ", 2, command.features.age_factor));
             out.push_grapheme_str(format!("lng: {:.*} ", 2, command.features.length_factor));
             out.push_grapheme_str(format!("ext: {:.*} ", 0, command.features.exit_factor));
-            out.push_grapheme_str(format!("r_ext: {:.*} ", 0, command.features.recent_failure_factor));
+            out.push_grapheme_str(format!(
+                "r_ext: {:.*} ",
+                0, command.features.recent_failure_factor
+            ));
             out.push_grapheme_str(format!("dir: {:.*} ", 3, command.features.dir_factor));
-            out.push_grapheme_str(format!("s_dir: {:.*} ", 3, command.features.selected_dir_factor));
+            out.push_grapheme_str(format!(
+                "s_dir: {:.*} ",
+                3, command.features.selected_dir_factor
+            ));
             out.push_grapheme_str(format!("ovlp: {:.*} ", 3, command.features.overlap_factor));
-            out.push_grapheme_str(format!("i_ovlp: {:.*} ", 3, command.features.immediate_overlap_factor));
-            out.push_grapheme_str(format!("occ: {:.*}", 2, command.features.occurrences_factor));
-            out.push_grapheme_str(format!("s_occ: {:.*} ", 2, command.features.selected_occurrences_factor));
+            out.push_grapheme_str(format!(
+                "i_ovlp: {:.*} ",
+                3, command.features.immediate_overlap_factor
+            ));
+            out.push_grapheme_str(format!(
+                "occ: {:.*}",
+                2, command.features.occurrences_factor
+            ));
+            out.push_grapheme_str(format!(
+                "s_occ: {:.*} ",
+                2, command.features.selected_occurrences_factor
+            ));
             out.push_str(&base_color);
         }
 
diff --git a/src/lib.rs b/src/lib.rs
index aa914200..2dc3a376 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,15 +1,15 @@
-pub mod bash_history;
 pub mod command_input;
 pub mod fake_typer;
 pub mod fixed_length_grapheme_string;
 pub mod history;
 pub mod history_cleaner;
 pub mod interface;
+pub mod network;
+pub mod node;
 pub mod path_update_helpers;
 pub mod settings;
+pub mod shell_history;
 pub mod simplified_command;
 pub mod trainer;
 pub mod training_cache;
 pub mod training_sample_generator;
-pub mod node;
-pub mod network;
diff --git a/src/main.rs b/src/main.rs
index 0b3d770f..59aa3be2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,12 @@
-use mcfly::bash_history;
 use mcfly::fake_typer;
 use mcfly::history::History;
 use mcfly::interface::Interface;
 use mcfly::settings::Mode;
 use mcfly::settings::Settings;
+use mcfly::shell_history;
 use mcfly::trainer::Trainer;
 use std::path::PathBuf;
-use std::env;
+use std::{env, fs};
 
 fn handle_addition(settings: &Settings, history: &mut History) {
     if history.should_add(&settings.command) {
@@ -20,9 +20,19 @@ fn handle_addition(settings: &Settings, history: &mut History) {
         );
 
         if settings.append_to_histfile {
-            let histfile = PathBuf::from(env::var("HISTFILE")
-                .unwrap_or_else(|err| panic!(format!("McFly error: Please ensure that HISTFILE is set ({})", err))));
-            bash_history::append_history_entry(&settings.command, &histfile)
+            let histfile = PathBuf::from(env::var("HISTFILE").unwrap_or_else(|err| {
+                panic!(format!(
+                    "McFly error: Please ensure that HISTFILE is set ({})",
+                    err
+                ))
+            }));
+            shell_history::append_history_entry(
+                &settings.command,
+                settings.when_run,
+                &histfile,
+                settings.zsh_extended_history,
+                settings.debug,
+            )
         }
     }
 }
@@ -30,10 +40,27 @@ fn handle_addition(settings: &Settings, history: &mut History) {
 fn handle_search(settings: &Settings, history: &History) {
     let result = Interface::new(settings, history).display();
     if let Some(cmd) = result.selection {
-        fake_typer::use_tiocsti(&cmd);
+        if let Some(path) = &settings.output_selection {
+            // Output selection to a file, with the first line indicating if the user chose to run the selection or not.
+            let mut out: String = String::new();
 
-        if result.run {
-            fake_typer::use_tiocsti(&"\n".to_string());
+            if result.run {
+                out.push_str("run\n");
+            } else {
+                out.push_str("display\n");
+            }
+
+            out.push_str(&cmd);
+
+            fs::write(path, &out).unwrap_or_else(|err| {
+                panic!(format!("McFly error: unable to write to {}: {}", path, err))
+            });
+        } else {
+            fake_typer::use_tiocsti(&cmd);
+
+            if result.run {
+                fake_typer::use_tiocsti(&"\n".to_string());
+            }
         }
     }
 }
diff --git a/src/network.rs b/src/network.rs
index e13cb000..56c00f84 100644
--- a/src/network.rs
+++ b/src/network.rs
@@ -1,7 +1,7 @@
 #![allow(clippy::unreadable_literal)]
+use crate::history::Features;
 use crate::node::Node;
 use crate::training_sample_generator::TrainingSampleGenerator;
-use crate::history::Features;
 use rand::Rng;
 
 #[derive(Debug, Copy, Clone)]
@@ -19,11 +19,7 @@ impl Default for Network {
     fn default() -> Network {
         Network {
             final_bias: -0.3829333755179377,
-            final_weights: [
-                0.44656858145177714,
-                -1.9550439349609872,
-                -2.963322601316632
-            ],
+            final_weights: [0.44656858145177714, -1.9550439349609872, -2.963322601316632],
             final_sum: 0.0,
             final_output: 0.0,
             hidden_nodes: [
@@ -38,7 +34,7 @@ impl Default for Network {
                     overlap: 0.4791784213482642,
                     immediate_overlap: 0.5565797758340211,
                     selected_occurrences: -0.3600203296209723,
-                    occurrences: 0.15694312742881805
+                    occurrences: 0.15694312742881805,
                 },
                 Node {
                     offset: -0.04362945902379799,
@@ -51,7 +47,7 @@ impl Default for Network {
                     overlap: -0.562274365705918,
                     immediate_overlap: -0.47252489212451904,
                     selected_occurrences: 0.2446391951417497,
-                    occurrences: -1.4846489581676605
+                    occurrences: -1.4846489581676605,
                 },
                 Node {
                     offset: -0.11992725490486622,
@@ -64,19 +60,11 @@ impl Default for Network {
                     overlap: 0.09372412920642742,
                     immediate_overlap: 0.393989158881144,
                     selected_occurrences: -0.2383372126951215,
-                    occurrences: -2.196219880265691
-                }
-            ],
-            hidden_node_sums: [
-                0.0,
-                0.0,
-                0.0
+                    occurrences: -2.196219880265691,
+                },
             ],
-            hidden_node_outputs: [
-                0.0,
-                0.0,
-                0.0
-            ]
+            hidden_node_sums: [0.0, 0.0, 0.0],
+            hidden_node_outputs: [0.0, 0.0, 0.0],
         }
     }
 }
@@ -87,7 +75,11 @@ impl Network {
 
         Network {
             final_bias: rng.gen_range(-1.0, 1.0),
-            final_weights: [rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0)],
+            final_weights: [
+                rng.gen_range(-1.0, 1.0),
+                rng.gen_range(-1.0, 1.0),
+                rng.gen_range(-1.0, 1.0),
+            ],
             hidden_nodes: [Node::random(), Node::random(), Node::random()],
             hidden_node_sums: [0.0, 0.0, 0.0],
             hidden_node_outputs: [0.0, 0.0, 0.0],
diff --git a/src/path_update_helpers.rs b/src/path_update_helpers.rs
index dcc3f870..234d9c44 100644
--- a/src/path_update_helpers.rs
+++ b/src/path_update_helpers.rs
@@ -1,23 +1,39 @@
 use relative_path::RelativePath;
-use shellexpand;
 use std::env;
 use std::path::Path;
 use unicode_segmentation::UnicodeSegmentation;
 
 pub fn normalize_path(incoming_path: &str) -> String {
-    let expanded_path = shellexpand::full(incoming_path).unwrap_or_else(|err| panic!(format!("McFly error: Unable to expand command path ({})", err)));
-
-    let current_dir = env::var("PWD").unwrap_or_else(|err| panic!(format!("McFly error: Unable to determine current directory ({})", err)));
+    let expanded_path = shellexpand::full(incoming_path).unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Unable to expand command path ({})",
+            err
+        ))
+    });
+
+    let current_dir = env::var("PWD").unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Unable to determine current directory ({})",
+            err
+        ))
+    });
     let current_dir_path = Path::new(&current_dir);
 
     let path_buf = if expanded_path.starts_with('/') {
-        RelativePath::new(&expanded_path.into_owned()).normalize().to_path("/")
+        RelativePath::new(&expanded_path.into_owned())
+            .normalize()
+            .to_path("/")
     } else {
         let to_current_dir = RelativePath::new(&expanded_path).to_path(current_dir_path);
-        RelativePath::new(to_current_dir.to_str().unwrap()).normalize().to_path("/")
+        RelativePath::new(to_current_dir.to_str().unwrap())
+            .normalize()
+            .to_path("/")
     };
 
-    path_buf.to_str().unwrap_or_else(|| panic!("McFly error: Path must be a valid UTF8 string")).to_string()
+    path_buf
+        .to_str()
+        .unwrap_or_else(|| panic!("McFly error: Path must be a valid UTF8 string"))
+        .to_string()
 }
 
 pub fn parse_mv_command(command: &str) -> Vec<String> {
@@ -94,8 +110,8 @@ pub fn parse_mv_command(command: &str) -> Vec<String> {
 
 #[cfg(test)]
 mod tests {
-    use std::env;
     use super::{normalize_path, parse_mv_command};
+    use std::env;
     use std::path::PathBuf;
 
     #[test]
@@ -107,8 +123,16 @@ mod tests {
 
     #[test]
     fn normalize_path_works_with_tilda() {
-        assert_eq!(normalize_path("~/"), String::from(env::var("HOME").unwrap()));
-        assert_eq!(normalize_path("~/foo"), PathBuf::from(env::var("HOME").unwrap()).join("foo").to_string_lossy());
+        assert_eq!(
+            normalize_path("~/"),
+            String::from(env::var("HOME").unwrap())
+        );
+        assert_eq!(
+            normalize_path("~/foo"),
+            PathBuf::from(env::var("HOME").unwrap())
+                .join("foo")
+                .to_string_lossy()
+        );
     }
 
     #[test]
@@ -119,7 +143,9 @@ mod tests {
         assert_eq!(normalize_path("/foo/bar/../.."), String::from("/"));
         assert_eq!(
             normalize_path("~/foo/bar/../baz"),
-            PathBuf::from(env::var("HOME").unwrap()).join("foo/baz").to_string_lossy()
+            PathBuf::from(env::var("HOME").unwrap())
+                .join("foo/baz")
+                .to_string_lossy()
         );
         assert_eq!(
             normalize_path("~/foo/bar/../.."),
@@ -129,26 +155,53 @@ mod tests {
 
     #[test]
     fn parse_mv_command_works_in_the_basic_case() {
-        assert_eq!(parse_mv_command("mv foo bar"), vec!["foo".to_string(), "bar".to_string()]);
+        assert_eq!(
+            parse_mv_command("mv foo bar"),
+            vec!["foo".to_string(), "bar".to_string()]
+        );
     }
 
     #[test]
     fn parse_mv_command_works_with_options() {
-        assert_eq!(parse_mv_command("mv -v foo bar"), vec!["foo".to_string(), "bar".to_string()]);
+        assert_eq!(
+            parse_mv_command("mv -v foo bar"),
+            vec!["foo".to_string(), "bar".to_string()]
+        );
     }
 
     #[test]
     fn parse_mv_command_works_with_escaped_strings() {
-        assert_eq!(parse_mv_command("mv \"foo baz\" 'bar bing'"), vec!["foo baz".to_string(), "bar bing".to_string()]);
-        assert_eq!(parse_mv_command("mv -v \"foo\" 'bar'"), vec!["foo".to_string(), "bar".to_string()]);
+        assert_eq!(
+            parse_mv_command("mv \"foo baz\" 'bar bing'"),
+            vec!["foo baz".to_string(), "bar bing".to_string()]
+        );
+        assert_eq!(
+            parse_mv_command("mv -v \"foo\" 'bar'"),
+            vec!["foo".to_string(), "bar".to_string()]
+        );
     }
 
     #[test]
     fn parse_mv_command_works_with_escaping() {
-        assert_eq!(parse_mv_command("mv \\foo bar"), vec!["foo".to_string(), "bar".to_string()]);
-        assert_eq!(parse_mv_command("mv foo\\ bar bing"), vec!["foo bar".to_string(), "bing".to_string()]);
-        assert_eq!(parse_mv_command("mv \"foo\\ bar\" bing"), vec!["foo bar".to_string(), "bing".to_string()]);
-        assert_eq!(parse_mv_command("mv \"'foo\\' bar\" bing"), vec!["'foo' bar".to_string(), "bing".to_string()]);
-        assert_eq!(parse_mv_command("mv \"\\\"foo\" bar"), vec!["\"foo".to_string(), "bar".to_string()]);
+        assert_eq!(
+            parse_mv_command("mv \\foo bar"),
+            vec!["foo".to_string(), "bar".to_string()]
+        );
+        assert_eq!(
+            parse_mv_command("mv foo\\ bar bing"),
+            vec!["foo bar".to_string(), "bing".to_string()]
+        );
+        assert_eq!(
+            parse_mv_command("mv \"foo\\ bar\" bing"),
+            vec!["foo bar".to_string(), "bing".to_string()]
+        );
+        assert_eq!(
+            parse_mv_command("mv \"'foo\\' bar\" bing"),
+            vec!["'foo' bar".to_string(), "bing".to_string()]
+        );
+        assert_eq!(
+            parse_mv_command("mv \"\\\"foo\" bar"),
+            vec!["\"foo".to_string(), "bar".to_string()]
+        );
     }
 }
diff --git a/src/settings.rs b/src/settings.rs
index 2fa5cfa7..8b0a9bde 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -1,35 +1,36 @@
-use crate::bash_history;
-use clap::{crate_version, crate_authors, value_t};
+use crate::shell_history;
 use clap::AppSettings;
+use clap::{crate_authors, crate_version, value_t};
 use clap::{App, Arg, SubCommand};
+use dirs::home_dir;
 use std::env;
 use std::path::PathBuf;
+use std::str::FromStr;
 use std::time::SystemTime;
 use std::time::UNIX_EPOCH;
-use std::str::FromStr;
-use dirs::home_dir;
-
 
 #[derive(Debug)]
 pub enum Mode {
     Add,
     Search,
     Train,
-    Move
+    Move,
 }
 
 #[derive(Debug)]
 pub enum KeyScheme {
     Emacs,
-    Vim
+    Vim,
 }
 
 #[derive(Debug)]
 pub struct Settings {
     pub mode: Mode,
     pub debug: bool,
+    pub zsh_extended_history: bool,
     pub session_id: String,
     pub mcfly_history: PathBuf,
+    pub output_selection: Option<String>,
     pub command: String,
     pub dir: String,
     pub results: u16,
@@ -39,13 +40,14 @@ pub struct Settings {
     pub append_to_histfile: bool,
     pub refresh_training_cache: bool,
     pub lightmode: bool,
-    pub key_scheme: KeyScheme
+    pub key_scheme: KeyScheme,
 }
 
 impl Default for Settings {
     fn default() -> Settings {
         Settings {
             mode: Mode::Add,
+            output_selection: None,
             command: String::new(),
             session_id: String::new(),
             mcfly_history: PathBuf::new(),
@@ -57,8 +59,9 @@ impl Default for Settings {
             refresh_training_cache: false,
             append_to_histfile: false,
             debug: false,
+            zsh_extended_history: false,
             lightmode: false,
-            key_scheme: KeyScheme::Emacs
+            key_scheme: KeyScheme::Emacs,
         }
     }
 }
@@ -96,6 +99,9 @@ impl Settings {
                 .arg(Arg::with_name("append_to_histfile")
                     .long("append-to-histfile")
                     .help("Also append new history to $HISTFILE (e.q., .bash_history)"))
+                .arg(Arg::with_name("zsh_extended_history")
+                    .long("zsh-extended-history")
+                    .help("If appending, use zsh's EXTENDED_HISTORY format"))
                 .arg(Arg::with_name("when")
                     .short("w")
                     .long("when")
@@ -135,6 +141,12 @@ impl Settings {
                     .value_name("NUMBER")
                     .help("Number of results to return")
                     .takes_value(true))
+                .arg(Arg::with_name("output_selection")
+                    .short("o")
+                    .long("output-selection")
+                    .value_name("PATH")
+                    .help("Write selection to file - the first line will be 'display' or 'run' depending on the selection, the second line will be the selection.")
+                    .takes_value(true))
                 .arg(Arg::with_name("command")
                     .help("The command search term(s)")
                     .value_name("COMMAND")
@@ -166,7 +178,7 @@ impl Settings {
 
         let mut settings = Settings::default();
 
-        settings.debug = matches.is_present("debug");
+        settings.debug = matches.is_present("debug") || env::var("MCFLY_DEBUG").is_ok();
         settings.session_id = matches
             .value_of("session_id")
             .map(|s| s.to_string())
@@ -178,11 +190,14 @@ impl Settings {
             matches
                 .value_of("mcfly_history")
                 .map(|s| s.to_string())
-                .unwrap_or_else(||
-                    env::var("MCFLY_HISTORY").unwrap_or_else(|err|
-                        panic!(format!("McFly error: Please ensure that MCFLY_HISTORY is set ({})", err))
-                    ),
-                ),
+                .unwrap_or_else(|| {
+                    env::var("MCFLY_HISTORY").unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: Please ensure that MCFLY_HISTORY is set ({})",
+                            err
+                        ))
+                    })
+                }),
         );
 
         match matches.subcommand() {
@@ -190,14 +205,18 @@ impl Settings {
                 settings.mode = Mode::Add;
 
                 settings.when_run = Some(
-                    value_t!(add_matches, "when", i64).unwrap_or(SystemTime::now()
-                        .duration_since(UNIX_EPOCH)
-                        .unwrap_or_else(|err| panic!(format!("McFly error: Time went backwards ({})", err)))
-                        .as_secs()
-                        as i64),
+                    value_t!(add_matches, "when", i64).unwrap_or(
+                        SystemTime::now()
+                            .duration_since(UNIX_EPOCH)
+                            .unwrap_or_else(|err| {
+                                panic!(format!("McFly error: Time went backwards ({})", err))
+                            })
+                            .as_secs() as i64,
+                    ),
                 );
 
                 settings.append_to_histfile = add_matches.is_present("append_to_histfile");
+                settings.zsh_extended_history = add_matches.is_present("zsh_extended_history");
 
                 if add_matches.value_of("exit").is_some() {
                     settings.exit_code =
@@ -207,7 +226,12 @@ impl Settings {
                 if let Some(dir) = add_matches.value_of("directory") {
                     settings.dir = dir.to_string();
                 } else {
-                    settings.dir = env::var("PWD").unwrap_or_else(|err| panic!(format!("McFly error: Unable to determine current directory ({})", err)));
+                    settings.dir = env::var("PWD").unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: Unable to determine current directory ({})",
+                            err
+                        ))
+                    });
                 }
 
                 if let Some(old_dir) = add_matches.value_of("old_directory") {
@@ -219,7 +243,7 @@ impl Settings {
                 if let Some(commands) = add_matches.values_of("command") {
                     settings.command = commands.collect::<Vec<_>>().join(" ");
                 } else {
-                    settings.command = bash_history::last_history_line(&settings.mcfly_history)
+                    settings.command = shell_history::last_history_line(&settings.mcfly_history)
                         .unwrap_or_else(String::new);
                 }
 
@@ -237,9 +261,15 @@ impl Settings {
                 if let Some(dir) = search_matches.value_of("directory") {
                     settings.dir = dir.to_string();
                 } else {
-                    settings.dir = env::var("PWD").unwrap_or_else(|err| panic!(format!("McFly error: Unable to determine current directory ({})", err)));
+                    settings.dir = env::var("PWD").unwrap_or_else(|err| {
+                        panic!(format!(
+                            "McFly error: Unable to determine current directory ({})",
+                            err
+                        ))
+                    });
                 }
-                if let Some(results) = env::var("MCFLY_RESULTS").ok() {
+
+                if let Ok(results) = env::var("MCFLY_RESULTS") {
                     if let Ok(results) = u16::from_str(&results) {
                         settings.results = results;
                     }
@@ -247,15 +277,23 @@ impl Settings {
                 if let Ok(results) = value_t!(search_matches.value_of("results"), u16) {
                     settings.results = results;
                 }
+
+                settings.output_selection = search_matches
+                    .value_of("output_selection")
+                    .map(|s| s.to_owned());
+
                 if let Some(values) = search_matches.values_of("command") {
                     settings.command = values.collect::<Vec<_>>().join(" ");
                 } else {
-                    settings.command = bash_history::last_history_line(&settings.mcfly_history)
+                    settings.command = shell_history::last_history_line(&settings.mcfly_history)
                         .unwrap_or_else(String::new)
                         .trim_start_matches("#mcfly: ")
                         .trim_start_matches("#mcfly:")
                         .to_string();
-                    bash_history::delete_last_history_entry_if_search(&settings.mcfly_history);
+                    shell_history::delete_last_history_entry_if_search(
+                        &settings.mcfly_history,
+                        settings.debug,
+                    );
                 }
             }
 
@@ -266,8 +304,16 @@ impl Settings {
 
             ("move", Some(move_matches)) => {
                 settings.mode = Mode::Move;
-                settings.old_dir = Some(String::from(move_matches.value_of("old_dir_path").unwrap_or_else(|| panic!("McFly error: Expected value for old_dir_path"))));
-                settings.dir = String::from(move_matches.value_of("new_dir_path").unwrap_or_else(|| panic!("McFly error: Expected value for new_dir_path")));
+                settings.old_dir = Some(String::from(
+                    move_matches
+                        .value_of("old_dir_path")
+                        .unwrap_or_else(|| panic!("McFly error: Expected value for old_dir_path")),
+                ));
+                settings.dir = String::from(
+                    move_matches
+                        .value_of("new_dir_path")
+                        .unwrap_or_else(|| panic!("McFly error: Expected value for new_dir_path")),
+                );
             }
 
             ("", None) => println!("No subcommand was used"), // If no subcommand was used it'll match the tuple ("", None)
@@ -276,11 +322,11 @@ impl Settings {
 
         settings.lightmode = match env::var_os("MCFLY_LIGHT") {
             Some(_val) => true,
-            None => false
+            None => false,
         };
         settings.key_scheme = match env::var("MCFLY_KEY_SCHEME").as_ref().map(String::as_ref) {
             Ok("vim") => KeyScheme::Vim,
-            _ => KeyScheme::Emacs
+            _ => KeyScheme::Emacs,
         };
 
         settings
diff --git a/src/shell_history.rs b/src/shell_history.rs
new file mode 100644
index 00000000..46fb6bab
--- /dev/null
+++ b/src/shell_history.rs
@@ -0,0 +1,173 @@
+use regex::Regex;
+use std::env;
+use std::fs;
+use std::fs::File;
+use std::fs::OpenOptions;
+use std::io::Read;
+use std::io::Write;
+use std::path::PathBuf;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+fn read_ignoring_utf_errors(path: &PathBuf) -> String {
+    let mut f =
+        File::open(path).unwrap_or_else(|_| panic!("McFly error: {:?} file not found", &path));
+    let mut buffer = Vec::new();
+    f.read_to_end(&mut buffer)
+        .unwrap_or_else(|_| panic!("McFly error: Unable to read from {:?}", &path));
+    String::from_utf8_lossy(&buffer).to_string()
+}
+
+#[allow(clippy::if_same_then_else)]
+fn has_leading_timestamp(line: &str) -> bool {
+    let mut matched_chars = 0;
+
+    for (index, c) in line.chars().enumerate() {
+        if index == 0 && c == '#' {
+            matched_chars += 1;
+        } else if index > 0 && index < 11 && (c.is_digit(10)) {
+            matched_chars += 1;
+        } else if index > 11 {
+            break;
+        }
+    }
+
+    matched_chars == 11
+}
+
+pub fn history_file_path() -> PathBuf {
+    let path = PathBuf::from(env::var("HISTFILE").unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Please ensure HISTFILE is set for your shell ({})",
+            err
+        ))
+    }));
+    fs::canonicalize(&path).unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: The contents of $HISTFILE appear invalid ({})",
+            err
+        ))
+    })
+}
+
+pub fn full_history(path: &PathBuf) -> Vec<String> {
+    let history_contents = read_ignoring_utf_errors(&path);
+
+    let zsh_timestamp_and_duration_regex = Regex::new(r"^: \d+:\d+;").unwrap();
+
+    history_contents
+        .split('\n')
+        .filter(|line| !has_leading_timestamp(line) && !line.is_empty())
+        .map(|line| zsh_timestamp_and_duration_regex.replace(line, ""))
+        .map(String::from)
+        .collect::<Vec<String>>()
+}
+
+pub fn last_history_line(path: &PathBuf) -> Option<String> {
+    // Could switch to https://github.com/mikeycgto/rev_lines
+    full_history(path).last().map(|s| s.trim().to_string())
+}
+
+pub fn delete_last_history_entry_if_search(path: &PathBuf, debug: bool) {
+    let history_contents = read_ignoring_utf_errors(&path);
+
+    let mut lines = history_contents
+        .split('\n')
+        .map(String::from)
+        .collect::<Vec<String>>();
+
+    if !lines.is_empty() && lines[lines.len() - 1].is_empty() {
+        lines.pop();
+    }
+
+    let starts_with_mcfly = Regex::new(r"^(: \d+:\d+;)?#mcfly:").unwrap();
+
+    if lines.is_empty() || !starts_with_mcfly.is_match(&lines[lines.len() - 1]) {
+        return; // Abort if empty or the last line isn't a comment.
+    }
+
+    if debug {
+        println!("McFly: Removed {:?} from file {:?}", lines.pop(), &path);
+    } else {
+        lines.pop();
+    }
+
+    if !lines.is_empty() && has_leading_timestamp(&lines[lines.len() - 1]) {
+        lines.pop();
+    }
+
+    lines.push(String::from("")); // New line at end of file expected by bash.
+
+    fs::write(&path, lines.join("\n"))
+        .unwrap_or_else(|_| panic!("McFly error: Unable to update {:?}", &path));
+}
+
+pub fn delete_lines(path: &PathBuf, command: &str) {
+    let history_contents = read_ignoring_utf_errors(&path);
+
+    let zsh_timestamp_and_duration_regex = Regex::new(r"^: \d+:\d+;").unwrap();
+
+    let lines = history_contents
+        .split('\n')
+        .map(String::from)
+        .filter(|cmd| !command.eq(&zsh_timestamp_and_duration_regex.replace(cmd, "")))
+        .collect::<Vec<String>>();
+
+    fs::write(&path, lines.join("\n"))
+        .unwrap_or_else(|_| panic!("McFly error: Unable to update {:?}", &path));
+}
+
+pub fn append_history_entry(
+    command: &str,
+    when_run: Option<i64>,
+    path: &PathBuf,
+    zsh_extended_history: bool,
+    debug: bool,
+) {
+    let mut file = OpenOptions::new()
+        .write(true)
+        .append(true)
+        .open(path)
+        .unwrap_or_else(|err| {
+            panic!(format!(
+                "McFly error: please make sure HISTFILE exists ({})",
+                err
+            ))
+        });
+
+    if debug {
+        println!("McFly: Appended '{}' to file {:?}", command, &path);
+    }
+
+    if zsh_extended_history {
+        let when = when_run.unwrap_or(
+            SystemTime::now()
+                .duration_since(UNIX_EPOCH)
+                .unwrap_or_else(|err| panic!(format!("McFly error: Time went backwards ({})", err)))
+                .as_secs() as i64,
+        );
+        if let Err(e) = writeln!(file, ": {}:0;{}", when, command) {
+            eprintln!("Couldn't append to file {:?}: {}", &path, e);
+        }
+    } else {
+        if let Err(e) = writeln!(file, "{}", command) {
+            eprintln!("Couldn't append to file {:?}: {}", &path, e);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::has_leading_timestamp;
+
+    #[test]
+    fn has_leading_timestamp_works() {
+        assert_eq!(false, has_leading_timestamp("abc"));
+        assert_eq!(false, has_leading_timestamp("#abc"));
+        assert_eq!(false, has_leading_timestamp("#123456"));
+        assert_eq!(true, has_leading_timestamp("#1234567890"));
+        assert_eq!(false, has_leading_timestamp("#123456789"));
+        assert_eq!(false, has_leading_timestamp("# 1234567890"));
+        assert_eq!(false, has_leading_timestamp("1234567890"));
+        assert_eq!(false, has_leading_timestamp("hello 1234567890"));
+    }
+}
diff --git a/src/simplified_command.rs b/src/simplified_command.rs
index 1dca7350..60800b44 100644
--- a/src/simplified_command.rs
+++ b/src/simplified_command.rs
@@ -18,7 +18,7 @@ pub struct SimplifiedCommand {
 impl SimplifiedCommand {
     pub fn new<S: Into<String>>(command: S, truncate: bool) -> SimplifiedCommand {
         let mut simplified_command = SimplifiedCommand {
-            original: command.into().clone(),
+            original: command.into(),
             result: String::new(),
             truncate,
         };
diff --git a/src/trainer.rs b/src/trainer.rs
index 48fa1cf9..b1228f4b 100644
--- a/src/trainer.rs
+++ b/src/trainer.rs
@@ -1,8 +1,8 @@
-use crate::history::History;
-use crate::settings::Settings;
 use crate::history::Features;
-use crate::node::Node;
+use crate::history::History;
 use crate::network::Network;
+use crate::node::Node;
+use crate::settings::Settings;
 use crate::training_sample_generator::TrainingSampleGenerator;
 
 #[derive(Debug)]
@@ -17,15 +17,21 @@ impl<'a> Trainer<'a> {
     }
 
     pub fn train(&mut self) {
-        let lr = 0.000005;
+        let lr = 0.000_005;
         let momentum = 0.0;
         let batch_size = 1000;
         let plateau_threshold = 3000;
         let generator = TrainingSampleGenerator::new(self.settings, self.history);
 
-        println!("Evaluating error rate on current {:#?}", self.history.network);
+        println!(
+            "Evaluating error rate on current {:#?}",
+            self.history.network
+        );
         let mut best_overall_network = self.history.network;
-        let mut best_overall_error = self.history.network.average_error(&generator, batch_size * 10);
+        let mut best_overall_error = self
+            .history
+            .network
+            .average_error(&generator, batch_size * 10);
         println!("Current network error rate is {}", best_overall_error);
 
         loop {
@@ -85,13 +91,17 @@ impl<'a> Trainer<'a> {
                     let d_o_3_d_s_3 = 1.0 - network.final_sum.tanh().powi(2);
 
                     // Output bias
-                    output_increments[0] = momentum * output_increments[0] + lr * d_e_d_o_3 * d_o_3_d_s_3 * 1.0;
+                    output_increments[0] =
+                        momentum * output_increments[0] + lr * d_e_d_o_3 * d_o_3_d_s_3 * 1.0;
                     // Final sum node 1 output weight
-                    output_increments[1] = momentum * output_increments[1] + lr * d_e_d_o_3 * d_o_3_d_s_3 * network.hidden_node_outputs[0];
+                    output_increments[1] = momentum * output_increments[1]
+                        + lr * d_e_d_o_3 * d_o_3_d_s_3 * network.hidden_node_outputs[0];
                     // Final sum node 2 output weight
-                    output_increments[2] = momentum * output_increments[2] + lr * d_e_d_o_3 * d_o_3_d_s_3 * network.hidden_node_outputs[1];
+                    output_increments[2] = momentum * output_increments[2]
+                        + lr * d_e_d_o_3 * d_o_3_d_s_3 * network.hidden_node_outputs[1];
                     // Final sum node 3 output weight
-                    output_increments[3] = momentum * output_increments[3] + lr * d_e_d_o_3 * d_o_3_d_s_3 * network.hidden_node_outputs[2];
+                    output_increments[3] = momentum * output_increments[3]
+                        + lr * d_e_d_o_3 * d_o_3_d_s_3 * network.hidden_node_outputs[2];
 
                     let d_s_3_d_o_0 = network.final_weights[0];
                     let d_s_3_d_o_1 = network.final_weights[1];
@@ -103,41 +113,83 @@ impl<'a> Trainer<'a> {
                     let d_e_d_s_1 = d_e_d_o_3 * d_o_3_d_s_3 * d_s_3_d_o_1 * d_o_1_d_s_1;
                     let d_e_d_s_2 = d_e_d_o_3 * d_o_3_d_s_3 * d_s_3_d_o_2 * d_o_2_d_s_2;
 
-                    node_increments[0].offset = momentum * node_increments[0].offset + lr * d_e_d_s_0 * 1.0;
-                    node_increments[0].age = momentum * node_increments[0].age + lr * d_e_d_s_0 * features.age_factor;
-                    node_increments[0].length = momentum * node_increments[0].length + lr * d_e_d_s_0 * features.length_factor;
-                    node_increments[0].exit = momentum * node_increments[0].exit + lr * d_e_d_s_0 * features.exit_factor;
-                    node_increments[0].recent_failure = momentum * node_increments[0].recent_failure + lr * d_e_d_s_0 * features.recent_failure_factor;
-                    node_increments[0].selected_dir = momentum * node_increments[0].selected_dir + lr * d_e_d_s_0 * features.selected_dir_factor;
-                    node_increments[0].dir = momentum * node_increments[0].dir + lr * d_e_d_s_0 * features.dir_factor;
-                    node_increments[0].overlap = momentum * node_increments[0].overlap + lr * d_e_d_s_0 * features.overlap_factor;
-                    node_increments[0].immediate_overlap = momentum * node_increments[0].immediate_overlap + lr * d_e_d_s_0 * features.immediate_overlap_factor;
-                    node_increments[0].selected_occurrences = momentum * node_increments[0].selected_occurrences + lr * d_e_d_s_0 * features.selected_occurrences_factor;
-                    node_increments[0].occurrences = momentum * node_increments[0].occurrences + lr * d_e_d_s_0 * features.occurrences_factor;
-
-                    node_increments[1].offset = momentum * node_increments[1].offset + lr * d_e_d_s_1 * 1.0;
-                    node_increments[1].age = momentum * node_increments[1].age + lr * d_e_d_s_1 * features.age_factor;
-                    node_increments[1].length = momentum * node_increments[1].length + lr * d_e_d_s_1 * features.length_factor;
-                    node_increments[1].exit = momentum * node_increments[1].exit + lr * d_e_d_s_1 * features.exit_factor;
-                    node_increments[1].recent_failure = momentum * node_increments[1].recent_failure + lr * d_e_d_s_1 * features.recent_failure_factor;
-                    node_increments[1].selected_dir = momentum * node_increments[1].selected_dir + lr * d_e_d_s_1 * features.selected_dir_factor;
-                    node_increments[1].dir = momentum * node_increments[1].dir + lr * d_e_d_s_1 * features.dir_factor;
-                    node_increments[1].overlap = momentum * node_increments[1].overlap + lr * d_e_d_s_1 * features.overlap_factor;
-                    node_increments[1].immediate_overlap = momentum * node_increments[1].immediate_overlap + lr * d_e_d_s_1 * features.immediate_overlap_factor;
-                    node_increments[1].selected_occurrences = momentum * node_increments[1].selected_occurrences + lr * d_e_d_s_1 * features.selected_occurrences_factor;
-                    node_increments[1].occurrences = momentum * node_increments[1].occurrences + lr * d_e_d_s_1 * features.occurrences_factor;
-
-                    node_increments[2].offset = momentum * node_increments[2].offset + lr * d_e_d_s_2 * 1.0;
-                    node_increments[2].age = momentum * node_increments[2].age + lr * d_e_d_s_2 * features.age_factor;
-                    node_increments[2].length = momentum * node_increments[2].length + lr * d_e_d_s_2 * features.length_factor;
-                    node_increments[2].exit = momentum * node_increments[2].exit + lr * d_e_d_s_2 * features.exit_factor;
-                    node_increments[2].recent_failure = momentum * node_increments[2].recent_failure + lr * d_e_d_s_2 * features.recent_failure_factor;
-                    node_increments[2].selected_dir = momentum * node_increments[2].selected_dir + lr * d_e_d_s_2 * features.selected_dir_factor;
-                    node_increments[2].dir = momentum * node_increments[2].dir + lr * d_e_d_s_2 * features.dir_factor;
-                    node_increments[2].overlap = momentum * node_increments[2].overlap + lr * d_e_d_s_2 * features.overlap_factor;
-                    node_increments[2].immediate_overlap = momentum * node_increments[2].immediate_overlap + lr * d_e_d_s_2 * features.immediate_overlap_factor;
-                    node_increments[2].selected_occurrences = momentum * node_increments[2].selected_occurrences + lr * d_e_d_s_2 * features.selected_occurrences_factor;
-                    node_increments[2].occurrences = momentum * node_increments[2].occurrences + lr * d_e_d_s_2 * features.occurrences_factor;
+                    node_increments[0].offset =
+                        momentum * node_increments[0].offset + lr * d_e_d_s_0 * 1.0;
+                    node_increments[0].age =
+                        momentum * node_increments[0].age + lr * d_e_d_s_0 * features.age_factor;
+                    node_increments[0].length = momentum * node_increments[0].length
+                        + lr * d_e_d_s_0 * features.length_factor;
+                    node_increments[0].exit =
+                        momentum * node_increments[0].exit + lr * d_e_d_s_0 * features.exit_factor;
+                    node_increments[0].recent_failure = momentum
+                        * node_increments[0].recent_failure
+                        + lr * d_e_d_s_0 * features.recent_failure_factor;
+                    node_increments[0].selected_dir = momentum * node_increments[0].selected_dir
+                        + lr * d_e_d_s_0 * features.selected_dir_factor;
+                    node_increments[0].dir =
+                        momentum * node_increments[0].dir + lr * d_e_d_s_0 * features.dir_factor;
+                    node_increments[0].overlap = momentum * node_increments[0].overlap
+                        + lr * d_e_d_s_0 * features.overlap_factor;
+                    node_increments[0].immediate_overlap = momentum
+                        * node_increments[0].immediate_overlap
+                        + lr * d_e_d_s_0 * features.immediate_overlap_factor;
+                    node_increments[0].selected_occurrences = momentum
+                        * node_increments[0].selected_occurrences
+                        + lr * d_e_d_s_0 * features.selected_occurrences_factor;
+                    node_increments[0].occurrences = momentum * node_increments[0].occurrences
+                        + lr * d_e_d_s_0 * features.occurrences_factor;
+
+                    node_increments[1].offset =
+                        momentum * node_increments[1].offset + lr * d_e_d_s_1 * 1.0;
+                    node_increments[1].age =
+                        momentum * node_increments[1].age + lr * d_e_d_s_1 * features.age_factor;
+                    node_increments[1].length = momentum * node_increments[1].length
+                        + lr * d_e_d_s_1 * features.length_factor;
+                    node_increments[1].exit =
+                        momentum * node_increments[1].exit + lr * d_e_d_s_1 * features.exit_factor;
+                    node_increments[1].recent_failure = momentum
+                        * node_increments[1].recent_failure
+                        + lr * d_e_d_s_1 * features.recent_failure_factor;
+                    node_increments[1].selected_dir = momentum * node_increments[1].selected_dir
+                        + lr * d_e_d_s_1 * features.selected_dir_factor;
+                    node_increments[1].dir =
+                        momentum * node_increments[1].dir + lr * d_e_d_s_1 * features.dir_factor;
+                    node_increments[1].overlap = momentum * node_increments[1].overlap
+                        + lr * d_e_d_s_1 * features.overlap_factor;
+                    node_increments[1].immediate_overlap = momentum
+                        * node_increments[1].immediate_overlap
+                        + lr * d_e_d_s_1 * features.immediate_overlap_factor;
+                    node_increments[1].selected_occurrences = momentum
+                        * node_increments[1].selected_occurrences
+                        + lr * d_e_d_s_1 * features.selected_occurrences_factor;
+                    node_increments[1].occurrences = momentum * node_increments[1].occurrences
+                        + lr * d_e_d_s_1 * features.occurrences_factor;
+
+                    node_increments[2].offset =
+                        momentum * node_increments[2].offset + lr * d_e_d_s_2 * 1.0;
+                    node_increments[2].age =
+                        momentum * node_increments[2].age + lr * d_e_d_s_2 * features.age_factor;
+                    node_increments[2].length = momentum * node_increments[2].length
+                        + lr * d_e_d_s_2 * features.length_factor;
+                    node_increments[2].exit =
+                        momentum * node_increments[2].exit + lr * d_e_d_s_2 * features.exit_factor;
+                    node_increments[2].recent_failure = momentum
+                        * node_increments[2].recent_failure
+                        + lr * d_e_d_s_2 * features.recent_failure_factor;
+                    node_increments[2].selected_dir = momentum * node_increments[2].selected_dir
+                        + lr * d_e_d_s_2 * features.selected_dir_factor;
+                    node_increments[2].dir =
+                        momentum * node_increments[2].dir + lr * d_e_d_s_2 * features.dir_factor;
+                    node_increments[2].overlap = momentum * node_increments[2].overlap
+                        + lr * d_e_d_s_2 * features.overlap_factor;
+                    node_increments[2].immediate_overlap = momentum
+                        * node_increments[2].immediate_overlap
+                        + lr * d_e_d_s_2 * features.immediate_overlap_factor;
+                    node_increments[2].selected_occurrences = momentum
+                        * node_increments[2].selected_occurrences
+                        + lr * d_e_d_s_2 * features.selected_occurrences_factor;
+                    node_increments[2].occurrences = momentum * node_increments[2].occurrences
+                        + lr * d_e_d_s_2 * features.occurrences_factor;
 
                     let node0 = network.hidden_nodes[0];
                     let node1 = network.hidden_nodes[1];
@@ -149,12 +201,15 @@ impl<'a> Trainer<'a> {
                                 age: node0.age - node_increments[0].age,
                                 length: node0.length - node_increments[0].length,
                                 exit: node0.exit - node_increments[0].exit,
-                                recent_failure: node0.recent_failure - node_increments[0].recent_failure,
+                                recent_failure: node0.recent_failure
+                                    - node_increments[0].recent_failure,
                                 selected_dir: node0.selected_dir - node_increments[0].selected_dir,
                                 dir: node0.dir - node_increments[0].dir,
                                 overlap: node0.overlap - node_increments[0].overlap,
-                                immediate_overlap: node0.immediate_overlap - node_increments[0].immediate_overlap,
-                                selected_occurrences: node0.selected_occurrences - node_increments[0].selected_occurrences,
+                                immediate_overlap: node0.immediate_overlap
+                                    - node_increments[0].immediate_overlap,
+                                selected_occurrences: node0.selected_occurrences
+                                    - node_increments[0].selected_occurrences,
                                 occurrences: node0.occurrences - node_increments[0].occurrences,
                             },
                             Node {
@@ -162,12 +217,15 @@ impl<'a> Trainer<'a> {
                                 age: node1.age - node_increments[1].age,
                                 length: node1.length - node_increments[1].length,
                                 exit: node1.exit - node_increments[1].exit,
-                                recent_failure: node1.recent_failure - node_increments[1].recent_failure,
+                                recent_failure: node1.recent_failure
+                                    - node_increments[1].recent_failure,
                                 selected_dir: node1.selected_dir - node_increments[1].selected_dir,
                                 dir: node1.dir - node_increments[1].dir,
                                 overlap: node1.overlap - node_increments[1].overlap,
-                                immediate_overlap: node1.immediate_overlap - node_increments[1].immediate_overlap,
-                                selected_occurrences: node1.selected_occurrences - node_increments[1].selected_occurrences,
+                                immediate_overlap: node1.immediate_overlap
+                                    - node_increments[1].immediate_overlap,
+                                selected_occurrences: node1.selected_occurrences
+                                    - node_increments[1].selected_occurrences,
                                 occurrences: node1.occurrences - node_increments[1].occurrences,
                             },
                             Node {
@@ -175,19 +233,26 @@ impl<'a> Trainer<'a> {
                                 age: node2.age - node_increments[2].age,
                                 length: node2.length - node_increments[2].length,
                                 exit: node2.exit - node_increments[2].exit,
-                                recent_failure: node2.recent_failure - node_increments[2].recent_failure,
+                                recent_failure: node2.recent_failure
+                                    - node_increments[2].recent_failure,
                                 selected_dir: node2.selected_dir - node_increments[2].selected_dir,
                                 dir: node2.dir - node_increments[2].dir,
                                 overlap: node2.overlap - node_increments[2].overlap,
-                                immediate_overlap: node2.immediate_overlap - node_increments[2].immediate_overlap,
-                                selected_occurrences: node2.selected_occurrences - node_increments[2].selected_occurrences,
+                                immediate_overlap: node2.immediate_overlap
+                                    - node_increments[2].immediate_overlap,
+                                selected_occurrences: node2.selected_occurrences
+                                    - node_increments[2].selected_occurrences,
                                 occurrences: node2.occurrences - node_increments[2].occurrences,
-                            }
+                            },
                         ],
                         hidden_node_sums: [0.0, 0.0, 0.0],
                         hidden_node_outputs: [0.0, 0.0, 0.0],
                         final_bias: network.final_bias - output_increments[0],
-                        final_weights: [network.final_weights[0] - output_increments[1], network.final_weights[1] - output_increments[2], network.final_weights[2] - output_increments[3]],
+                        final_weights: [
+                            network.final_weights[0] - output_increments[1],
+                            network.final_weights[1] - output_increments[2],
+                            network.final_weights[2] - output_increments[3],
+                        ],
                         final_sum: 0.0,
                         final_output: 0.0,
                     };
@@ -206,15 +271,21 @@ impl<'a> Trainer<'a> {
                             best_overall_error = best_restart_error;
                             best_overall_network = best_restart_network;
 
-                            println!("New best overall for {:#?} with error {} (new best)", best_overall_network, best_overall_error);
+                            println!(
+                                "New best overall for {:#?} with error {} (new best)",
+                                best_overall_network, best_overall_error
+                            );
                         } else {
-                            println!("Best overall remains {:#?} with error {} (old)", best_overall_network, best_overall_error);
+                            println!(
+                                "Best overall remains {:#?} with error {} (old)",
+                                best_overall_network, best_overall_error
+                            );
                         }
                         break;
                     }
                 }
 
-//                println!("Error of {} (vs {} {} ago)", batch_error / batch_samples, best_restart_error, cycles_since_best_restart_error);
+                //                println!("Error of {} (vs {} {} ago)", batch_error / batch_samples, best_restart_error, cycles_since_best_restart_error);
             }
         }
     }
diff --git a/src/training_cache.rs b/src/training_cache.rs
index 4ef4bb8b..d18d3f67 100644
--- a/src/training_cache.rs
+++ b/src/training_cache.rs
@@ -1,11 +1,16 @@
 use crate::history::Features;
+use csv::Reader;
 use csv::Writer;
 use std::fs::File;
-use csv::Reader;
 use std::path::PathBuf;
 
 pub fn write(data_set: &[(Features, bool)], cache_path: &PathBuf) {
-    let mut writer = Writer::from_path(cache_path).unwrap_or_else(|err| panic!(format!("McFly error: Expected to be able to write a CSV ({})", err)));
+    let mut writer = Writer::from_path(cache_path).unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Expected to be able to write a CSV ({})",
+            err
+        ))
+    });
     output_header(&mut writer);
 
     for (features, correct) in data_set {
@@ -16,10 +21,20 @@ pub fn write(data_set: &[(Features, bool)], cache_path: &PathBuf) {
 pub fn read(cache_path: &PathBuf) -> Vec<(Features, bool)> {
     let mut data_set: Vec<(Features, bool)> = Vec::new();
 
-    let mut reader = Reader::from_path(cache_path).unwrap_or_else(|err| panic!(format!("McFly error: Expected to be able to read from CSV ({})", err)));
+    let mut reader = Reader::from_path(cache_path).unwrap_or_else(|err| {
+        panic!(format!(
+            "McFly error: Expected to be able to read from CSV ({})",
+            err
+        ))
+    });
 
     for result in reader.records() {
-        let record = result.unwrap_or_else(|err| panic!(format!("McFly error: Expected to be able to unwrap cached result ({})", err)));
+        let record = result.unwrap_or_else(|err| {
+            panic!(format!(
+                "McFly error: Expected to be able to unwrap cached result ({})",
+                err
+            ))
+        });
 
         let features = Features {
             age_factor: record[0].parse().unwrap(),
@@ -31,7 +46,7 @@ pub fn read(cache_path: &PathBuf) -> Vec<(Features, bool)> {
             overlap_factor: record[6].parse().unwrap(),
             immediate_overlap_factor: record[7].parse().unwrap(),
             selected_occurrences_factor: record[8].parse().unwrap(),
-            occurrences_factor: record[9].parse().unwrap()
+            occurrences_factor: record[9].parse().unwrap(),
         };
 
         data_set.push((features, record[10].eq("t")));
@@ -56,7 +71,9 @@ fn output_header(writer: &mut Writer<File>) {
             "correct",
         ])
         .unwrap_or_else(|err| panic!(format!("McFly error: Expected to write to CSV ({})", err)));
-    writer.flush().unwrap_or_else(|err| panic!(format!("McFly error: Expected to flush CSV ({})", err)));
+    writer
+        .flush()
+        .unwrap_or_else(|err| panic!(format!("McFly error: Expected to flush CSV ({})", err)));
 }
 
 fn output_row(writer: &mut Writer<File>, features: &Features, correct: bool) {
@@ -79,5 +96,7 @@ fn output_row(writer: &mut Writer<File>, features: &Features, correct: bool) {
             },
         ])
         .unwrap_or_else(|err| panic!(format!("McFly error: Expected to write to CSV ({})", err)));
-    writer.flush().unwrap_or_else(|err| panic!(format!("McFly error: Expected to flush CSV ({})", err)));
+    writer
+        .flush()
+        .unwrap_or_else(|err| panic!(format!("McFly error: Expected to flush CSV ({})", err)));
 }
diff --git a/src/training_sample_generator.rs b/src/training_sample_generator.rs
index 6a4df713..10a817e8 100644
--- a/src/training_sample_generator.rs
+++ b/src/training_sample_generator.rs
@@ -3,7 +3,7 @@ use crate::history::Features;
 use crate::history::History;
 use crate::settings::Settings;
 use crate::training_cache;
-use rand::Rng;
+use rand::seq::IteratorRandom;
 
 #[derive(Debug)]
 pub struct TrainingSampleGenerator<'a> {
@@ -15,16 +15,19 @@ pub struct TrainingSampleGenerator<'a> {
 impl<'a> TrainingSampleGenerator<'a> {
     pub fn new(settings: &'a Settings, history: &'a History) -> TrainingSampleGenerator<'a> {
         let cache_path = Settings::mcfly_training_cache_path();
-        let data_set =
-            if settings.refresh_training_cache || !cache_path.exists() {
-                let ds = TrainingSampleGenerator::generate_data_set(history);
-                training_cache::write(&ds, &cache_path);
-                ds
-            } else {
-                training_cache::read(&cache_path)
-            };
-
-        TrainingSampleGenerator { settings, history, data_set }
+        let data_set = if settings.refresh_training_cache || !cache_path.exists() {
+            let ds = TrainingSampleGenerator::generate_data_set(history);
+            training_cache::write(&ds, &cache_path);
+            ds
+        } else {
+            training_cache::read(&cache_path)
+        };
+
+        TrainingSampleGenerator {
+            settings,
+            history,
+            data_set,
+        }
     }
 
     pub fn generate_data_set(history: &History) -> Vec<(Features, bool)> {
@@ -37,8 +40,12 @@ impl<'a> TrainingSampleGenerator<'a> {
         println!("Generating training set for {} commands", commands.len());
 
         for (i, command) in commands.iter().enumerate() {
-            if command.dir.is_none() || command.exit_code.is_none() || command.when_run.is_none() { continue; }
-            if command.cmd.is_empty() { continue; }
+            if command.dir.is_none() || command.exit_code.is_none() || command.when_run.is_none() {
+                continue;
+            }
+            if command.cmd.is_empty() {
+                continue;
+            }
 
             if i % 100 == 0 {
                 println!("Done with {}", i);
@@ -59,7 +66,9 @@ impl<'a> TrainingSampleGenerator<'a> {
 
             // Get the features for this command at the time it was logged.
             if positive_examples <= negative_examples {
-                if let Some(our_command_index) = results.iter().position(|ref c| c.cmd.eq(&command.cmd)) {
+                if let Some(our_command_index) =
+                    results.iter().position(|ref c| c.cmd.eq(&command.cmd))
+                {
                     let what_should_have_been_first = &results[our_command_index];
                     data_set.push((what_should_have_been_first.features.clone(), true));
                     positive_examples += 1;
@@ -67,15 +76,19 @@ impl<'a> TrainingSampleGenerator<'a> {
             }
 
             if negative_examples <= positive_examples {
+                let mut rng = rand::thread_rng();
+
                 // Get the features for another command that isn't the correct one.
-                if let Some(random_command) = rand::thread_rng().choose(&results
+                if let Some(random_command) = &results
                     .iter()
                     .filter(|c| !c.cmd.eq(&command.cmd))
-                    .collect::<Vec<&Command>>())
-                    {
-                        data_set.push((random_command.features.clone(), false));
-                        negative_examples += 1;
-                    }
+                    .collect::<Vec<&Command>>()
+                    .iter()
+                    .choose(&mut rng)
+                {
+                    data_set.push((random_command.features.clone(), false));
+                    negative_examples += 1;
+                }
             }
         }
 
@@ -84,13 +97,17 @@ impl<'a> TrainingSampleGenerator<'a> {
         data_set
     }
 
-    pub fn generate<F>(&self, records: Option<usize>, mut handler: F) where F: FnMut(&Features, bool) {
+    pub fn generate<F>(&self, records: Option<usize>, mut handler: F)
+    where
+        F: FnMut(&Features, bool),
+    {
         let mut positive_examples = 0;
         let mut negative_examples = 0;
         let records = records.unwrap_or_else(|| self.data_set.len());
+        let mut rng = rand::thread_rng();
 
         loop {
-            if let Some((features, correct)) = rand::thread_rng().choose(&self.data_set) {
+            if let Some((features, correct)) = &self.data_set.iter().choose(&mut rng) {
                 if *correct && positive_examples <= negative_examples {
                     handler(features, *correct);
                     positive_examples += 1;


Test patch: