├── .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 |
--------------------------------------------------------------------------------