diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 0000000..68cf318
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,12 @@
+-- Rerun tests only if their modification time changed.
+cache = true
+
+std = luajit
+codes = true
+
+self = false
+
+-- Global objects defined by the C code
+read_globals = {
+ "vim",
+}
diff --git a/.stylua.toml b/.stylua.toml
new file mode 100644
index 0000000..ecb6dca
--- /dev/null
+++ b/.stylua.toml
@@ -0,0 +1,6 @@
+column_width = 120
+line_endings = "Unix"
+indent_type = "Spaces"
+indent_width = 2
+quote_style = "AutoPreferDouble"
+call_parentheses = "None"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..14b5ab0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+
+
+# ivy.nvim
+
+An [ivy-mode](https://github.com/abo-abo/swiper#ivy) port to neovim. Ivy is a
+generic completion mechanism for ~~Emacs~~ Nvim
+
+
+
+## Installation
+
+### Manually
+
+```sh
+git clone https://github.com/AdeAttwood/ivy.nvim ~/.config/nvim/pack/bundle/start/ivy.nvim
+```
+
+### Plugin managers
+
+TODO: Add docs in the plugin managers I don't use any
+
+## Features
+
+### Commands
+
+A command can be run that will launch the completion UI
+
+| Command | Key Map | Description |
+| ---------- | ----------- | ------------------------------------------------------ |
+| IvyFd | \p | Find files in your project with the fd cli file finder |
+| IvyAg | \/ | Find content in files using the silver searcher |
+| IvyBuffers | | Search though open buffers |
+
+### Actions
+
+Action can be run on selected candidates provide functionality
+
+| Action | Description |
+| -------- | ------------------------------------------------------------------------------ |
+| Complete | Run the completion function, usually this will be opening a file |
+| Peek | Run the completion function on a selection, but don't close the results window |
+
+## API
+
+```lua
+ vim.ivy.run(
+ -- Call back function to get all the candidates that will be displayed in
+ -- the results window, The `input` will be passed in, so you can filter
+ -- your results with the value from the prompt
+ function(input) return { "One", "Two", Three } end,
+ -- Action callback that will be called on the completion or peek actions.
+ The currently selected item is passed in as the result.
+ function(result) vim.cmd("edit " .. result) end
+ )
+```
+
+## Other stuff you might like
+
+- [ivy-mode](https://github.com/abo-abo/swiper#ivy) - An emacs package that was the inspiration for this nvim plugin
+- [Command-T](https://github.com/wincent/command-t) - Vim plugin I used before I started this one
+- [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) - Another competition plugin, lots of people are using
diff --git a/lua/ivy/controller.lua b/lua/ivy/controller.lua
new file mode 100644
index 0000000..57b34fe
--- /dev/null
+++ b/lua/ivy/controller.lua
@@ -0,0 +1,56 @@
+local window = require "ivy.window"
+local prompt = require "ivy.prompt"
+
+local controller = {}
+
+controller.items = nil
+controller.callback = nil
+
+controller.run = function(items, callback)
+ controller.callback = callback
+ controller.items = items
+
+ window.initialize()
+ controller.input ""
+end
+
+controller.input = function(char)
+ prompt.input(char)
+ window.set_items(controller.items(prompt.text()))
+end
+
+controller.search = function(value)
+ prompt.set(value)
+ window.set_items(controller.items(prompt.text()))
+end
+
+controller.complete = function()
+ controller.checkpoint()
+ controller.destroy()
+end
+
+controller.checkpoint = function()
+ vim.api.nvim_set_current_win(window.previous)
+ controller.callback(window.get_current_selection())
+ vim.api.nvim_set_current_win(window.window)
+end
+
+controller.next = function()
+ window.index = window.index + 1
+ window.update()
+end
+
+controller.previous = function()
+ window.index = window.index - 1
+ window.update()
+end
+
+controller.destroy = function()
+ controller.items = nil
+ controller.callback = nil
+
+ window.destroy()
+ prompt.destroy()
+end
+
+return controller
diff --git a/lua/ivy/prompt.lua b/lua/ivy/prompt.lua
new file mode 100644
index 0000000..264cc1b
--- /dev/null
+++ b/lua/ivy/prompt.lua
@@ -0,0 +1,37 @@
+-- The prefix that will be before the search text for the user
+local prompt_prefix = ">> "
+
+local prompt = {}
+
+prompt.value = ""
+
+prompt.text = function()
+ return prompt.value
+end
+prompt.update = function()
+ vim.notify(prompt_prefix .. prompt.text())
+end
+
+prompt.input = function(char)
+ if char == "BACKSPACE" then
+ prompt.value = string.sub(prompt.value, 0, -2)
+ elseif char == "\\\\" then
+ prompt.value = prompt.value .. "\\"
+ else
+ prompt.value = prompt.value .. char
+ end
+
+ prompt.update()
+end
+
+prompt.set = function(value)
+ prompt.value = value
+ prompt.update()
+end
+
+prompt.destroy = function()
+ prompt.value = ""
+ vim.notify ""
+end
+
+return prompt
diff --git a/lua/ivy/utils.lua b/lua/ivy/utils.lua
new file mode 100644
index 0000000..c087167
--- /dev/null
+++ b/lua/ivy/utils.lua
@@ -0,0 +1,57 @@
+local utils = {}
+
+utils.command_finder = function(command, min)
+ if min == nil then
+ min = 3
+ end
+
+ return function(input)
+ -- Dont run the commands unless we have somting to search that wont
+ -- return a ton of results or on some commands the command files with
+ -- no search term
+ if #input < min then
+ return "-- Please type more than " .. min .. " chars --"
+ end
+
+ -- TODO(ade): Think if we want to start escaping the command here. I
+ -- dont know if its causing issues while trying to use regex especially
+ -- with word boundaries `input:gsub("'", "\\'"):gsub('"', '\\"')`
+ local handle = io.popen(command .. " " .. input .. " 2>&1")
+ if handle == nil then
+ return {}
+ end
+ local result = handle:read "*a"
+ handle:close()
+
+ return result
+ end
+end
+
+utils.vimgrep_action = function()
+ return function(item)
+ -- Match file and line form vimgrep style commands
+ local file = item:match "([^:]+):"
+ local line = item:match ":(%d+):"
+
+ -- Cant do anything if we cant find a file to go to
+ if file == nil then
+ return
+ end
+
+ vim.cmd("edit " .. file)
+ if line ~= nil then
+ vim.cmd(line)
+ end
+ end
+end
+
+utils.file_action = function()
+ return function(file)
+ if file == nil then
+ return
+ end
+ vim.cmd("edit " .. file)
+ end
+end
+
+return utils
diff --git a/lua/ivy/window.lua b/lua/ivy/window.lua
new file mode 100644
index 0000000..1c0c7c1
--- /dev/null
+++ b/lua/ivy/window.lua
@@ -0,0 +1,139 @@
+-- Constent options that will be used for the keymaps
+local opts = { noremap = true, silent = true, nowait = true }
+
+-- All of the base chars that will be used for an "input" operation on the
+-- prompt
+-- stylua: ignore
+local chars = {
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
+ "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
+ "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "<", ">", "`", "@", "#", "~", "!",
+ "\"", "$", "%", "^", "&", "/", "(", ")", "=", "+", "*", "-", "_", ".", ",", ";", ":", "?", "\\", "|", "'", "{", "}",
+ "[", "]", " ",
+}
+
+local function parse_lines(lines)
+ local items = {}
+ for line in lines:gmatch "[^\r\n]+" do
+ table.insert(items, line)
+ end
+
+ return items
+end
+
+local function parse_array(arr)
+ return arr
+end
+
+local window = {}
+
+window.index = 0
+window.previous = nil
+window.window = nil
+window.buffer = nil
+
+window.initialize = function()
+ window.make_buffer()
+end
+
+window.make_buffer = function()
+ window.previous = vim.api.nvim_get_current_win()
+
+ vim.api.nvim_command "botright split new"
+ window.buffer = vim.api.nvim_win_get_buf(0)
+ window.window = vim.api.nvim_get_current_win()
+
+ vim.api.nvim_win_set_option(window.window, "number", false)
+ vim.api.nvim_win_set_option(window.window, "relativenumber", false)
+ vim.api.nvim_win_set_option(window.window, "signcolumn", "no")
+
+ vim.api.nvim_buf_set_option(window.buffer, "filetype", "ivy")
+ vim.api.nvim_buf_set_var(window.buffer, "bufftype", "nofile")
+
+ for index = 1, #chars do
+ local char = chars[index]
+ if char == "'" then
+ char = "\\'"
+ end
+ if char == "\\" then
+ char = "\\\\\\\\"
+ end
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", chars[index], "lua vim.ivy.input('" .. char .. "')", opts)
+ end
+
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", "", "lua vim.ivy.destroy()", opts)
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", "", "lua vim.ivy.search('')", opts)
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", "", "lua vim.ivy.next()", opts)
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", "", "lua vim.ivy.previous()", opts)
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", "", "lua vim.ivy.next(); vim.ivy.checkpoint()", opts)
+ vim.api.nvim_buf_set_keymap(
+ window.buffer,
+ "n",
+ "",
+ "lua vim.ivy.previous(); vim.ivy.checkpoint()",
+ opts
+ )
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", "", "lua vim.ivy.complete()", opts)
+ vim.api.nvim_buf_set_keymap(window.buffer, "n", "", "lua vim.ivy.input('BACKSPACE')", opts)
+end
+
+window.get_current_selection = function()
+ local line = vim.api.nvim_buf_get_lines(window.buffer, window.index, window.index + 1, true)
+ if line == nil then
+ line = { "" }
+ end
+
+ return line[1]
+end
+
+window.get_buffer = function()
+ if window.buffer == nil then
+ window.make_buffer()
+ end
+
+ return window.buffer
+end
+
+window.update = function()
+ -- TODO(ade): Add a guard in so we can not go out of range on the results buffer
+ vim.api.nvim_win_set_cursor(window.window, { window.index + 1, 0 })
+end
+
+window.set_items = function(items)
+ local lines = {}
+
+ if type(items) == "string" then
+ lines = parse_lines(items)
+ elseif type(items) == "table" then
+ lines = parse_array(items)
+ end
+
+ if #lines == 0 then
+ lines = { "-- No Items --" }
+ end
+
+ vim.api.nvim_buf_set_lines(window.get_buffer(), 0, 9999, false, lines)
+
+ local line_count = #lines
+ window.index = 0
+
+ if line_count > 10 then
+ line_count = 10
+ end
+ vim.api.nvim_win_set_height(window.window, line_count)
+
+ window.update()
+end
+
+window.destroy = function()
+ if type(window.buffer) == "number" then
+ vim.api.nvim_buf_delete(window.buffer, { force = true })
+ end
+
+ window.buffer = nil
+ window.window = nil
+ window.previous = nil
+ window.index = 0
+end
+
+return window
diff --git a/plugin/ivy.lua b/plugin/ivy.lua
new file mode 100644
index 0000000..96bdf5f
--- /dev/null
+++ b/plugin/ivy.lua
@@ -0,0 +1,33 @@
+local controller = require "ivy.controller"
+local utils = require "ivy.utils"
+
+-- Put the controller in to the vim global so we can access it in mappings
+-- better without requires. You can call controller commands like `vim.ivy.xxx`.
+vim.ivy = controller
+
+vim.api.nvim_create_user_command("IvyAg", function()
+ vim.ivy.run(utils.command_finder "ag", utils.vimgrep_action())
+end, { bang = true, desc = "Run ag to search for content in files" })
+
+vim.api.nvim_create_user_command("IvyFd", function()
+ vim.ivy.run(utils.command_finder("fd --hidden --type f --exclude .git", 0), utils.file_action())
+end, { bang = true, desc = "Find files in the project" })
+
+vim.api.nvim_create_user_command("IvyBuffers", function()
+ vim.ivy.run(function(input)
+ local list = {}
+ local buffers = vim.api.nvim_list_bufs()
+ for index = 1, #buffers do
+ local buffer = buffers[index]
+ local buffer_name = vim.api.nvim_buf_get_name(buffer)
+ if vim.api.nvim_buf_is_loaded(buffer) and #buffer_name > 0 then
+ table.insert(list, buffer_name)
+ end
+ end
+
+ return list
+ end, utils.file_action())
+end, { bang = true, desc = "List all of the current open buffers" })
+
+vim.api.nvim_set_keymap("n", "p", "IvyFd", { nowait = true, silent = true })
+vim.api.nvim_set_keymap("n", "/", "IvyAg", { nowait = true, silent = true })