Home

Awesome

Nvim Quick Switcher

Quickly navigate to related/alternate files/extensions based on the current file name. Written in Lua.

https://user-images.githubusercontent.com/14320878/205079483-82f2cd39-915e-485a-a5c6-20b188e1b26b.mp4

Features

Installation

Vim-plug

Plug 'Everduin94/nvim-quick-switcher'

Packer

use {
  "Everduin94/nvim-quick-switcher",
}

Usage

Switcher has 4 functions. switch, toggle, find, inline_ts_switch. No setup function call required. Just call a function with the desired arguments.

For more examples, see Recipes

➡️ Switch

Gets "prefix" of file name, switches to prefix.suffix

switch(search_string, options)

Example

require('nvim-quick-switcher').switch('component.ts')

Options

{
  split = 'vertical'|'horizontal'|nil -- nil is default
  size = 100 -- # of columns | rows. default is 50% split
  ignore_prefix = false -- useful for navigating to files like "index.ts" or "+page.svelte"
}

🔄 Toggle

Toggles prefix.suffix, based on current suffix / file extension

toggle(search_string_one, search_string_two, options)

For options see Common Options

Example

require('nvim-quick-switcher').toggle('cpp', 'h')

🔍 Find

Uses gnu/linux find & grep to find file and switch to prefix+pattern

find(search_string, options)

Example

require('nvim-quick-switcher').find('*query*')

require('nvim-quick-switcher').find('.+css|.+scss|.+sass', { regex = true })

If multiple matches are found will prompt UI Select.

If no results are found, will search backwards one directory, see reverse

Options

{
  split = 'vertical'|'horizontal'|nil
  size = 100 
  regex = false, -- If true, first argument uses regex instead of find string/wildcard.
  maxdepth = 2, -- directory depth 
  reverse = true, -- false will disable reverse search when no results are found
  path = nil, -- overwrite path (experimental).
  prefix = 'default', 
    -- full: stop at last period
    -- short: stop at first _ or -
    -- long: stop at last _ 
    -- default: stop at first period.
    -- lua function (you can pass a lua function to create a custom prefix)
  regex_type = 'E' -- default regex extended. See grep for types.
  ignore_prefix = false -- useful for navigating to files like "index.ts" or "+page.svelte"
}

🌳 Inline Switch (Treesitter)

Requires nvim-treesitter/nvim-treesitter

Uses treesitter queries to navigate inside of a file

inline_ts_switch(file_type, query_string, options)

Example

require('nvim-quick-switcher').inline_ts_switch('svelte', '(script_element (end_tag) @capture)')

Options

{
  goto_end = false, -- go to start of node if false, go to end of node if true
  avoid_set_jump = false, -- do not add to jumplist if true
}

🚀 Find by Function (Advanced)

Accepts a lua function that provides path/file information as an argument, and returns a file path as a string. Useful for switching across directories / folders. i.e. the user is provided file path data to then build an entire file path, that gets passed to vim select, that gets passed to navigate.

require('nvim-quick-switcher').find_by_fn(fn, options)

Example

This example switches from a test folder to a src folder (and vice versa) like in to a Java J-Unit project.

local ex_find_test_fn = function (p)
  local path = p.path;
  local file_name = p.prefix;
  local result = path:gsub('src', 'test') .. '/' .. file_name .. '*';
  return result;
end

local find_src_fn = function (p)
  local path = p.path;
  local file_name = p.prefix;
  local result = path:gsub('test', 'src') .. '/' .. file_name .. '*';
  return result;
end

require('nvim-quick-switcher').find_by_fn(ex_find_test_fn)
require('nvim-quick-switcher').find_by_fn(find_src_fn)

Args

prefix -- The prefix of the file (task.component.ts) --> task
full_prefix -- (task.component.ts) --> task.component
full_suffix -- (task.component.ts) --> component.ts
short_prefix -- (task-util.lua) --> task
file_type -- (task-util.lua) --> lua
file_name -- (src/tasks/task-util.lua) --> task-util.lua
path -- (src/tasks/task-util.lua) --> src/tasks/task-util.lua

🌌 Common Options

Options common for file location functions (switch/toggle/find/find_by_fn).

{
  only_existing = false,
  only_existing_notify = false,
}

only_existing Causes the switcher to check if the target file exists before switching to it. only_existing_notify will print if the file does not exist.

Recipes (My Keymaps)

My configuration for nvim-quick-switcher. Written in Lua

