Quickly navigate to other files/extensions based on the current file name or jump to nodes in a file via treesitter. Written in Lua.
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
- ๐ฆ Switch to files with common prefix (example: "tasks"):
tasks.component.ts --> tasks.component.html
- tasks.component.html --> tasks.component.scss
- tasks.component.scss --> tasks.component.ts
- ๐ฆ Toggle between file extensions
tasks.cpp <--> tasks.h
- tasks.html <--> tasks.css
- ๐ Switch to files with different suffix
tasks.component.ts --> tasks.module.ts
- tasks.component.ts --> tasks.component.spec.ts
- tasks.query.ts --> tasks.store.ts
- ๐ Find files by Wilcard (Glob) or Regex
tasks.ts --> tasks.spec.ts | tasks.test.ts
- tasks.ts --> tasks.css | tasks.scss | tasks.sass
- /tasks.components.ts --> state/tasks.query.ts
- state/tasks.query.ts --> ../tasks.component.ts
- controller.lua --> controller_spec.lua
- controller-util.lua --> controller-service.lua
- ๐ณ Navigate inside files with Treesitter Queries
Installation
Vim-plugPlug 'Everduin94/nvim-quick-switcher'
Packer
use { "Everduin94/nvim-quick-switcher", }
Usage
Switcher has 4 functions. switch, toggle, find, inlinetsswitch. 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 toprefix.suffix
switch(search_string, options)
Example
require('nvim-quick-switcher').switch('component.ts')
ticket.component.html-->ticket.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
Togglesprefix.suffix, based on current suffix / file extension
toggle(searchstringone, searchstringtwo, options)
For options see Common Options
Example
require('nvim-quick-switcher').toggle('cpp', 'h')
node.cpp-->node.hnode.h-->node.cpp
๐ Find
Uses gnu/linuxfind & grep to find file and switch to prefix+pattern
find(search_string, options)
Example
require('nvim-quick-switcher').find('query')
- Allows wild cards, i.e. first argument is search parameter for gnu/linux find
- file:
ticket.component.ts--> pattern:ticketquery
require('nvim-quick-switcher').find('.+css|.+scss|.+sass', { regex = true })
- Uses find, then filters via grep regex, i.e. first argument is regex
- file:
ticket.component.ts--> find:ticket*--> grep:.+css|.+scss|.+sass - When using backslash, may have to escape via
\\
- You can use options like
prefixorregexto make your searches more specific. SeeRecipesfor examples.
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)
Requiresnvim-treesitter/nvim-treesitter
Uses treesitter queries to navigate inside of a file
inlinetsswitch(filetype, querystring, options)
Example
require('nvim-quick-switcher').inlinetsswitch('svelte', '(scriptelement (endtag) @capture)')
- Places cursor at start of
</script>node
Options
{
goto_end = false, -- go to start of node if false, go to end of node if true
avoidsetjump = 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').findbyfn(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 exfindtest_fn = function (p)
local path = p.path;
local file_name = p.prefix;
local result = path:gsub('src', 'test') .. '/' .. file_name .. '*';
return result;
end
local findsrcfn = 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').findbyfn(exfindtest_fn) require('nvim-quick-switcher').findbyfn(findsrcfn)
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/findbyfn).
{
only_existing = false,
onlyexistingnotify = false,
}
only_existing Causes the switcher to check if the target file exists
before switching to it.
onlyexistingnotify will print if the file does not exist.
Recipes (My Keymaps)
My configuration for nvim-quick-switcher. Written in Lualocal 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 inlinetsswitch(file_type, scheme) return function() require('nvim-quick-switcher').inlinetsswitch(file_type, scheme) end end
local function findbyfn(fn, opts) return function() require('nvim-quick-switcher').findbyfn(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.nvimcreateautocmd({'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", inlinetsswitch('svelte', '(scriptelement (endtag) @capture)'), opts) vim.keymap.set("n", "<leader>ok", inlinetsswitch('svelte', '(styleelement (starttag) @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 findtestfn = function (p) local path = p.path; local file_name = p.prefix; local result = path:gsub('src', 'test') .. '/' .. file_name .. '*'; return result; end
local findsrcfn = 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", findbyfn(findtestfn), opts) vim.keymap.set("n", "<leader>ojk", findbyfn(findsrcfn), 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.