Full Code of FabianWirth/search.nvim for AI

main 7b8f2315d031 cached
11 files
27.7 KB
7.6k tokens
1 requests
Download .txt
Repository: FabianWirth/search.nvim
Branch: main
Commit: 7b8f2315d031
Files: 11
Total size: 27.7 KB

Directory structure:
gitextract_zr5l5e27/

├── LICENSE.md
├── README.md
├── lua/
│   └── search/
│       ├── init.lua
│       ├── settings.lua
│       ├── tab_bar.lua
│       ├── tabs/
│       │   ├── collection.lua
│       │   ├── init.lua
│       │   └── tab.lua
│       └── util.lua
└── tests/
    ├── configuration_spec.lua
    └── minimal_init.vim

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) 2023 Fabian Wirth

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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

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.


================================================
FILE: README.md
================================================
# search.nvim

*"**search.nvim** is a Neovim plugin that enhances the functionality of the [Telescope](https://github.com/nvim-telescope/telescope.nvim) plugin by providing a tab-based search experience. It allows you to seamlessly switch between different search modes within the Telescope window using tabs"* - ChatGPT

![example](https://raw.githubusercontent.com/FabianWirth/search.nvim/main/example.gif)

> [!WARNING]
> this plugin is in early development and might have some bugs. You can also expect changes to the configuration api.

## Features

- **Tab-based Searching**: Easily switch between different search modes, each represented by a tab.
- **Integration with Telescope**: Leverages the power of the Telescope plugin for versatile searching.
- **Customizable Tabs**: Configure the available tabs according to your preferences. (coming soon)
- **keybindings for switching tabs**: switch tabs by configurable keys

## Installation

Install search.nvim using your preferred plugin manager. For example:
```lua
--- lazy nvim
{
    "FabianWirth/search.nvim",
    dependencies = { "nvim-telescope/telescope.nvim" }
}
```

## default tabs
the default tabs are:
- find_files
- git_files
- live_grep

they can be configured in the setup function.

## Usage

### Opening the Tabsearch Window
To open the search.nvim window, use the following command:

```lua
require('search').open()
```
This will activate the default tab and open the Telescope window with the specified layout.
it is also possible to provide a tab_id or tab_name to directly activate a specific tab (id takes precedence over name).
Any tab collections defined can also be accessed via the collection key.

```lua
require('search').open({ tab_id = 2 })
require('search').open({ tab_name = 'Grep' }) -- if multiple tabs are named the same, the first is selected
require('search').open({ collection = 'git' }) -- Open the 'git' collection of pickers
```

### Switching Tabs
Navigate between tabs using the **`<Tab>`** and **`<S-Tab>`** keys in normal and insert modes. This allows you to switch between different search modes conveniently.

### Customizing Tabs
You can customize the available tabs by modifying the tabs table in the plugin configuration. Each tab should be defined as a Lua table with the following properties:

- name: Display name of the tab.
- tele_func: The Telescope function associated with the tab.
- available (optional): A function to determine if the tab is currently available based on certain conditions.
For example:

```lua
local builtin = require('telescope.builtin')
require("search").setup({
  mappings = { -- optional: configure the mappings for switching tabs (will be set in normal and insert mode(!))
    next = "<Tab>",
    prev = "<S-Tab>"
  },
  append_tabs = { -- append_tabs will add the provided tabs to the default ones
    {
      "Commits", -- or name = "Commits"
      builtin.git_commits, -- or tele_func = require('telescope.builtin').git_commits
      available = function() -- optional
        return vim.fn.isdirectory(".git") == 1
      end
    }
  },
  -- its also possible to overwrite the default tabs using the tabs key instead of append_tabs
  tabs = {
    {
      "Files",
      function(opts)
        opts = opts or {}
        if vim.fn.isdirectory(".git") == 1 then
          builtin.git_files(opts)
        else
          builtin.find_files(opts)
        end
      end
    }
  },
})
```

### Customizing key bindings
Simple rebind, will bind the the keys in both normal mode and insert mode.
```lua
  mappings = {
    next = "<Tab>",
    prev = "<S-Tab>"
  }
```
You can also bind keys in specific modes by supplying a list of key-mode pairs. The following would bind H and L to previous and next in normal mode
in addition to binding tab and shift+tab like in the example above.
```lua
  mappings = {
    next = { { "L", "n" }, { "<Tab>", "n" }, { "<Tab>", "i" } },
    prev = { { "H", "n" }, { "<S-Tab>", "n" }, { "<S-Tab>", "i" } }
  }
```

### Tab Collections
If you want to group certain pickers together into separate search windows you can use the collections keyword:

```lua
local builtin = require('telescope.builtin')
require("search").setup({
  initial_tab = 1,
  tabs = { ... }, -- As shown above
  collections = {
    -- Here the "git" collection is defined. It follows the same configuraton layout as tabs.
    git = {
      initial_tab = 1, -- Git branches
      tabs = {
        { name = "Branches", tele_func = builtin.git_branches },
        { name = "Commits", tele_func = builtin.git_commits },
        { name = "Stashes", tele_func = builtin.git_stash },
      }
    }
  }
})
``` 

### Passing options to telescope

#### Passing simple telescope options

Options can be passed to each telescope picker using the `tele_opts` table. 

For example having two tabs, one with hidden files and one without.
```lua
local builtin = require('telescope.builtin')
require("search").setup({
  initial_tab = 1,
  tabs = {
    { name = "Files",      tele_func = builtin.find_files },
    { name = "All Files",  tele_func = builtin.find_files, tele_opts = { no_ignore = true, hidden = true }},
  },
})
```

#### Passing default_text

The above method should be useful for most use cases. One exception though is passing `default_text` to telescope.
Since search.nvim persists the prompt between tabs providing a `default_text` to each tab will break it.

Instead default_text can be passed when calling `open`.
```lua
require('search').open({ tab_name = 'Grep', default_text = get_visual_selection() })
-- or
-- NOTE: tele_opts defined here are only sent to the initial tab.
require('search').open({ tab_name = 'Grep', tele_opts = { default_text = get_visual_selection() } })
```

#### Advanced picker configuration
More advanced use cases are best handled by making your own `tele_func` and handle your logic there.

```lua
require("search").setup({
  initial_tab = 1,
  tabs = {
    {
      name = "Files",
      tele_func = function() 
        -- Your custom config logic.
        telescope.find_files({})
      end 
    },
  },
})
```

### known issues
- pickers with more-than-average loading time (like lsp related, or http sending pickers) can feel a bit off, since the UI will wait for them to be ready.
- heavily custom configured telescope settings (like in many nvim distros) might lead to unexpected errors, please open an issue if you encounter any.
- A window with no available pickers can cause neovim to hang.

## License

This plugin is licensed under the MIT License. See the [LICENSE](https://github.com/FabianWirth/search.nvim?tab=MIT-1-ov-file) file for details.

-------------------------------------------------------------------------------


Happy searching with search.nvim! 🚀


================================================
FILE: lua/search/init.lua
================================================
local M = {}

local util = require('search.util')
local settings = require('search.settings')
local tab_bar = require('search.tab_bar')
local tabs = require('search.tabs')


--- opens the tab window and anchors it to the telescope window
--- @param telescope_win_id number the id of the telescope window
--- @return nil
local tab_window = function(telescope_win_id)
	-- the width of the prompt
	local telescope_width = vim.fn.winwidth(telescope_win_id)

	-- if the telescope window is closed, we exit early
	-- this can happen when the user holds down the tab key
	if telescope_width == -1 then
		return
	end

	-- create the tab bar window, anchoring it to the telescope window
	local tab_bar_win = tab_bar.create({
		width = telescope_width,
		relative = 'win',
		win = telescope_win_id,
		col = 0,
		row = 2,
	})

	-- make this window disappear when the telescope window is closed
	local tele_buf = vim.api.nvim_get_current_buf()
	vim.api.nvim_create_autocmd("WinLeave", {
		buffer = tele_buf,
		nested = true,
		once = true,
		callback = function()
			vim.api.nvim_win_close(tab_bar_win, true)
		end,
	})
end


--- opens the telescope window and sets the prompt to the one that was used before
local open_telescope = function(telescope_opts)
	M.busy = true
	local tab = tabs.current()
	local prompt = M.current_prompt
	local mode = vim.api.nvim_get_mode().mode

	-- since some telescope functions are linked to lsp, we need to make sure that we are in the correct buffer
	-- this would become an issue if we are coming from another tab
	if vim.api.nvim_get_current_win() ~= M.opened_on_win then
		vim.api.nvim_set_current_win(M.opened_on_win)
	end
	tab:start_waiting()

	-- Pass along any telescope options. Set the title to the tab name.
	local tele_opts = tab.tele_opts or {}
	tele_opts.prompt_title = tab.name

	-- Merge telescope options passed from different places.
	-- Merge telescope_opts and tab.tele_opts
	for k,v in pairs(telescope_opts or {}) do
		tele_opts[k] = v
	end

	-- then we spawn the telescope window
	local success = pcall(tab.tele_func, tele_opts)

	-- this (only) happens, if the telescope function actually errors out.
	-- if the telescope window does not open without error, this is not handled here
	if not success then
		M.busy = false
		tab:fail()
		M.continue_tab(false)
		return
	end


	-- find a better way to do this
	-- we might need to wait for the telescope window to open
	util.do_when(function()
			-- wait for the window change
			return M.opened_on_win ~= vim.api.nvim_get_current_win()
		end,
		function()
			tab:stop_waiting()
			local current_win_id = vim.api.nvim_get_current_win()
			util.set_keymap()

			-- now we set the prompt to the one we had before
			vim.api.nvim_feedkeys(prompt, 't', true)

			-- If keymaps for navigating panes are defined in normal mode, the prompt should remain in normal mode to allow
			-- navigating multiple maps at a time.
			-- If the mode was normal mode before the tab change, then change back to normal mode. This is unless the search
			-- is being opened using open(), since then then user could be using normal mode in their previous active buffer.
			if mode == "n" and M.opened_from_buffer == false then
				vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), 'n', false)
			end
			M.opened_from_buffer = false

			-- TODO: find a better way to do this - defer_fn will work, but will also cause some kind of redrawing
			-- using vim.wait(n) does not work
			vim.defer_fn(function()
				tab_window(current_win_id)
				M.busy = false
			end, 4)
		end,
		2000, -- wait for 2 second at most
		function()
			M.busy = false
			tab:fail()
			M.continue_tab(false)
		end
	)
