Repository: nvim-lua/plenary.nvim
Pull request #116 (2021-04-03 11:35:55)

feat: allow for sync logging


Patch:
diff --git a/README.md b/README.md
index 458b1ca..1b671a3 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,6 @@ Plug 'nvim-lua/plenary.nvim'
 - `plenary.context_manager`
 - `plenary.test_harness`
 - `plenary.filetype`
-- `plenary.neorocks` (This may move to packer.nvim, but I have added some improvements to use it more as a library.)
 
 ### plenary.job
 
@@ -96,7 +95,7 @@ nmap <leader>t <Plug>PlenaryTestFile
 To run a whole directory from the command line, you could do something like:
 
 ```
-nvim --headless -c 'PlenaryBustedDirectory tests/plenary/ {minimal_init = "tests/minimal_init.vim"}'
+nvim --headless -c "PlenaryBustedDirectory tests/plenary/ {minimal_init = 'tests/minimal_init.vim'}"
 ```
 
 Where the first argument is the directory you'd like to test. It will search for files with
@@ -205,40 +204,5 @@ This will enable debuggin for the plugin.
 
 ### plenary.neorocks
 
-Install lua packages with `luarocks`!
+DELETED: Please use packer.nvim or other lua-rocks wrapper instead. This no longer exists.
 
