[
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2023 Fabian Wirth\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# search.nvim\n\n*\"**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\n\n![example](https://raw.githubusercontent.com/FabianWirth/search.nvim/main/example.gif)\n\n> [!WARNING]\n> this plugin is in early development and might have some bugs. You can also expect changes to the configuration api.\n\n## Features\n\n- **Tab-based Searching**: Easily switch between different search modes, each represented by a tab.\n- **Integration with Telescope**: Leverages the power of the Telescope plugin for versatile searching.\n- **Customizable Tabs**: Configure the available tabs according to your preferences. (coming soon)\n- **keybindings for switching tabs**: switch tabs by configurable keys\n\n## Installation\n\nInstall search.nvim using your preferred plugin manager. For example:\n```lua\n--- lazy nvim\n{\n    \"FabianWirth/search.nvim\",\n    dependencies = { \"nvim-telescope/telescope.nvim\" }\n}\n```\n\n## default tabs\nthe default tabs are:\n- find_files\n- git_files\n- live_grep\n\nthey can be configured in the setup function.\n\n## Usage\n\n### Opening the Tabsearch Window\nTo open the search.nvim window, use the following command:\n\n```lua\nrequire('search').open()\n```\nThis will activate the default tab and open the Telescope window with the specified layout.\nit is also possible to provide a tab_id or tab_name to directly activate a specific tab (id takes precedence over name).\nAny tab collections defined can also be accessed via the collection key.\n\n```lua\nrequire('search').open({ tab_id = 2 })\nrequire('search').open({ tab_name = 'Grep' }) -- if multiple tabs are named the same, the first is selected\nrequire('search').open({ collection = 'git' }) -- Open the 'git' collection of pickers\n```\n\n### Switching Tabs\nNavigate between tabs using the **`<Tab>`** and **`<S-Tab>`** keys in normal and insert modes. This allows you to switch between different search modes conveniently.\n\n### Customizing Tabs\nYou 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:\n\n- name: Display name of the tab.\n- tele_func: The Telescope function associated with the tab.\n- available (optional): A function to determine if the tab is currently available based on certain conditions.\nFor example:\n\n```lua\nlocal builtin = require('telescope.builtin')\nrequire(\"search\").setup({\n  mappings = { -- optional: configure the mappings for switching tabs (will be set in normal and insert mode(!))\n    next = \"<Tab>\",\n    prev = \"<S-Tab>\"\n  },\n  append_tabs = { -- append_tabs will add the provided tabs to the default ones\n    {\n      \"Commits\", -- or name = \"Commits\"\n      builtin.git_commits, -- or tele_func = require('telescope.builtin').git_commits\n      available = function() -- optional\n        return vim.fn.isdirectory(\".git\") == 1\n      end\n    }\n  },\n  -- its also possible to overwrite the default tabs using the tabs key instead of append_tabs\n  tabs = {\n    {\n      \"Files\",\n      function(opts)\n        opts = opts or {}\n        if vim.fn.isdirectory(\".git\") == 1 then\n          builtin.git_files(opts)\n        else\n          builtin.find_files(opts)\n        end\n      end\n    }\n  },\n})\n```\n\n### Customizing key bindings\nSimple rebind, will bind the the keys in both normal mode and insert mode.\n```lua\n  mappings = {\n    next = \"<Tab>\",\n    prev = \"<S-Tab>\"\n  }\n```\nYou 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\nin addition to binding tab and shift+tab like in the example above.\n```lua\n  mappings = {\n    next = { { \"L\", \"n\" }, { \"<Tab>\", \"n\" }, { \"<Tab>\", \"i\" } },\n    prev = { { \"H\", \"n\" }, { \"<S-Tab>\", \"n\" }, { \"<S-Tab>\", \"i\" } }\n  }\n```\n\n### Tab Collections\nIf you want to group certain pickers together into separate search windows you can use the collections keyword:\n\n```lua\nlocal builtin = require('telescope.builtin')\nrequire(\"search\").setup({\n  initial_tab = 1,\n  tabs = { ... }, -- As shown above\n  collections = {\n    -- Here the \"git\" collection is defined. It follows the same configuraton layout as tabs.\n    git = {\n      initial_tab = 1, -- Git branches\n      tabs = {\n        { name = \"Branches\", tele_func = builtin.git_branches },\n        { name = \"Commits\", tele_func = builtin.git_commits },\n        { name = \"Stashes\", tele_func = builtin.git_stash },\n      }\n    }\n  }\n})\n``` \n\n### Passing options to telescope\n\n#### Passing simple telescope options\n\nOptions can be passed to each telescope picker using the `tele_opts` table. \n\nFor example having two tabs, one with hidden files and one without.\n```lua\nlocal builtin = require('telescope.builtin')\nrequire(\"search\").setup({\n  initial_tab = 1,\n  tabs = {\n    { name = \"Files\",      tele_func = builtin.find_files },\n    { name = \"All Files\",  tele_func = builtin.find_files, tele_opts = { no_ignore = true, hidden = true }},\n  },\n})\n```\n\n#### Passing default_text\n\nThe above method should be useful for most use cases. One exception though is passing `default_text` to telescope.\nSince search.nvim persists the prompt between tabs providing a `default_text` to each tab will break it.\n\nInstead default_text can be passed when calling `open`.\n```lua\nrequire('search').open({ tab_name = 'Grep', default_text = get_visual_selection() })\n-- or\n-- NOTE: tele_opts defined here are only sent to the initial tab.\nrequire('search').open({ tab_name = 'Grep', tele_opts = { default_text = get_visual_selection() } })\n```\n\n#### Advanced picker configuration\nMore advanced use cases are best handled by making your own `tele_func` and handle your logic there.\n\n```lua\nrequire(\"search\").setup({\n  initial_tab = 1,\n  tabs = {\n    {\n      name = \"Files\",\n      tele_func = function() \n        -- Your custom config logic.\n        telescope.find_files({})\n      end \n    },\n  },\n})\n```\n\n### known issues\n- 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.\n- heavily custom configured telescope settings (like in many nvim distros) might lead to unexpected errors, please open an issue if you encounter any.\n- A window with no available pickers can cause neovim to hang.\n\n## License\n\nThis plugin is licensed under the MIT License. See the [LICENSE](https://github.com/FabianWirth/search.nvim?tab=MIT-1-ov-file) file for details.\n\n-------------------------------------------------------------------------------\n\n\nHappy searching with search.nvim! 🚀\n"
  },
  {
    "path": "lua/search/init.lua",
    "content": "local M = {}\n\nlocal util = require('search.util')\nlocal settings = require('search.settings')\nlocal tab_bar = require('search.tab_bar')\nlocal tabs = require('search.tabs')\n\n\n--- opens the tab window and anchors it to the telescope window\n--- @param telescope_win_id number the id of the telescope window\n--- @return nil\nlocal tab_window = function(telescope_win_id)\n\t-- the width of the prompt\n\tlocal telescope_width = vim.fn.winwidth(telescope_win_id)\n\n\t-- if the telescope window is closed, we exit early\n\t-- this can happen when the user holds down the tab key\n\tif telescope_width == -1 then\n\t\treturn\n\tend\n\n\t-- create the tab bar window, anchoring it to the telescope window\n\tlocal tab_bar_win = tab_bar.create({\n\t\twidth = telescope_width,\n\t\trelative = 'win',\n\t\twin = telescope_win_id,\n\t\tcol = 0,\n\t\trow = 2,\n\t})\n\n\t-- make this window disappear when the telescope window is closed\n\tlocal tele_buf = vim.api.nvim_get_current_buf()\n\tvim.api.nvim_create_autocmd(\"WinLeave\", {\n\t\tbuffer = tele_buf,\n\t\tnested = true,\n\t\tonce = true,\n\t\tcallback = function()\n\t\t\tvim.api.nvim_win_close(tab_bar_win, true)\n\t\tend,\n\t})\nend\n\n\n--- opens the telescope window and sets the prompt to the one that was used before\nlocal open_telescope = function(telescope_opts)\n\tM.busy = true\n\tlocal tab = tabs.current()\n\tlocal prompt = M.current_prompt\n\tlocal mode = vim.api.nvim_get_mode().mode\n\n\t-- since some telescope functions are linked to lsp, we need to make sure that we are in the correct buffer\n\t-- this would become an issue if we are coming from another tab\n\tif vim.api.nvim_get_current_win() ~= M.opened_on_win then\n\t\tvim.api.nvim_set_current_win(M.opened_on_win)\n\tend\n\ttab:start_waiting()\n\n\t-- Pass along any telescope options. Set the title to the tab name.\n\tlocal tele_opts = tab.tele_opts or {}\n\ttele_opts.prompt_title = tab.name\n\n\t-- Merge telescope options passed from different places.\n\t-- Merge telescope_opts and tab.tele_opts\n\tfor k,v in pairs(telescope_opts or {}) do\n\t\ttele_opts[k] = v\n\tend\n\n\t-- then we spawn the telescope window\n\tlocal success = pcall(tab.tele_func, tele_opts)\n\n\t-- this (only) happens, if the telescope function actually errors out.\n\t-- if the telescope window does not open without error, this is not handled here\n\tif not success then\n\t\tM.busy = false\n\t\ttab:fail()\n\t\tM.continue_tab(false)\n\t\treturn\n\tend\n\n\n\t-- find a better way to do this\n\t-- we might need to wait for the telescope window to open\n\tutil.do_when(function()\n\t\t\t-- wait for the window change\n\t\t\treturn M.opened_on_win ~= vim.api.nvim_get_current_win()\n\t\tend,\n\t\tfunction()\n\t\t\ttab:stop_waiting()\n\t\t\tlocal current_win_id = vim.api.nvim_get_current_win()\n\t\t\tutil.set_keymap()\n\n\t\t\t-- now we set the prompt to the one we had before\n\t\t\tvim.api.nvim_feedkeys(prompt, 't', true)\n\n\t\t\t-- If keymaps for navigating panes are defined in normal mode, the prompt should remain in normal mode to allow\n\t\t\t-- navigating multiple maps at a time.\n\t\t\t-- If the mode was normal mode before the tab change, then change back to normal mode. This is unless the search\n\t\t\t-- is being opened using open(), since then then user could be using normal mode in their previous active buffer.\n\t\t\tif mode == \"n\" and M.opened_from_buffer == false then\n\t\t\t\tvim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(\"<Esc>\", true, false, true), 'n', false)\n\t\t\tend\n\t\t\tM.opened_from_buffer = false\n\n\t\t\t-- TODO: find a better way to do this - defer_fn will work, but will also cause some kind of redrawing\n\t\t\t-- using vim.wait(n) does not work\n\t\t\tvim.defer_fn(function()\n\t\t\t\ttab_window(current_win_id)\n\t\t\t\tM.busy = false\n\t\t\tend, 4)\n\t\tend,\n\t\t2000, -- wait for 2 second at most\n\t\tfunction()\n\t\t\tM.busy = false\n\t\t\ttab:fail()\n\t\t\tM.continue_tab(false)\n\t\tend\n\t)\nend\n\n--- the prompt that was used before\nM.current_prompt = \"\"\n\nM.direction = \"next\"\n\nM.busy = false\n\nM.continue_tab = function(remember)\n\tif M.direction == \"next\" then\n\t\tM.next_tab(remember)\n\telse\n\t\tM.previous_tab(remember)\n\tend\nend\n\n--- switches to the next tab, preserving the prompt\n--- only switches to tabs that are available\nM.next_tab = function(remember)\n\tremember = remember == nil and true or remember\n\tM.direction = \"next\"\n\n\tif M.busy then\n\t\treturn\n\tend\n\tutil.next_available()\n\n\tif remember then\n\t\tM.remember_prompt()\n\tend\n\n\topen_telescope()\nend\n\n--- switches to the previous tab, preserving the prompt\nM.previous_tab = function(remember)\n\tremember = remember == nil and true or remember\n\tM.direction = \"previous\"\n\n\tif M.busy then\n\t\treturn\n\tend\n\tutil.previous_available()\n\n\tif remember then\n\t\tM.remember_prompt()\n\tend\n\n\topen_telescope()\nend\n\n--- remembers the prompt that was used before\nM.remember_prompt = function()\n\tlocal current_prompt = vim.api.nvim_get_current_line()\n\t-- removing the prefix, by cutting the length\n\tlocal without_prefix = string.sub(current_prompt, M.prefix_len + 1)\n\tM.current_prompt = without_prefix\nend\n\n--- resets the state of the search module\nM.reset = function(opts)\n\topts = opts or {}\n\n\ttabs.current_collection_id = \"default\"\n\tif opts.collection then\n\t\ttabs.current_collection_id = opts.collection\n\tend\n\n\tif opts.tab_id then\n\t\ttabs.set_by_id(opts.tab_id)\n\telseif opts.tab_name then\n\t\ttabs.set_by_name(opts.tab_name)\n\telse\n\t\ttabs.initial_tab()\n\tend\n\n\tM.current_prompt = \"\"\n\tM.opened_on_win = -1\n\tM.opened_from_buffer = true\nend\n\n-- the prefix can be defined in the telescope config, so we need to read\n-- it's length in the open() method.\n-- @todo: maybe do the reading somewhere else, to avoid doing so multiple times\nM.prefix_len = 2\n\nM.opened_on_win = -1\n\nM.opened_from_buffer = true\n\n--- opens the telescope window with the current prompt\n--- this is the function that should be called from the outside\nM.open = function(opts)\n\n\t-- TODO: find a better way to do this\n\t-- this is just a workaround to make sure that the settings are initialized\n\t-- if the user did not call setup() themselves\n\tif not settings.initialized then\n\t\tsettings.setup()\n\tend\n\n\tlocal prefix = require(\"telescope.config\").values.prompt_prefix or \"> \"\n\tM.prefix_len = #prefix\n\n\tM.reset(opts)\n\tM.opened_on_win = vim.api.nvim_get_current_win()\n\tM.busy = true\n\tM.opened_from_buffer = true\n\n\t-- Pass along tele_opts to telescope\n\tlocal tele_func_opts = {}\n\tif opts ~= nil then\n\t\ttele_func_opts = opts.tele_opts or {}\n\t\tif tele_func_opts.default_text == nil then\n\t\t\ttele_func_opts.default_text = opts.default_text or \"\"\n\t\tend\n\tend\n\topen_telescope(tele_func_opts)\nend\n\n-- configuration\nM.setup = function(opts)\n\tsettings.setup(opts)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/search/settings.lua",
    "content": "local M = {}\n\nM.default_initial_tab = 1\n\nM.initialized = false\n\nM.default_keys = {\n\tnext = { { \"<Tab>\", \"n\" }, { \"<Tab>\", \"i\" } },\n\tprev = { { \"<S-Tab>\", \"n\" }, { \"<S-Tab>\", \"i\" } },\n}\n\nM.keys = vim.deepcopy(M.default_keys)\n\nlocal builtin = require('telescope.builtin')\nM.defaults = {\n\t{\n\t\t\"Files\",\n\t\tbuiltin.find_files,\n\t},\n\t{\n\t\t\"Git files\",\n\t\tbuiltin.git_files,\n\t\tavailable = function()\n\t\t\treturn vim.fn.isdirectory(\".git\") == 1\n\t\tend\n\t},\n\t{\n\t\t\"Grep\",\n\t\tbuiltin.live_grep,\n\t},\n}\n\nM.setup = function(opts)\n\topts = opts or {}\n\n\t-- the deepcopz is needed to avoid the tabs being shared between\n\t-- between calls of the setup function - not sure if we need this\n\t-- but it's better to be safe than sorry\n\tlocal tabs = vim.deepcopy(M.defaults)\n\tlocal initial_tab = M.default_initial_tab\n\tlocal collections = {}\n\n\t-- if the user has specified a custom list of tabs, use that instead\n\t-- of the default\n\tif opts.tabs ~= nil then\n\t\ttabs = opts.tabs\n\tend\n\n\tif opts.collections ~= nil then\n\t\tcollections = opts.collections\n\tend\n\n\t-- if the user has specified a custom list of tabs to append, append\n\t-- them to the current list of tabs\n\tif opts.append_tabs ~= nil then\n\t\tfor _, tab in ipairs(opts.append_tabs) do\n\t\t\ttable.insert(tabs, tab)\n\t\tend\n\tend\n\n\tM.keys = vim.deepcopy(M.default_keys)\n\tif opts.mappings ~= nil then\n\t\tif opts.mappings.next ~= nil then\n\t\t\tM.keys.next = opts.mappings.next\n\t\tend\n\t\tif opts.mappings.prev ~= nil then\n\t\t\tM.keys.prev = opts.mappings.prev\n\t\tend\n\tend\n\n\t-- if the user has specified a custom initial tab, use that instead\n\t-- of the default\n\tif opts.initial_tab ~= nil then\n\t\tinitial_tab = opts.initial_tab\n\tend\n\n\trequire(\"search.tabs\").init({\n\t\ttabs = tabs,\n\t\tcollections = collections,\n\t\tinitial_id = initial_tab,\n\t})\n\n\tM.initialized = true\nend\n\n\nreturn M\n"
  },
  {
    "path": "lua/search/tab_bar.lua",
    "content": "local M = {}\n\nlocal tabs_module = require(\"search.tabs\")\n\nM.seperator = \"|\"\n\nM.create = function(conf)\n\tlocal buf_id = vim.api.nvim_create_buf(false, true)\n\tlocal max_width = conf.width\n\tlocal tabline_config = M.make(buf_id, max_width)\n\tlocal config = vim.tbl_extend(\"keep\", tabline_config, conf)\n\treturn vim.api.nvim_open_win(buf_id, false, config)\nend\n\nM.make = function(buf_id, max_width)\n\tlocal collection = tabs_module.current_collection()\n\tlocal tabs = collection:all()\n\n\tlocal current_row = \"\"\n\tlocal content = {}\n\tlocal hil_groups = {}\n\n\tfor _, tab in ipairs(tabs) do\n\t\tlocal tab_name = \" \" .. tab.name .. \" \"\n\t\tlocal len = #tab_name + #M.seperator\n\n\t\tif #current_row + len > max_width then\n\t\t\t--- we have to add a new row\n\t\t\ttable.insert(content, current_row)\n\t\t\tcurrent_row = \"\"\n\t\tend\n\n\t\tlocal group = \"\";\n\t\tif tab:is_current(collection) then\n\t\t\tgroup = \"ActiveSearchTab\"\n\t\tend\n\t\tif tab:has_failed() then\n\t\t\tgroup = \"FailedSearchTab\"\n\t\tend\n\t\tif not tab:is_available() then\n\t\t\tgroup = \"InactiveSearchTab\"\n\t\tend\n\t\tif tab:is_waiting() then\n\t\t\tgroup = \"WaitingSearchTab\"\n\t\tend\n\n\t\tif group ~= \"\" then\n\t\t\ttable.insert(hil_groups, {\n\t\t\t\ts = #current_row,\n\t\t\t\te = #current_row + #tab_name,\n\t\t\t\tr = #content,\n\t\t\t\tg = group\n\t\t\t})\n\t\tend\n\t\tcurrent_row = current_row .. tab_name .. M.seperator\n\tend\n\n\tif current_row ~= \"\" then\n\t\ttable.insert(content, current_row)\n\tend\n\n\tvim.api.nvim_buf_set_lines(buf_id, 0, -1, false, content)\n\t-- Other highlight groups can be found at https://neovim.io/doc/user/syntax.html#%3Ahighlight\n\tvim.api.nvim_set_hl(0, 'ActiveSearchTab', vim.api.nvim_get_hl(0, {name=\"IncSearch\"}))\n\tvim.api.nvim_set_hl(0, 'FailedSearchTab', vim.api.nvim_get_hl(0, {name=\"Error\"}))\n\tvim.api.nvim_set_hl(0, 'InactiveSearchTab', vim.api.nvim_get_hl(0, {name=\"Conceal\"}))\n\tvim.api.nvim_set_hl(0, 'WaitingSearchTab', vim.api.nvim_get_hl(0, {name=\"PmenuKind\"}))\n\tfor _, group in ipairs(hil_groups) do\n\t\tvim.api.nvim_buf_add_highlight(buf_id, -1, group.g, group.r, group.s, group.e)\n\tend\n\n\treturn {\n\t\twidth = max_width,\n\t\theight = #content,\n\t\tstyle = 'minimal',\n\t\tfocusable = false,\n\t\tnoautocmd = true,\n\t}\nend\n\n\nreturn M\n"
  },
  {
    "path": "lua/search/tabs/collection.lua",
    "content": "\nlocal Tab = require('search.tabs.tab')\n\nlocal TabCollection = {}\n\nfunction TabCollection:new(opts)\n\tlocal tab_list = {}\n\tfor id, t in ipairs(opts.tabs) do\n\t\tlocal tab = Tab:new(t, id)\n\t\ttab_list[id] = tab\n\tend\n\tlocal id = opts.initial_tab or 1\n\n\tlocal o = {\n\t\ttab_list = tab_list,\n\t\tcurrent_id = id,\n\t\tinitial_id = id,\n\t}\n\tsetmetatable(o, self)\n\tself.__index = self\n\treturn o\nend\n\nfunction TabCollection:all()\n\treturn self.tab_list\nend\n\nfunction TabCollection:current_tab()\n\treturn self.current_id\nend\n\n--- get the current tab\nfunction TabCollection:current()\n\treturn self.tab_list[self.current_id]\nend\n\nfunction TabCollection:set_current(id)\n\tself.current_id = id\nend\n\n--- get the next tab\n--- @return Tab # the next tab\nfunction TabCollection:next()\n\tself.current_id = self.current_id + 1\n\tif self.current_id > #self.tab_list then\n\t\tself.current_id = 1\n\tend\n\treturn self:current()\nend\n\n--- get the previous tab\n--- @return Tab # the previous tab\nfunction TabCollection:previous()\n\tself.current_id = self.current_id - 1\n\tif self.current_id < 1 then\n\t\tself.current_id = #self.tab_list\n\tend\n\treturn self:current()\nend\n\nfunction TabCollection:initial_tab()\n\tself.current_id = self.initial_id\n\treturn self:current()\nend\n\n--- get the tab with the given name\n--- @param name string the name of the tab\n--- @return Tab|nil # the tab with the given name\nfunction TabCollection:id_by_name(name)\n\tfor _, tab in ipairs(self.tab_list) do\n\t\tif tab.name == name then\n\t\t\treturn tab\n\t\tend\n\tend\n\treturn nil\nend\n\n--- set the tab with the given name as the current tab\n--- @param name string the name of the tab\n--- @return boolean # true if the tab was found, false otherwise\nfunction TabCollection:set_by_name(name)\n\tlocal tab = self:id_by_name( name)\n\tif tab then\n\t\tself.current_id = tab.id\n\t\treturn true\n\tend\n\n\treturn false\nend\n\nfunction TabCollection:set_by_id(id)\n\tself.current_id = id\nend\n\n--- get the tab with the given id\n--- @param id number the id of the tab\n--- @return Tab|nil # the tab with the given id\nfunction TabCollection:find_by_id(id)\n\tfor _, tab in ipairs(self.tab_list) do\n\t\tif tab.id == id then\n\t\t\treturn tab\n\t\tend\n\tend\n\treturn nil\nend\n\nreturn TabCollection\n"
  },
  {
    "path": "lua/search/tabs/init.lua",
    "content": "local M = {}\n\nTabCollection = require(\"search.tabs.collection\")\n---\n\nM.collections = {}\n\nM.current_collection_id = \"default\"\n\n--- get all tabs\n--- @return table # list of all tabs\nM.all = function()\n\treturn M.current_collection():all()\nend\n\n\nM.current_collection = function()\n\treturn M.collections[M.current_collection_id]\nend\n\n--- initialize the tabs module\n--- @param opts table with the following keys:\n-- - tabs: list of tabs\n-- - inital_tab: id of the tab to start with\nM.init = function(opts)\n\tM.collections[\"default\"] = TabCollection:new(opts)\n\tfor id, collection_config in pairs(opts.collections) do\n\t\tlocal collection = TabCollection:new(collection_config)\n\t\tM.collections[id] = collection\n\tend\nend\n\n--- get the current tab\nM.current = function()\n\treturn M.current_collection():current()\nend\n\n\n--- get the next tab\n--- @return Tab # the next tab\nM.next = function()\n\treturn M.current_collection():next()\nend\n\n--- get the previous tab\n--- @return Tab # the previous tab\nM.previous = function()\n\treturn M.current_collection():previous()\nend\n\nM.initial_tab = function()\n\treturn M.current_collection():initial_tab()\nend\n\n--- get the tab with the given name\n--- @param name string the name of the tab\n--- @return Tab|nil # the tab with the given name\nM.id_by_name = function(name)\n\treturn M.current_collection():id_by_name(name)\nend\n\n--- set the tab with the given name as the current tab\n--- @param name string the name of the tab\n--- @return boolean # true if the tab was found, false otherwise\nM.set_by_name = function(name)\n\treturn M.current_collection():set_by_name(name)\nend\n\nM.set_by_id = function(id)\n\tM.current_collection():set_current(id)\nend\n\n--- get the tab with the given id\n--- @param id number the id of the tab\n--- @return Tab|nil # the tab with the given id\nM.find_by_id = function(id)\n\treturn M.current_collection():find_by_id(id)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/search/tabs/tab.lua",
    "content": "\nlocal Tab = {}\n\n--- @class Tab\n--- @field id number\n--- @field name string\n--- @field tele_func function\n--- @field available_func function|nil\n--- @field failed boolean\n--- @field wait_for number|nil\n--- @function new create a new tab\nfunction Tab:new(tab, id)\n\tlocal name = tab.name or tab[1]\n\tlocal tele_func = tab.tele_func or tab[2]\n\tlocal tele_opts = tab.tele_opts or tab[3]\n\n\t-- this enables the user to define the function as second argument\n\t-- even if the first argument is named as name\n\tif name ~= nil and tab[1] ~= nil and type(tab[1]) == \"function\" then\n\t\ttele_func = tab[1]\n\tend\n\n\tlocal o = {\n\t\tid = id,\n\t\tname = name,\n\t\ttele_func = tele_func,\n\t\ttele_opts = tele_opts,\n\t\tavailable_func = tab.available,\n\t\tfailed = false,\n\t\twaiting = false,\n\t}\n\tsetmetatable(o, self)\n\tself.__index = self\n\treturn o\nend\n\n--- @param self Tab\n--- @return boolean\nfunction Tab:is_current(collection)\n\treturn collection:current_tab() == self.id\nend\n\n--- @return boolean\nfunction Tab:is_available()\n\treturn self.available_func == nil or self.available_func()\nend\n\nfunction Tab:fail()\n\tself.waiting = false\n\tself.failed = true\nend\n\nfunction Tab:has_failed()\n\treturn self.failed\nend\n\nfunction Tab:reset_failed()\n\tself.failed = false\nend\n\nfunction Tab:is_waiting()\n\treturn self.waiting\nend\n\nfunction Tab:stop_waiting()\n\tself.waiting = false\nend\n\nfunction Tab:start_waiting()\n\tself.failed = false\n\tself.waiting = true\nend\n\nreturn Tab\n"
  },
  {
    "path": "lua/search/util.lua",
    "content": "local M = {}\n\nlocal tabs = require(\"search.tabs\")\nlocal settings = require(\"search.settings\")\n\n--- the amount of milliseconds to wait between checks\nM.await_time = 10\n\n--- runs a function when a condition is met\n--- @param condition function a function that returns a boolean\n--- @param callback function a function that is called when the condition is met\n--- @param max_ms number the maximum amount of milliseconds to wait\n--- @param fail_callback function a function that is called when the condition is not met in time\n--- @return any the return value of the callback\nM.do_when = function(condition, callback, max_ms, fail_callback)\n\tif max_ms == nil then\n\t\tmax_ms = 1000\n\tend\n\n\twhile max_ms > 0 do\n\t\tif condition() then\n\t\t\treturn callback()\n\t\tend\n\t\tvim.wait(M.await_time)\n\t\tmax_ms = max_ms - M.await_time\n\tend\n\n\tif fail_callback ~= nil then\n\t\treturn fail_callback()\n\tend\nend\n\n\n--- binds the tab key to the next tab\n--- and the shift tab key to the previous tab\nM.set_keymap = function()\n\t-- now we bind our tab key to the next tab\n\tlocal opts = { noremap = true, silent = true }\n\tlocal cmd = \"<cmd>lua require('search').next_tab()<CR>\"\n\tlocal cmd_p = \"<cmd>lua require('search').previous_tab()<CR>\"\n\n\n  local function set_keymap(keymap, cmd)\n    if type(keymap) == \"string\" then\n      vim.api.nvim_buf_set_keymap(0, 'n', keymap, cmd, opts)\n      vim.api.nvim_buf_set_keymap(0, 'i', keymap, cmd, opts)\n    else\n      for _, value in ipairs(keymap) do\n        vim.api.nvim_buf_set_keymap(0, value[2], value[1], cmd, opts)\n      end\n    end\n  end\n\n  set_keymap(settings.keys.next, cmd)\n  set_keymap(settings.keys.prev, cmd_p)\n  end\n\n--- switches to the next available tab\n--- @return Tab # the next available tab\nM.next_available = function()\n\tlocal start = tabs.current().id\n\twhile true do\n\t\tlocal tab = tabs.next()\n\t\tif tab:is_available() then\n\t\t\treturn tab\n\t\tend\n\t\tif tab.id == start then\n\t\t\treturn tab\n\t\tend\n\tend\nend\n\n--- switches to the previous available tab\n--- @return Tab # the previous available tab\nM.previous_available = function()\n\tlocal start = tabs.current().id\n\twhile true do\n\t\tlocal tab = tabs.previous()\n\t\tif tab:is_available() then\n\t\t\treturn tab\n\t\tend\n\t\tif tab.id == start then\n\t\t\treturn tab\n\t\tend\n\tend\nend\nreturn M;\n"
  },
  {
    "path": "tests/configuration_spec.lua",
    "content": "local init = require('search.init')\nlocal _tabs = require('search.tabs')\nlocal _settings = require('search.settings')\n\nlocal eq = assert.are.same\n\n-- for my lsp to stop complaining\nlocal describe = describe\nlocal it = it\nlocal before_each = before_each\n\ndescribe(\"can configure search.nvim\", function()\n\tbefore_each(function()\n\t\t_tabs.tab_list = {}\n\tend)\n\n\n\tit(\"can use default config\", function()\n\t\tinit.setup()\n\n\t\tlocal tabs = _tabs.all()\n\t\teq(3, #tabs)\n\t\teq('Files', tabs[1].name)\n\t\teq('Git files', tabs[2].name)\n\t\teq('Grep', tabs[3].name)\n\tend)\n\n\tit(\"can append a tab using long syntax\", function()\n\t\tlocal config = {\n\t\t\tappend_tabs = {\n\t\t\t\t{\n\t\t\t\t\t\"Custom\",\n\t\t\t\t\tfunction() return \"custom\" end\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tinit.setup(config)\n\t\teq(4, #_tabs.all())\n\t\teq('Custom', _tabs.all()[4].name)\n\t\teq('custom', _tabs.all()[4].tele_func())\n\tend)\n\n\tit(\"can append a tab using short syntax\", function()\n\t\t_tabs.tab_list = {}\n\t\tlocal config = {\n\t\t\tappend_tabs = {\n\t\t\t\t{ \"Custom\", function() return \"custom\" end }\n\t\t\t}\n\t\t}\n\n\t\tinit.setup(config)\n\t\teq(4, #_tabs.all())\n\t\teq('Custom', _tabs.all()[4].name)\n\t\teq('custom', _tabs.all()[4].tele_func())\n\tend)\n\n\tit(\"can append tab using partially short syntax\", function()\n\t\tlocal config = {\n\t\t\tappend_tabs = {\n\t\t\t\t{ \"Custom\", tele_func = function() return \"custom\" end }\n\t\t\t}\n\t\t}\n\n\t\tinit.setup(config)\n\t\teq(4, #_tabs.all())\n\t\teq('Custom', _tabs.all()[4].name)\n\t\teq('custom', _tabs.all()[4].tele_func())\n\tend)\n\n\tit(\"can append tab using partially short syntax2\", function()\n\t\tlocal config = {\n\t\t\tappend_tabs = {\n\t\t\t\t{ name = \"Custom\", function() return \"custom\" end }\n\t\t\t}\n\t\t}\n\n\t\tinit.setup(config)\n\t\teq(4, #_tabs.all())\n\t\teq('Custom', _tabs.all()[4].name)\n\t\teq('custom', _tabs.all()[4].tele_func())\n\tend)\n\n\tit(\"can define a available function\", function()\n\t\tlocal config = {\n\t\t\ttabs = { { \"Custom\", function() return \"custom\" end, available = function() return false end } }\n\t\t}\n\n\t\tinit.setup(config)\n\t\teq(1, #_tabs.all())\n\t\teq('Custom', _tabs.all()[1].name)\n\t\teq('custom', _tabs.all()[1].tele_func())\n\t\teq(false, _tabs.all()[1]:is_available())\n\tend)\n\n\tit(\"can configure mappings\", function()\n\t\tlocal config = {\n\t\t\tmappings = {\n\t\t\t\tnext = '<leader>l',\n\t\t\t\tprev = '<leader>h',\n\t\t\t}\n\t\t}\n\n\t\tinit.setup(config)\n\t\teq('<leader>l', _settings.keys.next)\n\t\teq('<leader>h', _settings.keys.prev)\n\tend)\n\n\tit(\"has default mappings\", function()\n\t\tinit.setup()\n\t\teq('<Tab>', _settings.keys.next)\n\t\teq('<S-Tab>', _settings.keys.prev)\n\tend)\nend)\n"
  },
  {
    "path": "tests/minimal_init.vim",
    "content": "set rtp+=../plenary.nvim\nset rtp+=../telescope.nvim\nruntime! plenary.nvim/lua/plenary.lua\n"
  }
]