end

--- the prompt that was used before
M.current_prompt = ""

M.direction = "next"

M.busy = false

M.continue_tab = function(remember)
	if M.direction == "next" then
		M.next_tab(remember)
	else
		M.previous_tab(remember)
	end
end

--- switches to the next tab, preserving the prompt
--- only switches to tabs that are available
M.next_tab = function(remember)
	remember = remember == nil and true or remember
	M.direction = "next"

	if M.busy then
		return
	end
	util.next_available()

	if remember then
		M.remember_prompt()
	end

	open_telescope()
end

--- switches to the previous tab, preserving the prompt
M.previous_tab = function(remember)
	remember = remember == nil and true or remember
	M.direction = "previous"

	if M.busy then
		return
	end
	util.previous_available()

	if remember then
		M.remember_prompt()
	end

	open_telescope()
end

--- remembers the prompt that was used before
M.remember_prompt = function()
	local current_prompt = vim.api.nvim_get_current_line()
	-- removing the prefix, by cutting the length
	local without_prefix = string.sub(current_prompt, M.prefix_len + 1)
	M.current_prompt = without_prefix
end

--- resets the state of the search module
M.reset = function(opts)
	opts = opts or {}

	tabs.current_collection_id = "default"
	if opts.collection then
		tabs.current_collection_id = opts.collection
	end

	if opts.tab_id then
		tabs.set_by_id(opts.tab_id)
	elseif opts.tab_name then
		tabs.set_by_name(opts.tab_name)
	else
		tabs.initial_tab()
	end

	M.current_prompt = ""
	M.opened_on_win = -1
	M.opened_from_buffer = true