-Include the following somewhere in your configuration (either heredoc or sourced lua file):
-
-```lua
-local neorocks = require("plenary.neorocks")
-
--- Installs neorocks if necessary and then installs the package you need.
---  Is not synchronous, so should be called from Neovim's command line most likely.
-neorocks.install('penlight', 'pl')
-
--- ensure_installed(package_name, lua_name, headless)
--- Only installs if currently not installed.
---
---   package_name : str             - Name of the package for luarocks
---   lua_name     : (optional) str  - Name of the package that you can require. Used to determine if we have it installed already (not from luarocks)
-neorocks.ensure_installed('penlight', 'pl')
-neorocks.ensure_installed('lua-cjson', 'cjson')
-```
-
-Inspiration: https://github.com/theHamsta/nvim_rocks . However, I've used quite a different end goal (following XDG_CONFIG standards, using `package.path` and `package.cpath` to load the packages and a different strategy of loading).
-
-
-### And more to come :)
-
-- [ ] Floating window wrappers
-- [ ] Easy border windows to any floating window
-
-
-
-### Used by:
-
-- [express_line.nvim](https://github.com/tjdevries/express_line.nvim)
-- [apyrori.nvim](https://github.com/tjdevries/apyrori.nvim)
-- [nlua.nvim](https://github.com/tjdevries/nlua.nvim)
-- (TODO) [popup.nvim](https://github.com/nvim-lua/popup.nvim)
-- (TODO) [telescope.nvim](https://github.com/nvim-lua/telescope.nvim)
diff --git a/data/plenary/filetypes/builtin.lua b/data/plenary/filetypes/builtin.lua
index d826d1a..512b884 100644
--- a/data/plenary/filetypes/builtin.lua
+++ b/data/plenary/filetypes/builtin.lua
@@ -1,7 +1,7 @@
 local shebang_prefixes = { '/usr/bin/', '/bin/', '/usr/bin/env ', '/bin/env ' }
 local shebang_fts = {
   ['sh'] = 'sh',
-  ['bash'] = 'bash',
+  ['bash'] = 'sh',
   ['zsh'] = 'zsh',
   ['python'] = 'python',
   ['python2'] = 'python',
diff --git a/lua/plenary/async_lib/async.lua b/lua/plenary/async_lib/async.lua
index 027d640..2ff047d 100644
--- a/lua/plenary/async_lib/async.lua
+++ b/lua/plenary/async_lib/async.lua
@@ -2,6 +2,7 @@ local co = coroutine
 local errors = require('plenary.errors')
 local traceback_error = errors.traceback_error
 local f = require('plenary.functional')
+local tbl = require('plenary.tbl')
 
 local M = {}
 
@@ -55,16 +56,19 @@ M.wrap = function(func, argc)
   end
 
   return function(...)
-    local params = {...}
+    local params = tbl.pack(...)
 
     local function future(step)
       if step then
         if type(argc) == "number" then
           params[argc] = step
+          params.n = argc
         else
           table.insert(params, step) -- change once not optional
+          params.n = params.n + 1
         end
-        return func(unpack(params))
+
+        return func(tbl.unpack(params))
       else
         return co.yield(future)
       end
@@ -171,6 +175,10 @@ M.void = function(async_func)
   end
 end
 
+M.async_void = function(func)
+  return M.void(M.async(func))
+end
+
 ---creates an async function
 ---@param func function
 ---@return function: returns an async function
@@ -180,10 +188,10 @@ M.async = function(func)
   end
 
   return function(...)
-    local args = {...}
+    local args = tbl.pack(...)
     local function future(step)
       if step == nil then
-        return func(unpack(args))
+        return func(tbl.unpack(args))
       else
         execute(future, step)
       end
diff --git a/lua/plenary/async_lib/lsp.lua b/lua/plenary/async_lib/lsp.lua
index 6f96354..237cc52 100644
--- a/lua/plenary/async_lib/lsp.lua
+++ b/lua/plenary/async_lib/lsp.lua
@@ -2,7 +2,14 @@ local a = require('plenary.async_lib.async')
 
 local M = {}
 
----Same as vim.lsp.buf_request but works with async await
+---This will be deprecated because the callback can be called multiple times.
+---This will give a coroutine error because the coroutine will be resumed multiple times.
+---Please use buf_request_all instead.
 M.buf_request = a.wrap(vim.lsp.buf_request, 4)
 
+--This was recently merged into master so we just check if it is there
+if vim.lsp.buf_request_all ~= nil then
+  M.buf_request_all = a.wrap(vim.lsp.buf_request_all, 4)
+end
+
 return M
diff --git a/lua/plenary/async_lib/util.lua b/lua/plenary/async_lib/util.lua
index 917dfef..d719c7d 100644
--- a/lua/plenary/async_lib/util.lua
+++ b/lua/plenary/async_lib/util.lua
@@ -84,10 +84,10 @@ end, 2)
 function Condvar:notify_all()
   if #self.handles == 0 then return end
 
-  for _, callback in ipairs(self.handles) do
+  for i, callback in ipairs(self.handles) do
     callback()
+    self.handles[i] = nil
   end
-  self.handles = {} -- reset all handles as they have been used up
 end
 
 ---notify randomly one person that is waiting on this Condvar
@@ -127,9 +127,9 @@ end
 ---when a permit can be acquired returns it
 ---call permit:forget() to forget the permit
 Semaphore.acquire = a.wrap(function(self, callback)
-  self.permits = self.permits - 1
-
-  if self.permits <= 0 then
+  if self.permits > 0 then
+    self.permits = self.permits - 1
+  else
     table.insert(self.handles, callback)
     return
   end
@@ -140,9 +140,9 @@ Semaphore.acquire = a.wrap(function(self, callback)
     self.permits = self.permits + 1
 
     if self.permits > 0 and #self.handles > 0 then
+      self.permits = self.permits - 1
       local callback = table.remove(self.handles)
       callback(self_permit)
-      self.permits = self.permits - 1
     end
   end
 
diff --git a/lua/plenary/functional.lua b/lua/plenary/functional.lua
index f417ce7..9618e8e 100644
--- a/lua/plenary/functional.lua
+++ b/lua/plenary/functional.lua
@@ -1,14 +1,5 @@
 local f = {}
 
-function f.map(fun, iter)
-  local results = {}
-  for _, v in pairs(iter) do
-    table.insert(results, fun(v))
-  end
-
-  return results
-end
-
 function f.kv_pairs(t)
   local results = {}
   for k, v in pairs(t) do
@@ -32,9 +23,9 @@ function f.partial(fun, ...)
   end
 end
 
-function f.any(f, iterable)
+function f.any(fun, iterable)
   for k, v in pairs(iterable) do
-    if f(k, v) then
+    if fun(k, v) then
       return true
     end
   end
@@ -42,9 +33,9 @@ function f.any(f, iterable)
   return false
 end
 
-function f.all(f, iterable)
+function f.all(fun, iterable)
   for k, v in pairs(iterable) do
-    if not f(k, v) then
+    if not fun(k, v) then
       return false
     end
   end
diff --git a/lua/plenary/iterators.lua b/lua/plenary/iterators.lua
index 78648fa..e2fbde0 100644
--- a/lua/plenary/iterators.lua
+++ b/lua/plenary/iterators.lua
@@ -1,16 +1,36 @@
--- Adaptation of luafun for neovim
+---@brief [[
+---An adaptation of luafun for neovim.
+---This library will use neovim specific functions.
+---Some documentation is the same as from luafun.
+---Some extra functions are present that are not in luafun
+---@brief ]]
+
 local co = coroutine
+local f = require('plenary.functional')
 
 --------------------------------------------------------------------------------
 -- Tools
 --------------------------------------------------------------------------------
+
 local exports = {}
 
+---@class Iterator
+---@field gen function
+---@field param any
+---@field state any
 local Iterator = {}
 Iterator.__index = Iterator
 
+---Makes a for loop work
+---If not called without param or state, will just generate with the starting state
+---This is useful because the original luafun will also return param and state in addition to the iterator as a multival
+---This can cause problems because when using iterators as expressions the multivals can bleed
+---For example i.iter { 1, 2, i.iter { 3, 4 } } will not work because the inner iterator returns a multival thus polluting the list with internal values
+---So instead we do not return param and state as multivals when doing wrap
+---This causes the first loop iteration to call param and state with nil because we didn't return them as multivals
+---We have to use or to check for nil and default to interal starting state and param
 function Iterator:__call(param, state)
-  return self.gen(param, state)
+  return self.gen(param or self.param, state or self.state)
 end
 
 function Iterator:__tostring()
@@ -102,18 +122,35 @@ local rawiter = function(obj, param, state)
   error(string.format('object %s of type "%s" is not iterable', obj, type(obj)))
 end
 
+---Wraps the iterator triplet into a table to allow metamethods and calling with method form
+---Important! We do not return param and state as multivals like the original luafun
+---Se the __call metamethod for more information
+---@param gen any
+---@param param any
+---@param state any
+---@return Iterator
 local function wrap(gen, param, state)
   return setmetatable({
     gen = gen,
     param = param,
     state = state
-  }, Iterator), param, state
+  }, Iterator)
 end
 
+---Unwrap an iterator metatable into the iterator triplet
+---@param self Iterator
+---@return any
+---@return any
+---@return any
 local unwrap = function(self)
-    return self.gen, self.param, self.state
+  return self.gen, self.param, self.state
 end
 
+---Create an iterator from an object
+---@param obj any
+---@param param any (optional)
+---@param state any (optional)
+---@return Iterator
 local iter = function(obj, param, state)
   return wrap(rawiter(obj, param, state))
 end
@@ -123,20 +160,41 @@ exports.wrap = wrap
 exports.unwrap = unwrap
 
 function Iterator:for_each(fn)
-  local state = self.state
-  local param = self.param
+  local param, state = self.param, self.state
   repeat
     state = call_if_not_empty(fn, self.gen(param, state))
   until state == nil
 end
 
 function Iterator:stateful()
-  return co.wrap(function()
+  return wrap(co.wrap(function()
     self:for_each(function(...)
-      co.yield(...)
+      co.yield(f.first(...), ...)
     end)
-  end)
+
+    -- too make sure that we always return nil if there are no more
+    while true do
+      co.yield()
+    end
+  end), nil, nil)
 end
+
+-- function Iterator:stateful()
+--   local gen, param, state = self.gen, self.param, self.state
+
+--   local function return_and_set_state(state_x, ...)
+--     state = state_x
+--     if state == nil then return end
+--     return state_x, ...
+--   end
+
+--   local stateful_gen = function()
+--     return return_and_set_state(gen(param, state))
+--   end
+
+--   return wrap(stateful_gen, false, false)
+-- end
+
 --------------------------------------------------------------------------------
 -- Generators
 --------------------------------------------------------------------------------
@@ -158,6 +216,11 @@ local range_rev_gen = function(param, state)
   return state, state
 end
 
+---Creates a range iterator
+---@param start number
+---@param stop number
+---@param step number
+---@return Iterator
 local range = function(start, stop, step)
   if step == nil then
     if stop == nil then
@@ -181,19 +244,181 @@ local range = function(start, stop, step)
     return wrap(range_rev_gen, {stop, step}, start - step)
   end
 end
-
 exports.range = range
 
+local duplicate_table_gen = function(param_x, state_x)
+  return state_x + 1, unpack(param_x)
+end
+
+local duplicate_fun_gen = function(param_x, state_x)
+  return state_x + 1, param_x(state_x)
+end
+
+local duplicate_gen = function(param_x, state_x)
+  return state_x + 1, param_x
+end
+
+---Creates an infinite iterator that will yield the arguments
+---If multiple arguments are passed, the args will be packed and unpacked
+---@param ...: the arguments to duplicate
+---@return Iterator
+local duplicate = function(...)
+  if select('#', ...) <= 1 then
+    return wrap(duplicate_gen, select(1, ...), 0)
+  else
+    return wrap(duplicate_table_gen, {...}, 0)
+  end
+end
+exports.duplicate = duplicate
+
+---Creates an iterator from a function
+---NOTE: if the function is a closure and modifies state, the resulting iterator will not be stateless
+---@param fun function
+---@return Iterator
+local from_fun = function(fun)
+  assert(type(fun) == "function")
+  return wrap(duplicate_fun_gen, fun, 0)
+end
+exports.from_fun = from_fun
+
+---Creates an infinite iterator that will yield zeros.
+---This is an alias to calling duplicate(0)
+---@return Iterator
+local zeros = function()
+  return wrap(duplicate_gen, 0, 0)
+end
+exports.zeros = zeros
+
+---Creates an infinite iterator that will yield ones.
+---This is an alias to calling duplicate(1)
+---@return Iterator
+local ones = function()
+  return wrap(duplicate_gen, 1, 0)
+end
+exports.ones = ones
+
+local rands_gen = function(param_x, _state_x)
+  return 0, math.random(param_x[1], param_x[2])
+end
+
+local rands_nil_gen = function(_param_x, _state_x)
+  return 0, math.random()
+end
+
+---Creates an infinite iterator that will yield random values.
+---@param n number
+---@param m number
+---@return Iterator
+local rands = function(n, m)
+  if n == nil and m == nil then
+    return wrap(rands_nil_gen, 0, 0)
+  end
+  assert(type(n) == "number", "invalid first arg to rands")
+  if m == nil then
+    m = n
+    n = 0
+  else
+    assert(type(m) == "number", "invalid second arg to rands")
+  end
+  assert(n < m, "empty interval")
+  return wrap(rands_gen, {n, m - 1}, 0)
+end
+exports.rands = rands
+
+local split_gen = function(param, state)
+  local input, sep = param[1], param[2]
+  local input_len = #input
+
+  if state > input_len + 1 then
+    return
+  end
+
+  local start, finish = string.find(input, sep, state, true)
+  if not start then
+    start = input_len + 1
+    finish = input_len + 1
+  end
+
+  local sub_str = input:sub(state, start - 1)
+
+  return finish + 1, sub_str
+end
+
+---Return an iterator of substrings separated by a string
+---@param input string: the string to split
+---@param sep string: the separator to find and split based on
+---@return Iterator
+local split = function(input, sep)
+  return wrap(split_gen, {input, sep}, 1)
+end
+exports.split = split
+
+---Splits a string based on a single space
+---An alias for split(input, " ")
+---@param input any
+---@return any
+local words = function(input)
+  return split(input, " ")
+end
+exports.words = words
+
+local lines = function(input)
+  -- TODO: platform specific linebreaks
+  return split(input, "\n")
+end
+exports.lines = lines
+
 --------------------------------------------------------------------------------
 -- Transformations
 --------------------------------------------------------------------------------
 local map_gen = function(param, state)
-    local gen_x, param_x, fun = param[1], param[2], param[3]
-    return call_if_not_empty(fun, gen_x(param_x, state))
+  local gen_x, param_x, fun = param[1], param[2], param[3]
+  return call_if_not_empty(fun, gen_x(param_x, state))
+end
+
+---Iterator adapter that maps the previous iterator with a function
+---@param fun function: The function to map with. Will be called on each element
+---@return Iterator
+function Iterator:map(fun)
+  return wrap(map_gen, {self.gen, self.param, fun}, self.state)
 end
 
-function Iterator:map(fn)
-  return wrap(map_gen, {self.gen, self.param, fn}, self.state)
+local flatten_gen1
+do
+  local it = function(new_iter, state_x, ...)
+    if state_x == nil then return nil end
+    return {new_iter.gen, new_iter.param, state_x}, ...
+  end
+
+  flatten_gen1 = function(state, state_x, ...)
+    if state_x == nil then
+      return nil
+    end
+
+    local first_arg = f.first(...)
+
+    -- experimental part
+    if getmetatable(first_arg) == Iterator then
+      -- attach the iterator to the rest
+      local new_iter = (first_arg .. wrap(state[1], state[2], state_x)):flatten()
+      -- advance the iterator by one
+      return it(new_iter, new_iter.gen(new_iter.param, new_iter.state))
+    end
+
+    return {state[1], state[2], state_x}, ...
+  end
+end
+
+local flatten_gen = function(_, state)
+  if state == nil then return end
+  local gen_x, param_x, state_x = state[1], state[2], state[3]
+  return flatten_gen1(state, gen_x(param_x, state_x))
+end
+
+---Iterator adapter that will recursivley flatten nested iterator structure
+---@return Iterator
+function Iterator:flatten()
+  return wrap(flatten_gen, false, {self.gen, self.param, self.state})
 end
 
 --------------------------------------------------------------------------------
@@ -240,45 +465,61 @@ local filter_gen = function(param, state_x)
     return filter_detect(fun, gen_x, param_x, gen_x(param_x, state_x))
 end
 
-function Iterator:filter(fn)
-  return wrap(filter_gen, {self.gen, self.param, fn}, self.state)
+---Iterator adapter that will filter values
+---@param fun function: The function to filter values with. If the function returns true, the value will be kept.
+---@return Iterator
+function Iterator:filter(fun)
+  return wrap(filter_gen, {self.gen, self.param, fun}, self.state)
 end
 
 --------------------------------------------------------------------------------
 -- Reducing
 --------------------------------------------------------------------------------
-function Iterator:any(fn)
+
+---Returns true if any of the values in the iterator satisfy a predicate
+---@param fun function
+---@return boolean
+function Iterator:any(fun)
   local r
   local state, param, gen = self.state, self.param, self.gen
   repeat
-    state, r = call_if_not_empty(fn, gen(param, state))
+    state, r = call_if_not_empty(fun, gen(param, state))
   until state == nil or r
   return r
 end
 
-function Iterator:all(fn)
+---Returns true if all of the values in the iterator satisfy a predicate
+---@param fun function
+---@return boolean
+function Iterator:all(fun)
   local r
   local state, param, gen = self.state, self.param, self.gen
   repeat
-    state, r = call_if_not_empty(fn, gen(param, state))
+    state, r = call_if_not_empty(fun, gen(param, state))
   until state == nil or not r
   return state == nil
 end
 
+---Finds a value that is equal to the provided value of satisfies a predicate.
+---@param val_or_fn any
+---@return any
 function Iterator:find(val_or_fn)
-  local fn
-  if type(val_or_fn) ~= "function" then
-    fn = function(...)
-      return ... == val_or_fn
-    end
+  local gen, param, state = self.gen, self.param, self.state
+  if type(val_or_fn) == "function" then
+    return return_if_not_empty(filter_detect(val_or_fn, gen, param, gen(param, state)))
   else
-    fn = val_or_fn
+    for _, r in gen, param, state do
+      if r == val_or_fn then
+        return r
+      end
+    end
+    return nil
   end
-
-  local gen, param, state = self.gen, self.param, self.state
-  return return_if_not_empty(filter_detect(fn, gen, param, gen(param, state)))
 end
 
+---Turns an iterator into a list.
+---If the iterator yields multivals only the first multival will be used.
+---@return table
 function Iterator:tolist()
   local list = {}
   self:for_each(function(a)
@@ -287,6 +528,9 @@ function Iterator:tolist()
   return list
 end
 
+---Turns an iterator into a list.
+---If the iterator yields multivals all multivals will be used and packed into a table.
+---@return table
 function Iterator:tolistn()
   local list = {}
   self:for_each(function(...)
@@ -295,6 +539,10 @@ function Iterator:tolistn()
   return list
 end
 
+---Turns an iterator into a map.
+---The first multival that the iterator yields will be the key.
+---The second multival that the iterator yields will be the value.
+---@return table
 function Iterator:tomap()
   local map = {}
   self:for_each(function(key, value)
@@ -310,12 +558,11 @@ end
 local chain_gen_r1
 local chain_gen_r2 = function(param, state, state_x, ...)
   if state_x == nil then
-    local i = state[1]
-    i = i + 1
+    local i = state[1] + 1
     if param[3 * i - 1] == nil then
       return nil
     end
-    local state_x = param[3 * i]
+    state_x = param[3 * i]
     return chain_gen_r1(param, {i, state_x})
   end
   return {state[1], state_x}, ...
@@ -324,16 +571,24 @@ end
 chain_gen_r1 = function(param, state)
   local i, state_x = state[1], state[2]
   local gen_x, param_x = param[3 * i - 2], param[3 * i - 1]
-  return chain_gen_r2(param, state, gen_x(param_x, state[2]))
+  return chain_gen_r2(param, state, gen_x(param_x, state_x))
 end
 
+---Make an iterator that returns elements from the first iterator until it is exhausted, then proceeds to the next iterator,
+---until all of the iterators are exhausted.
+---Used for treating consecutive iterators as a single iterator.
+---Infinity iterators are supported, but are not recommended.
+---@param ...: the iterators to chain
+---@return Iterator
 local chain = function(...)
   local n = numargs(...)
+
   if n == 0 then
     return wrap(nil_gen, nil, nil)
   end
 
   local param = { [3 * n] = 0 }
+
   local i, gen_x, param_x, state_x
   for i = 1, n, 1 do
     local elem = select(i, ...)
@@ -350,52 +605,52 @@ Iterator.chain = chain
 Iterator.__concat = chain
 exports.chain = chain
 
---------------------------------------------------------------------------------
--- Operators
---------------------------------------------------------------------------------
-exports.operator = {
-    ----------------------------------------------------------------------------
-    -- Comparison operators
-    ----------------------------------------------------------------------------
-    lt  = function(a, b) return a < b end,
-    le  = function(a, b) return a <= b end,
-    eq  = function(a, b) return a == b end,
-    ne  = function(a, b) return a ~= b end,
-    ge  = function(a, b) return a >= b end,
-    gt  = function(a, b) return a > b end,
-
-    ----------------------------------------------------------------------------
-    -- Arithmetic operators
-    ----------------------------------------------------------------------------
-    add = function(a, b) return a + b end,
-    div = function(a, b) return a / b end,
-    floordiv = function(a, b) return math.floor(a/b) end,
-    intdiv = function(a, b)
-        local q = a / b
-        if a >= 0 then return math.floor(q) else return math.ceil(q) end
-    end,
-    mod = function(a, b) return a % b end,
-    mul = function(a, b) return a * b end,
-    neq = function(a) return -a end,
-    unm = function(a) return -a end, -- an alias
-    pow = function(a, b) return a ^ b end,
-    sub = function(a, b) return a - b end,
-    truediv = function(a, b) return a / b end,
-
-    ----------------------------------------------------------------------------
-    -- String operators
-    ----------------------------------------------------------------------------
-    concat = function(a, b) return a..b end,
-    len = function(a) return #a end,
-    length = function(a) return #a end, -- an alias
-
-    ----------------------------------------------------------------------------
-    -- Logical operators
-    ----------------------------------------------------------------------------
-    land = function(a, b) return a and b end,
-    lor = function(a, b) return a or b end,
-    lnot = function(a) return not a end,
-    truth = function(a) return not not a end,
-}
+local function zip_gen_r(param, state, state_new, ...)
+  if #state_new == #param / 2 then
+    return state_new, ...
+  end
+
+  local i = #state_new + 1
+  local gen_x, param_x = param[2 * i - 1], param[2 * i]
+  local state_x, r = gen_x(param_x, state[i])
+  if state_x == nil then
+    return nil
+  end
+  table.insert(state_new, state_x)
+  return zip_gen_r(param, state, state_new, r, ...)
+end
+
+local zip_gen = function(param, state)
+  return zip_gen_r(param, state, {})
+end
+
+---Return a new iterator where i-th return value contains the i-th element from each of the iterators.
+---The returned iterator is truncated in length to the length of the shortest iterator.
+---For multi-return iterators only the first variable is used.
+---@param ...: the iterators to zip
+---@return Iterator
+local zip = function(...)
+  local n = numargs(...)
+  if n == 0 then
+    return wrap(nil_gen, nil, nil)
+  end
+  local param = { [2 * n] = 0 }
+  local state = { [n] = 0 }
+
+  local i, gen_x, param_x, state_x
+  for i = 1, n, 1 do
+    local it = select(n - i + 1, ...)
+    gen_x, param_x, state_x = rawiter(it)
+    param[2 * i - 1] = gen_x
+    param[2 * i] = param_x
+    state[i] = state_x
+  end
+
+  return wrap(zip_gen, param, state)
+end
+
+Iterator.zip = zip
+Iterator.__div = zip
+exports.zip = zip
 
 return exports
diff --git a/lua/plenary/job.lua b/lua/plenary/job.lua
index ffdb5f3..267dd35 100644
--- a/lua/plenary/job.lua
+++ b/lua/plenary/job.lua
@@ -48,8 +48,9 @@ end
 
 local function expand(path)
   if vim.in_fast_event() then
-    return uv.fs_realpath(path)
+    return assert(uv.fs_realpath(path), string.format("Path must be valid: %s", path))
   else
+    -- TODO: Probably want to check that this is valid here... otherwise that's weird.
     return vim.fn.expand(path, true)
   end
 end
@@ -108,7 +109,7 @@ function Job:new(o)
 
   obj.command = command
   obj.args = args
-  obj.cwd = o.cwd and expand(o.cwd)
+  obj._raw_cwd = o.cwd
   if o.env then
     if type(o.env) ~= "table" then error('[plenary.job] env has to be a table') end
 
@@ -264,7 +265,7 @@ function Job:_create_uv_options()
   options.args = self.args
   options.stdio = { self.stdin, self.stdout, self.stderr }
 
-  if self.cwd then options.cwd = self.cwd end
+  if self._raw_cwd then options.cwd = expand(self._raw_cwd) end
   if self.env then options.env = self.env end
 
   return options
@@ -468,7 +469,11 @@ function Job:wait(timeout, wait_interval, should_redraw)
   wait_interval = wait_interval or 10
 
   if self.handle == nil then
-    vim.api.nvim_err_writeln(vim.inspect(self))
+    local msg = vim.inspect(self)
+    vim.schedule(function()
+      vim.api.nvim_err_writeln(msg)
+    end)
+
     return
   end
 
@@ -555,6 +560,7 @@ end
 
 function Job:after(fn)
   self:add_on_exit_callback(fn)
+  return self
 end
 
 function Job:and_then_on_success(next_job)
diff --git a/lua/plenary/log.lua b/lua/plenary/log.lua
index e10e963..7671d75 100644
--- a/lua/plenary/log.lua
+++ b/lua/plenary/log.lua
@@ -18,7 +18,8 @@ local default_config = {
   plugin = 'plenary',
 
   -- Should print the output to neovim while running
-  use_console = true,
+  -- values: 'sync','async',false
+  use_console = 'async',
 
   -- Should highlighting be used in console (using echohl)
   highlights = true,
@@ -51,7 +52,7 @@ local unpack = unpack or table.unpack
 log.new = function(config, standalone)
   config = vim.tbl_deep_extend("force", default_config, config)
 
-  local outfile = string.format('%s/%s.log', vim.api.nvim_call_function('stdpath', {'data'}), config.plugin)
+  local outfile = string.format('%s/%s.log', vim.api.nvim_call_function('stdpath', {'cache'}), config.plugin)
 
   local obj
   if standalone then
@@ -103,7 +104,7 @@ log.new = function(config, standalone)
 
     -- Output to console
     if config.use_console then
-      vim.schedule(function()
+      local log_to_console = function()
         local console_string = string.format(
           "[%-6s%s] %s: %s",
           nameupper,
@@ -129,7 +130,12 @@ log.new = function(config, standalone)
         if config.highlights and level_config.hl then
           vim.cmd "echohl NONE"
         end
-      end)
+      end
+      if config.use_console == 'sync' and not vim.in_fast_event() then
+        log_to_console()
+      else
+        vim.schedule(log_to_console)
+      end
     end
 
     -- Output to log file
diff --git a/lua/plenary/neorocks/init.lua b/lua/plenary/neorocks/init.lua
index 7e1785a..a29f53c 100644
--- a/lua/plenary/neorocks/init.lua
+++ b/lua/plenary/neorocks/init.lua
@@ -1,430 +1,1 @@
-local Job = require('plenary.job')
-local Path = require('plenary.path')
-
-local log = require('plenary.log')
-local run = require('plenary.run')
-local window_float = require('plenary.window.float')
-
--- TODO: We should consider not making windows when headless.
-local headless = require('plenary.nvim_meta').is_headless
-
-local neorocks = {}
-
-neorocks.scheduler = require('plenary.neorocks.scheduler'):new()
-
-neorocks.job_with_display_output = function(title_text, command, args)
-  log.debug("Starting job:", title_text, command, args)
-
-  if type(title_text) == "string" then
-    title_text = {title_text}
-  end
-
-  if headless then
-    io.write(command .. '\n')
-    io.write(vim.inspect(args) .. '\n')
-  end
-
-  local outputter = vim.schedule_wrap(function(_, data, self)
-    if data == nil then
-      return
-    end
-
-    if not self then
-      return
-    end
-
-    if headless then
-      io.write(data .. '\n')
-      return
-    end
-
-    local bufnr = self.user_data.views.bufnr
-    local win_id = self.user_data.views.win_id
-
-    if not vim.api.nvim_buf_is_valid(bufnr) then
-      return
-    end
-
-    local split_data = vim.split(data, "\n")
-    if #split_data > 1 and split_data[#split_data] == "" then
-      split_data[#split_data] = nil
-    end
-
-    vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, split_data)
-
-    if not vim.api.nvim_win_is_valid(win_id) then
-      return
-    end
-
-    local final_row = #vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-    vim.api.nvim_win_set_cursor(win_id, {final_row, 0})
-  end)
-
-  return Job:new {
-    -- TODO: Should test if this splits or not... otherwise gotta do that annoyin thing
-    command = command,
-    args = args,
-
-    on_start = function(self)
-      self.user_data.views = window_float.centered_with_top_win(title_text, {winblend = 0})
-
-      vim.fn.win_gotoid(self.user_data.views.win_id)
-      vim.api.nvim_win_set_option(self.user_data.views.win_id, 'wrap', false)
-    end,
-
-    on_stdout = outputter,
-    on_stderr = outputter,
-
-    on_exit = vim.schedule_wrap(function(self, code)
-      local bufnr = self.user_data.views.bufnr
-      if not vim.api.nvim_buf_is_valid(bufnr) then return end
-
-      if code == 0 then
-        vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {"", ("="):rep(40), "  Success! <Enter> to close.", ("="):rep(40)})
-      else
-        vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {"", ("="):rep(40), "  Failed to complete. <Enter> to close.", ("="):rep(40)})
-      end
-
-      local win_id = self.user_data.views.win_id
-      if not vim.api.nvim_win_is_valid(win_id) then return end
-
-      local final_row = #vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-      vim.api.nvim_win_set_cursor(win_id, {final_row, 0})
-
-      vim.api.nvim_buf_set_keymap(bufnr, 'n', '<CR>', ':quit<CR>', {silent = true, noremap = true})
-    end)
-  }
-end
-
----@return table Of the form: {
----     lua: Lua Version (5.1, 5.2, etc.)
----     jit: Jit Version (2.1.0-beta3, or nil)
----     dir: Directory of hererocks installation
---- }
-neorocks._lua_version = (function()
-  if jit then
-    return {
-      lua = string.gsub(_VERSION, "Lua ", ""),
-      jit = string.gsub(jit.version, "LuaJIT ", ""),
-      dir = string.gsub(jit.version, "LuaJIT ", "")
-    }
-  end
-
-  error("NEOROCKS: Unsupported Lua Versions", _VERSION)
-end)()
-
-neorocks._base_path                  = Path:new(vim.fn.stdpath('cache'), 'plenary_hererocks')
-neorocks._hererocks_file             = Path:new(vim.fn.stdpath('cache'), 'hererocks.py')
-neorocks._hererocks_install_location = Path:new(neorocks._base_path, neorocks._lua_version.dir)
-neorocks._is_setup                   = vim.fn.isdirectory(Path:new(neorocks._hererocks_install_location, "lib"):absolute()) > 0
-
-neorocks._get_hererocks_job = function()
-  local url_loc = 'https://raw.githubusercontent.com/luarocks/hererocks/latest/hererocks.py'
-
-  local command, args
-  if vim.fn.executable('curl') > 0 then
-    command = 'curl'
-    args = {url_loc, '-o', neorocks._hererocks_file:absolute()}
-  elseif vim.fn.executable('wget') > 0 then
-    command = 'wget'
-    args = {url_loc, '-O', neorocks._hererocks_file:absolute(), '--verbose'}
-  else
-    error('"curl" or "wget" is required')
-  end
-
-  return neorocks.job_with_display_output(
-    {"Installing hererocks"},
-    command,
-    args
-  )
-end
-
-neorocks._get_setup_job = function(force, opts)
-  local lua_version = neorocks._lua_version
-  local install_location = neorocks._hererocks_install_location
-
-  if force == nil then
-    force = false
-  end
-
-  if opts == nil then
-    opts = {}
-    opts.split = true
-    opts.wait = true
-  end
-
-  if neorocks._is_setup and not force then
-    return
-  end
-
-  if vim.fn.filereadable(neorocks._hererocks_file:absolute()) == 0 then
-    neorocks.scheduler:insert(neorocks._get_hererocks_job(opts))
-  end
-
-  if lua_version.jit then
-    return neorocks.job_with_display_output(
-      {"Installing luajit & luarocks"},
-      -- TODO: This should be a user specified python, just in case?
-      "python",
-      {
-        neorocks._hererocks_file:absolute(),
-        "--verbose",
-        force and "-i" or "",
-        "-j",
-        lua_version.jit,
-        "-r",
-        "latest",
-        install_location:absolute()
-      }
-    )
-  else
-    error("Unsupported version")
-  end
-end
-
-neorocks.setup = function(force, quit)
-  vim.fn.mkdir(neorocks._base_path:absolute(), "p")
-  quit = (quit == nil and true) or quit
-  neorocks.scheduler:insert(neorocks._get_setup_job(force))
-
-  if quit then
-    neorocks.scheduler:insert {
-      start = function()
-        vim.cmd [[qa!]]
-      end,
-
-      add_on_exit_callback = function()
-      end,
-    }
-  end
-end
-
-neorocks._get_package_paths = function()
-  local lua_version = neorocks._lua_version
-  local install_location = neorocks._hererocks_install_location
-
-  local install_path = install_location:joinpath(
-    "lib",
-    "luarocks",
-    string.format("rocks-%s", lua_version.lua)
-  )
-
-  local share_path = install_location:joinpath(
-    "share",
-    "lua",
-    lua_version.lua
-  )
-
-  local gen_pattern = function(directory)
-    return string.format(
-    "%s/?.lua;%s/?/init.lua",
-    directory,
-    directory
-  )
-  end
-
-  return gen_pattern(share_path:absolute()) .. ';' .. gen_pattern(install_path:absolute())
-end
-
---- Idempotent adding of paths for both package.path and package.cpath
-neorocks.setup_paths = function()
-  if not neorocks._is_setup then
-    return
-  end
-
-  if neorocks._path_setup_complete then
-    return
-  end
-
-  local lua_version = neorocks._lua_version
-  local install_location = neorocks._hererocks_install_location
-
-  local match_install_path = neorocks._get_package_paths()
-
-  if not string.find(package.path, match_install_path, 1, true) then
-    package.path = package.path .. ';' .. match_install_path
-  end
-
-  local install_cpath = install_location:joinpath("lib", "lua", lua_version.lua)
-  local match_install_cpath = string.format(
-    "%s/?.so",
-    install_cpath:absolute()
-  )
-  if not string.find(package.cpath, match_install_cpath, 1, true) then
-    package.cpath = package.cpath .. ';' .. match_install_cpath
-  end
-
-  neorocks._path_setup_complete = true
-end
-
--- activate hererocks based on current $SHELL
-local function source_activate(install_location, activate_file)
-  return string.format('source %s', install_location:joinpath('bin', activate_file):absolute())
-end
-
---- Get the string to source hererocks
-neorocks._source_string = function(install_location)
-  local user_shell = os.getenv("SHELL") or "bash"
-  local shell = user_shell:gmatch("([^/]*)$")()
-  if shell == "fish" then
-    return source_activate(install_location, 'activate.fish')
-  elseif shell == "csh" then
-    return source_activate(install_location, 'activate.csh')
-  end
-  return source_activate(install_location, 'activate')
-end
-
-neorocks._luarocks_exec = function(luarocks_arg)
-  local install_location = neorocks._hererocks_install_location
-
-  return vim.fn.systemlist(string.format(
-    "%s && luarocks %s",
-    neorocks._source_string(install_location),
-    luarocks_arg
-  ))
-end
-
---- Properly source and run a luarocks command. Will run `luarocks $luarocks_arg`
-neorocks._luarocks_run = function(luarocks_arg)
-  local install_location = neorocks._hererocks_install_location
-  local source_string = neorocks._source_string(install_location)
-
-  run.with_displayed_output(
-    {"Lua rocks install"},
-    string.format(
-      '%s && luarocks %s',
-      source_string,
-      luarocks_arg
-    ),
-    {
-      split = true,
-      wait = true
-    }
-  )
-end
-
---- Properly source and return the output of a luarocks command.
-neorocks._get_luarocks_job = function(luarocks_arg)
-  local install_location = neorocks._hererocks_install_location
-  local source_string = neorocks._source_string(install_location)
-
-  local command = "bash"
-  local args = {"-c", string.format('%s && luarocks %s', source_string, luarocks_arg)}
-
-  return neorocks.job_with_display_output("Luarocks: " .. luarocks_arg, command, args)
-end
-
-
-neorocks._get_install_job = function(package_name)
-  return neorocks._get_luarocks_job(string.format('install %s', package_name))
-end
-
-neorocks.install = function(package_name, lua_name, force, should_quit)
-  neorocks.scheduler:insert(neorocks._get_setup_job())
-
-  if not force and neorocks.is_package_installed(package_name, lua_name) then
-    print(package_name, 'is already installed!')
-    return
-  end
-
-  local install_job = neorocks.scheduler:insert(neorocks._get_install_job(package_name))
-
-  if headless and should_quit == nil then
-    should_quit = true
-  end
-
-  if should_quit then
-    neorocks.scheduler:insert {
-      start = function()
-        vim.cmd [[qa!]]
-      end,
-
-      add_on_exit_callback = function()
-      end,
-    }
-  end
-
-  return install_job
-end
-
-neorocks.ensure_installed = function(package_name, lua_name)
-  -- Don't try and install on startup. It's annoying.
-  -- Maybe someday we can do it.
-  if not neorocks._is_setup then
-    vim.api.nvim_err_writeln("Neorocks is not yet set up. Please manually install")
-    return
-  end
-
-  neorocks.setup_paths()
-
-  if lua_name == nil then
-    lua_name = package_name
-  end
-
-  if neorocks.is_package_installed(package_name, lua_name) then
-    return
-  end
-
-  neorocks.install(package_name, lua_name):wait(60000, 100, true)
-end
-
-neorocks.remove = function(package_name)
-  neorocks._luarocks_run(string.format('remove %s', package_name))
-end
-
-neorocks.list = function(package_filter)
-  if package_filter == nil then
-    package_filter = ''
-  end
-
-  local result = {}
-  for _, line in ipairs(
-    neorocks._luarocks_exec(
-      string.format('list %s --porcelain', package_filter),
-      true
-    )
-  ) do
-    for l_package, version, status, install_path in string.gmatch(line, "([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)") do
-      table.insert(result, {
-        l_package = l_package,
-        version = version,
-        status = status,
-        install_path = install_path
-      })
-    end
-  end
-
-  return result
-end
-
-neorocks.is_package_installed = function(package_name, lua_name)
-  -- TODO: Decided if I need anything more than this.
-  -- This tells me if it's available or not, which is really all I care about.
-  if lua_name == nil then
-    lua_name = package_name
-  end
-
-  if package.loaded[package_name] then
-    return true
-  end
-
-  local can_import, _ = pcall(function() return require(lua_name) end)
-  if can_import then
-    return true
-  end
-
-  local options = neorocks.list(package_name)
-
-  for _, p in ipairs(options) do
-    if p.l_package == package_name then
-      return true
-    end
-  end
-
-  return false
-end
-
--- package.searchpath(${1:name: string}, ${2:path: string}, ${3:sep: string}, ${4:rep: string})
--- package.searchpath("pl", package.path)
-
-return neorocks
+error("neorocks is no longer supported. Please use packer.nvim or other project for neorocks usage.")
diff --git a/lua/plenary/neorocks/scheduler.lua b/lua/plenary/neorocks/scheduler.lua
deleted file mode 100644
index 4127e71..0000000
--- a/lua/plenary/neorocks/scheduler.lua
+++ /dev/null
@@ -1,44 +0,0 @@
-local scheduler = {}
-
-function scheduler:new()
-  return setmetatable({
-    items = {},
-
-    in_progress = nil,
-  }, {
-    __index = self,
-  })
-end
-
-function scheduler:insert(item)
-  if not item then
-    return item
-  end
-
-  table.insert(self.items, item)
-
-  if not self.in_progress then
-    self:_run_item(item)
-  else
-    self:_chain_item(item)
-  end
-
-  return item
-end
-
-function scheduler:_run_item(item)
-  self.in_progress = true
-  item:add_on_exit_callback(function()
-    self.in_progress = false
-  end)
-
-  item:start()
-end
-
-function scheduler:_chain_item(item)
-  self.items[#self.items - 1]:add_on_exit_callback(vim.schedule_wrap(function()
-    self:_run_item(item)
-  end))
-end
-
-return scheduler
diff --git a/lua/plenary/operators.lua b/lua/plenary/operators.lua
new file mode 100644
index 0000000..2f80ef1
--- /dev/null
+++ b/lua/plenary/operators.lua
@@ -0,0 +1,50 @@
+---@brief [[
+---Operators that are functions.
+---This is useful when you want to pass operators to higher order functions.
+---Lua has no currying so we have to make a function for each operator.
+---@brief ]]
+
+return {
+  ----------------------------------------------------------------------------
+  -- Comparison operators
+  ----------------------------------------------------------------------------
+  lt  = function(a, b) return a < b end,
+  le  = function(a, b) return a <= b end,
+  eq  = function(a, b) return a == b end,
+  ne  = function(a, b) return a ~= b end,
+  ge  = function(a, b) return a >= b end,
+  gt  = function(a, b) return a > b end,
+
+  ----------------------------------------------------------------------------
+  -- Arithmetic operators
+  ----------------------------------------------------------------------------
+  add = function(a, b) return a + b end,
+  div = function(a, b) return a / b end,
+  floordiv = function(a, b) return math.floor(a/b) end,
+  intdiv = function(a, b)
+    local q = a / b
+    if a >= 0 then return math.floor(q) else return math.ceil(q) end
+  end,
+  mod = function(a, b) return a % b end,
+  mul = function(a, b) return a * b end,
+  neq = function(a) return -a end,
+  unm = function(a) return -a end, -- an alias
+  pow = function(a, b) return a ^ b end,
+  sub = function(a, b) return a - b end,
+  truediv = function(a, b) return a / b end,
+
+  ----------------------------------------------------------------------------
+  -- String operators
+  ----------------------------------------------------------------------------
+  concat = function(a, b) return a..b end,
+  len = function(a) return #a end,
+  length = function(a) return #a end, -- an alias
+
+  ----------------------------------------------------------------------------
+  -- Logical operators
+  ----------------------------------------------------------------------------
+  land = function(a, b) return a and b end,
+  lor = function(a, b) return a or b end,
+  lnot = function(a) return not a end,
+  truth = function(a) return not not a end,
+}
diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua
index 02e0025..2de31c2 100644
--- a/lua/plenary/path.lua
+++ b/lua/plenary/path.lua
@@ -7,8 +7,6 @@ local uv = vim.loop
 
 local F = require('plenary.functional')
 
-
-
 local S_IF = {
   -- S_IFDIR  = 0o040000  # directory
   DIR = 0x4000,
@@ -54,11 +52,47 @@ end
 
 local function is_root(pathname)
   if path.sep == '\\' then
-    return string.match(pathname, '^[A-Z]:\\$')
+    return string.match(pathname, '^[A-Z]:\\?$')
   end
   return pathname == '/'
 end
 
+local _split_by_separator = (function()
+    local formatted =  string.format("([^%s]+)", path.sep)
+    return function(filepath)
+        local t = {}
+        for str in string.gmatch(filepath, formatted) do
+            table.insert(t, str)
+        end
+        return t
+    end
+end)()
+
+local function _normalize_path(filename)
+  local out_file = filename
+
+  local has = string.find(filename, "..", 1, true)
+
+  if has then
+      local parts = _split_by_separator(filename)
+
+      local idx = 1
+      repeat
+          if parts[idx] == ".." then
+              table.remove(parts, idx)
+              table.remove(parts, idx - 1)
+              idx = idx - 2
+          end
+          idx = idx + 1
+      until idx > #parts
+
+      out_file = path.root(filename) .. table.concat(parts, path.sep)
+  end
+
+  return out_file
+end
+
+
 local clean = function(pathname)
   -- Remove double path seps, it's annoying
   pathname = pathname:gsub(path.sep .. path.sep, path.sep)
@@ -206,9 +240,9 @@ end
 
 function Path:absolute()
   if self:is_absolute() then
-    return self.filename
+    return _normalize_path(self.filename)
   else
-    return self._absolute or table.concat({self._cwd, self.filename}, self._sep)
+    return _normalize_path(self._absolute or table.concat({self._cwd, self.filename}, self._sep))
   end
 end
 
@@ -266,11 +300,32 @@ function Path:normalize(cwd)
   -- Substitute home directory w/ "~"
   self.filename = self.filename:gsub("^" .. path.home, '~', 1)
 
-  return self.filename
+  return _normalize_path(self.filename)
+end
+
+local function shorten_len(filename, len)
+  local final_match
+  local final_path_components = {}
+  for match in (filename..path.sep):gmatch("(.-)"..path.sep) do
+    if #match > len then
+      table.insert(final_path_components, string.sub(match, 1, len))
+    else
+      table.insert(final_path_components, match)
+    end
+    table.insert(final_path_components, path.sep)
+    final_match = match
+  end
+
+  local l = #final_path_components -- so that we don't need to keep calculating length
+  table.remove(final_path_components, l) -- remove final slash
+  table.remove(final_path_components, l-1) -- remvove shortened final component
+  table.insert(final_path_components, final_match) -- insert full final component
+
+  return table.concat(final_path_components)
 end
 
 local shorten = (function()
-  if jit then
+  if jit and path.sep ~= '\\' then
     local ffi = require('ffi') ffi.cdef [[
     typedef unsigned char char_u;
     char_u *shorten_dir(char_u *str);
@@ -286,11 +341,15 @@ local shorten = (function()
     end
   end
   return function(filename)
-    return filename
+    shorten_len(filename, 1)
   end
 end)()
 
-function Path:shorten()
+function Path:shorten(len)
+  assert(len ~= 0, 'len must be at least 1')
+  if len and len > 1 then
+    return shorten_len(self.filename, len)
+  end
   return shorten(self.filename)
 end
 
@@ -422,7 +481,7 @@ function Path:rm(opts)
     -- first unlink all files
     scan.scan_dir(abs, { hidden = true, on_insert = function(file) uv.fs_unlink(file) end})
 
-    local dirs = scan.scan_dir(abs, { add_dirs = true })
+    local dirs = scan.scan_dir(abs, { add_dirs = true, hidden = true })
     -- iterate backwards to clean up remaining dirs
     for i = #dirs, 1, -1 do
       uv.fs_rmdir(dirs[i])
diff --git a/lua/plenary/tbl.lua b/lua/plenary/tbl.lua
index 9de42b1..44de295 100644
--- a/lua/plenary/tbl.lua
+++ b/lua/plenary/tbl.lua
@@ -16,4 +16,25 @@ function tbl.apply_defaults(original, defaults)
   return original
 end
 
+function tbl.pack(...)
+  return {n=select('#',...); ...}
+end
+
+function tbl.unpack(t, i, j)
+  return unpack(t, i or 1, j or t.n or #t)
+end
+
+---Freeze a table. A frozen table is not able to be modified.
+---http://lua-users.org/wiki/ReadOnlyTables
+---@param t table
+---@return table
+function tbl.freeze(t)
+  return setmetatable({}, {
+    __index = t,
+    __newindex = function()
+      error("Attempt to modify frozen table")
+    end
+  })
+end
+
 return tbl
diff --git a/plugin/plenary.vim b/plugin/plenary.vim
index a6ae381..d0ac14b 100644
--- a/plugin/plenary.vim
+++ b/plugin/plenary.vim
@@ -1,7 +1,4 @@
 
-" Set up neorocks if it is installed.
-lua pcall(function() require('plenary.neorocks').setup_paths() end)
-
 " Create command for running busted
 command! -nargs=1 -complete=file PlenaryBustedFile
       \ lua require('plenary.busted').run(vim.fn.expand("<args>"))
diff --git a/scratch/iterators.lua b/scratch/iterators.lua
new file mode 100644
index 0000000..ebc1ea2
--- /dev/null
+++ b/scratch/iterators.lua
@@ -0,0 +1,4 @@
+local i = require('plenary.iterators')
+
+dump(i.split("  hello person dude ", " "):tolist())
+-- dump(("hello person"):find("person"))
diff --git a/scratch/neorocks_thoughts.txt b/scratch/neorocks_thoughts.txt
deleted file mode 100644
index b5dfcd1..0000000
--- a/scratch/neorocks_thoughts.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-
-# Happy path
-neorocks.ensure_installed
--> oh hey, neorocks is setup (does nothing)
--> oh hey, already installed
-
-
-# Worst path
-neorocks.install
--> no hererocks.
-    -> wget / curl hererocks
-    -> build / install lua & luarocks
-    -> OK, I'm done
--> once install is done
-    -> build / install the lua package
--> return control flow back?
-
-# First version is, you call install.
-# Opens up window
-# You see that it's doing stuff
-# So you wait (as a person) for it to be done
-# Then you require it.
-
-# Second version is, somehow we do this during startup
-# But still allow you to complete startup w/out freezing for a long time
-
-# Alternative version:
-# Run the install w/ headless nvim
-# Could make a script, available in plenary/scripts or something like that, that allows you to do so.
- nvim --headless -c "lua require('plenary.neorocks').install('lua-cjson')" -c "qa"


Test patch:
diff --git a/lua/plenary/test_harness.lua b/lua/plenary/test_harness.lua
index 1c4bf46..35caf63 100644
--- a/lua/plenary/test_harness.lua
+++ b/lua/plenary/test_harness.lua
@@ -67,7 +67,7 @@ function harness.test_directory(directory, opts)
 
   local path_len = #paths
 
-  local jobs = f.map(
+  local jobs = vim.tbl_map(
     function(p)
       local args = {
         '--headless',
@@ -83,7 +83,7 @@ function harness.test_directory(directory, opts)
       end
 
       return Job:new {
-        command = 'nvim',
+        command = vim.v.progpath,
         args = args,
 
         -- Can be turned on to debug
@@ -145,7 +145,7 @@ function harness._find_files_to_run(directory)
     args = {directory, '-type', 'f', '-name', '*_spec.lua'},
   }
 
-  return f.map(Path.new, finder:sync())
+  return vim.tbl_map(Path.new, finder:sync())
 end
 
 function harness._run_path(test_type, directory)
diff --git a/scratch/here_rock_tester.lua b/scratch/here_rock_tester.lua
deleted file mode 100644
index 21e7c28..0000000
--- a/scratch/here_rock_tester.lua
+++ /dev/null
@@ -1,11 +0,0 @@
-
-require('plenary.reload').reload_module('plenary')
-
--- local rocks = require('nvim_rocks')
--- rocks.ensure_installed('lua-cjson')
--- require('cjson')
-
-local neorocks = require('plenary.neorocks')
-
-neorocks.install('lua-cjson')
--- neorocks.remove('busted')
diff --git a/scratch/test_neorock_stuff.lua b/scratch/test_neorock_stuff.lua
deleted file mode 100644
index 0c71622..0000000
--- a/scratch/test_neorock_stuff.lua
+++ /dev/null
@@ -1,19 +0,0 @@
-require('plenary.reload').reload_module('plenary')
-
--- TODO: We could add some stuff to delete this at the beginning to start fresh.
--- Probably would be useful to have an uninstall anyways
-
-local neorocks = require('plenary.neorocks')
-neorocks.install('luasocket', nil, true)
-
--- local job = neorocks._get_setup_job(false)
--- neorocks.ensure_installed('effil', 'effil')
--- neorocks.ensure_installed('lua-cjson')
--- neorocks.ensure_installed('luasocket')
-
--- if job then
---   job:start()
--- else
---   print("Already installed")
--- end
-
diff --git a/tests/plenary/async_lib/semaphore_spec.lua b/tests/plenary/async_lib/semaphore_spec.lua
index f2908fb..f3df6a4 100644
--- a/tests/plenary/async_lib/semaphore_spec.lua
+++ b/tests/plenary/async_lib/semaphore_spec.lua
@@ -12,6 +12,47 @@ a.describe('semaphore', function()
     eq(status, false)
   end)
 
-  a.it('should count properly', function()
+  a.it('should acquire a permit if available', function ()
+    local sem = a.util.Semaphore.new(1)
+    local permit = await(sem:acquire())
+    assert(permit ~= nil)
+  end)
+
+  a.it('should block if no permit is available', function ()
+    local sem = a.util.Semaphore.new(1)
+    await(sem:acquire())
+    
+    local completed = false
+    local blocking = async(function ()
+      await(sem:acquire())
+      completed = true
+    end)
+    a.run(blocking())
+
+    eq(completed, false)
+  end)
+
+  a.it('should give another permit when an acquired permit is released', function ()
+    local sem = a.util.Semaphore.new(1)
+    local permit = await(sem:acquire())
+    permit:forget()
+    local next_permit = await(sem:acquire())
+    assert(next_permit ~= nil)
+  end)
+
+  a.it('should permit the next waiting client when a permit is released', function () 
+    local sem = a.util.Semaphore.new(1)
+    local permit = await(sem:acquire())
+
+    local completed = false
+    local blocking = async(function ()
+      await(sem:acquire())
+      completed = true
+    end)
+
+    a.run(blocking())
+    permit:forget()
+
+    eq(completed, true)
   end)
 end)
diff --git a/tests/plenary/filetype_spec.lua b/tests/plenary/filetype_spec.lua
index eaa2ae2..fc0e7b7 100644
--- a/tests/plenary/filetype_spec.lua
+++ b/tests/plenary/filetype_spec.lua
@@ -78,7 +78,7 @@ describe('filetype', function()
     end)
 
     it('should work for bash', function()
-      assert.are.same('bash', filetype._parse_shebang('#!/bin/bash'))
+      assert.are.same('sh', filetype._parse_shebang('#!/bin/bash'))
     end)
 
     it('should work for usr/bin/env shell', function()
diff --git a/tests/plenary/iterators_spec.lua b/tests/plenary/iterators_spec.lua
index ab726d2..3717fc2 100644
--- a/tests/plenary/iterators_spec.lua
+++ b/tests/plenary/iterators_spec.lua
@@ -54,4 +54,53 @@ describe('iterators', function()
   it('should make a range', function()
     eq({1, 2, 3, 4, 5}, i.range(5):tolist())
   end)
+
+  it('should be able to make a stateful', function()
+    local iter = i.range(3):stateful()
+    eq(iter(), 1)
+    eq(iter(), 2)
+    eq(iter(), 3)
+    assert(not iter())
+    assert(not iter())
+    assert(not iter())
+
+    local iter = i.range(3):stateful()
+    -- dump(iter:tolist())
+    eq(iter:tolist(), { 1, 2, 3 })
+  end)
+
+  it('should be able to flatten', function()
+    local iter = i.range(3):map(function(_) return i.iter { 5, 7, 9 } end):flatten():stateful()
+    eq(iter(), 5)
+    eq(iter(), 7)
+    eq(iter(), 9)
+    eq(iter(), 5)
+    eq(iter(), 7)
+    eq(iter(), 9)
+    eq(iter(), 5)
+    eq(iter(), 7)
+    eq(iter(), 9)
+    local iter = i.range(3):map(function(_) return i.iter { 5, 7, 9 } end):flatten()
+    eq(iter:tolist(), { 5, 7, 9, 5, 7, 9, 5, 7, 9 })
+  end)
+
+  it('should be able to flatten very nested stuff', function()
+    local iter = i.iter { 5, 6, i.iter { i.iter { 5, 5 }, 7, 8 }, i.iter { 9, 10, i.iter { 1, 2 } } }:flatten()
+    eq(iter:tolist(), { 5, 6, 5, 5, 7, 8, 9, 10, 1, 2 })
+  end)
+
+  it('chaining nil should work', function()
+    local iter = i.iter(""):chain(i.iter { 5, 7, 9 })
+    eq(#iter:tolist(), 3)
+  end)
+
+  describe('generators', function()
+    it('should be able to split', function()
+      local iter = i.split(" hello person world ", " ")
+      eq(iter:tolist(), {"", "hello", "person", "world", ""})
+
+      iter = i.split("\n\n\nfirst\nsecond\nthird\n\n", "\n")
+      eq(iter:tolist(), {"", "", "", "first", "second", "third", "", ""})
+    end)
+  end)
 end)
diff --git a/tests/plenary/job_spec.lua b/tests/plenary/job_spec.lua
index b239549..809bce3 100644
--- a/tests/plenary/job_spec.lua
+++ b/tests/plenary/job_spec.lua
@@ -392,8 +392,7 @@ describe('Job', function()
 
       local second_job = Job:new{ command = 'false' }
 
-      first_job:after(function() code = code + 10 end)
-      first_job:and_then(second_job)
+      first_job:after(function() code = code + 10 end):and_then(second_job)
       second_job:after(function(_, c) code = code + c end)
 
       first_job:sync()
diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua
index 0b12a72..571d89f 100644
--- a/tests/plenary/path_spec.lua
+++ b/tests/plenary/path_spec.lua
@@ -24,6 +24,11 @@ describe('Path', function()
       local p = Path:new { "./home/tj/" , sep = '/'}
       assert(not p:is_absolute(), "Is absolute")
     end)
+
+    it('will normalize the path', function()
+      local p = Path:new { "lua", "..", "README.md" , sep = '/'}
+      assert.are.same(p:absolute(), vim.fn.fnamemodify("README.md", ":p"))
+    end)
   end)
 
   it('can join paths by constructor or join path', function()
@@ -153,16 +158,48 @@ describe('Path', function()
 
   describe(':normalize', function()
     it('can take paths with double separators change them to single separators', function()
-      local orig = 'lua//plenary/path.lua'
+      local orig = '/lua//plenary/path.lua'
       local final = Path:new(orig):normalize()
-      assert.are.same(final, 'lua/plenary/path.lua')
+      assert.are.same(final, '/lua/plenary/path.lua')
     end)
     -- this may be redundant since normalize just calls make_relative which is tested above
     it('can take absolute paths with double seps'
       .. 'and make them relative with single seps', function()
-      local orig = vim.loop.cwd() .. '/lua//plenary/path.lua'
+      local orig = '/lua//plenary/path.lua'
       local final = Path:new(orig):normalize()
-      assert.are.same(final, 'lua/plenary/path.lua')
+      assert.are.same(final, '/lua/plenary/path.lua')
+    end)
+
+    it('can remove the .. in paths', function()
+      local orig = '/lua//plenary/path.lua/foo/bar/../..'
+      local final = Path:new(orig):normalize()
+      assert.are.same(final, '/lua/plenary/path.lua')
+    end)
+  end)
+
+  describe(':shorten', function()
+    it('can shorten a path', function()
+      local long_path = '/this/is/a/long/path'
+      local short_path = Path:new(long_path):shorten()
+      assert.are.same(short_path, '/t/i/a/l/path')
+    end)
+  end)
+
+  describe(':shorten', function()
+    it('can shorten a path components to a given length', function()
+      local long_path = '/this/is/a/long/path'
+      local short_path = Path:new(long_path):shorten(2)
+      assert.are.same(short_path, '/th/is/a/lo/path')
+
+      -- without the leading /
+      long_path = 'this/is/a/long/path'
+      short_path = Path:new(long_path):shorten(3)
+      assert.are.same(short_path, 'thi/is/a/lon/path')
+
+      -- where len is greater than the length of the final component
+      long_path = 'this/is/an/extremely/long/path'
+      short_path = Path:new(long_path):shorten(5)
+      assert.are.same(short_path, 'this/is/an/extre/long/path')
     end)
   end)
 
@@ -237,14 +274,18 @@ describe('Path', function()
     it('does create dirs if nested in none existing dirs', function()
       local p1 = Path:new({ "nested", "nested2", "test_file.lua" })
       local p2 = Path:new({ "nested", "asdf", ".hidden" })
+      local d1 = Path:new({ "nested", "dir", ".hidden" })
       assert(pcall(p1.touch, p1, { parents = true }))
       assert(pcall(p2.touch, p2, { parents = true }))
+      assert(pcall(d1.mkdir, d1, { parents = true }))
       assert(p1:exists())
       assert(p2:exists())
+      assert(d1:exists())
 
       Path:new({ "nested" }):rm({ recursive = true })
       assert(not p1:exists())
       assert(not p2:exists())
+      assert(not d1:exists())
       assert(not Path:new({ "nested" }):exists())
     end)
   end)
diff --git a/tests/plenary/tbl_spec.lua b/tests/plenary/tbl_spec.lua
new file mode 100644
index 0000000..60faa18
--- /dev/null
+++ b/tests/plenary/tbl_spec.lua
@@ -0,0 +1,27 @@
+local tbl = require('plenary.tbl')
+
+local function should_fail(fun)
+  local stat = pcall(fun)
+  assert(not stat, "Function should have errored")
+end
+
+describe('tbl utilities', function()
+  it('should be able to freeze a table', function()
+    local t = {1, 2, 3}
+    local frozen = tbl.freeze(t)
+    assert(t[1] == frozen[1])
+    assert(t[2] == frozen[2])
+    assert(t[3] == frozen[3])
+
+    should_fail(function()
+      frozen[4] = "thisthis"
+    end)
+
+    should_fail(function()
+      frozen.hello = "asdfasdf"
+    end)
+
+    assert(not frozen[5])
+    assert(not frozen.hello)
+  end)
+end)