├── LICENSE ├── README.md └── lua └── telescope └── _extensions ├── zoxide.lua └── zoxide ├── config.lua ├── list.lua └── utils.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 telescope-zoxide 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telescope Zoxide 2 | 3 | An extension for [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) that allows you operate [zoxide](https://github.com/ajeetdsouza/zoxide) within Neovim. 4 | 5 | ## Requirements 6 | 7 | [zoxide](https://github.com/ajeetdsouza/zoxide) is required to use this plugin. 8 | 9 | ## Installation 10 | 11 | ```vim 12 | Plug 'nvim-lua/popup.nvim' 13 | Plug 'nvim-lua/plenary.nvim' 14 | Plug 'nvim-telescope/telescope.nvim' 15 | Plug 'jvgrootveld/telescope-zoxide' 16 | ``` 17 | 18 | ## Configuration 19 | 20 | You can add, extend and update Telescope Zoxide config by using [Telescope's default configuration mechanism for extensions](https://github.com/nvim-telescope/telescope.nvim#telescope-setup-structure). 21 | An example config: 22 | 23 | ```lua 24 | -- Useful for easily creating commands 25 | local z_utils = require("telescope._extensions.zoxide.utils") 26 | 27 | require('telescope').setup{ 28 | -- (other Telescope configuration...) 29 | extensions = { 30 | zoxide = { 31 | prompt_title = "[ Walking on the shoulders of TJ ]", 32 | mappings = { 33 | default = { 34 | after_action = function(selection) 35 | print("Update to (" .. selection.z_score .. ") " .. selection.path) 36 | end 37 | }, 38 | [""] = { 39 | before_action = function(selection) print("before C-s") end, 40 | action = function(selection) 41 | vim.cmd.edit(selection.path) 42 | end 43 | }, 44 | -- Opens the selected entry in a new split 45 | [""] = { action = z_utils.create_basic_command("split") }, 46 | }, 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | You can add new mappings and extend default mappings. 53 | _(Note: The mapping with the key 'default' is the mapping invoked on pressing ``)_. 54 | Every keymapping must have an `action` function and supports the optional functions `before_action` and `after_action`. 55 | 56 | Tip: If the action is a telescope picker, you should also set `keepinsert = true` to open it in insert mode. Else you can't directly type into the next telescope picker. 57 | 58 | All action functions are called with the current `selection` object as parameter which contains the selected path and Zoxide score. 59 | 60 | Tip: Make use of the supplied `z_utils.create_basic_command` helper function to easily invoke a vim command for the selected path. 61 | 62 | ## Loading the extension 63 | 64 | You can then load the extension by adding the following after your call to telescope's own `setup()` function: 65 | 66 | ```lua 67 | require("telescope").load_extension('zoxide') 68 | ``` 69 | 70 | Loading the extension will allow you to use the following functionality: 71 | 72 | ### List 73 | 74 | With Telescope command: 75 | 76 | ```vim 77 | :Telescope zoxide list 78 | ``` 79 | 80 | In Lua: 81 | 82 | ```lua 83 | require("telescope").extensions.zoxide.list({picker_opts}) 84 | ``` 85 | 86 | You can also bind the function to a key: 87 | 88 | ```lua 89 | vim.keymap.set("n", "cd", require("telescope").extensions.zoxide.list) 90 | ``` 91 | 92 | ## Full example 93 | 94 | ```lua 95 | local t = require("telescope") 96 | local z_utils = require("telescope._extensions.zoxide.utils") 97 | 98 | -- Configure the extension 99 | t.setup({ 100 | extensions = { 101 | zoxide = { 102 | prompt_title = "[ Walking on the shoulders of TJ ]", 103 | mappings = { 104 | default = { 105 | after_action = function(selection) 106 | print("Update to (" .. selection.z_score .. ") " .. selection.path) 107 | end 108 | }, 109 | [""] = { 110 | before_action = function(selection) print("before C-s") end, 111 | action = function(selection) 112 | vim.cmd.edit(selection.path) 113 | end 114 | }, 115 | [""] = { action = z_utils.create_basic_command("split") }, 116 | }, 117 | }, 118 | }, 119 | }) 120 | 121 | -- Load the extension 122 | t.load_extension('zoxide') 123 | 124 | -- Add a mapping 125 | vim.keymap.set("n", "cd", t.extensions.zoxide.list) 126 | ``` 127 | 128 | ## Default config 129 | 130 | ```lua 131 | { 132 | prompt_title = "[ Zoxide List ]", 133 | 134 | -- Zoxide list command with score 135 | list_command = "zoxide query -ls", 136 | mappings = { 137 | default = { 138 | action = function(selection) 139 | vim.cmd.cd(selection.path) 140 | end, 141 | after_action = function(selection) 142 | vim.notify("Directory changed to " .. selection.path) 143 | end, 144 | }, 145 | [""] = { action = z_utils.create_basic_command("split") }, 146 | [""] = { action = z_utils.create_basic_command("vsplit") }, 147 | [""] = { action = z_utils.create_basic_command("edit") }, 148 | [""] = { 149 | keepinsert = true, 150 | action = function(selection) 151 | builtin.find_files({ cwd = selection.path }) 152 | end, 153 | }, 154 | [""] = { 155 | action = function(selection) 156 | vim.cmd.tcd(selection.path) 157 | end, 158 | }, 159 | } 160 | } 161 | ``` 162 | 163 | ## Default mappings 164 | 165 | | Action | Description | Command executed | 166 | | ------- | ---------------------------------------------------- | ------------------------------------------------ | 167 | | `` | Change current directory to selection | `cd ` | 168 | | `` | Change current tab's directory to selection | `tcd ` | 169 | | `` | Open selection in a split | `split ` | 170 | | `` | Open selection in a vertical split | `vsplit ` | 171 | | `` | Open selection in current window | `edit ` | 172 | | `` | Open selection in telescope's `builtin.find_files` | `builtin.find_files({ cwd = selection.path })` | 173 | 174 | ## Extensions 175 | 176 | ### Open Selection in Telescope File Browser 177 | 178 | This action requires installing the [Telescope file browser extension](https://github.com/nvim-telescope/telescope-file-browser.nvim). You can register this mapping by adding the following to your config: 179 | 180 | ```lua 181 | { 182 | mappings = { 183 | [""] = { 184 | keepinsert = true, 185 | action = function(selection) 186 | require("telescope").extensions.file_browser.file_browser({ cwd = selection.path }) 187 | end 188 | }, 189 | } 190 | } 191 | ``` 192 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/zoxide.lua: -------------------------------------------------------------------------------- 1 | local has_telescope, telescope = pcall(require, 'telescope') 2 | if not has_telescope then 3 | error('This plugin requires nvim-telescope/telescope.nvim') 4 | end 5 | 6 | return telescope.register_extension { 7 | setup = require("telescope._extensions.zoxide.config").setup, 8 | exports = { 9 | list = require("telescope._extensions.zoxide.list") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/zoxide/config.lua: -------------------------------------------------------------------------------- 1 | local builtin = require("telescope.builtin") 2 | local utils = require("telescope.utils") 3 | local z_utils = require("telescope._extensions.zoxide.utils") 4 | 5 | local config = {} 6 | 7 | local default_config = { 8 | prompt_title = "[ Zoxide List ]", 9 | 10 | -- Zoxide list command with score 11 | list_command = "zoxide query -ls", 12 | mappings = { 13 | default = { 14 | action = function(selection) 15 | vim.cmd.cd(selection.path) 16 | end, 17 | after_action = function(selection) 18 | vim.notify("Directory changed to " .. selection.path) 19 | end, 20 | }, 21 | [""] = { action = z_utils.create_basic_command("split") }, 22 | [""] = { action = z_utils.create_basic_command("vsplit") }, 23 | [""] = { action = z_utils.create_basic_command("edit") }, 24 | [""] = { 25 | keepinsert = true, 26 | action = function(selection) 27 | builtin.find_files({ cwd = selection.path }) 28 | end, 29 | }, 30 | [""] = { 31 | action = function(selection) 32 | vim.cmd.tcd(selection.path) 33 | end, 34 | }, 35 | } 36 | } 37 | 38 | local current_config = default_config 39 | 40 | config.get_config = function() 41 | return current_config 42 | end 43 | 44 | config.setup = function(user_config) 45 | local temp_config = {} 46 | 47 | -- Map everything except 'mappings' 48 | for key, value in pairs(default_config) do 49 | if key ~= "mappings" then 50 | temp_config[key] = vim.F.if_nil(user_config[key], value) 51 | end 52 | end 53 | 54 | -- Map mappings 55 | local temp_mappings = {} 56 | 57 | -- Copy defaults in temp mapping 58 | for map_key, map_value in pairs(default_config.mappings) do 59 | for action_key, action_value in pairs(map_value) do 60 | if temp_mappings[map_key] == nil then 61 | temp_mappings[map_key] = {} 62 | end 63 | 64 | temp_mappings[map_key][action_key] = action_value 65 | end 66 | end 67 | 68 | -- Override mapping with user mappings 69 | user_config.mappings = user_config.mappings or {} 70 | for map_key, map_value in pairs(user_config.mappings) do 71 | -- If user mapping is new, just set, else merge 72 | if temp_mappings[map_key] == nil then 73 | temp_mappings[map_key] = map_value 74 | else 75 | for action_key, action_value in pairs(map_value) do 76 | temp_mappings[map_key][action_key] = action_value 77 | end 78 | end 79 | end 80 | 81 | -- Set mappings 82 | temp_config.mappings = temp_mappings 83 | 84 | -- Set new merged config 85 | current_config = temp_config 86 | end 87 | 88 | return config 89 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/zoxide/list.lua: -------------------------------------------------------------------------------- 1 | local actions = require('telescope.actions') 2 | local action_state = require('telescope.actions.state') 3 | local finders = require('telescope.finders') 4 | local pickers = require('telescope.pickers') 5 | local sorters = require('telescope.sorters') 6 | local utils = require('telescope.utils') 7 | 8 | local map_both = function(map, keys, func) 9 | map("i", keys, func) 10 | map("n", keys, func) 11 | end 12 | 13 | -- Copied unexported highlighter from telescope/sorters.lua 14 | local ngram_highlighter = function(ngram_len, prompt, display) 15 | local highlights = {} 16 | display = display:lower() 17 | 18 | for disp_index = 1, #display do 19 | local char = display:sub(disp_index, disp_index + ngram_len - 1) 20 | if prompt:find(char, 1, true) then 21 | table.insert(highlights, { 22 | start = disp_index, 23 | finish = disp_index + ngram_len - 1 24 | }) 25 | end 26 | end 27 | 28 | return highlights 29 | end 30 | 31 | local fuzzy_with_z_score_bias = function(opts) 32 | opts = opts or {} 33 | opts.ngram_len = 2 34 | 35 | local fuzzy_sorter = sorters.get_generic_fuzzy_sorter(opts) 36 | 37 | return sorters.Sorter:new { 38 | highlighter = opts.highlighter or function(_, prompt, display) 39 | return ngram_highlighter(opts.ngram_len, prompt, display) 40 | end, 41 | scoring_function = function(_, prompt, _, entry) 42 | local base_score = fuzzy_sorter:score( 43 | prompt, 44 | entry, 45 | function(val) return val end, 46 | function() return -1 end 47 | ) 48 | 49 | if base_score == -1 then 50 | return -1 51 | end 52 | 53 | if base_score == 0 then 54 | return -entry.z_score 55 | else 56 | return math.min(math.pow(entry.index, 0.25), 2) * base_score 57 | end 58 | end 59 | } 60 | end 61 | 62 | local entry_maker = function(item) 63 | local trimmed = string.gsub(item, '^%s*(.-)%s*$', '%1') 64 | local item_path = string.gsub(trimmed, '^[^%s]* (.*)$', '%1') 65 | local score = tonumber(string.gsub(trimmed, '^([^%s]*) .*$', '%1'), 10) 66 | 67 | return { 68 | value = item_path, 69 | ordinal = item_path, 70 | display = item_path, 71 | z_score = score, 72 | path = item_path 73 | } 74 | end 75 | 76 | local create_mapping = function(prompt_bufnr, mapping_config) 77 | return function() 78 | local selection = action_state.get_selected_entry() 79 | if mapping_config.before_action ~= nil then 80 | mapping_config.before_action(selection) 81 | end 82 | 83 | -- Close Telescope window 84 | actions._close(prompt_bufnr, mapping_config.keepinsert or false) 85 | 86 | mapping_config.action(selection) 87 | 88 | if mapping_config.after_action ~= nil then 89 | mapping_config.after_action(selection) 90 | end 91 | end 92 | end 93 | 94 | return function(opts) 95 | opts = opts or {} 96 | 97 | local z_config = require("telescope._extensions.zoxide.config") 98 | local cmd = z_config.get_config().list_command 99 | local shell_arg = "-c" 100 | if vim.o.shell == "cmd.exe" then 101 | shell_arg = "/c" 102 | end 103 | opts.cmd = vim.F.if_nil(opts.cmd, {vim.o.shell, shell_arg, cmd}) 104 | 105 | pickers.new(opts, { 106 | prompt_title = z_config.get_config().prompt_title, 107 | previewer = require("telescope.previewers.buffer_previewer").cat.new(opts), 108 | 109 | finder = finders.new_table { 110 | results = utils.get_os_command_output(opts.cmd), 111 | entry_maker = entry_maker 112 | }, 113 | sorter = fuzzy_with_z_score_bias(opts), 114 | attach_mappings = function(prompt_bufnr, map) 115 | local mappings = z_config.get_config().mappings 116 | 117 | -- Set default mapping '' 118 | actions.select_default:replace(create_mapping(prompt_bufnr, mappings.default)) 119 | 120 | -- Add extra mappings 121 | for mapping_key, mapping_config in pairs(mappings) do 122 | if mapping_key ~= "default" then 123 | map_both(map, mapping_key, create_mapping(prompt_bufnr, mapping_config)) 124 | end 125 | end 126 | 127 | return true 128 | end, 129 | }):find() 130 | end 131 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/zoxide/utils.lua: -------------------------------------------------------------------------------- 1 | local utils = {} 2 | 3 | utils.create_basic_command = function(command) 4 | return function(selection) 5 | vim.cmd[command](selection.path) 6 | end 7 | end 8 | 9 | return utils 10 | --------------------------------------------------------------------------------