end

-- the prefix can be defined in the telescope config, so we need to read
-- it's length in the open() method.
-- @todo: maybe do the reading somewhere else, to avoid doing so multiple times
M.prefix_len = 2

M.opened_on_win = -1

M.opened_from_buffer = true

--- opens the telescope window with the current prompt
--- this is the function that should be called from the outside
M.open = function(opts)

	-- TODO: find a better way to do this
	-- this is just a workaround to make sure that the settings are initialized
	-- if the user did not call setup() themselves
	if not settings.initialized then
		settings.setup()
	end

	local prefix = require("telescope.config").values.prompt_prefix or "> "
	M.prefix_len = #prefix

	M.reset(opts)
	M.opened_on_win = vim.api.nvim_get_current_win()
	M.busy = true
	M.opened_from_buffer = true

	-- Pass along tele_opts to telescope
	local tele_func_opts = {}
	if opts ~= nil then
		tele_func_opts = opts.tele_opts or {}
		if tele_func_opts.default_text == nil then
			tele_func_opts.default_text = opts.default_text or ""
		end
	end
	open_telescope(tele_func_opts)
end

-- configuration
M.setup = function(opts)
	settings.setup(opts)
end

return M


================================================
FILE: lua/search/settings.lua
================================================
local M = {}

M.default_initial_tab = 1

