├── .stylua.toml ├── LICENSE ├── README.md ├── doc └── telescope-tabs.txt └── lua ├── telescope-tabs.lua └── telescope └── _extensions ├── telescope-tabs.lua └── telescope-tabs └── main.lua /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Tabs" 4 | indent_width = 4 5 | quote_style = "AutoPreferSingle" 6 | call_parentheses = "None" 7 | collapse_simple_statement = "Never" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright 2022 Lukas Pietzschmann 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its contributors may 16 | be used to endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 24 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # telescope-tabs 2 | Fly through your tabs in neovim ✈️ 3 | 4 |

5 | 6 |

7 | 8 | ## Important Note 9 | If you don't want to use telescope, there's also a version using `vim.ui.select` instead of telescope. Checkout the [`vim_ui_select`](https://github.com/LukasPietzschmann/telescope-tabs/tree/vim_ui_select) branch :) 10 | 11 | ## Usage 12 | You can show the picker from neovim's cmd-line by executing 13 | ``` 14 | :Telescope telescope-tabs list_tabs 15 | ``` 16 | 17 | Or straight from the plugin's path with lua 18 | ```viml 19 | :lua require('telescope-tabs').list_tabs() 20 | ``` 21 | 22 | You can press `C-d` (insert mode) or `D` (normal mode) on any Item in the picker to close the tab (respectively all windows in it). To change the keybinding, look at [configure](https://github.com/LukasPietzschmann/telescope-tabs#configure). 23 |

24 | 25 |