local opts = { noremap = true, silent = true }

local function find(file_regex, opts)
  return function() require('nvim-quick-switcher').find(file_regex, opts) end
end

local function inline_ts_switch(file_type, scheme)
  return function() require('nvim-quick-switcher').inline_ts_switch(file_type, scheme) end
end

local function find_by_fn(fn, opts)
  return function() require('nvim-quick-switcher').find_by_fn(fn, opts) end
end

-- Styles
vim.keymap.set("n", "<leader>oi", find('.+css|.+scss|.+sass', { regex = true, prefix='full' }), opts)

-- Types
vim.keymap.set("n", "<leader>orm", find('.+model.ts|.+models.ts|.+types.ts', { regex = true }), opts)

-- Util
vim.keymap.set("n", "<leader>ol", find('*util.*', { prefix = 'short' }), opts)

-- Tests
vim.keymap.set("n", "<leader>ot", find('.+test|.+spec', { regex = true, prefix='full' }), opts)

-- Project Specific Keymaps
-- * Maps keys based on project using an auto command. Ideal for reusing keymaps based on context.
-- * Example: In Angular, `oo` switches to .component.html. In Svelte, `oo` switches to *page.svelte
vim.api.nvim_create_autocmd({'UIEnter'}, {
    callback = function(event)
      local is_angular = next(vim.fs.find({ "angular.json", "nx.json" }, { upward = true }))
      local is_svelte = next(vim.fs.find({ "svelte.config.js", "svelte.config.ts" }, { upward = true }))

      -- Angular
      if is_angular then
        print('Angular')
        vim.keymap.set("n", "<leader>oo", find('.component.html'), opts)
        vim.keymap.set("n", "<leader>ou", find('.component.ts'), opts)
        vim.keymap.set("n", "<leader>op", find('.module.ts'), opts)
        vim.keymap.set("n", "<leader>oy", find('.service.ts'), opts)
      end

      -- SvelteKit
      if is_svelte then
        print('Svelte')
        vim.keymap.set("n", "<leader>oo", find('*page.svelte', { maxdepth = 1, ignore_prefix = true }), opts)
        vim.keymap.set("n", "<leader>ou", find('.*page.server(.+js|.+ts)|.*page(.+js|.+ts)', { maxdepth = 1, regex = true, ignore_prefix = true }), opts)
        vim.keymap.set("n", "<leader>op", find('*layout.svelte', { maxdepth = 1, ignore_prefix = true }), opts)

         -- Inline TS
        vim.keymap.set("n", "<leader>oj", inline_ts_switch('svelte', '(script_element (end_tag) @capture)'), opts)
        vim.keymap.set("n", "<leader>ok", inline_ts_switch('svelte', '(style_element (start_tag) @capture)'), opts)
      end
    end
})

-- Redux-like
vim.keymap.set("n", "<leader>ore", find('*effects.ts'), opts)
vim.keymap.set("n", "<leader>ora", find('*actions.ts'), opts)
vim.keymap.set("n", "<leader>orw", find('*store.ts'), opts)
vim.keymap.set("n", "<leader>orf", find('*facade.ts'), opts)
vim.keymap.set("n", "<leader>ors", find('.+query.ts|.+selectors.ts|.+selector.ts', { regex = true }), opts)
vim.keymap.set("n", "<leader>orr", find('.+reducer.ts|.+repository.ts', { regex = true }), opts)

-- Java J-Unit (Advanced Example)
local find_test_fn = function (p)
  local path = p.path;
  local file_name = p.prefix;
  local result = path:gsub('src', 'test') .. '/' .. file_name .. '*';
  return result;
end

local find_src_fn = function (p)
  local path = p.path;
  local file_name = p.prefix;
  local result = path:gsub('test', 'src') .. '/' .. file_name .. '*';
  return result;
end

vim.keymap.set("n", "<leader>ojj", find_by_fn(find_test_fn), opts)
vim.keymap.set("n", "<leader>ojk", find_by_fn(find_src_fn), opts)

Personal Motivation

Many moons ago, as a sweet summer child, I used VS Code with an extension called "Angular Switcher". Angular switcher enables switching to various file extensions related to the current component.

I wanted to take that idea and make it work for many frameworks or file extensions

I currently use nvim-quick-switcher on a daily basis for Svelte / Svelte-Kit, Angular Components, Tests, Stylesheets, Lua util files, and Redux-like files.

Alternatives