M.initialized = false

M.default_keys = {
	next = { { "<Tab>", "n" }, { "<Tab>", "i" } },
	prev = { { "<S-Tab>", "n" }, { "<S-Tab>", "i" } },
}

M.keys = vim.deepcopy(M.default_keys)

local builtin = require('telescope.builtin')
M.defaults = {
	{
		"Files",
		builtin.find_files,
	},
	{
		"Git files",
		builtin.git_files,
		available = function()
			return vim.fn.isdirectory(".git") == 1
		end
	},
	{
		"Grep",
		builtin.live_grep,
	},
}

M.setup = function(opts)
	opts = opts or {}

	-- the deepcopz is needed to avoid the tabs being shared between
	-- between calls of the setup function - not sure if we need this
	-- but it's better to be safe than sorry
	local tabs = vim.deepcopy(M.defaults)
	local initial_tab = M.default_initial_tab
	local collections = {}

	-- if the user has specified a custom list of tabs, use that instead
	-- of the default
	if opts.tabs ~= nil then
		tabs = opts.tabs
	end

	if opts.collections ~= nil then
		collections = opts.collections
	end

	-- if the user has specified a custom list of tabs to append, append
	-- them to the current list of tabs
	if opts.append_tabs ~= nil then
		for _, tab in ipairs(opts.append_tabs) do
			table.insert(tabs, tab)
		end
	end

	M.keys = vim.deepcopy(M.default_keys)
	if opts.mappings ~= nil then
		if opts.mappings.next ~= nil then
			M.keys.next = opts.mappings.next
		end
		if opts.mappings.prev ~= nil then
			M.keys.prev = opts.mappings.prev
		end
	end

	-- if the user has specified a custom initial tab, use that instead
	-- of the default
	if opts.initial_tab ~= nil then
		initial_tab = opts.initial_tab
	end

	require("search.tabs").init({
		tabs = tabs,
		collections = collections,
		initial_id = initial_tab,
	})

	M.initialized = true
end


return M


================================================
FILE: lua/search/tab_bar.lua
================================================
local M = {}

local tabs_module = require("search.tabs")

M.seperator = "|"

M.create = function(conf)
	local buf_id = vim.api.nvim_create_buf(false, true)
	local max_width = conf.width
	local tabline_config = M.make(buf_id, max_width)
	local config = vim.tbl_extend("keep", tabline_config, conf)
	return vim.api.nvim_open_win(buf_id, false, config)