26 | 27 | But `telescope-tabs` does not only provide a picker! You can also call 28 | ```viml 29 | :lua require('telescope-tabs').go_to_previous() 30 | ``` 31 | to switch to the last opened tab immediately. 32 | This does not only work when switching tabs with this extension, but also when you use Neovims builtin tab movement methods. 33 | 34 | This plugin improves upon the already present `g` shortcut by keeping track of a "last shown tab stack". Consequently, if you close the most previously visited tab, `g` will fail. However, telescope-tabs will happily show the second last tab. 35 | 36 | I would recommend to bind this to a shortcut if you wanna use this regularly :^) 37 | 38 | 39 | ## Installation 40 | Install with your favorite Neovim package manager. 41 | 42 | Example with lazy.nvim: 43 | ```lua 44 | { 45 | 'LukasPietzschmann/telescope-tabs', 46 | config = function() 47 | require('telescope').load_extension 'telescope-tabs' 48 | require('telescope-tabs').setup { 49 | -- Your custom config :^) 50 | } 51 | end, 52 | dependencies = { 'nvim-telescope/telescope.nvim' }, 53 | } 54 | ``` 55 | 56 | Example with packer.nvim: 57 | ```lua 58 | use { 59 | 'LukasPietzschmann/telescope-tabs', 60 | requires = { 'nvim-telescope/telescope.nvim' }, 61 | config = function() 62 | require'telescope-tabs'.setup{ 63 | -- Your custom config :^) 64 | } 65 | end 66 | } 67 | ``` 68 | ## Configure 69 | Different configurations can be seen in the [configs wiki](https://github.com/LukasPietzschmann/telescope-tabs/wiki/Configs#configs). Feel free to add your own! 70 | 71 | If you want to come up with your own config, these are the settings you can tweak: 72 | 73 | ### entry_formatter 74 | This changes how a tab is represented in the picker. By default the following function is used: 75 | ```lua 76 | entry_formatter = function(tab_id, buffer_ids, file_names, file_paths, is_current) 77 | local entry_string = table.concat(file_names, ', ') 78 | return string.format('%d: %s%s', tab_id, entry_string, is_current and ' <' or '') 79 | end, 80 | ``` 81 | To alter this behaviour, just assign your own function. 82 | 83 | ### entry_ordinal 84 | This changes how tabs are sorted in the picker. The ordinal is also used to determine which entries match a given search query. The following function is used by default: 85 | ```lua 86 | entry_ordinal = function(tab_id, buffer_ids, file_names, file_paths, is_current) 87 | return table.concat(file_names, ' ') 88 | end, 89 | ``` 90 | 91 | ### show_preview 92 | This controls whether a preview is shown or not. The default is `true`: 93 | ```lua 94 | show_preview = true, 95 | ``` 96 | 97 | ### close_tab_shortcut 98 | These shortcuts allow you to close a selected tabs right from the picker. The defaults are... 99 | ```lua 100 | close_tab_shortcut_i = '', -- if you're in insert mode 101 | close_tab_shortcut_n = 'D', -- if you're in normal mode 102 | ``` 103 | Note, that their value do not get parsed or checked, so they should follow the regular format for keybindings. 104 | 105 | ## Documentation 106 | See [telescope-tabs.txt](https://github.com/LukasPietzschmann/telescope-tabs/blob/master/doc/telescope-tabs.txt). 107 | -------------------------------------------------------------------------------- /doc/telescope-tabs.txt: -------------------------------------------------------------------------------- 1 | telescope-tabs.txt Fly through your tabs in neovim 2 | 3 | Author: Lukas Pietzschmann 4 | Repo: https://github.com/LukasPietzschmann/telescope-tabs 5 | License: BSD-3-Clause 6 | 7 | ============================================================================== 8 | INTRODUCTION *telescope-tabs* 9 | 10 | telescope-tabs is a small extension for telescope.nvim. It makes it more 11 | convenient to switch between tabs in a fast way. 12 | Additionally you can switch back to your last used tab immediately, without 13 | having to open the telescope-picker. 14 | Note that this extension wont work well with buffer-line extensions, as it 15 | does not show single buffers (like :Telescope buffers does). Only "real" tabs 16 | are displayed! 17 | 18 | ============================================================================== 19 | CONFIGURATION *configuration* 20 | 21 | Call the setup function with a table as the argument to override the default 22 | configuration. 23 | 24 | require'telescope-tabs'.setup { 25 | -- This function is used to convert a tab to a string that is then 26 | -- displayed in telescopes picker 27 | -- tab_id: id of the tab. This id can be passed to neovims api in order 28 | -- to get information about the tab. 29 | -- buffer_ids: A list of ids for every buffer opened in the tab. Those 30 | -- ids can be passed to neovims api in order to get 31 | -- information about the buffer. 32 | -- file_names: A list of names (strings) for every buffer opened in the 33 | -- tab. 34 | -- file_paths: A list of paths (strings) for every buffer opened in the 35 | -- tab. 36 | -- is_current: Boolean value, true if it is the current tab. 37 | -- 38 | -- buffer_ids, file_names and file_paths contain their information in 39 | -- the same order. So buffer_ids[1], file_names[1] and file_paths[1] 40 | -- refer to the same buffer. 41 | entry_formatter = function(tab_id, buffer_ids, file_names, file_paths, is_current) 42 | local entry_string = table.concat(file_names, ', ') 43 | return string.format('%d: %s%s', tab_id, entry_string, is_current and ' <' or '') 44 | end, 45 | 46 | -- This function is used to convert a tab to a string that is then 47 | -- used to sort all entries. The string is also used to determine if 48 | -- if a tab matches a given search query. 49 | -- tab_id: id of the tab. This id can be passed to neovims api in order 50 | -- to get information about the tab. 51 | -- buffer_ids: A list of ids for every buffer opened in the tab. Those 52 | -- ids can be passed to neovims api in order to get 53 | -- information about the buffer. 54 | -- file_names: A list of names (strings) for every buffer opened in the 55 | -- tab. 56 | -- file_paths: A list of paths (strings) for every buffer opened in the 57 | -- tab. 58 | -- is_current: Boolean value, true if it is the current tab. 59 | -- 60 | -- buffer_ids, file_names and file_paths contain their information in 61 | -- the same order. So buffer_ids[1], file_names[1] and file_paths[1] 62 | -- refer to the same buffer. 63 | entry_ordinal = function(tab_id, buffer_ids, file_names, file_paths, is_current) 64 | return table.concat(file_names, ' ') 65 | end, 66 | 67 | -- This boolean value controls whether telescopes previewer is shown, or 68 | -- not. 69 | show_preview = true, 70 | 71 | -- These shortcuts can be pressed to close the selected tab (and all buffers 72 | -- opened in it). Their values have to be valid neovim-shortcuts. 73 | close_tab_shortcut_i = '', -- if you're in insert mode 74 | close_tab_shortcut_n = 'D', -- if you're in normal mode 75 | } 76 | 77 | ============================================================================== 78 | USAGE *telescope-tabs-usage* 79 | 80 | :Telescope telescope-tabs list_tabs *list_tabs* 81 | This opens the telescope picker that displays all currently opened tabs. 82 | You can pass arguments passed to this function in order to configure the picker 83 | itself. See the telescope docs for more information. 84 | 85 | :lua require('telescope-tabs').go_to_previous() *go_to_previous* 86 | This function opens the last used tab immediately. This works by registering an 87 | auto-command for the 'TabEnter' event that keeps track of your tab-navigations. 88 | So this function does not only work if you switch tabs using the picker, it 89 | also functions just fine when navigating using neovims tab movement methods. 90 | 91 | ============================================================================== 92 | API *telescope-tabs-api* 93 | 94 | The available command can also be accessed from lua. 95 | 96 | local tabs = require'telescope-tabs' 97 | 98 | tabs.setup(opts: {table}) 99 | See configuration 100 | 101 | tabs.list_tabs(opts: {table}) 102 | This opens the telescope picker that displays all currently opened tabs. 103 | You can pass arguments passed to this function in order to configure the 104 | picker itself. See the telescope docs for more information. 105 | opts are directly passed to telescope. 106 | 107 | tabs.go_to_previous() 108 | This function opens the last used tab immediately. This works by registering 109 | an auto-command for the 'TabEnter' event that keeps track of your 110 | tab-navigations. So this function does not only work if you switch tabs using 111 | the picker, it also functions just fine when navigating using neovims tab 112 | movement methods. 113 | -------------------------------------------------------------------------------- /lua/telescope-tabs.lua: -------------------------------------------------------------------------------- 1 | return require 'telescope._extensions.telescope-tabs.main' 2 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/telescope-tabs.lua: -------------------------------------------------------------------------------- 1 | local has_telescope, telescope = pcall(require, 'telescope') 2 | local TelescopeTabs = require 'telescope._extensions.telescope-tabs.main' 3 | 4 | if not has_telescope then 5 | error 'This plugin requires telescope.nvim (https://github.com/nvim-telescope/telescope.nvim)' 6 | end 7 | 8 | return telescope.register_extension { 9 | setup = TelescopeTabs.setup, 10 | exports = { 11 | list_tabs = TelescopeTabs.list_tabs, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/telescope-tabs/main.lua: -------------------------------------------------------------------------------- 1 | local is_empty_table = function(t) 2 | if t == nil then 3 | return true 4 | end 5 | return next(t) == nil 6 | end 7 | 8 | local normalize = function(config, existing) 9 | local conf = existing 10 | if is_empty_table(config) then 11 | return conf 12 | end 13 | 14 | for k, v in pairs(config) do 15 | conf[k] = v 16 | end 17 | 18 | return conf 19 | end 20 | 21 | local pickers = require 'telescope.pickers' 22 | local finders = require 'telescope.finders' 23 | local actions = require 'telescope.actions' 24 | local action_state = require 'telescope.actions.state' 25 | local conf = require('telescope.config').values 26 | 27 | local close_tab = function(bufnr) 28 | local current_picker = action_state.get_current_picker(bufnr) 29 | local current_entry = action_state:get_selected_entry() 30 | if vim.api.nvim_get_current_tabpage() == current_entry.value[5] then 31 | vim.notify('You cannot close the currently visible tab :(', vim.log.levels.ERROR) 32 | return 33 | end 34 | current_picker:delete_selection(function(selection) 35 | for _, wid in ipairs(selection.value[4]) do 36 | vim.api.nvim_win_close(wid, false) 37 | end 38 | end) 39 | end 40 | 41 | local M = { 42 | config = {}, 43 | } 44 | 45 | local default_conf = { 46 | entry_formatter = function(tab_id, buffer_ids, file_names, file_paths, is_current) 47 | local entry_string = table.concat(file_names, ', ') 48 | return string.format('%d: %s%s', tab_id, entry_string, is_current and ' <' or '') 49 | end, 50 | entry_ordinal = function(tab_id, buffer_ids, file_names, file_paths, is_current) 51 | return table.concat(file_names, ' ') 52 | end, 53 | show_preview = true, 54 | close_tab_shortcut_i = '', 55 | close_tab_shortcut_n = 'D', 56 | } 57 | 58 | M.conf = default_conf 59 | 60 | M.setup = function(opts) 61 | normalize(opts, M.conf) 62 | end 63 | 64 | local visible_tab = vim.api.nvim_get_current_tabpage() 65 | local tab_stack = {} 66 | 67 | vim.api.nvim_create_autocmd('TabEnter', { 68 | group = vim.api.nvim_create_augroup('WatchTabs', { clear = true }), 69 | callback = function() 70 | table.insert(tab_stack, visible_tab) 71 | visible_tab = vim.api.nvim_get_current_tabpage() 72 | end, 73 | }) 74 | 75 | M.go_to_previous = function() 76 | local last_tab = table.remove(tab_stack) 77 | while last_tab ~= nil and not vim.api.nvim_tabpage_is_valid(last_tab) do 78 | last_tab = table.remove(tab_stack) 79 | end 80 | if last_tab == nil then 81 | vim.notify('No previous tab to go to', vim.log.levels.ERROR) 82 | return 83 | end 84 | vim.api.nvim_set_current_tabpage(last_tab) 85 | end 86 | 87 | M.list_tabs = function(opts) 88 | opts = vim.tbl_deep_extend('force', M.conf, opts or {}) 89 | local res = {} 90 | local current_tab = { number = vim.api.nvim_tabpage_get_number(0), index = nil } 91 | for index, tid in ipairs(vim.api.nvim_list_tabpages()) do 92 | local file_names = {} 93 | local file_paths = {} 94 | local file_ids = {} 95 | local window_ids = {} 96 | local is_current = current_tab.number == vim.api.nvim_tabpage_get_number(tid) 97 | for _, wid in ipairs(vim.api.nvim_tabpage_list_wins(tid)) do 98 | -- Only consider the normal windows and ignore the floating windows 99 | if vim.api.nvim_win_get_config(wid).relative == "" then 100 | local bid = vim.api.nvim_win_get_buf(wid) 101 | local path = vim.api.nvim_buf_get_name(bid) 102 | local file_name = vim.fn.fnamemodify(path, ':t') 103 | table.insert(file_names, file_name) 104 | table.insert(file_paths, path) 105 | table.insert(file_ids, bid) 106 | table.insert(window_ids, wid) 107 | end 108 | end 109 | if is_current then 110 | current_tab.index = index 111 | end 112 | table.insert(res, { file_names, file_paths, file_ids, window_ids, tid, is_current }) 113 | end 114 | pickers 115 | .new(opts, { 116 | prompt_title = 'Tabs', 117 | finder = finders.new_table { 118 | results = res, 119 | entry_maker = function(entry) 120 | local entry_string = opts.entry_formatter(entry[5], entry[3], entry[1], entry[2], entry[6]) 121 | local ordinal_string = opts.entry_ordinal(entry[5], entry[3], entry[1], entry[2], entry[6]) 122 | return { 123 | value = entry, 124 | path = entry[2][1], 125 | display = entry_string, 126 | ordinal = ordinal_string, 127 | } 128 | end, 129 | }, 130 | sorter = conf.generic_sorter {}, 131 | attach_mappings = function(prompt_bufnr, map) 132 | actions.select_default:replace(function() 133 | local selection = action_state.get_selected_entry() 134 | if not selection then 135 | vim.notify('No matching tab found', vim.log.levels.WARN) 136 | return 137 | end 138 | actions.close(prompt_bufnr) 139 | vim.api.nvim_set_current_tabpage(selection.value[5]) 140 | end) 141 | map('i', opts.close_tab_shortcut_i, close_tab) 142 | map('n', opts.close_tab_shortcut_n, close_tab) 143 | return true 144 | end, 145 | previewer = opts.show_preview and conf.file_previewer {} or nil, 146 | on_complete = { 147 | function(picker) 148 | picker:set_selection(current_tab.index - 1) 149 | end, 150 | }, 151 | }) 152 | :find() 153 | end 154 | 155 | return M 156 | --------------------------------------------------------------------------------