end

M.make = function(buf_id, max_width)
	local collection = tabs_module.current_collection()
	local tabs = collection:all()

	local current_row = ""
	local content = {}
	local hil_groups = {}

	for _, tab in ipairs(tabs) do
		local tab_name = " " .. tab.name .. " "
		local len = #tab_name + #M.seperator

		if #current_row + len > max_width then
			--- we have to add a new row
			table.insert(content, current_row)
			current_row = ""
		end

		local group = "";
		if tab:is_current(collection) then
			group = "ActiveSearchTab"
		end
		if tab:has_failed() then
			group = "FailedSearchTab"
		end
		if not tab:is_available() then
			group = "InactiveSearchTab"
		end
		if tab:is_waiting() then
			group = "WaitingSearchTab"
		end

		if group ~= "" then
			table.insert(hil_groups, {
				s = #current_row,
				e = #current_row + #tab_name,
				r = #content,
				g = group
			})
		end
		current_row = current_row .. tab_name .. M.seperator
	end

	if current_row ~= "" then
		table.insert(content, current_row)
	end

	vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, content)
	-- Other highlight groups can be found at https://neovim.io/doc/user/syntax.html#%3Ahighlight
	vim.api.nvim_set_hl(0, 'ActiveSearchTab', vim.api.nvim_get_hl(0, {name="IncSearch"}))
	vim.api.nvim_set_hl(0, 'FailedSearchTab', vim.api.nvim_get_hl(0, {name="Error"}))
	vim.api.nvim_set_hl(0, 'InactiveSearchTab', vim.api.nvim_get_hl(0, {name="Conceal"}))
	vim.api.nvim_set_hl(0, 'WaitingSearchTab', vim.api.nvim_get_hl(0, {name="PmenuKind"}))
	for _, group in ipairs(hil_groups) do
		vim.api.nvim_buf_add_highlight(buf_id, -1, group.g, group.r, group.s, group.e)
	end

	return {
		width = max_width,
		height = #content,
		style = 'minimal',
		focusable = false,
		noautocmd = true,
	}
end


return M


================================================
FILE: lua/search/tabs/collection.lua
================================================

local Tab = require('search.tabs.tab')

local TabCollection = {}

function TabCollection:new(opts)
	local tab_list = {}
	for id, t in ipairs(opts.tabs) do
		local tab = Tab:new(t, id)
		tab_list[id] = tab
	end
	local id = opts.initial_tab or 1

	local o = {
		tab_list = tab_list,
		current_id = id,
		initial_id = id,
	}
	setmetatable(o, self)
	self.__index = self
	return o
end

function TabCollection:all()
	return self.tab_list
end

function TabCollection:current_tab()
	return self.current_id
end

--- get the current tab
function TabCollection:current()
	return self.tab_list[self.current_id]
end

function TabCollection:set_current(id)
	self.current_id = id
end

--- get the next tab
--- @return Tab # the next tab
function TabCollection:next()
	self.current_id = self.current_id + 1
	if self.current_id > #self.tab_list then
		self.current_id = 1
	end
	return self:current()
end

--- get the previous tab
--- @return Tab # the previous tab
function TabCollection:previous()
	self.current_id = self.current_id - 1
	if self.current_id < 1 then
		self.current_id = #self.tab_list
	end
	return self:current()
end

function TabCollection:initial_tab()
	self.current_id = self.initial_id
	return self:current()
end

--- get the tab with the given name
--- @param name string the name of the tab
--- @return Tab|nil # the tab with the given name
function TabCollection:id_by_name(name)
	for _, tab in ipairs(self.tab_list) do
		if tab.name == name then
			return tab
		end
	end
	return nil
end

--- set the tab with the given name as the current tab
--- @param name string the name of the tab
--- @return boolean # true if the tab was found, false otherwise
function TabCollection:set_by_name(name)
	local tab = self:id_by_name( name)
	if tab then
		self.current_id = tab.id
		return true
	end

	return false
end

function TabCollection:set_by_id(id)
	self.current_id = id
end

--- get the tab with the given id
--- @param id number the id of the tab
--- @return Tab|nil # the tab with the given id
function TabCollection:find_by_id(id)
	for _, tab in ipairs(self.tab_list) do
		if tab.id == id then
			return tab
		end
	end
	return nil
end

return TabCollection


================================================
FILE: lua/search/tabs/init.lua
================================================
local M = {}

TabCollection = require("search.tabs.collection")
---

M.collections = {}

M.current_collection_id = "default"

--- get all tabs
--- @return table # list of all tabs
M.all = function()
	return M.current_collection():all()
end


M.current_collection = function()
	return M.collections[M.current_collection_id]
end

--- initialize the tabs module
--- @param opts table with the following keys:
-- - tabs: list of tabs
-- - inital_tab: id of the tab to start with
M.init = function(opts)
	M.collections["default"] = TabCollection:new(opts)
	for id, collection_config in pairs(opts.collections) do
		local collection = TabCollection:new(collection_config)
		M.collections[id] = collection
	end
end

--- get the current tab
M.current = function()
	return M.current_collection():current()
end


--- get the next tab
--- @return Tab # the next tab
M.next = function()
	return M.current_collection():next()
end

--- get the previous tab
--- @return Tab # the previous tab
M.previous = function()
	return M.current_collection():previous()
end

M.initial_tab = function()
	return M.current_collection():initial_tab()
end

--- get the tab with the given name
--- @param name string the name of the tab
--- @return Tab|nil # the tab with the given name
M.id_by_name = function(name)
	return M.current_collection():id_by_name(name)
end

--- set the tab with the given name as the current tab
--- @param name string the name of the tab
--- @return boolean # true if the tab was found, false otherwise
M.set_by_name = function(name)
	return M.current_collection():set_by_name(name)
end

M.set_by_id = function(id)
	M.current_collection():set_current(id)
end

--- get the tab with the given id
--- @param id number the id of the tab
--- @return Tab|nil # the tab with the given id
M.find_by_id = function(id)
	return M.current_collection():find_by_id(id)
end

return M


================================================
FILE: lua/search/tabs/tab.lua
================================================

local Tab = {}

--- @class Tab
--- @field id number
--- @field name string
--- @field tele_func function
--- @field available_func function|nil
--- @field failed boolean
--- @field wait_for number|nil
--- @function new create a new tab
function Tab:new(tab, id)
	local name = tab.name or tab[1]
	local tele_func = tab.tele_func or tab[2]
	local tele_opts = tab.tele_opts or tab[3]

	-- this enables the user to define the function as second argument
	-- even if the first argument is named as name
	if name ~= nil and tab[1] ~= nil and type(tab[1]) == "function" then
		tele_func = tab[1]
	end

	local o = {
		id = id,
		name = name,
		tele_func = tele_func,
		tele_opts = tele_opts,
		available_func = tab.available,
		failed = false,
		waiting = false,
	}
	setmetatable(o, self)
	self.__index = self
	return o
end

--- @param self Tab
--- @return boolean
function Tab:is_current(collection)
	return collection:current_tab() == self.id
end

--- @return boolean
function Tab:is_available()
	return self.available_func == nil or self.available_func()
end

function Tab:fail()
	self.waiting = false
	self.failed = true
end

function Tab:has_failed()
	return self.failed
end

function Tab:reset_failed()
	self.failed = false
end

function Tab:is_waiting()
	return self.waiting
end

function Tab:stop_waiting()
	self.waiting = false
end

function Tab:start_waiting()
	self.failed = false
	self.waiting = true
end

return Tab


================================================
FILE: lua/search/util.lua
================================================
local M = {}

local tabs = require("search.tabs")
local settings = require("search.settings")

--- the amount of milliseconds to wait between checks
M.await_time = 10

--- runs a function when a condition is met
--- @param condition function a function that returns a boolean
--- @param callback function a function that is called when the condition is met
--- @param max_ms number the maximum amount of milliseconds to wait
--- @param fail_callback function a function that is called when the condition is not met in time
--- @return any the return value of the callback
M.do_when = function(condition, callback, max_ms, fail_callback)
	if max_ms == nil then
		max_ms = 1000
	end

	while max_ms > 0 do
		if condition() then
			return callback()
		end
		vim.wait(M.await_time)
		max_ms = max_ms - M.await_time
	end

	if fail_callback ~= nil then
		return fail_callback()
	end
end


--- binds the tab key to the next tab
--- and the shift tab key to the previous tab
M.set_keymap = function()
	-- now we bind our tab key to the next tab
	local opts = { noremap = true, silent = true }
	local cmd = "<cmd>lua require('search').next_tab()<CR>"
	local cmd_p = "<cmd>lua require('search').previous_tab()<CR>"


  local function set_keymap(keymap, cmd)
    if type(keymap) == "string" then
      vim.api.nvim_buf_set_keymap(0, 'n', keymap, cmd, opts)
      vim.api.nvim_buf_set_keymap(0, 'i', keymap, cmd, opts)
    else
      for _, value in ipairs(keymap) do
        vim.api.nvim_buf_set_keymap(0, value[2], value[1], cmd, opts)
      end
    end
  end

  set_keymap(settings.keys.next, cmd)
  set_keymap(settings.keys.prev, cmd_p)
  end

--- switches to the next available tab
--- @return Tab # the next available tab
M.next_available = function()
	local start = tabs.current().id
	while true do
		local tab = tabs.next()
		if tab:is_available() then
			return tab
		end
		if tab.id == start then
			return tab
		end
	end
end

--- switches to the previous available tab
--- @return Tab # the previous available tab
M.previous_available = function()
	local start = tabs.current().id
	while true do
		local tab = tabs.previous()
		if tab:is_available() then
			return tab
		end
		if tab.id == start then
			return tab
		end
	end
end
return M;


================================================
FILE: tests/configuration_spec.lua
================================================
local init = require('search.init')
local _tabs = require('search.tabs')
local _settings = require('search.settings')

local eq = assert.are.same

-- for my lsp to stop complaining
local describe = describe
local it = it
local before_each = before_each

describe("can configure search.nvim", function()
	before_each(function()
		_tabs.tab_list = {}
	end)


	it("can use default config", function()
		init.setup()

		local tabs = _tabs.all()
		eq(3, #tabs)
		eq('Files', tabs[1].name)
		eq('Git files', tabs[2].name)
		eq('Grep', tabs[3].name)
	end)

	it("can append a tab using long syntax", function()
		local config = {
			append_tabs = {
				{
					"Custom",
					function() return "custom" end
				}
			}
		}

		init.setup(config)
		eq(4, #_tabs.all())
		eq('Custom', _tabs.all()[4].name)
		eq('custom', _tabs.all()[4].tele_func())
	end)

	it("can append a tab using short syntax", function()
		_tabs.tab_list = {}
		local config = {
			append_tabs = {
				{ "Custom", function() return "custom" end }
			}
		}

		init.setup(config)
		eq(4, #_tabs.all())
		eq('Custom', _tabs.all()[4].name)
		eq('custom', _tabs.all()[4].tele_func())
	end)

	it("can append tab using partially short syntax", function()
		local config = {
			append_tabs = {
				{ "Custom", tele_func = function() return "custom" end }
			}
		}

		init.setup(config)
		eq(4, #_tabs.all())
		eq('Custom', _tabs.all()[4].name)
		eq('custom', _tabs.all()[4].tele_func())
	end)

	it("can append tab using partially short syntax2", function()
		local config = {
			append_tabs = {
				{ name = "Custom", function() return "custom" end }
			}
		}

		init.setup(config)
		eq(4, #_tabs.all())
		eq('Custom', _tabs.all()[4].name)
		eq('custom', _tabs.all()[4].tele_func())
	end)

	it("can define a available function", function()
		local config = {
			tabs = { { "Custom", function() return "custom" end, available = function() return false end } }
		}

		init.setup(config)
		eq(1, #_tabs.all())
		eq('Custom', _tabs.all()[1].name)
		eq('custom', _tabs.all()[1].tele_func())
		eq(false, _tabs.all()[1]:is_available())
	end)

	it("can configure mappings", function()
		local config = {
			mappings = {
				next = '<leader>l',
				prev = '<leader>h',
			}
		}

		init.setup(config)
		eq('<leader>l', _settings.keys.next)
		eq('<leader>h', _settings.keys.prev)
	end)

	it("has default mappings", function()
		init.setup()
		eq('<Tab>', _settings.keys.next)
		eq('<S-Tab>', _settings.keys.prev)
	end)
end)


================================================
FILE: tests/minimal_init.vim
================================================
set rtp+=../plenary.nvim
set rtp+=../telescope.nvim
runtime! plenary.nvim/lua/plenary.lua
Download .txt
gitextract_zr5l5e27/

├── LICENSE.md
├── README.md
├── lua/
│   └── search/
│       ├── init.lua
│       ├── settings.lua
│       ├── tab_bar.lua
│       ├── tabs/
│       │   ├── collection.lua
│       │   ├── init.lua
│       │   └── tab.lua
│       └── util.lua
└── tests/
    ├── configuration_spec.lua
    └── minimal_init.vim
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
  {
    "path": "LICENSE.md",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2023 Fabian Wirth\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 6746,
    "preview": "# search.nvim\n\n*\"**search.nvim** is a Neovim plugin that enhances the functionality of the [Telescope](https://github.co"
  },
  {
    "path": "lua/search/init.lua",
    "chars": 6449,
    "preview": "local M = {}\n\nlocal util = require('search.util')\nlocal settings = require('search.settings')\nlocal tab_bar = require('s"
  },
  {
    "path": "lua/search/settings.lua",
    "chars": 1780,
    "preview": "local M = {}\n\nM.default_initial_tab = 1\n\nM.initialized = false\n\nM.default_keys = {\n\tnext = { { \"<Tab>\", \"n\" }, { \"<Tab>\""
  },
  {
    "path": "lua/search/tab_bar.lua",
    "chars": 2124,
    "preview": "local M = {}\n\nlocal tabs_module = require(\"search.tabs\")\n\nM.seperator = \"|\"\n\nM.create = function(conf)\n\tlocal buf_id = v"
  },
  {
    "path": "lua/search/tabs/collection.lua",
    "chars": 2165,
    "preview": "\nlocal Tab = require('search.tabs.tab')\n\nlocal TabCollection = {}\n\nfunction TabCollection:new(opts)\n\tlocal tab_list = {}"
  },
  {
    "path": "lua/search/tabs/init.lua",
    "chars": 1867,
    "preview": "local M = {}\n\nTabCollection = require(\"search.tabs.collection\")\n---\n\nM.collections = {}\n\nM.current_collection_id = \"defa"
  },
  {
    "path": "lua/search/tabs/tab.lua",
    "chars": 1422,
    "preview": "\nlocal Tab = {}\n\n--- @class Tab\n--- @field id number\n--- @field name string\n--- @field tele_func function\n--- @field ava"
  },
  {
    "path": "lua/search/util.lua",
    "chars": 2238,
    "preview": "local M = {}\n\nlocal tabs = require(\"search.tabs\")\nlocal settings = require(\"search.settings\")\n\n--- the amount of millise"
  },
  {
    "path": "tests/configuration_spec.lua",
    "chars": 2464,
    "preview": "local init = require('search.init')\nlocal _tabs = require('search.tabs')\nlocal _settings = require('search.settings')\n\nl"
  },
  {
    "path": "tests/minimal_init.vim",
    "chars": 90,
    "preview": "set rtp+=../plenary.nvim\nset rtp+=../telescope.nvim\nruntime! plenary.nvim/lua/plenary.lua\n"
  }
]

About this extraction

This page contains the full source code of the FabianWirth/search.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (27.7 KB), approximately 7.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!