1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 07:00:04 +08:00

perf(telescope): use telescope 0.1.2 for nvim 0.7.0

This commit is contained in:
Eric Wong 2024-02-23 15:35:33 +08:00
parent d7e45e884b
commit e5573683ae
214 changed files with 25946 additions and 1 deletions

View File

@ -45,7 +45,11 @@ endfunction
function! SpaceVim#layers#telescope#plugins() abort function! SpaceVim#layers#telescope#plugins() abort
let plugins = [] let plugins = []
call add(plugins, [g:_spacevim_root_dir . 'bundle/telescope.nvim', {'merged' : 0, 'loadconf' : 1}]) if has('nvim-0.7.2')
call add(plugins, [g:_spacevim_root_dir . 'bundle/telescope.nvim-0.1.5', {'merged' : 0, 'loadconf' : 1}])
elseif has('nvim-0.7.0')
call add(plugins, [g:_spacevim_root_dir . 'bundle/telescope.nvim-0.1.2', {'merged' : 0, 'loadconf' : 1}])
endif
call add(plugins, [g:_spacevim_root_dir . 'bundle/plenary.nvim', {'merged' : 0}]) call add(plugins, [g:_spacevim_root_dir . 'bundle/plenary.nvim', {'merged' : 0}])
call add(plugins, [g:_spacevim_root_dir . 'bundle/telescope-menu', {'merged' : 0}]) call add(plugins, [g:_spacevim_root_dir . 'bundle/telescope-menu', {'merged' : 0}])
call add(plugins, [g:_spacevim_root_dir . 'bundle/telescope-ctags-outline.nvim', {'merged' : 0}]) call add(plugins, [g:_spacevim_root_dir . 'bundle/telescope-ctags-outline.nvim', {'merged' : 0}])

View File

@ -0,0 +1,68 @@
# Contributing
## Submitting a new feature
Thanks for taking the time to submit code to Telescope if you're reading this! We love having new contributors and love seeing the Neovim community come around this plugin and keep making it better If you are submitting a new PR with a feature addition, please make sure that you add the appropriate documentation. For examples of how to document a new picker for instance, check the `lua/telescope/builtin/init.lua` file to see how we write function headers for all of the pickers there. To learn how we go about writing documentation for this project, keep reading below!
## Documentation with treesitter
We are generating docs based on the tree sitter syntax tree. TJ wrote a grammar that includes the documentation in this syntax tree so we can do take this function header documentation and transform it into vim documentation. All documentation that is part of the returning module will be exported. For example:
```lua
local m = {}
--- Test Header
--@return 1: Returns always 1
function m.a() -- or m:a()
return 1
end
--- Documentation
function m.__b() -- or m:__b()
return 2
end
--- Documentation
local c = function()
return 2
end
return m
```
This will export function `a` with header documentation and the return value. Module function `b` and local function `c` will not be exported.
For a more in-depth look at how to write documentation take a look at this guide: [how to](https://github.com/tjdevries/tree-sitter-lua/blob/master/HOWTO.md)
This guide contains all annotations and we will update it when we add new annotations.
## What is missing?
The docgen has some problems on which people can work. This would happen in [https://github.com/tjdevries/tree-sitter-lua](https://github.com/tjdevries/tree-sitter-lua) and documentation of some modules here.
I would suggest we are documenting lua/telescope/builtin/init.lua rather than the files itself. We can use that init.lua file as "header" file, so we are not cluttering the other files.
How to help out with documentation:
## Auto-updates from CI
The easy way would be:
- write some docs
- commit, push and create draft PR
- wait a minute until the CI generates a new commit with the changes
- Look at this commit and the changes
- Modify documentation until its perfect. You can do `git commit --amend` and `git push --force` to remove the github ci commit again
## Generate on your local machine
The other option would be setting up https://github.com/tjdevries/tree-sitter-lua
- Install Treesitter, either with package manager or with github release
- Install plugin as usual
- cd to plugin
- `mkdir -p build parser` sadly those don't exist
- `make build_parser`
- `ln -s ../build/parser.so parser/lua.so` We need the shared object in parser/ so it gets picked up by neovim. Either copy or symbolic link
- Make sure that nvim-treesitter lua parser is not installed and also delete the lua queries in that repository. `queries/lua/*`. If you are not doing that you will have a bad time!
- cd into this project
- Write doc
- Run `make docgen`
- Repeat last two steps

View File

@ -0,0 +1,607 @@
# telescope.nvim
[![Gitter](https://badges.gitter.im/nvim-telescope/community.svg)](https://gitter.im/nvim-telescope/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![LuaRocks](https://img.shields.io/luarocks/v/Conni2461/telescope.nvim?logo=lua&color=purple)](https://luarocks.org/modules/Conni2461/telescope.nvim)
Gaze deeply into unknown regions using the power of the moon.
## What Is Telescope?
`telescope.nvim` is a highly extendable fuzzy finder over lists. Built on the
latest awesome features from `neovim` core. Telescope is centered around
modularity, allowing for easy customization.
Community driven builtin [pickers](#pickers), [sorters](#sorters) and
[previewers](#previewers).
![Preview](https://i.imgur.com/TTTja6t.gif)
<sub>For more showcases of Telescope, please visit the [Showcase
section](https://github.com/nvim-telescope/telescope.nvim/wiki/Showcase) in the
Telescope Wiki</sub>
## Telescope Table of Contents
- [Getting Started](#getting-started)
- [Usage](#usage)
- [Customization](#customization)
- [Default Mappings](#default-mappings)
- [Pickers](#pickers)
- [Previewers](#previewers)
- [Sorters](#sorters)
- [Layout](#layout-display)
- [Themes](#themes)
- [Commands](#vim-commands)
- [Autocmds](#autocmds)
- [Extensions](#extensions)
- [API](#api)
- [Media](#media)
- [Contributing](#contributing)
- [Changelog](https://github.com/nvim-telescope/telescope.nvim/blob/master/doc/telescope_changelog.txt)
## Getting Started
This section should guide you to run your first builtin pickers.
[Neovim (v0.7.0)](https://github.com/neovim/neovim/releases/tag/v0.7.0) or the
latest neovim nightly commit is required for `telescope.nvim` to work.
### Required dependencies
- [nvim-lua/plenary.nvim](https://github.com/nvim-lua/plenary.nvim) is required.
### Suggested dependencies
- [BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep) is required for
`live_grep` and `grep_string`
We also suggest you install one native telescope sorter to significantly improve
sorting performance. Take a look at either
[telescope-fzf-native.nvim](https://github.com/nvim-telescope/telescope-fzf-native.nvim)
or
[telescope-fzy-native.nvim](https://github.com/nvim-telescope/telescope-fzy-native.nvim).
For more information and a performance benchmark take a look at the
[Extensions](https://github.com/nvim-telescope/telescope.nvim/wiki/Extensions)
wiki.
### Optional dependencies
- [sharkdp/fd](https://github.com/sharkdp/fd) (finder)
- [nvim-treesitter/nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) (finder/preview)
- [neovim LSP]( https://neovim.io/doc/user/lsp.html) (picker)
- [devicons](https://github.com/nvim-tree/nvim-web-devicons) (icons)
### Installation
It is suggested to either use the latest release
[tag](https://github.com/nvim-telescope/telescope.nvim/tags) or our release
branch (which will get consistent updates)
[0.1.x](https://github.com/nvim-telescope/telescope.nvim/tree/0.1.x).
It is not suggested to run latest master.
Using [vim-plug](https://github.com/junegunn/vim-plug)
```viml
Plug 'nvim-lua/plenary.nvim'
Plug 'nvim-telescope/telescope.nvim', { 'tag': '0.1.1' }
" or , { 'branch': '0.1.x' }
```
Using [dein](https://github.com/Shougo/dein.vim)
```viml
call dein#add('nvim-lua/plenary.nvim')
call dein#add('nvim-telescope/telescope.nvim', { 'rev': '0.1.1' })
" or , { 'rev': '0.1.x' })
```
Using [packer.nvim](https://github.com/wbthomason/packer.nvim)
```lua
use {
'nvim-telescope/telescope.nvim', tag = '0.1.1',
-- or , branch = '0.1.x',
requires = { {'nvim-lua/plenary.nvim'} }
}
```
Using [lazy.nvim](https://github.com/folke/lazy.nvim)
```lua
-- init.lua:
{
'nvim-telescope/telescope.nvim', tag = '0.1.1',
-- or , branch = '0.1.1',
dependencies = { 'nvim-lua/plenary.nvim' }
}
-- plugins/telescope.lua:
return {
'nvim-telescope/telescope.nvim', tag = '0.1.1',
-- or , branch = '0.1.1',
dependencies = { 'nvim-lua/plenary.nvim' }
}
```
### checkhealth
Make sure you call `:checkhealth telescope` after installing telescope to ensure
everything is set up correctly.
After this setup you can continue reading here or switch to `:help telescope`
to get an understanding of how to use Telescope and how to configure it.
## Usage
Try the command `:Telescope find_files<cr>`
to see if `telescope.nvim` is installed correctly.
Using VimL:
```viml
" Find files using Telescope command-line sugar.
nnoremap <leader>ff <cmd>Telescope find_files<cr>
nnoremap <leader>fg <cmd>Telescope live_grep<cr>
nnoremap <leader>fb <cmd>Telescope buffers<cr>
nnoremap <leader>fh <cmd>Telescope help_tags<cr>
" Using Lua functions
nnoremap <leader>ff <cmd>lua require('telescope.builtin').find_files()<cr>
nnoremap <leader>fg <cmd>lua require('telescope.builtin').live_grep()<cr>
nnoremap <leader>fb <cmd>lua require('telescope.builtin').buffers()<cr>
nnoremap <leader>fh <cmd>lua require('telescope.builtin').help_tags()<cr>
```
Using Lua:
```lua
local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', builtin.find_files, {})
vim.keymap.set('n', '<leader>fg', builtin.live_grep, {})
vim.keymap.set('n', '<leader>fb', builtin.buffers, {})
vim.keymap.set('n', '<leader>fh', builtin.help_tags, {})
```
See [builtin pickers](#pickers) for a list of all builtin functions.
## Customization
This section should help you explore available options to configure and
customize your `telescope.nvim`.
Unlike most vim plugins, `telescope.nvim` can be customized by either applying
customizations globally, or individually per picker.
- **Global Customization** affecting all pickers can be done through the main
`setup()` method (see defaults below)
- **Individual Customization** affecting a single picker by passing `opts` to
builtin pickers (e.g. `builtin.find_files(opts)`) see
[Configuration recipes](https://github.com/nvim-telescope/telescope.nvim/wiki/Configuration-Recipes)
wiki page for ideas.
### Telescope setup structure
```lua
require('telescope').setup{
defaults = {
-- Default configuration for telescope goes here:
-- config_key = value,
mappings = {
i = {
-- map actions.which_key to <C-h> (default: <C-/>)
-- actions.which_key shows the mappings for your picker,
-- e.g. git_{create, delete, ...}_branch for the git_branches picker
["<C-h>"] = "which_key"
}
}
},
pickers = {
-- Default configuration for builtin pickers goes here:
-- picker_name = {
-- picker_config_key = value,
-- ...
-- }
-- Now the picker_config_key will be applied every time you call this
-- builtin picker
},
extensions = {
-- Your extension configuration goes here:
-- extension_name = {
-- extension_config_key = value,
-- }
-- please take a look at the readme of the extension you want to configure
}
}
```
To look at what default configuration options exist please read: `:help
telescope.setup()`. For picker specific `opts` please read: `:help
telescope.builtin`.
To embed the above code snippet in a `.vim` file
(for example in `after/plugin/telescope.nvim.vim`),
wrap it in `lua << EOF code-snippet EOF`:
```lua
lua << EOF
require('telescope').setup{
-- ...
}
EOF
```
## Default Mappings
Mappings are fully customizable.
Many familiar mapping patterns are set up as defaults.
| Mappings | Action |
|----------------|------------------------------------------------------|
| `<C-n>/<Down>` | Next item |
| `<C-p>/<Up>` | Previous item |
| `j/k` | Next/previous (in normal mode) |
| `H/M/L` | Select High/Middle/Low (in normal mode) |
| `gg/G` | Select the first/last item (in normal mode) |
| `<CR>` | Confirm selection |
| `<C-x>` | Go to file selection as a split |
| `<C-v>` | Go to file selection as a vsplit |
| `<C-t>` | Go to a file in a new tab |
| `<C-u>` | Scroll up in preview window |
| `<C-d>` | Scroll down in preview window |
| `<C-/>` | Show mappings for picker actions (insert mode) |
| `?` | Show mappings for picker actions (normal mode) |
| `<C-c>` | Close telescope |
| `<Esc>` | Close telescope (in normal mode) |
| `<Tab>` | Toggle selection and move to next selection |
| `<S-Tab>` | Toggle selection and move to prev selection |
| `<C-q>` | Send all items not filtered to quickfixlist (qflist) |
| `<M-q>` | Send all selected items to qflist |
To see the full list of mappings, check out `lua/telescope/mappings.lua` and the
`default_mappings` table.
**Tip**: you can use `<C-/>` and `?` in insert and normal mode, respectively, to show the actions mapped to your picker.
Much like [builtin pickers](#pickers), there are a number of
[actions](https://github.com/nvim-telescope/telescope.nvim/blob/master/lua/telescope/actions/init.lua)
you can pick from to remap your telescope buffer mappings, or create a new
custom action:
```lua
-- Built-in actions
local transform_mod = require('telescope.actions.mt').transform_mod
-- or create your custom action
local my_cool_custom_action = transform_mod({
x = function(prompt_bufnr)
print("This function ran after another action. Prompt_bufnr: " .. prompt_bufnr)
-- Enter your function logic here. You can take inspiration from lua/telescope/actions.lua
end,
})
```
To remap telescope mappings, please read `:help telescope.defaults.mappings`.
To do picker specific mappings, its suggested to do this with the `pickers`
table in `telescope.setup`. Each picker accepts a `mappings` table like its
explained in `:help telescope.defaults.mappings`.
## Pickers
Built-in functions. Ready to be bound to any key you like.
```vim
:lua require'telescope.builtin'.planets{}
:nnoremap <Leader>pp :lua require'telescope.builtin'.planets{}
```
### File Pickers
| Functions | Description |
|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `builtin.find_files` | Lists files in your current working directory, respects .gitignore |
| `builtin.git_files` | Fuzzy search through the output of `git ls-files` command, respects .gitignore |
| `builtin.grep_string` | Searches for the string under your cursor in your current working directory |
| `builtin.live_grep` | Search for a string in your current working directory and get results live as you type, respects .gitignore. (Requires [ripgrep](https://github.com/BurntSushi/ripgrep)) |
### Vim Pickers
| Functions | Description |
|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `builtin.buffers` | Lists open buffers in current neovim instance |
| `builtin.oldfiles` | Lists previously open files |
| `builtin.commands` | Lists available plugin/user commands and runs them on `<cr>` |
| `builtin.tags` | Lists tags in current directory with tag location file preview (users are required to run ctags -R to generate tags or update when introducing new changes) |
| `builtin.command_history` | Lists commands that were executed recently, and reruns them on `<cr>` |
| `builtin.search_history` | Lists searches that were executed recently, and reruns them on `<cr>` |
| `builtin.help_tags` | Lists available help tags and opens a new window with the relevant help info on `<cr>` |
| `builtin.man_pages` | Lists manpage entries, opens them in a help window on `<cr>` |
| `builtin.marks` | Lists vim marks and their value |
| `builtin.colorscheme` | Lists available colorschemes and applies them on `<cr>` |
| `builtin.quickfix` | Lists items in the quickfix list |
| `builtin.quickfixhistory` | Lists all quickfix lists in your history and open them with `builtin.quickfix` |
| `builtin.loclist` | Lists items from the current window's location list |
| `builtin.jumplist` | Lists Jump List entries |
| `builtin.vim_options` | Lists vim options, allows you to edit the current value on `<cr>` |
| `builtin.registers` | Lists vim registers, pastes the contents of the register on `<cr>` |
| `builtin.autocommands` | Lists vim autocommands and goes to their declaration on `<cr>` |
| `builtin.spell_suggest` | Lists spelling suggestions for the current word under the cursor, replaces word with selected suggestion on `<cr>` |
| `builtin.keymaps` | Lists normal mode keymappings |
| `builtin.filetypes` | Lists all available filetypes |
| `builtin.highlights` | Lists all available highlights |
| `builtin.current_buffer_fuzzy_find` | Live fuzzy search inside of the currently open buffer |
| `builtin.current_buffer_tags` | Lists all of the tags for the currently open buffer, with a preview |
| `builtin.resume` | Lists the results incl. multi-selections of the previous picker |
| `builtin.pickers` | Lists the previous pickers incl. multi-selections (see `:h telescope.defaults.cache_picker`) |
### Neovim LSP Pickers
| Functions | Description |
|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
| `builtin.lsp_references` | Lists LSP references for word under the cursor |
| `builtin.lsp_incoming_calls` | Lists LSP incoming calls for word under the cursor |
| `builtin.lsp_outgoing_calls` | Lists LSP outgoing calls for word under the cursor |
| `builtin.lsp_document_symbols` | Lists LSP document symbols in the current buffer |
| `builtin.lsp_workspace_symbols` | Lists LSP document symbols in the current workspace |
| `builtin.lsp_dynamic_workspace_symbols` | Dynamically Lists LSP for all workspace symbols |
| `builtin.diagnostics` | Lists Diagnostics for all open buffers or a specific buffer. Use option `bufnr=0` for current buffer. |
| `builtin.lsp_implementations` | Goto the implementation of the word under the cursor if there's only one, otherwise show all options in Telescope |
| `builtin.lsp_definitions` | Goto the definition of the word under the cursor, if there's only one, otherwise show all options in Telescope |
| `builtin.lsp_type_definitions` | Goto the definition of the type of the word under the cursor, if there's only one, otherwise show all options in Telescope|
### Git Pickers
| Functions | Description |
|-------------------------------------|------------------------------------------------------------------------------------------------------------|
| `builtin.git_commits` | Lists git commits with diff preview, checkout action `<cr>`, reset mixed `<C-r>m`, reset soft `<C-r>s` and reset hard `<C-r>h` |
| `builtin.git_bcommits` | Lists buffer's git commits with diff preview and checks them out on `<cr>` |
| `builtin.git_branches` | Lists all branches with log preview, checkout action `<cr>`, track action `<C-t>`, rebase action`<C-r>`, create action `<C-a>`, switch action `<C-s>`, delete action `<C-d>` and merge action `<C-y>` |
| `builtin.git_status` | Lists current changes per file with diff preview and add action. (Multi-selection still WIP) |
| `builtin.git_stash` | Lists stash items in current repository with ability to apply them on `<cr>` |
### Treesitter Picker
| Functions | Description |
|-------------------------------------|---------------------------------------------------|
| `builtin.treesitter` | Lists Function names, variables, from Treesitter! |
### Lists Picker
| Functions | Description |
|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `builtin.planets` | Use the telescope... |
| `builtin.builtin` | Lists Built-in pickers and run them on `<cr>`. |
| `builtin.reloader` | Lists Lua modules and reload them on `<cr>`. |
| `builtin.symbols` | Lists symbols inside a file `data/telescope-sources/*.json` found in your rtp. More info and symbol sources can be found [here](https://github.com/nvim-telescope/telescope-symbols.nvim) |
## Previewers
| Previewers | Description |
|------------------------------------|-----------------------------------------------------------|
| `previewers.vim_buffer_cat.new` | Default previewer for files. Uses vim buffers |
| `previewers.vim_buffer_vimgrep.new`| Default previewer for grep and similar. Uses vim buffers |
| `previewers.vim_buffer_qflist.new` | Default previewer for qflist. Uses vim buffers |
| `previewers.cat.new` | Terminal previewer for files. Uses `cat`/`bat` |
| `previewers.vimgrep.new` | Terminal previewer for grep and similar. Uses `cat`/`bat` |
| `previewers.qflist.new` | Terminal previewer for qflist. Uses `cat`/`bat` |
The default previewers are from now on `vim_buffer_` previewers. They use vim
buffers for displaying files and use tree-sitter or regex for file highlighting.
These previewers are guessing the filetype of the selected file, so there might
be cases where they miss, leading to wrong highlights. This is because we can't
determine the filetype in the traditional way: We don't do `bufload` and instead
read the file asynchronously with `vim.loop.fs_` and attach only a highlighter;
otherwise the speed of the previewer would slow down considerably. If you want
to configure more filetypes, take a look at
[plenary wiki](https://github.com/nvim-lua/plenary.nvim#plenaryfiletype).
If you want to configure the `vim_buffer_` previewer (e.g. you want the line to wrap), do this:
```vim
autocmd User TelescopePreviewerLoaded setlocal wrap
```
## Sorters
| Sorters | Description |
|------------------------------------|-----------------------------------------------------------------|
| `sorters.get_fuzzy_file` | Telescope's default sorter for files |
| `sorters.get_generic_fuzzy_sorter` | Telescope's default sorter for everything else |
| `sorters.get_levenshtein_sorter` | Using Levenshtein distance algorithm (don't use :D) |
| `sorters.get_fzy_sorter` | Using fzy algorithm |
| `sorters.fuzzy_with_index_bias` | Used to list stuff with consideration to when the item is added |
A `Sorter` is called by the `Picker` on each item returned by the `Finder`. It
returns a number, which is equivalent to the "distance" between the current
`prompt` and the `entry` returned by a `finder`.
## Layout (display)
Layout can be configured by choosing a specific `layout_strategy` and
specifying a particular `layout_config` for that strategy.
For more details on available strategies and configuration options,
see `:help telescope.layout`.
Some options for configuring sizes in layouts are "resolvable". This means that
they can take different forms, and will be interpreted differently according to
which form they take.
For example, if we wanted to set the `width` of a picker using the `vertical`
layout strategy to 50% of the screen width, we would specify that width
as `0.5`, but if we wanted to specify the `width` to be exactly 80
characters wide, we would specify it as `80`.
For more details on resolving sizes, see `:help telescope.resolve`.
As an example, if we wanted to specify the layout strategy and width,
but only for this instance, we could do something like:
```
:lua require('telescope.builtin').find_files({layout_strategy='vertical',layout_config={width=0.5}})
```
If we wanted to change the width for every time we use the `vertical`
layout strategy, we could add the following to our `setup()` call:
```lua
require('telescope').setup({
defaults = {
layout_config = {
vertical = { width = 0.5 }
-- other layout configuration here
},
-- other defaults configuration here
},
-- other configuration values here
})
```
## Themes
Common groups of settings can be set up to allow for themes.
We have some built in themes but are looking for more cool options.
![dropdown](https://i.imgur.com/SorAcXv.png)
| Themes | Description |
|--------------------------|---------------------------------------------------------------------------------------------|
| `themes.get_dropdown` | A list like centered list. [dropdown](https://i.imgur.com/SorAcXv.png) |
| `themes.get_cursor` | [A cursor relative list.](https://github.com/nvim-telescope/telescope.nvim/pull/878) |
| `themes.get_ivy` | Bottom panel overlay. [Ivy #771](https://github.com/nvim-telescope/telescope.nvim/pull/771) |
To use a theme, simply append it to a builtin function:
```vim
nnoremap <Leader>f :lua require'telescope.builtin'.find_files(require('telescope.themes').get_dropdown({}))<cr>
" Change an option
nnoremap <Leader>f :lua require'telescope.builtin'.find_files(require('telescope.themes').get_dropdown({ winblend = 10 }))<cr>
```
Or use with a command:
```vim
Telescope find_files theme=dropdown
```
Or you can configure it in the pickers table in `telescope.setup`:
```lua
require('telescope').setup{
defaults = {
-- ...
},
pickers = {
find_files = {
theme = "dropdown",
}
},
extensions = {
-- ...
}
}
```
Themes should work with every `telescope.builtin` function. If you wish to make
a theme, check out `lua/telescope/themes.lua`.
## Vim Commands
All `telescope.nvim` functions are wrapped in `vim` commands for easy access,
tab completions and setting options.
```viml
" Show all builtin pickers
:Telescope
" Tab completion
:Telescope |<tab>
:Telescope find_files
" Setting options
:Telescope find_files prompt_prefix=🔍
" If the option accepts a Lua table as its value, you can use, to connect each
" command string, e.g.: find_command, vimgrep_arguments are both options that
" accept a Lua table as a value. So, you can configure them on the command line
"like so:
:Telescope find_files find_command=rg,--ignore,--hidden,--files prompt_prefix=🔍
```
for more information and how to realize more complex commands please read
`:help telescope.command`.
## Autocmds
Telescope user autocmds:
| Event | Description |
|---------------------------------|---------------------------------------------------------|
| `User TelescopeFindPre` | Do it before Telescope creates all the floating windows |
| `User TelescopePreviewerLoaded` | Do it after Telescope previewer window is created |
## Extensions
Telescope provides the capabilities to create & register extensions, which
improves telescope in a variety of ways.
Some extensions provide integration with external tools, outside of the scope of
`builtins`. Others provide performance enhancements by using compiled C and
interfacing directly with Lua over LuaJIT's FFI library.
A list of community extensions can be found in the
[Extensions](https://github.com/nvim-telescope/telescope.nvim/wiki/Extensions)
wiki. Always read the README of the extension you want to install, but here is a
general overview of how most extensions work.
### Loading extensions
To load an extension, use the `load_extension` function as shown in the example
below:
```lua
-- This will load fzy_native and have it override the default file sorter
require('telescope').load_extension('fzy_native')
```
You may skip explicitly loading extensions (they will then be lazy-loaded), but
tab completions will not be available right away.
### Accessing pickers from extensions
Pickers from extensions are added to the `:Telescope` command under their
respective name. For example:
```viml
" Run the `configurations` picker from nvim-dap
:Telescope dap configurations
```
They can also be called directly from Lua:
```lua
-- Run the `configurations` picker from nvim-dap
require('telescope').extensions.dap.configurations()
```
## API
For writing your own picker and for information about the API please read the
[Developers Documentation](developers.md).
## Media
- [What is Telescope? (Video)](https://www.twitch.tv/teej_dv/clip/RichDistinctPlumberPastaThat)
- [More advanced configuration (Video)](https://www.twitch.tv/videos/756229115)
- [Example video](https://www.youtube.com/watch?v=65AVwHZflsU)
## Contributing
All contributions are welcome! Just open a pull request.
Please read [CONTRIBUTING.md](./CONTRIBUTING.md)
## Related Projects
- [fzf.vim](https://github.com/junegunn/fzf.vim)
- [denite.nvim](https://github.com/Shougo/denite.nvim)
- [vim-clap](https://github.com/liuchengxu/vim-clap)

View File

@ -0,0 +1,418 @@
# Developers
- [Introduction](#introduction)
- [Guide to your first Picker](#guide-to-your-first-picker)
- [Requires](#requires)
- [First Picker](#first-picker)
- [Replacing Actions](#replacing-actions)
- [Entry Maker](#entry-maker)
- [Oneshot job](#oneshot-job)
- [Previewer](#previewer)
- [More examples](#more-examples)
- [Bundling as Extension](#bundling-as-extension)
- [Technical](#technical)
- [picker](#picker)
- [finders](#finders)
- [actions](#actions)
- [previewers](#previewers)
## Introduction
So you want to develop your own picker and/or extension for telescope? Then you
are in the right place! This file will first present an introduction on how to
do this. After that, this document will present a technical explanation of
pickers, finders, actions and the previewer. Should you now yet have an idea of
the general telescope architecture and its components, it is first recommend to
familiarize yourself with the architectural flow-chart that is provided in
vim docs (`:h telescope.nvim`). You can find more information in specific help
pages and we will probably move some of the technical stuff to our vim help docs
in the future.
This guide is mainly for telescope so it will assume that you already have some knowledge of the Lua
programming language. If not then you can find information for Lua here:
- [Lua 5.1 Manual](https://www.lua.org/manual/5.1/)
- [Getting started using Lua in Neovim](https://github.com/nanotee/nvim-lua-guide)
## Guide to your first Picker
To guide you along the way to your first picker we will open an empty lua
scratch file, in which we will develop the picker and run it each time using
`:luafile %`. Later we will bundle this file as an extension.
### Requires
The most important includes are the following modules:
```lua
local pickers = require "telescope.pickers"
local finders = require "telescope.finders"
local conf = require("telescope.config").values
```
- `pickers`: main module which is used to create a new picker.
- `finders`: provides interfaces to fill the picker with items.
- `config`: `values` table which holds the user's configuration.
So to make it easier we access this table directly in `conf`.
### First Picker
We will now make the simplest color picker. (We will approach this example step by step,
you will still need to have the previous requires section above this code.)
```lua
-- our picker function: colors
local colors = function(opts)
opts = opts or {}
pickers.new(opts, {
prompt_title = "colors",
finder = finders.new_table {
results = { "red", "green", "blue" }
},
sorter = conf.generic_sorter(opts),
}):find()
end
-- to execute the function
colors()
```
Running this code with `:luafile %` should open a telescope picker with the entries `red`,
`green`, `blue`. Selecting a color and pressing enter will open a new file. In this case
it's not what we want, so we will address this after explaining this snippet.
We will define a new function `colors` which accepts a table `opts`. This is good
practice because now the user can change how telescope behaves by passing in their
own `opts` table when calling `colors`.
For example the user can pass in a configuration in `opts` which allows them to change
the theme used for the picker. To allow this, we make sure to pass the `opts` table
as the first argument to `pickers.new`. The second argument is a table
which defines the default behavior of the picker.
We have defined a `prompt_title` but this isn't required. This will default to use
the text `Prompt` if not set.
`finder` is a required field that needs to be set to the result of a `finders`
function. In this case we take `new_table` which allows us to define a static
set of values, `results`, which is an array of elements, in this case our colors
as strings. It doesn't have to be an array of strings, it can also be an array of
tables. More on this later.
`sorter` on the other hand is not a required field but it's good practice to
define it, because the default value will set it to `empty()`, meaning no sorter
is attached and you can't filter the results. Good practice is to set the sorter
to either `conf.generic_sorter(opts)` or `conf.file_sorter(opts)`.
Setting it to a value from `conf` will respect the user's configuration, so if a user has set-up
`fzf-native` as the sorter then this decision will be respected and the `fzf-native` sorter
will be attached. It's also suggested to pass in `opts` here because the sorter
could make use of it. As an example the fzf sorter can be configured to be case
sensitive or insensitive. A user can set-up a default behavior and then alter
this behavior with the `opts` table.
After the picker is defined you need to call `find()` to actually start the
picker.
### Replacing Actions
Now calling `colors()` will result in the opening of telescope with the values:
`red`, `green` and `blue`. The default theme isn't optimal for this picker so we
want to change it and thanks to the acceptance of `opts` we can. We will replace
the last line with the following to open the picker with the `dropdown` theme.
```lua
colors(require("telescope.themes").get_dropdown{})
```
Now let's address the issue that selecting a color opens a new buffer. For that
we need to replace the default select action. The benefit of replacing rather than
mapping a new function to `<CR>` is that it will respect the user's configuration. So
if a user has remapped `select_default` to another key then this decision will
be respected and it works as expected for the user.
To make this work we need more requires at the top of the file.
```lua
local actions = require "telescope.actions"
local action_state = require "telescope.actions.state"
```
- `actions`: holds all actions that can be mapped by a user. We also need it to
access the default action so we can replace it. Also see `:help
telescope.actions`
- `action_state`: gives us a few utility functions we can use to get the
current picker, current selection or current line. Also see `:help
telescope.actions.state`
So let's replace the default action. For that we need to define a new key value
pair in our table that we pass into `pickers.new`, for example after `sorter`.
```lua
attach_mappings = function(prompt_bufnr, map)
actions.select_default:replace(function()
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
-- print(vim.inspect(selection))
vim.api.nvim_put({ selection[1] }, "", false, true)
end)
return true
end,
```
We do this by setting the `attach_mappings` key to a function. This function
needs to return either `true` or `false`. If it returns false it means that only
the actions defined in the function should be attached. In this case it would
remove the default actions to move the selected item in the picker,
`move_selection_{next,previous}`. So in most cases you'll want to return `true`.
If the function does not return anything then an error is thrown.
The `attach_mappings` function has two parameters, `prompt_bufnr` is the buffer number
of the prompt buffer, which we can use to get the pickers object and `map` is a function
we can use to map actions or functions to arbitrary key sequences.
Now we are replacing `select_default` the default action, which is mapped to `<CR>`
by default. To do this we need to call `actions.select_default:replace` and
pass in a new function.
In this new function we first close the picker with `actions.close` and then
get the `selection` with `action_state`. It's important
to notice that you can still get the selection and current prompt input
(`action_state.get_current_line()`) with `action_state` even after the picker is
closed.
You can look at the selection with `print(vim.inspect(selection))` and see that it differs from our input
(string), this is because internally we pack it into a table with different
keys. You can specify this behavior and we'll talk about that in the next
section. Now all that is left is to do something with the selection we have. In
this case we just put the text in the current buffer with `vim.api.nvim_put`.
### Entry Maker
Entry maker is a function used to transform an item from the finder to an
internal entry table, which has a few required keys. It allows us to display
one string but match something completly different. It also allows us to set
an absolute path when working with files (so the file will always be found)
and a relative file path for display and sorting. This means the relative file
path doesn't even need to be valid in the context of the current working directory.
We will now try to define our entry maker for our example by providing an
`entry_maker` to `finders.new_table` and changing our table to be a little bit
more interesting. We will end up with the following new code for `finders.new_table`:
```lua
finder = finders.new_table {
results = {
{ "red", "#ff0000" },
{ "green", "#00ff00" },
{ "blue", "#0000ff" },
},
entry_maker = function(entry)
return {
value = entry,
display = entry[1],
ordinal = entry[1],
}
end
},
```
With the new snippet, we no longer have an array of strings but an array of
tables. Each table has a color, name, and the color's hex value.
`entry_maker` is a function that will receive each table and then we can set the
values we need. It's best practice to have a `value` reference to the
original entry, that way we will always have access to the complete table in our
action.
The `display` key is required and is either a string or a `function(tbl)`,
where `tbl` is the table returned by `entry_maker`. So in this example `tbl` would
give our `display` function access to `value` and `ordinal`.
If our picker will have a lot of values it's suggested to use a function for `display`,
especially if you are modifying the text to display. This way the function will only be executed
for the entries being displayed. For an example of an entry maker take a look at
`lua/telescope/make_entry.lua`.
A good way to make your `display` more like a table is to use a `displayer` which can be found in
`lua/telescope/pickers/entry_display.lua`. A simpler example of `displayer` is the
function `gen_from_git_commits` in `make_entry.lua`.
The `ordinal` is also required, which is used for sorting. As already mentioned
this allows us to have different display and sorting values. This allows `display`
to be more complex with icons and special indicators but `ordinal` could be a simpler
sorting key.
There are other important keys which can be set, but do not make sense in the
current context as we are not dealing with files:
- `path`: to set the absolute path of the file to make sure it's always found
- `lnum`: to specify a line number in the file. This will allow the
`conf.grep_previewer` to show that line and the default action to jump to
that line.
### Previewer
We will not write a previewer for this picker because it isn't required for
basic colors and is a more advanced topic. It's already well documented in `:help
telescope.previewers` so you can read this section if you want to write your
own `previewer`. If you want a file previewer without columns you should
default to `conf.file_previewer` or `conf.grep_previewer`.
### Oneshot Job
The `oneshot_job` finder can be used to have an asynchronous external process which will
find results and call `entry_maker` for each entry. An example usage would be
`find`.
```lua
finder = finders.new_oneshot_job { "find", opts },
```
### More examples
A good way to find more examples is to look into the [lua/telescope/builtin](https://github.com/nvim-telescope/telescope.nvim/tree/master/lua/telescope/builtin)
directory which contains all of the builtin pickers. Another way to find more examples
is to take a look at the [extension wiki page](https://github.com/nvim-telescope/telescope.nvim/wiki/Extensions)
as this provides many extensions people have already written which use these concepts.
If you still have any questions after reading this guide please feel free to ask us for
more information on [gitter](https://gitter.im/nvim-telescope/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
and we will happily answer your questions and hopefully allow us to improve this guide. You can also
help us to improve this guide by sending a PR.
### Bundling as extension
If you now want to bundle your picker as extension, so it is available as
picker via the `:Telescope` command, the following has to be done.
Structure your plugin as follows, so it can be found by telescope:
```
.
└── lua
├── plugin_name # Your actual plugin code
│ ├── init.lua
│ └── some_file.lua
└── telescope
└── _extensions # The underscore is significant
└─ plugin_name.lua # Init and register your extension
```
The `lua/telescope/_extensions/plugin_name.lua` file needs to return the
following: (see `:help telescope.register_extension`)
```lua
return require("telescope").register_extension {
setup = function(ext_config, config)
-- access extension config and user config
end,
exports = {
stuff = require("plugin_name").stuff
},
}
```
The setup function can be used to access the extension config and setup
extension specific global configuration. You also have access to the user
telescope default config, so you can override specific internal function. For
example sorters if you have an extension that provides a replacement sorter,
like
[telescope-fzf-native](https://github.com/nvim-telescope/telescope-fzf-native.nvim).
The exports table declares the exported pickers that can then be accessed via
`Telescope plugin_name stuff`. If you only provide one export it is suggested
that you name the key like the plugin, so you can access it with `Telescope
plugin_name`.
## Technical
### Picker
This section is an overview of how custom pickers can be created and configured.
```lua
-- lua/telescope/pickers.lua
Picker:new{
prompt_title = "",
finder = FUNCTION, -- see lua/telescope/finder.lua
sorter = FUNCTION, -- see lua/telescope/sorter.lua
previewer = FUNCTION, -- see lua/telescope/previewer.lua
selection_strategy = "reset", -- follow, reset, row
border = {},
borderchars = {"─", "│", "─", "│", "┌", "┐", "┘", "└"},
default_selection_index = 1, -- Change the index of the initial selection row
}
```
### Finders
<!-- TODO what are finders -->
```lua
-- lua/telescope/finders.lua
Finder:new{
entry_maker = function(line) end,
fn_command = function() { command = "", args = { "ls-files" } } end,
static = false,
maximum_results = false
}
```
### Actions
#### Overriding actions/action_set
How to override what different functions / keys do.
TODO: Talk about what actions vs actions sets are
##### Relevant Files
- `lua/telescope/actions/init.lua`
- The most "user-facing" of the files, which has the builtin actions that we provide
- `lua/telescope/actions/set.lua`
- The second most "user-facing" of the files. This provides actions that are consumed by several builtin actions, which allows for only overriding ONE item, instead of copying the same configuration / function several times.
- `lua/telescope/actions/state.lua`
- Provides APIs for interacting with the state of telescope from within actions.
- These are useful for writing your own actions and interacting with telescope
- `lua/telescope/actions/mt.lua`
- You probably don't need to look at this, but it defines the behavior of actions.
##### `:replace(function)`
Directly override an action with a new function
```lua
local actions = require('telescope.actions')
actions.select_default:replace(git_checkout_function)
```
##### `:replace_if(conditional, function)`
Override an action only when `conditional` returns true.
```lua
local action_set = require('telescope.actions.set')
action_set.select:replace_if(
function()
return action_state.get_selected_entry().path:sub(-1) == os_sep
end, function(_, type)
-- type is { "default", "horizontal", "vertical", "tab" }
local path = actions.get_selected_entry().path
action_state.get_current_picker(prompt_bufnr):refresh(gen_new_finder(new_cwd), { reset_prompt = true})
end
)
```
##### `:replace_map(configuration)`
```lua
local action_set = require('telescope.actions.set')
-- Use functions as keys to map to which function to execute when called.
action_set.select:replace_map {
[function(e) return e > 0 end] = function(e) return (e / 10) end,
[function(e) return e == 0 end] = function(e) return (e + 10) end,
}
```
### Previewers
See `:help telescope.previewers`

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,323 @@
local uv = vim.loop
local Object = require "plenary.class"
local log = require "plenary.log"
local async = require "plenary.async"
local channel = require("plenary.async").control.channel
local M = {}
local AsyncJob = {}
AsyncJob.__index = AsyncJob
function AsyncJob.new(opts)
local self = setmetatable({}, AsyncJob)
self.command, self.uv_opts = M.convert_opts(opts)
self.stdin = opts.stdin or M.NullPipe()
self.stdout = opts.stdout or M.NullPipe()
self.stderr = opts.stderr or M.NullPipe()
if opts.cwd and opts.cwd ~= "" then
self.uv_opts.cwd = vim.fn.expand(vim.fn.escape(opts.cwd, "$"))
-- this is a "illegal" hack for windows. E.g. If the git command returns `/` rather than `\` as delimiter,
-- vim.fn.expand might just end up returning an empty string. Weird
-- Because empty string is not allowed in libuv the job will not spawn. Solution is we just set it to opts.cwd
if self.uv_opts.cwd == "" then
self.uv_opts.cwd = opts.cwd
end
end
self.uv_opts.stdio = {
self.stdin.handle,
self.stdout.handle,
self.stderr.handle,
}
return self
end
function AsyncJob:_for_each_pipe(f, ...)
for _, pipe in ipairs { self.stdin, self.stdout, self.stderr } do
f(pipe, ...)
end
end
function AsyncJob:close(force)
if force == nil then
force = true
end
self:_for_each_pipe(function(p)
p:close(force)
end)
uv.process_kill(self.handle, "SIGTERM")
log.debug "[async_job] closed"
end
M.spawn = function(opts)
local self = AsyncJob.new(opts)
self.handle, self.pid = uv.spawn(
self.command,
self.uv_opts,
async.void(function()
self:close(false)
if not self.handle:is_closing() then
self.handle:close()
end
end)
)
if not self.handle then
error(debug.traceback("Failed to spawn process: " .. vim.inspect(self)))
end
return self
end
---@class uv_pipe_t
--- A pipe handle from libuv
---@field read_start function: Start reading
---@field read_stop function: Stop reading
---@field close function: Close the handle
---@field is_closing function: Whether handle is currently closing
---@field is_active function: Whether the handle is currently reading
---@class BasePipe
---@field super Object: Always available
---@field handle uv_pipe_t: A pipe handle
---@field extend function: Extend
local BasePipe = Object:extend()
function BasePipe:new()
self.eof_tx, self.eof_rx = channel.oneshot()
end
function BasePipe:close(force)
if force == nil then
force = true
end
assert(self.handle, "Must have a pipe to close. Otherwise it's weird!")
if self.handle:is_closing() then
return
end
-- If we're not forcing the stop, allow waiting for eof
-- This ensures that we don't end up with weird race conditions
if not force then
self.eof_rx()
end
self.handle:read_stop()
if not self.handle:is_closing() then
self.handle:close()
end
self._closed = true
end
---@class LinesPipe : BasePipe
local LinesPipe = BasePipe:extend()
function LinesPipe:new()
LinesPipe.super.new(self)
self.handle = uv.new_pipe(false)
end
function LinesPipe:read()
local read_tx, read_rx = channel.oneshot()
self.handle:read_start(function(err, data)
assert(not err, err)
self.handle:read_stop()
read_tx(data)
if data == nil then
self.eof_tx()
end
end)
return read_rx()
end
function LinesPipe:iter(schedule)
if schedule == nil then
schedule = true
end
local text = nil
local index = nil
local get_next_text = function(previous)
index = nil
local read = self:read()
if previous == nil and read == nil then
return
end
read = string.gsub(read or "", "\r", "")
return (previous or "") .. read
end
local next_value = nil
next_value = function()
if schedule then
async.util.scheduler()
end
if text == nil or (text == "" and index == nil) then
return nil
end
local start = index
index = string.find(text, "\n", index, true)
if index == nil then
text = get_next_text(string.sub(text, start or 1))
return next_value()
end
index = index + 1
return string.sub(text, start or 1, index - 2)
end
text = get_next_text()
return function()
return next_value()
end
end
---@class NullPipe : BasePipe
local NullPipe = BasePipe:extend()
function NullPipe:new()
NullPipe.super.new(self)
self.start = function() end
self.read_start = function() end
self.close = function() end
-- This always has eof tx done, so can just call it now
self.eof_tx()
end
---@class ChunkPipe : BasePipe
local ChunkPipe = BasePipe:extend()
function ChunkPipe:new()
ChunkPipe.super.new(self)
self.handle = uv.new_pipe(false)
end
function ChunkPipe:read()
local read_tx, read_rx = channel.oneshot()
self.handle:read_start(function(err, data)
assert(not err, err)
self.handle:read_stop()
read_tx(data)
if data == nil then
self.eof_tx()
end
end)
return read_rx()
end
function ChunkPipe:iter()
return function()
if self._closed then
return nil
end
return self:read()
end
end
---@class ErrorPipe : BasePipe
local ErrorPipe = BasePipe:extend()
function ErrorPipe:new()
ErrorPipe.super.new(self)
self.handle = uv.new_pipe(false)
end
function ErrorPipe:start()
self.handle:read_start(function(err, data)
if not err and not data then
return
end
self.handle:read_stop()
self.handle:close()
error(string.format("Err: %s, Data: '%s'", err, data))
end)
end
M.NullPipe = NullPipe
M.LinesPipe = LinesPipe
M.ChunkPipe = ChunkPipe
M.ErrorPipe = ErrorPipe
M.convert_opts = function(o)
if not o then
error(debug.traceback "Options are required for Job:new")
end
local command = o.command
if not command then
if o[1] then
command = o[1]
else
error(debug.traceback "'command' is required for Job:new")
end
elseif o[1] then
error(debug.traceback "Cannot pass both 'command' and array args")
end
local args = o.args
if not args then
if #o > 1 then
args = { select(2, unpack(o)) }
end
end
local ok, is_exe = pcall(vim.fn.executable, command)
if not o.skip_validation and ok and 1 ~= is_exe then
error(debug.traceback(command .. ": Executable not found"))
end
local obj = {}
obj.args = args
if o.env then
if type(o.env) ~= "table" then
error(debug.traceback "'env' has to be a table")
end
local transform = {}
for k, v in pairs(o.env) do
if type(k) == "number" then
table.insert(transform, v)
elseif type(k) == "string" then
table.insert(transform, k .. "=" .. tostring(v))
end
end
obj.env = transform
end
return command, obj
end
return M

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,230 @@
---@tag telescope.actions.set
---@config { ["module"] = "telescope.actions.set", ["name"] = "ACTIONS_SET" }
---@brief [[
--- Telescope action sets are used to provide an interface for managing
--- actions that all primarily do the same thing, but with slight tweaks.
---
--- For example, when editing files you may want it in the current split,
--- a vertical split, etc. Instead of making users have to overwrite EACH
--- of those every time they want to change this behavior, they can instead
--- replace the `set` itself and then it will work great and they're done.
---@brief ]]
local a = vim.api
local log = require "telescope.log"
local Path = require "plenary.path"
local state = require "telescope.state"
local utils = require "telescope.utils"
local action_state = require "telescope.actions.state"
local transform_mod = require("telescope.actions.mt").transform_mod
local action_set = setmetatable({}, {
__index = function(_, k)
error("'telescope.actions.set' does not have a value: " .. tostring(k))
end,
})
--- Move the current selection of a picker {change} rows.
--- Handles not overflowing / underflowing the list.
---@param prompt_bufnr number: The prompt bufnr
---@param change number: The amount to shift the selection by
action_set.shift_selection = function(prompt_bufnr, change)
local count = vim.v.count
count = count == 0 and 1 or count
count = a.nvim_get_mode().mode == "n" and count or 1
action_state.get_current_picker(prompt_bufnr):move_selection(change * count)
end
--- Select the current entry. This is the action set to overwrite common
--- actions by the user.
---
--- By default maps to editing a file.
---@param prompt_bufnr number: The prompt bufnr
---@param type string: The type of selection to make
-- Valid types include: "default", "horizontal", "vertical", "tabedit"
action_set.select = function(prompt_bufnr, type)
return action_set.edit(prompt_bufnr, action_state.select_key_to_edit_key(type))
end
-- goal: currently we have a workaround in actions/init.lua where we do this for all files
-- action_set.select = {
-- -- Will not be called if `select_default` is replaced rather than `action_set.select` because we never get here
-- pre = function(prompt_bufnr)
-- action_state.get_current_history():append(
-- action_state.get_current_line(),
-- action_state.get_current_picker(prompt_bufnr)
-- )
-- end,
-- action = function(prompt_bufnr, type)
-- return action_set.edit(prompt_bufnr, action_state.select_key_to_edit_key(type))
-- end
-- }
local edit_buffer
do
local map = {
edit = "buffer",
new = "sbuffer",
vnew = "vert sbuffer",
tabedit = "tab sb",
}
edit_buffer = function(command, bufnr)
command = map[command]
if command == nil then
error "There was no associated buffer command"
end
vim.cmd(string.format("%s %d", command, bufnr))
end
end
--- Edit a file based on the current selection.
---@param prompt_bufnr number: The prompt bufnr
---@param command string: The command to use to open the file.
-- Valid commands include: "edit", "new", "vedit", "tabedit"
action_set.edit = function(prompt_bufnr, command)
local entry = action_state.get_selected_entry()
if not entry then
utils.notify("actions.set.edit", {
msg = "Nothing currently selected",
level = "WARN",
})
return
end
local filename, row, col
if entry.path or entry.filename then
filename = entry.path or entry.filename
-- TODO: Check for off-by-one
row = entry.row or entry.lnum
col = entry.col
elseif not entry.bufnr then
-- TODO: Might want to remove this and force people
-- to put stuff into `filename`
local value = entry.value
if not value then
utils.notify("actions.set.edit", {
msg = "Could not do anything with blank line...",
level = "WARN",
})
return
end
if type(value) == "table" then
value = entry.display
end
local sections = vim.split(value, ":")
filename = sections[1]
row = tonumber(sections[2])
col = tonumber(sections[3])
end
local entry_bufnr = entry.bufnr
local picker = action_state.get_current_picker(prompt_bufnr)
require("telescope.pickers").on_close_prompt(prompt_bufnr)
pcall(vim.api.nvim_set_current_win, picker.original_win_id)
local win_id = picker.get_selection_window(picker, entry)
if picker.push_cursor_on_edit then
vim.cmd "normal! m'"
end
if picker.push_tagstack_on_edit then
local from = { vim.fn.bufnr "%", vim.fn.line ".", vim.fn.col ".", 0 }
local items = { { tagname = vim.fn.expand "<cword>", from = from } }
vim.fn.settagstack(vim.fn.win_getid(), { items = items }, "t")
end
if win_id ~= 0 and a.nvim_get_current_win() ~= win_id then
vim.api.nvim_set_current_win(win_id)
end
if entry_bufnr then
if not vim.api.nvim_buf_get_option(entry_bufnr, "buflisted") then
vim.api.nvim_buf_set_option(entry_bufnr, "buflisted", true)
end
edit_buffer(command, entry_bufnr)
else
-- check if we didn't pick a different buffer
-- prevents restarting lsp server
if vim.api.nvim_buf_get_name(0) ~= filename or command ~= "edit" then
filename = Path:new(filename):normalize(vim.loop.cwd())
pcall(vim.cmd, string.format("%s %s", command, vim.fn.fnameescape(filename)))
end
end
local pos = vim.api.nvim_win_get_cursor(0)
if col == nil then
if row == pos[1] then
col = pos[2] + 1
elseif row == nil then
row, col = pos[1], pos[2] + 1
else
col = 1
end
end
if row and col then
local ok, err_msg = pcall(a.nvim_win_set_cursor, 0, { row, col })
if not ok then
log.debug("Failed to move to cursor:", err_msg, row, col)
end
end
end
--- Scrolls the previewer up or down.
--- Defaults to a half page scroll, but can be overridden using the `scroll_speed`
--- option in `layout_config`. See |telescope.layout| for more details.
---@param prompt_bufnr number: The prompt bufnr
---@param direction number: The direction of the scrolling
-- Valid directions include: "1", "-1"
action_set.scroll_previewer = function(prompt_bufnr, direction)
local previewer = action_state.get_current_picker(prompt_bufnr).previewer
local status = state.get_status(prompt_bufnr)
-- Check if we actually have a previewer and a preview window
if type(previewer) ~= "table" or previewer.scroll_fn == nil or status.preview_win == nil then
return
end
local default_speed = vim.api.nvim_win_get_height(status.preview_win) / 2
local speed = status.picker.layout_config.scroll_speed or default_speed
previewer:scroll_fn(math.floor(speed * direction))
end
--- Scrolls the results up or down.
--- Defaults to a half page scroll, but can be overridden using the `scroll_speed`
--- option in `layout_config`. See |telescope.layout| for more details.
---@param prompt_bufnr number: The prompt bufnr
---@param direction number: The direction of the scrolling
-- Valid directions include: "1", "-1"
action_set.scroll_results = function(prompt_bufnr, direction)
local status = state.get_status(prompt_bufnr)
local default_speed = vim.api.nvim_win_get_height(status.results_win) / 2
local speed = status.picker.layout_config.scroll_speed or default_speed
local input = direction > 0 and [[]] or [[]]
vim.api.nvim_win_call(status.results_win, function()
vim.cmd([[normal! ]] .. math.floor(speed) .. input)
end)
action_set.shift_selection(prompt_bufnr, math.floor(speed) * direction)
end
-- ==================================================
-- Transforms modules and sets the corect metatables.
-- ==================================================
action_set = transform_mod(action_set)
return action_set

View File

@ -0,0 +1,152 @@
local conf = require("telescope.config").values
local finders = require "telescope.finders"
local make_entry = require "telescope.make_entry"
local pickers = require "telescope.pickers"
local utils = require "telescope.utils"
local diagnostics = {}
local convert_diagnostic_type = function(severities, severity)
-- convert from string to int
if type(severity) == "string" then
-- make sure that e.g. error is uppercased to ERROR
return severities[severity:upper()]
end
-- otherwise keep original value, incl. nil
return severity
end
local diagnostics_to_tbl = function(opts)
opts = vim.F.if_nil(opts, {})
local items = {}
local severities = vim.diagnostic.severity
local current_buf = vim.api.nvim_get_current_buf()
opts.severity = convert_diagnostic_type(severities, opts.severity)
opts.severity_limit = convert_diagnostic_type(severities, opts.severity_limit)
opts.severity_bound = convert_diagnostic_type(severities, opts.severity_bound)
local diagnosis_opts = { severity = {}, namespace = opts.namespace }
if opts.severity ~= nil then
if opts.severity_limit ~= nil or opts.severity_bound ~= nil then
utils.notify("builtin.diagnostics", {
msg = "Invalid severity parameters. Both a specific severity and a limit/bound is not allowed",
level = "ERROR",
})
return {}
end
diagnosis_opts.severity = opts.severity
else
if opts.severity_limit ~= nil then
diagnosis_opts.severity["min"] = opts.severity_limit
end
if opts.severity_bound ~= nil then
diagnosis_opts.severity["max"] = opts.severity_bound
end
end
opts.root_dir = opts.root_dir == true and vim.loop.cwd() or opts.root_dir
local bufnr_name_map = {}
local filter_diag = function(diagnostic)
if bufnr_name_map[diagnostic.bufnr] == nil then
bufnr_name_map[diagnostic.bufnr] = vim.api.nvim_buf_get_name(diagnostic.bufnr)
end
local root_dir_test = not opts.root_dir
or string.sub(bufnr_name_map[diagnostic.bufnr], 1, #opts.root_dir) == opts.root_dir
local listed_test = not opts.no_unlisted or vim.api.nvim_buf_get_option(diagnostic.bufnr, "buflisted")
return root_dir_test and listed_test
end
local preprocess_diag = function(diagnostic)
return {
bufnr = diagnostic.bufnr,
filename = bufnr_name_map[diagnostic.bufnr],
lnum = diagnostic.lnum + 1,
col = diagnostic.col + 1,
text = vim.trim(diagnostic.message:gsub("[\n]", "")),
type = severities[diagnostic.severity] or severities[1],
}
end
for _, d in ipairs(vim.diagnostic.get(opts.bufnr, diagnosis_opts)) do
if filter_diag(d) then
table.insert(items, preprocess_diag(d))
end
end
-- sort results by bufnr (prioritize cur buf), severity, lnum
table.sort(items, function(a, b)
if a.bufnr == b.bufnr then
if a.type == b.type then
return a.lnum < b.lnum
else
return a.type < b.type
end
else
-- prioritize for current bufnr
if a.bufnr == current_buf then
return true
end
if b.bufnr == current_buf then
return false
end
return a.bufnr < b.bufnr
end
end)
return items
end
diagnostics.get = function(opts)
if opts.bufnr ~= 0 then
opts.bufnr = nil
end
if opts.bufnr == nil then
opts.path_display = vim.F.if_nil(opts.path_display, {})
end
if type(opts.bufnr) == "string" then
opts.bufnr = tonumber(opts.bufnr)
end
local locations = diagnostics_to_tbl(opts)
if vim.tbl_isempty(locations) then
utils.notify("builtin.diagnostics", {
msg = "No diagnostics found",
level = "INFO",
})
return
end
opts.path_display = vim.F.if_nil(opts.path_display, "hidden")
pickers
.new(opts, {
prompt_title = opts.bufnr == nil and "Workspace Diagnostics" or "Document Diagnostics",
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_diagnostics(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.prefilter_sorter {
tag = "type",
sorter = conf.generic_sorter(opts),
},
})
:find()
end
local function apply_checks(mod)
for k, v in pairs(mod) do
mod[k] = function(opts)
opts = opts or {}
v(opts)
end
end
return mod
end
return apply_checks(diagnostics)

View File

@ -0,0 +1,590 @@
local action_state = require "telescope.actions.state"
local action_set = require "telescope.actions.set"
local actions = require "telescope.actions"
local finders = require "telescope.finders"
local make_entry = require "telescope.make_entry"
local pickers = require "telescope.pickers"
local previewers = require "telescope.previewers"
local sorters = require "telescope.sorters"
local utils = require "telescope.utils"
local conf = require("telescope.config").values
local log = require "telescope.log"
local Path = require "plenary.path"
local flatten = vim.tbl_flatten
local filter = vim.tbl_filter
local files = {}
local escape_chars = function(string)
return string.gsub(string, "[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%.]", {
["\\"] = "\\\\",
["-"] = "\\-",
["("] = "\\(",
[")"] = "\\)",
["["] = "\\[",
["]"] = "\\]",
["{"] = "\\{",
["}"] = "\\}",
["?"] = "\\?",
["+"] = "\\+",
["*"] = "\\*",
["^"] = "\\^",
["$"] = "\\$",
["."] = "\\.",
})
end
local get_open_filelist = function(grep_open_files, cwd)
if not grep_open_files then
return nil
end
local bufnrs = filter(function(b)
if 1 ~= vim.fn.buflisted(b) then
return false
end
return true
end, vim.api.nvim_list_bufs())
if not next(bufnrs) then
return
end
local filelist = {}
for _, bufnr in ipairs(bufnrs) do
local file = vim.api.nvim_buf_get_name(bufnr)
table.insert(filelist, Path:new(file):make_relative(cwd))
end
return filelist
end
local opts_contain_invert = function(args)
local invert = false
local files_with_matches = false
for _, v in ipairs(args) do
if v == "--invert-match" then
invert = true
elseif v == "--files-with-matches" or v == "--files-without-match" then
files_with_matches = true
end
if #v >= 2 and v:sub(1, 1) == "-" and v:sub(2, 2) ~= "-" then
local non_option = false
for i = 2, #v do
local vi = v:sub(i, i)
if vi == "=" then -- ignore option -g=xxx
break
elseif vi == "g" or vi == "f" or vi == "m" or vi == "e" or vi == "r" or vi == "t" or vi == "T" then
non_option = true
elseif non_option == false and vi == "v" then
invert = true
elseif non_option == false and vi == "l" then
files_with_matches = true
end
end
end
end
return invert, files_with_matches
end
-- Special keys:
-- opts.search_dirs -- list of directory to search in
-- opts.grep_open_files -- boolean to restrict search to open files
files.live_grep = function(opts)
local vimgrep_arguments = opts.vimgrep_arguments or conf.vimgrep_arguments
local search_dirs = opts.search_dirs
local grep_open_files = opts.grep_open_files
opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd()
local filelist = get_open_filelist(grep_open_files, opts.cwd)
if search_dirs then
for i, path in ipairs(search_dirs) do
search_dirs[i] = vim.fn.expand(path)
end
end
local additional_args = {}
if opts.additional_args ~= nil then
if type(opts.additional_args) == "function" then
additional_args = opts.additional_args(opts)
elseif type(opts.additional_args) == "table" then
additional_args = opts.additional_args
end
end
if opts.type_filter then
additional_args[#additional_args + 1] = "--type=" .. opts.type_filter
end
if type(opts.glob_pattern) == "string" then
additional_args[#additional_args + 1] = "--glob=" .. opts.glob_pattern
elseif type(opts.glob_pattern) == "table" then
for i = 1, #opts.glob_pattern do
additional_args[#additional_args + 1] = "--glob=" .. opts.glob_pattern[i]
end
end
local args = flatten { vimgrep_arguments, additional_args }
opts.__inverted, opts.__matches = opts_contain_invert(args)
local live_grepper = finders.new_job(function(prompt)
if not prompt or prompt == "" then
return nil
end
local search_list = {}
if grep_open_files then
search_list = filelist
elseif search_dirs then
search_list = search_dirs
end
return flatten { args, "--", prompt, search_list }
end, opts.entry_maker or make_entry.gen_from_vimgrep(opts), opts.max_results, opts.cwd)
pickers
.new(opts, {
prompt_title = "Live Grep",
finder = live_grepper,
previewer = conf.grep_previewer(opts),
-- TODO: It would be cool to use `--json` output for this
-- and then we could get the highlight positions directly.
sorter = sorters.highlighter_only(opts),
attach_mappings = function(_, map)
map("i", "<c-space>", actions.to_fuzzy_refine)
return true
end,
})
:find()
end
files.grep_string = function(opts)
-- TODO: This should probably check your visual selection as well, if you've got one
opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd()
local vimgrep_arguments = vim.F.if_nil(opts.vimgrep_arguments, conf.vimgrep_arguments)
local word = vim.F.if_nil(opts.search, vim.fn.expand "<cword>")
local search = opts.use_regex and word or escape_chars(word)
local additional_args = {}
if opts.additional_args ~= nil then
if type(opts.additional_args) == "function" then
additional_args = opts.additional_args(opts)
elseif type(opts.additional_args) == "table" then
additional_args = opts.additional_args
end
end
if search == "" then
search = { "-v", "--", "^[[:space:]]*$" }
else
search = { "--", search }
end
local args = flatten {
vimgrep_arguments,
additional_args,
opts.word_match,
search,
}
opts.__inverted, opts.__matches = opts_contain_invert(args)
if opts.grep_open_files then
for _, file in ipairs(get_open_filelist(opts.grep_open_files, opts.cwd)) do
table.insert(args, file)
end
elseif opts.search_dirs then
for _, path in ipairs(opts.search_dirs) do
table.insert(args, vim.fn.expand(path))
end
end
opts.entry_maker = opts.entry_maker or make_entry.gen_from_vimgrep(opts)
pickers
.new(opts, {
prompt_title = "Find Word (" .. word:gsub("\n", "\\n") .. ")",
finder = finders.new_oneshot_job(args, opts),
previewer = conf.grep_previewer(opts),
sorter = conf.generic_sorter(opts),
})
:find()
end
files.find_files = function(opts)
local find_command = (function()
if opts.find_command then
if type(opts.find_command) == "function" then
return opts.find_command(opts)
end
return opts.find_command
elseif 1 == vim.fn.executable "rg" then
return { "rg", "--files", "--color", "never" }
elseif 1 == vim.fn.executable "fd" then
return { "fd", "--type", "f", "--color", "never" }
elseif 1 == vim.fn.executable "fdfind" then
return { "fdfind", "--type", "f", "--color", "never" }
elseif 1 == vim.fn.executable "find" and vim.fn.has "win32" == 0 then
return { "find", ".", "-type", "f" }
elseif 1 == vim.fn.executable "where" then
return { "where", "/r", ".", "*" }
end
end)()
if not find_command then
utils.notify("builtin.find_files", {
msg = "You need to install either find, fd, or rg",
level = "ERROR",
})
return
end
local command = find_command[1]
local hidden = opts.hidden
local no_ignore = opts.no_ignore
local no_ignore_parent = opts.no_ignore_parent
local follow = opts.follow
local search_dirs = opts.search_dirs
local search_file = opts.search_file
if search_dirs then
for k, v in pairs(search_dirs) do
search_dirs[k] = vim.fn.expand(v)
end
end
if command == "fd" or command == "fdfind" or command == "rg" then
if hidden then
find_command[#find_command + 1] = "--hidden"
end
if no_ignore then
find_command[#find_command + 1] = "--no-ignore"
end
if no_ignore_parent then
find_command[#find_command + 1] = "--no-ignore-parent"
end
if follow then
find_command[#find_command + 1] = "-L"
end
if search_file then
if command == "rg" then
find_command[#find_command + 1] = "-g"
find_command[#find_command + 1] = "*" .. search_file .. "*"
else
find_command[#find_command + 1] = search_file
end
end
if search_dirs then
if command ~= "rg" and not search_file then
find_command[#find_command + 1] = "."
end
vim.list_extend(find_command, search_dirs)
end
elseif command == "find" then
if not hidden then
table.insert(find_command, { "-not", "-path", "*/.*" })
find_command = flatten(find_command)
end
if no_ignore ~= nil then
log.warn "The `no_ignore` key is not available for the `find` command in `find_files`."
end
if no_ignore_parent ~= nil then
log.warn "The `no_ignore_parent` key is not available for the `find` command in `find_files`."
end
if follow then
table.insert(find_command, 2, "-L")
end
if search_file then
table.insert(find_command, "-name")
table.insert(find_command, "*" .. search_file .. "*")
end
if search_dirs then
table.remove(find_command, 2)
for _, v in pairs(search_dirs) do
table.insert(find_command, 2, v)
end
end
elseif command == "where" then
if hidden ~= nil then
log.warn "The `hidden` key is not available for the Windows `where` command in `find_files`."
end
if no_ignore ~= nil then
log.warn "The `no_ignore` key is not available for the Windows `where` command in `find_files`."
end
if no_ignore_parent ~= nil then
log.warn "The `no_ignore_parent` key is not available for the Windows `where` command in `find_files`."
end
if follow ~= nil then
log.warn "The `follow` key is not available for the Windows `where` command in `find_files`."
end
if search_dirs ~= nil then
log.warn "The `search_dirs` key is not available for the Windows `where` command in `find_files`."
end
if search_file ~= nil then
log.warn "The `search_file` key is not available for the Windows `where` command in `find_files`."
end
end
if opts.cwd then
opts.cwd = vim.fn.expand(opts.cwd)
end
opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts)
pickers
.new(opts, {
prompt_title = "Find Files",
finder = finders.new_oneshot_job(find_command, opts),
previewer = conf.file_previewer(opts),
sorter = conf.file_sorter(opts),
})
:find()
end
local function prepare_match(entry, kind)
local entries = {}
if entry.node then
table.insert(entries, entry)
else
for name, item in pairs(entry) do
vim.list_extend(entries, prepare_match(item, name))
end
end
return entries
end
-- TODO: finish docs for opts.show_line
files.treesitter = function(opts)
opts.show_line = vim.F.if_nil(opts.show_line, true)
local has_nvim_treesitter, _ = pcall(require, "nvim-treesitter")
if not has_nvim_treesitter then
utils.notify("builtin.treesitter", {
msg = "User need to install nvim-treesitter needs to be installed",
level = "ERROR",
})
return
end
local parsers = require "nvim-treesitter.parsers"
if not parsers.has_parser(parsers.get_buf_lang(opts.bufnr)) then
utils.notify("builtin.treesitter", {
msg = "No parser for the current buffer",
level = "ERROR",
})
return
end
local ts_locals = require "nvim-treesitter.locals"
local results = {}
for _, definition in ipairs(ts_locals.get_definitions(opts.bufnr)) do
local entries = prepare_match(ts_locals.get_local_nodes(definition))
for _, entry in ipairs(entries) do
entry.kind = vim.F.if_nil(entry.kind, "")
table.insert(results, entry)
end
end
if vim.tbl_isempty(results) then
return
end
pickers
.new(opts, {
prompt_title = "Treesitter Symbols",
finder = finders.new_table {
results = results,
entry_maker = opts.entry_maker or make_entry.gen_from_treesitter(opts),
},
previewer = conf.grep_previewer(opts),
sorter = conf.prefilter_sorter {
tag = "kind",
sorter = conf.generic_sorter(opts),
},
})
:find()
end
files.current_buffer_fuzzy_find = function(opts)
-- All actions are on the current buffer
local filename = vim.fn.expand(vim.api.nvim_buf_get_name(opts.bufnr))
local filetype = vim.api.nvim_buf_get_option(opts.bufnr, "filetype")
local lines = vim.api.nvim_buf_get_lines(opts.bufnr, 0, -1, false)
local lines_with_numbers = {}
for lnum, line in ipairs(lines) do
table.insert(lines_with_numbers, {
lnum = lnum,
bufnr = opts.bufnr,
filename = filename,
text = line,
})
end
local ts_ok, ts_parsers = pcall(require, "nvim-treesitter.parsers")
if ts_ok then
filetype = ts_parsers.ft_to_lang(filetype)
end
local _, ts_configs = pcall(require, "nvim-treesitter.configs")
local parser_ok, parser = pcall(vim.treesitter.get_parser, opts.bufnr, filetype)
local get_query = vim.treesitter.query.get or vim.treesitter.get_query
local query_ok, query = pcall(get_query, filetype, "highlights")
if parser_ok and query_ok and ts_ok and ts_configs.is_enabled("highlight", filetype, opts.bufnr) then
local root = parser:parse()[1]:root()
local line_highlights = setmetatable({}, {
__index = function(t, k)
local obj = {}
rawset(t, k, obj)
return obj
end,
})
-- update to changes on Neovim master, see https://github.com/neovim/neovim/pull/19931
-- TODO(clason): remove when dropping support for Neovim 0.7
local get_hl_from_capture = (function()
if vim.fn.has "nvim-0.8" == 1 then
return function(q, id)
return "@" .. q.captures[id]
end
else
local highlighter = vim.treesitter.highlighter.new(parser)
local highlighter_query = highlighter:get_query(filetype)
return function(_, id)
return highlighter_query:_get_hl_from_capture(id)
end
end
end)()
for id, node in query:iter_captures(root, opts.bufnr, 0, -1) do
local hl = get_hl_from_capture(query, id)
if hl and type(hl) ~= "number" then
local row1, col1, row2, col2 = node:range()
if row1 == row2 then
local row = row1 + 1
for index = col1, col2 do
line_highlights[row][index] = hl
end
else
local row = row1 + 1
for index = col1, #lines[row] do
line_highlights[row][index] = hl
end
while row < row2 + 1 do
row = row + 1
for index = 0, #(lines[row] or {}) do
line_highlights[row][index] = hl
end
end
end
end
end
opts.line_highlights = line_highlights
end
pickers
.new(opts, {
prompt_title = "Current Buffer Fuzzy",
finder = finders.new_table {
results = lines_with_numbers,
entry_maker = opts.entry_maker or make_entry.gen_from_buffer_lines(opts),
},
sorter = conf.generic_sorter(opts),
previewer = conf.grep_previewer(opts),
attach_mappings = function()
action_set.select:enhance {
post = function()
local selection = action_state.get_selected_entry()
vim.api.nvim_win_set_cursor(0, { selection.lnum, 0 })
end,
}
return true
end,
push_cursor_on_edit = true,
})
:find()
end
files.tags = function(opts)
local tagfiles = opts.ctags_file and { opts.ctags_file } or vim.fn.tagfiles()
for i, ctags_file in ipairs(tagfiles) do
tagfiles[i] = vim.fn.expand(ctags_file, true)
end
if vim.tbl_isempty(tagfiles) then
utils.notify("builtin.tags", {
msg = "No tags file found. Create one with ctags -R",
level = "ERROR",
})
return
end
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_ctags(opts))
pickers
.new(opts, {
prompt_title = "Tags",
finder = finders.new_oneshot_job(flatten { "cat", tagfiles }, opts),
previewer = previewers.ctags.new(opts),
sorter = conf.generic_sorter(opts),
attach_mappings = function()
action_set.select:enhance {
post = function()
local selection = action_state.get_selected_entry()
if not selection then
return
end
if selection.scode then
-- un-escape / then escape required
-- special chars for vim.fn.search()
-- ] ~ *
local scode = selection.scode:gsub([[\/]], "/"):gsub("[%]~*]", function(x)
return "\\" .. x
end)
vim.cmd "norm! gg"
vim.fn.search(scode)
vim.cmd "norm! zz"
else
vim.api.nvim_win_set_cursor(0, { selection.lnum, 0 })
end
end,
}
return true
end,
})
:find()
end
files.current_buffer_tags = function(opts)
return files.tags(vim.tbl_extend("force", {
prompt_title = "Current Buffer Tags",
only_current_file = true,
path_display = "hidden",
}, opts))
end
local function apply_checks(mod)
for k, v in pairs(mod) do
mod[k] = function(opts)
opts = opts or {}
v(opts)
end
end
return mod
end
return apply_checks(files)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,398 @@
local channel = require("plenary.async.control").channel
local actions = require "telescope.actions"
local sorters = require "telescope.sorters"
local conf = require("telescope.config").values
local finders = require "telescope.finders"
local make_entry = require "telescope.make_entry"
local pickers = require "telescope.pickers"
local utils = require "telescope.utils"
local lsp = {}
lsp.references = function(opts)
local filepath = vim.api.nvim_buf_get_name(opts.bufnr)
local lnum = vim.api.nvim_win_get_cursor(opts.winnr)[1]
local params = vim.lsp.util.make_position_params(opts.winnr)
local include_current_line = vim.F.if_nil(opts.include_current_line, false)
params.context = { includeDeclaration = vim.F.if_nil(opts.include_declaration, true) }
vim.lsp.buf_request(opts.bufnr, "textDocument/references", params, function(err, result, ctx, _)
if err then
vim.api.nvim_err_writeln("Error when finding references: " .. err.message)
return
end
local locations = {}
if result then
local results = vim.lsp.util.locations_to_items(result, vim.lsp.get_client_by_id(ctx.client_id).offset_encoding)
if include_current_line then
locations = vim.tbl_filter(function(v)
-- Remove current line from result
return not (v.filename == filepath and v.lnum == lnum)
end, vim.F.if_nil(results, {}))
else
locations = vim.F.if_nil(results, {})
end
end
if vim.tbl_isempty(locations) then
return
end
pickers
.new(opts, {
prompt_title = "LSP References",
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_quickfix(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.generic_sorter(opts),
push_cursor_on_edit = true,
push_tagstack_on_edit = true,
})
:find()
end)
end
local function call_hierarchy(opts, method, title, direction, item)
vim.lsp.buf_request(opts.bufnr, method, { item = item }, function(err, result)
if err then
vim.api.nvim_err_writeln("Error handling " .. title .. ": " .. err.message)
return
end
if not result or vim.tbl_isempty(result) then
return
end
local locations = {}
for _, ch_call in pairs(result) do
local ch_item = ch_call[direction]
for _, range in pairs(ch_call.fromRanges) do
table.insert(locations, {
filename = vim.uri_to_fname(ch_item.uri),
text = ch_item.name,
lnum = range.start.line + 1,
col = range.start.character + 1,
})
end
end
pickers
.new(opts, {
prompt_title = title,
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_quickfix(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.generic_sorter(opts),
push_cursor_on_edit = true,
push_tagstack_on_edit = true,
})
:find()
end)
end
local function pick_call_hierarchy_item(call_hierarchy_items)
if not call_hierarchy_items then
return
end
if #call_hierarchy_items == 1 then
return call_hierarchy_items[1]
end
local items = {}
for i, item in pairs(call_hierarchy_items) do
local entry = item.detail or item.name
table.insert(items, string.format("%d. %s", i, entry))
end
local choice = vim.fn.inputlist(items)
if choice < 1 or choice > #items then
return
end
return choice
end
local function calls(opts, direction)
local params = vim.lsp.util.make_position_params()
vim.lsp.buf_request(opts.bufnr, "textDocument/prepareCallHierarchy", params, function(err, result)
if err then
vim.api.nvim_err_writeln("Error when preparing call hierarchy: " .. err)
return
end
local call_hierarchy_item = pick_call_hierarchy_item(result)
if not call_hierarchy_item then
return
end
if direction == "from" then
call_hierarchy(opts, "callHierarchy/incomingCalls", "LSP Incoming Calls", direction, call_hierarchy_item)
else
call_hierarchy(opts, "callHierarchy/outgoingCalls", "LSP Outgoing Calls", direction, call_hierarchy_item)
end
end)
end
lsp.incoming_calls = function(opts)
calls(opts, "from")
end
lsp.outgoing_calls = function(opts)
calls(opts, "to")
end
local function list_or_jump(action, title, opts)
local params = vim.lsp.util.make_position_params(opts.winnr)
vim.lsp.buf_request(opts.bufnr, action, params, function(err, result, ctx, _)
if err then
vim.api.nvim_err_writeln("Error when executing " .. action .. " : " .. err.message)
return
end
local flattened_results = {}
if result then
-- textDocument/definition can return Location or Location[]
if not vim.tbl_islist(result) then
flattened_results = { result }
end
vim.list_extend(flattened_results, result)
end
local offset_encoding = vim.lsp.get_client_by_id(ctx.client_id).offset_encoding
if #flattened_results == 0 then
return
elseif #flattened_results == 1 and opts.jump_type ~= "never" then
local uri = params.textDocument.uri
if uri ~= flattened_results[1].uri and uri ~= flattened_results[1].targetUri then
if opts.jump_type == "tab" then
vim.cmd "tabedit"
elseif opts.jump_type == "split" then
vim.cmd "new"
elseif opts.jump_type == "vsplit" then
vim.cmd "vnew"
end
end
vim.lsp.util.jump_to_location(flattened_results[1], offset_encoding)
else
local locations = vim.lsp.util.locations_to_items(flattened_results, offset_encoding)
pickers
.new(opts, {
prompt_title = title,
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_quickfix(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.generic_sorter(opts),
push_cursor_on_edit = true,
push_tagstack_on_edit = true,
})
:find()
end
end)
end
lsp.definitions = function(opts)
return list_or_jump("textDocument/definition", "LSP Definitions", opts)
end
lsp.type_definitions = function(opts)
return list_or_jump("textDocument/typeDefinition", "LSP Type Definitions", opts)
end
lsp.implementations = function(opts)
return list_or_jump("textDocument/implementation", "LSP Implementations", opts)
end
lsp.document_symbols = function(opts)
local params = vim.lsp.util.make_position_params(opts.winnr)
vim.lsp.buf_request(opts.bufnr, "textDocument/documentSymbol", params, function(err, result, _, _)
if err then
vim.api.nvim_err_writeln("Error when finding document symbols: " .. err.message)
return
end
if not result or vim.tbl_isempty(result) then
utils.notify("builtin.lsp_document_symbols", {
msg = "No results from textDocument/documentSymbol",
level = "INFO",
})
return
end
local locations = vim.lsp.util.symbols_to_items(result or {}, opts.bufnr) or {}
locations = utils.filter_symbols(locations, opts)
if locations == nil then
-- error message already printed in `utils.filter_symbols`
return
end
if vim.tbl_isempty(locations) then
utils.notify("builtin.lsp_document_symbols", {
msg = "No document_symbol locations found",
level = "INFO",
})
return
end
opts.path_display = { "hidden" }
pickers
.new(opts, {
prompt_title = "LSP Document Symbols",
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.prefilter_sorter {
tag = "symbol_type",
sorter = conf.generic_sorter(opts),
},
push_cursor_on_edit = true,
push_tagstack_on_edit = true,
})
:find()
end)
end
lsp.workspace_symbols = function(opts)
local params = { query = opts.query or "" }
vim.lsp.buf_request(opts.bufnr, "workspace/symbol", params, function(err, server_result, _, _)
if err then
vim.api.nvim_err_writeln("Error when finding workspace symbols: " .. err.message)
return
end
local locations = vim.lsp.util.symbols_to_items(server_result or {}, opts.bufnr) or {}
locations = utils.filter_symbols(locations, opts)
if locations == nil then
-- error message already printed in `utils.filter_symbols`
return
end
if vim.tbl_isempty(locations) then
utils.notify("builtin.lsp_workspace_symbols", {
msg = "No results from workspace/symbol. Maybe try a different query: "
.. "'Telescope lsp_workspace_symbols query=example'",
level = "INFO",
})
return
end
opts.ignore_filename = vim.F.if_nil(opts.ignore_filename, false)
pickers
.new(opts, {
prompt_title = "LSP Workspace Symbols",
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.prefilter_sorter {
tag = "symbol_type",
sorter = conf.generic_sorter(opts),
},
})
:find()
end)
end
local function get_workspace_symbols_requester(bufnr, opts)
local cancel = function() end
return function(prompt)
local tx, rx = channel.oneshot()
cancel()
_, cancel = vim.lsp.buf_request(bufnr, "workspace/symbol", { query = prompt }, tx)
-- Handle 0.5 / 0.5.1 handler situation
local err, res = rx()
assert(not err, err)
local locations = vim.lsp.util.symbols_to_items(res or {}, bufnr) or {}
if not vim.tbl_isempty(locations) then
locations = utils.filter_symbols(locations, opts) or {}
end
return locations
end
end
lsp.dynamic_workspace_symbols = function(opts)
pickers
.new(opts, {
prompt_title = "LSP Dynamic Workspace Symbols",
finder = finders.new_dynamic {
entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts),
fn = get_workspace_symbols_requester(opts.bufnr, opts),
},
previewer = conf.qflist_previewer(opts),
sorter = sorters.highlighter_only(opts),
attach_mappings = function(_, map)
map("i", "<c-space>", actions.to_fuzzy_refine)
return true
end,
})
:find()
end
local function check_capabilities(feature, bufnr)
local clients = vim.lsp.buf_get_clients(bufnr)
local supported_client = false
for _, client in pairs(clients) do
supported_client = client.server_capabilities[feature]
if supported_client then
break
end
end
if supported_client then
return true
else
if #clients == 0 then
utils.notify("builtin.lsp_*", {
msg = "no client attached",
level = "INFO",
})
else
utils.notify("builtin.lsp_*", {
msg = "server does not support " .. feature,
level = "INFO",
})
end
return false
end
end
local feature_map = {
["document_symbols"] = "documentSymbolProvider",
["references"] = "referencesProvider",
["definitions"] = "definitionProvider",
["type_definitions"] = "typeDefinitionProvider",
["implementations"] = "implementationProvider",
["workspace_symbols"] = "workspaceSymbolProvider",
["incoming_calls"] = "callHierarchyProvider",
["outgoing_calls"] = "callHierarchyProvider",
}
local function apply_checks(mod)
for k, v in pairs(mod) do
mod[k] = function(opts)
opts = opts or {}
local feature_name = feature_map[k]
if feature_name and not check_capabilities(feature_name, opts.bufnr) then
return
end
v(opts)
end
end
return mod
end
return apply_checks(lsp)

View File

@ -0,0 +1,542 @@
---@tag telescope.builtin
---@config { ['field_heading'] = "Options", ["module"] = "telescope.builtin" }
---@brief [[
--- Telescope Builtins is a collection of community maintained pickers to support common workflows. It can be used as
--- reference when writing PRs, Telescope extensions, your own custom pickers, or just as a discovery tool for all of
--- the amazing pickers already shipped with Telescope!
---
--- Any of these functions can just be called directly by doing:
---
--- :lua require('telescope.builtin').$NAME_OF_PICKER()
---
--- To use any of Telescope's default options or any picker-specific options, call your desired picker by passing a lua
--- table to the picker with all of the options you want to use. Here's an example with the live_grep picker:
---
--- <code>
--- :lua require('telescope.builtin').live_grep({
--- prompt_title = 'find string in open buffers...',
--- grep_open_files = true
--- })
--- -- or with dropdown theme
--- :lua require('telescope.builtin').find_files(require('telescope.themes').get_dropdown{
--- previewer = false
--- })
--- </code>
---@brief ]]
local builtin = {}
-- Ref: https://github.com/tjdevries/lazy.nvim
local function require_on_exported_call(mod)
return setmetatable({}, {
__index = function(_, picker)
return function(...)
return require(mod)[picker](...)
end
end,
})
end
--
--
-- File-related Pickers
--
--
--- Search for a string and get results live as you type, respects .gitignore
---@param opts table: options to pass to the picker
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
---@field grep_open_files boolean: if true, restrict search to open files only, mutually exclusive with `search_dirs`
---@field search_dirs table: directory/directories/files to search, mutually exclusive with `grep_open_files`
---@field glob_pattern string|table: argument to be used with `--glob`, e.g. "*.toml", can use the opposite "!*.toml"
---@field type_filter string: argument to be used with `--type`, e.g. "rust", see `rg --type-list`
---@field additional_args function|table: additional arguments to be passed on. Can be fn(opts) -> tbl
---@field max_results number: define a upper result value
---@field disable_coordinates boolean: don't show the line & row numbers (default: false)
---@field file_encoding string: file encoding for the entry & previewer
builtin.live_grep = require_on_exported_call("telescope.builtin.__files").live_grep
--- Searches for the string under your cursor in your current working directory
---@param opts table: options to pass to the picker
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
---@field search string: the query to search
---@field grep_open_files boolean: if true, restrict search to open files only, mutually exclusive with `search_dirs`
---@field search_dirs table: directory/directories/files to search, mutually exclusive with `grep_open_files`
---@field use_regex boolean: if true, special characters won't be escaped, allows for using regex (default: false)
---@field word_match string: can be set to `-w` to enable exact word matches
---@field additional_args function|table: additional arguments to be passed on. Can be fn(opts) -> tbl
---@field disable_coordinates boolean: don't show the line and row numbers (default: false)
---@field only_sort_text boolean: only sort the text, not the file, line or row (default: false)
---@field file_encoding string: file encoding for the entry & previewer
builtin.grep_string = require_on_exported_call("telescope.builtin.__files").grep_string
--- Search for files (respecting .gitignore)
---@param opts table: options to pass to the picker
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
---@field find_command function|table: cmd to use for the search. Can be a fn(opts) -> tbl (default: autodetect)
---@field follow boolean: if true, follows symlinks (i.e. uses `-L` flag for the `find` command)
---@field hidden boolean: determines whether to show hidden files or not (default: false)
---@field no_ignore boolean: show files ignored by .gitignore, .ignore, etc. (default: false)
---@field no_ignore_parent boolean: show files ignored by .gitignore, .ignore, etc. in parent dirs. (default: false)
---@field search_dirs table: directory/directories/files to search
---@field search_file string: specify a filename to search for
---@field file_encoding string: file encoding for the previewer
builtin.find_files = require_on_exported_call("telescope.builtin.__files").find_files
--- This is an alias for the `find_files` picker
builtin.fd = builtin.find_files
--- Lists function names, variables, and other symbols from treesitter queries
--- - Default keymaps:
--- - `<C-l>`: show autocompletion menu to prefilter your query by kind of ts node you want to see (i.e. `:var:`)
---@field show_line boolean: if true, shows the row:column that the result is found at (default: true)
---@field bufnr number: specify the buffer number where treesitter should run. (default: current buffer)
---@field symbol_highlights table: string -> string. Matches symbol with hl_group
---@field file_encoding string: file encoding for the previewer
builtin.treesitter = require_on_exported_call("telescope.builtin.__files").treesitter
--- Live fuzzy search inside of the currently open buffer
---@param opts table: options to pass to the picker
---@field skip_empty_lines boolean: if true we don't display empty lines (default: false)
---@field file_encoding string: file encoding for the previewer
builtin.current_buffer_fuzzy_find = require_on_exported_call("telescope.builtin.__files").current_buffer_fuzzy_find
--- Lists tags in current directory with tag location file preview (users are required to run ctags -R to generate tags
--- or update when introducing new changes)
---@param opts table: options to pass to the picker
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
---@field ctags_file string: specify a particular ctags file to use
---@field show_line boolean: if true, shows the content of the line the tag is found on in the picker (default: true)
---@field only_sort_tags boolean: if true we will only sort tags (default: false)
---@field fname_width number: defines the width of the filename section (default: 30)
builtin.tags = require_on_exported_call("telescope.builtin.__files").tags
--- Lists all of the tags for the currently open buffer, with a preview
---@param opts table: options to pass to the picker
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
---@field ctags_file string: specify a particular ctags file to use
---@field show_line boolean: if true, shows the content of the line the tag is found on in the picker (default: true)
---@field only_sort_tags boolean: if true we will only sort tags (default: false)
---@field fname_width number: defines the width of the filename section (default: 30)
builtin.current_buffer_tags = require_on_exported_call("telescope.builtin.__files").current_buffer_tags
--
--
-- Git-related Pickers
--
--
--- Fuzzy search for files tracked by Git. This command lists the output of the `git ls-files` command,
--- respects .gitignore
--- - Default keymaps:
--- - `<cr>`: opens the currently selected file
---@param opts table: options to pass to the picker
---@field cwd string: specify the path of the repo
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
---@field show_untracked boolean: if true, adds `--others` flag to command and shows untracked files (default: false)
---@field recurse_submodules boolean: if true, adds the `--recurse-submodules` flag to command (default: false)
---@field git_command table: command that will be executed. {"git","ls-files","--exclude-standard","--cached"}
---@field file_encoding string: file encoding for the previewer
builtin.git_files = require_on_exported_call("telescope.builtin.__git").files
--- Lists commits for current directory with diff preview
--- - Default keymaps:
--- - `<cr>`: checks out the currently selected commit
--- - `<C-r>m`: resets current branch to selected commit using mixed mode
--- - `<C-r>s`: resets current branch to selected commit using soft mode
--- - `<C-r>h`: resets current branch to selected commit using hard mode
---@param opts table: options to pass to the picker
---@field cwd string: specify the path of the repo
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
---@field git_command table: command that will be executed. {"git","log","--pretty=oneline","--abbrev-commit","--","."}
builtin.git_commits = require_on_exported_call("telescope.builtin.__git").commits
--- Lists commits for current buffer with diff preview
--- - Default keymaps or your overridden `select_` keys:
--- - `<cr>`: checks out the currently selected commit
--- - `<c-v>`: opens a diff in a vertical split
--- - `<c-x>`: opens a diff in a horizontal split
--- - `<c-t>`: opens a diff in a new tab
---@param opts table: options to pass to the picker
---@field cwd string: specify the path of the repo
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
---@field current_file string: specify the current file that should be used for bcommits (default: current buffer)
---@field git_command table: command that will be executed. {"git","log","--pretty=oneline","--abbrev-commit"}
builtin.git_bcommits = require_on_exported_call("telescope.builtin.__git").bcommits
--- List branches for current directory, with output from `git log --oneline` shown in the preview window
--- - Default keymaps:
--- - `<cr>`: checks out the currently selected branch
--- - `<C-t>`: tracks currently selected branch
--- - `<C-r>`: rebases currently selected branch
--- - `<C-a>`: creates a new branch, with confirmation prompt before creation
--- - `<C-d>`: deletes the currently selected branch, with confirmation prompt before deletion
--- - `<C-y>`: merges the currently selected branch, with confirmation prompt before deletion
---@param opts table: options to pass to the picker
---@field cwd string: specify the path of the repo
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
---@field pattern string: specify the pattern to match all refs
builtin.git_branches = require_on_exported_call("telescope.builtin.__git").branches
--- Lists git status for current directory
--- - Default keymaps:
--- - `<Tab>`: stages or unstages the currently selected file
--- - `<cr>`: opens the currently selected file
---@param opts table: options to pass to the picker
---@field cwd string: specify the path of the repo
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
---@field git_icons table: string -> string. Matches name with icon (see source code, make_entry.lua git_icon_defaults)
builtin.git_status = require_on_exported_call("telescope.builtin.__git").status
--- Lists stash items in current repository
--- - Default keymaps:
--- - `<cr>`: runs `git apply` for currently selected stash
---@param opts table: options to pass to the picker
---@field cwd string: specify the path of the repo
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
---@field show_branch boolean: if we should display the branch name for git stash entries (default: true)
builtin.git_stash = require_on_exported_call("telescope.builtin.__git").stash
--
--
-- Internal and Vim-related Pickers
--
--
--- Lists all of the community maintained pickers built into Telescope
---@param opts table: options to pass to the picker
---@field include_extensions boolean: if true will show the pickers of the installed extensions (default: false)
---@field use_default_opts boolean: if the selected picker should use its default options (default: false)
builtin.builtin = require_on_exported_call("telescope.builtin.__internal").builtin
--- Opens the previous picker in the identical state (incl. multi selections)
--- - Notes:
--- - Requires `cache_picker` in setup or when having invoked pickers, see |telescope.defaults.cache_picker|
---@param opts table: options to pass to the picker
---@field cache_index number: what picker to resume, where 1 denotes most recent (default: 1)
builtin.resume = require_on_exported_call("telescope.builtin.__internal").resume
--- Opens a picker over previously cached pickers in their preserved states (incl. multi selections)
--- - Default keymaps:
--- - `<C-x>`: delete the selected cached picker
--- - Notes:
--- - Requires `cache_picker` in setup or when having invoked pickers, see |telescope.defaults.cache_picker|
---@param opts table: options to pass to the picker
builtin.pickers = require_on_exported_call("telescope.builtin.__internal").pickers
--- Use the telescope...
---@param opts table: options to pass to the picker
---@field show_pluto boolean: we love Pluto (default: false, because its a hidden feature)
---@field show_moon boolean: we love the Moon (default: false, because its a hidden feature)
builtin.planets = require_on_exported_call("telescope.builtin.__internal").planets
--- Lists symbols inside of `data/telescope-sources/*.json` found in your runtime path
--- or found in `stdpath("data")/telescope/symbols/*.json`. The second path can be customized.
--- We provide a couple of default symbols which can be found in
--- https://github.com/nvim-telescope/telescope-symbols.nvim. This repos README also provides more
--- information about the format in which the symbols have to be.
---@param opts table: options to pass to the picker
---@field symbol_path string: specify the second path. Default: `stdpath("data")/telescope/symbols/*.json`
---@field sources table: specify a table of sources you want to load this time
builtin.symbols = require_on_exported_call("telescope.builtin.__internal").symbols
--- Lists available plugin/user commands and runs them on `<cr>`
---@param opts table: options to pass to the picker
---@field show_buf_command boolean: show buf local command (Default: true)
builtin.commands = require_on_exported_call("telescope.builtin.__internal").commands
--- Lists items in the quickfix list, jumps to location on `<cr>`
---@param opts table: options to pass to the picker
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field fname_width number: defines the width of the filename section (default: 30)
---@field nr number: specify the quickfix list number
builtin.quickfix = require_on_exported_call("telescope.builtin.__internal").quickfix
--- Lists all quickfix lists in your history and open them with `builtin.quickfix`. It seems that neovim
--- only keeps the full history for 10 lists
---@param opts table: options to pass to the picker
builtin.quickfixhistory = require_on_exported_call("telescope.builtin.__internal").quickfixhistory
--- Lists items from the current window's location list, jumps to location on `<cr>`
---@param opts table: options to pass to the picker
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field fname_width number: defines the width of the filename section (default: 30)
builtin.loclist = require_on_exported_call("telescope.builtin.__internal").loclist
--- Lists previously open files, opens on `<cr>`
---@param opts table: options to pass to the picker
---@field only_cwd boolean: show only files in the cwd (default: false)
---@field cwd_only boolean: alias for only_cwd
---@field file_encoding string: file encoding for the previewer
builtin.oldfiles = require_on_exported_call("telescope.builtin.__internal").oldfiles
--- Lists commands that were executed recently, and reruns them on `<cr>`
--- - Default keymaps:
--- - `<C-e>`: open the command line with the text of the currently selected result populated in it
---@param opts table: options to pass to the picker
builtin.command_history = require_on_exported_call("telescope.builtin.__internal").command_history
--- Lists searches that were executed recently, and reruns them on `<cr>`
--- - Default keymaps:
--- - `<C-e>`: open a search window with the text of the currently selected search result populated in it
---@param opts table: options to pass to the picker
builtin.search_history = require_on_exported_call("telescope.builtin.__internal").search_history
--- Lists vim options, allows you to edit the current value on `<cr>`
---@param opts table: options to pass to the picker
builtin.vim_options = require_on_exported_call("telescope.builtin.__internal").vim_options
--- Lists available help tags and opens a new window with the relevant help info on `<cr>`
---@param opts table: options to pass to the picker
---@field lang string: specify language (default: vim.o.helplang)
---@field fallback boolean: fallback to en if language isn't installed (default: true)
builtin.help_tags = require_on_exported_call("telescope.builtin.__internal").help_tags
--- Lists manpage entries, opens them in a help window on `<cr>`
---@param opts table: options to pass to the picker
---@field sections table: a list of sections to search, use `{ "ALL" }` to search in all sections (default: { "1" })
---@field man_cmd function: that returns the man command. (Default: `apropos ""` on linux, `apropos " "` on macos)
builtin.man_pages = require_on_exported_call("telescope.builtin.__internal").man_pages
--- Lists lua modules and reloads them on `<cr>`
---@param opts table: options to pass to the picker
---@field column_len number: define the max column len for the module name (default: dynamic, longest module name)
builtin.reloader = require_on_exported_call("telescope.builtin.__internal").reloader
--- Lists open buffers in current neovim instance, opens selected buffer on `<cr>`
---@param opts table: options to pass to the picker
---@field show_all_buffers boolean: if true, show all buffers, including unloaded buffers (default: true)
---@field ignore_current_buffer boolean: if true, don't show the current buffer in the list (default: false)
---@field only_cwd boolean: if true, only show buffers in the current working directory (default: false)
---@field cwd_only boolean: alias for only_cwd
---@field sort_lastused boolean: Sorts current and last buffer to the top and selects the lastused (default: false)
---@field sort_mru boolean: Sorts all buffers after most recent used. Not just the current and last one (default: false)
---@field bufnr_width number: Defines the width of the buffer numbers in front of the filenames (default: dynamic)
---@field file_encoding string: file encoding for the previewer
builtin.buffers = require_on_exported_call("telescope.builtin.__internal").buffers
--- Lists available colorschemes and applies them on `<cr>`
---@param opts table: options to pass to the picker
---@field enable_preview boolean: if true, will preview the selected color
builtin.colorscheme = require_on_exported_call("telescope.builtin.__internal").colorscheme
--- Lists vim marks and their value, jumps to the mark on `<cr>`
---@param opts table: options to pass to the picker
---@field file_encoding string: file encoding for the previewer
builtin.marks = require_on_exported_call("telescope.builtin.__internal").marks
--- Lists vim registers, pastes the contents of the register on `<cr>`
--- - Default keymaps:
--- - `<C-e>`: edit the contents of the currently selected register
---@param opts table: options to pass to the picker
builtin.registers = require_on_exported_call("telescope.builtin.__internal").registers
--- Lists normal mode keymappings, runs the selected keymap on `<cr>`
---@param opts table: options to pass to the picker
---@field modes table: a list of short-named keymap modes to search (default: { "n", "i", "c", "x" })
---@field show_plug boolean: if true, the keymaps for which the lhs contains "<Plug>" are also shown (default: true)
builtin.keymaps = require_on_exported_call("telescope.builtin.__internal").keymaps
--- Lists all available filetypes, sets currently open buffer's filetype to selected filetype in Telescope on `<cr>`
---@param opts table: options to pass to the picker
builtin.filetypes = require_on_exported_call("telescope.builtin.__internal").filetypes
--- Lists all available highlights
---@param opts table: options to pass to the picker
builtin.highlights = require_on_exported_call("telescope.builtin.__internal").highlights
--- Lists vim autocommands and goes to their declaration on `<cr>`
---@param opts table: options to pass to the picker
builtin.autocommands = require_on_exported_call("telescope.builtin.__internal").autocommands
--- Lists spelling suggestions for the current word under the cursor, replaces word with selected suggestion on `<cr>`
---@param opts table: options to pass to the picker
builtin.spell_suggest = require_on_exported_call("telescope.builtin.__internal").spell_suggest
--- Lists the tag stack for the current window, jumps to tag on `<cr>`
---@param opts table: options to pass to the picker
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field fname_width number: defines the width of the filename section (default: 30)
builtin.tagstack = require_on_exported_call("telescope.builtin.__internal").tagstack
--- Lists items from Vim's jumplist, jumps to location on `<cr>`
---@param opts table: options to pass to the picker
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field fname_width number: defines the width of the filename section (default: 30)
builtin.jumplist = require_on_exported_call("telescope.builtin.__internal").jumplist
--
--
-- LSP-related Pickers
--
--
--- Lists LSP references for word under the cursor, jumps to reference on `<cr>`
---@param opts table: options to pass to the picker
---@field include_declaration boolean: include symbol declaration in the lsp references (default: true)
---@field include_current_line boolean: include current line (default: false)
---@field fname_width number: defines the width of the filename section (default: 30)
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field file_encoding string: file encoding for the previewer
builtin.lsp_references = require_on_exported_call("telescope.builtin.__lsp").references
--- Lists LSP incoming calls for word under the cursor, jumps to reference on `<cr>`
---@param opts table: options to pass to the picker
---@field fname_width number: defines the width of the filename section (default: 30)
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field file_encoding string: file encoding for the previewer
builtin.lsp_incoming_calls = require_on_exported_call("telescope.builtin.__lsp").incoming_calls
--- Lists LSP outgoing calls for word under the cursor, jumps to reference on `<cr>`
---@param opts table: options to pass to the picker
---@field fname_width number: defines the width of the filename section (default: 30)
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field file_encoding string: file encoding for the previewer
builtin.lsp_outgoing_calls = require_on_exported_call("telescope.builtin.__lsp").outgoing_calls
--- Goto the definition of the word under the cursor, if there's only one, otherwise show all options in Telescope
---@param opts table: options to pass to the picker
---@field jump_type string: how to goto definition if there is only one and the definition file is different from the current file, values: "tab", "split", "vsplit", "never"
---@field fname_width number: defines the width of the filename section (default: 30)
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field file_encoding string: file encoding for the previewer
builtin.lsp_definitions = require_on_exported_call("telescope.builtin.__lsp").definitions
--- Goto the definition of the type of the word under the cursor, if there's only one,
--- otherwise show all options in Telescope
---@param opts table: options to pass to the picker
---@field jump_type string: how to goto definition if there is only one and the definition file is different from the current file, values: "tab", "split", "vsplit", "never"
---@field fname_width number: defines the width of the filename section (default: 30)
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field file_encoding string: file encoding for the previewer
builtin.lsp_type_definitions = require("telescope.builtin.__lsp").type_definitions
--- Goto the implementation of the word under the cursor if there's only one, otherwise show all options in Telescope
---@param opts table: options to pass to the picker
---@field jump_type string: how to goto implementation if there is only one and the definition file is different from the current file, values: "tab", "split", "vsplit", "never"
---@field fname_width number: defines the width of the filename section (default: 30)
---@field show_line boolean: show results text (default: true)
---@field trim_text boolean: trim results text (default: false)
---@field file_encoding string: file encoding for the previewer
builtin.lsp_implementations = require_on_exported_call("telescope.builtin.__lsp").implementations
--- Lists LSP document symbols in the current buffer
--- - Default keymaps:
--- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`)
---@param opts table: options to pass to the picker
---@field fname_width number: defines the width of the filename section (default: 30)
---@field symbol_width number: defines the width of the symbol section (default: 25)
---@field symbol_type_width number: defines the width of the symbol type section (default: 8)
---@field show_line boolean: if true, shows the content of the line the tag is found on (default: false)
---@field symbols string|table: filter results by symbol kind(s)
---@field ignore_symbols string|table: list of symbols to ignore
---@field symbol_highlights table: string -> string. Matches symbol with hl_group
---@field file_encoding string: file encoding for the previewer
builtin.lsp_document_symbols = require_on_exported_call("telescope.builtin.__lsp").document_symbols
--- Lists LSP document symbols in the current workspace
--- - Default keymaps:
--- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`)
---@param opts table: options to pass to the picker
---@field query string: for what to query the workspace (default: "")
---@field fname_width number: defines the width of the filename section (default: 30)
---@field symbol_width number: defines the width of the symbol section (default: 25)
---@field symbol_type_width number: defines the width of the symbol type section (default: 8)
---@field show_line boolean: if true, shows the content of the line the tag is found on (default: false)
---@field symbols string|table: filter results by symbol kind(s)
---@field ignore_symbols string|table: list of symbols to ignore
---@field symbol_highlights table: string -> string. Matches symbol with hl_group
---@field file_encoding string: file encoding for the previewer
builtin.lsp_workspace_symbols = require_on_exported_call("telescope.builtin.__lsp").workspace_symbols
--- Dynamically lists LSP for all workspace symbols
--- - Default keymaps:
--- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`)
---@param opts table: options to pass to the picker
---@field fname_width number: defines the width of the filename section (default: 30)
---@field show_line boolean: if true, shows the content of the line the symbol is found on (default: false)
---@field symbols string|table: filter results by symbol kind(s)
---@field ignore_symbols string|table: list of symbols to ignore
---@field symbol_highlights table: string -> string. Matches symbol with hl_group
---@field file_encoding string: file encoding for the previewer
builtin.lsp_dynamic_workspace_symbols = require_on_exported_call("telescope.builtin.__lsp").dynamic_workspace_symbols
--
--
-- Diagnostics Pickers
--
--
--- Lists diagnostics
--- - Fields:
--- - `All severity flags can be passed as `string` or `number` as per `:vim.diagnostic.severity:`
--- - Default keymaps:
--- - `<C-l>`: show autocompletion menu to prefilter your query with the diagnostic you want to see (i.e. `:warning:`)
---@param opts table: options to pass to the picker
---@field bufnr number|nil: Buffer number to get diagnostics from. Use 0 for current buffer or nil for all buffers
---@field severity string|number: filter diagnostics by severity name (string) or id (number)
---@field severity_limit string|number: keep diagnostics equal or more severe wrt severity name (string) or id (number)
---@field severity_bound string|number: keep diagnostics equal or less severe wrt severity name (string) or id (number)
---@field root_dir string|boolean: if set to string, get diagnostics only for buffers under this dir otherwise cwd
---@field no_unlisted boolean: if true, get diagnostics only for listed buffers
---@field no_sign boolean: hide DiagnosticSigns from Results (default: false)
---@field line_width number: set length of diagnostic entry text in Results
---@field namespace number: limit your diagnostics to a specific namespace
builtin.diagnostics = require_on_exported_call("telescope.builtin.__diagnostics").get
local apply_config = function(mod)
for k, v in pairs(mod) do
mod[k] = function(opts)
local pickers_conf = require("telescope.config").pickers
opts = opts or {}
opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
opts.winnr = opts.winnr or vim.api.nvim_get_current_win()
local pconf = pickers_conf[k] or {}
local defaults = (function()
if pconf.theme then
return require("telescope.themes")["get_" .. pconf.theme](pconf)
end
return vim.deepcopy(pconf)
end)()
if pconf.mappings then
defaults.attach_mappings = function(_, map)
for mode, tbl in pairs(pconf.mappings) do
for key, action in pairs(tbl) do
map(mode, key, action)
end
end
return true
end
end
if pconf.attach_mappings and opts.attach_mappings then
local opts_attach = opts.attach_mappings
opts.attach_mappings = function(prompt_bufnr, map)
pconf.attach_mappings(prompt_bufnr, map)
return opts_attach(prompt_bufnr, map)
end
end
v(vim.tbl_extend("force", defaults, opts))
end
end
return mod
end
-- We can't do this in one statement because tree-sitter-lua docgen gets confused if we do
builtin = apply_config(builtin)
return builtin

View File

@ -0,0 +1,256 @@
---@tag telescope.command
---@config { ["module"] = "telescope.command" }
---@brief [[
---
--- Telescope commands can be called through two apis,
--- the lua api and the viml api.
---
--- The lua api is the more direct way to interact with Telescope, as you directly call the
--- lua functions that Telescope defines.
--- It can be called in a lua file using commands like:
--- <pre>
--- `require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
--- </pre>
--- If you want to use this api from a vim file you should prepend `lua` to the command, as below:
--- <pre>
--- `lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
--- </pre>
--- If you want to use this api from a neovim command line you should prepend `:lua` to
--- the command, as below:
--- <pre>
--- `:lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
--- </pre>
---
--- The viml api is more indirect, as first the command must be parsed to the relevant lua
--- equivalent, which brings some limitations.
--- The viml api can be called using commands like:
--- <pre>
--- `:Telescope find_files hidden=true layout_config={"prompt_position":"top"}`
--- </pre>
--- This involves setting options using an `=` and using viml syntax for lists and
--- dictionaries when the corresponding lua function requires a table.
---
--- One limitation of the viml api is that there can be no spaces in any of the options.
--- For example, if you want to use the `cwd` option for `find_files` to specify that you
--- only want to search within the folder `/foo bar/subfolder/` you could not do that using the
--- viml api, as the path name contains a space.
--- Similarly, you could NOT set the `prompt_position` to `"top"` using the following command:
--- <pre>
--- `:Telescope find_files layout_config={ "prompt_position" : "top" }`
--- </pre>
--- as there are spaces in the option.
---
---@brief ]]
local themes = require "telescope.themes"
local builtin = require "telescope.builtin"
local extensions = require("telescope._extensions").manager
local config = require "telescope.config"
local utils = require "telescope.utils"
local command = {}
local arg_value = {
["nil"] = nil,
['""'] = "",
['"'] = "",
}
local bool_type = {
["false"] = false,
["true"] = true,
}
local split_keywords = {
["find_command"] = true,
["vimgrep_arguments"] = true,
["sections"] = true,
["search_dirs"] = true,
["symbols"] = true,
["ignore_symbols"] = true,
}
-- convert command line string arguments to
-- lua number boolean type and nil value
command.convert_user_opts = function(user_opts)
local default_opts = config.values
local _switch = {
["boolean"] = function(key, val)
if val == "false" then
user_opts[key] = false
return
end
user_opts[key] = true
end,
["number"] = function(key, val)
user_opts[key] = tonumber(val)
end,
["string"] = function(key, val)
if arg_value[val] ~= nil then
user_opts[key] = arg_value[val]
return
end
if bool_type[val] ~= nil then
user_opts[key] = bool_type[val]
end
end,
["table"] = function(key, val)
local ok, eval = pcall(vim.fn.eval, val)
if ok then
user_opts[key] = eval
else
local err
eval, err = loadstring("return " .. val)
if err ~= nil then
-- discard invalid lua expression
user_opts[key] = nil
elseif eval ~= nil then
ok, eval = pcall(eval)
if ok and type(eval) == "table" then
-- allow if return a table only
user_opts[key] = eval
else
-- otherwise return nil (allows split check later)
user_opts[key] = nil
end
end
end
end,
}
local _switch_metatable = {
__index = function(_, k)
utils.notify("command", {
msg = string.format("Type of '%s' does not match", k),
level = "WARN",
})
end,
}
setmetatable(_switch, _switch_metatable)
for key, val in pairs(user_opts) do
if split_keywords[key] then
_switch["table"](key, val)
if user_opts[key] == nil then
user_opts[key] = vim.split(val, ",")
end
elseif default_opts[key] ~= nil then
_switch[type(default_opts[key])](key, val)
elseif tonumber(val) ~= nil then
_switch["number"](key, val)
else
_switch["string"](key, val)
end
end
end
-- receive the viml command args
-- it should be a table value like
-- {
-- cmd = 'find_files',
-- theme = 'dropdown',
-- extension_type = 'command'
-- opts = {
-- cwd = '***',
-- }
local function run_command(args)
local user_opts = args or {}
if next(user_opts) == nil and not user_opts.cmd then
utils.notify("command", {
msg = "Command missing arguments",
level = "ERROR",
})
return
end
local cmd = user_opts.cmd
local opts = user_opts.opts or {}
local extension_type = user_opts.extension_type or ""
local theme = user_opts.theme or ""
if next(opts) ~= nil then
command.convert_user_opts(opts)
end
if string.len(theme) > 0 then
local func = themes[theme] or themes["get_" .. theme]
opts = func(opts)
end
if string.len(extension_type) > 0 and extension_type ~= '"' then
extensions[cmd][extension_type](opts)
return
end
if builtin[cmd] then
builtin[cmd](opts)
return
end
if rawget(extensions, cmd) then
extensions[cmd][cmd](opts)
return
end
utils.notify("run_command", {
msg = "Unknown command",
level = "ERROR",
})
end
-- @Summary get extensions sub command
-- register extensions dap gh etc.
-- input in command line `Telescope gh <TAB>`
-- Returns a list for each extension.
function command.get_extensions_subcommand()
local exts = require("telescope._extensions").manager
local complete_ext_table = {}
for cmd, value in pairs(exts) do
if type(value) == "table" then
local subcmds = {}
for key, _ in pairs(value) do
table.insert(subcmds, key)
end
complete_ext_table[cmd] = subcmds
end
end
return complete_ext_table
end
function command.register_keyword(keyword)
split_keywords[keyword] = true
end
function command.load_command(cmd, ...)
local args = { ... }
if cmd == nil then
run_command { cmd = "builtin" }
return
end
local user_opts = {
cmd = cmd,
opts = {},
}
for _, arg in ipairs(args) do
if arg:find("=", 1) == nil then
user_opts["extension_type"] = arg
else
local param = vim.split(arg, "=")
local key = table.remove(param, 1)
param = table.concat(param, "=")
if key == "theme" then
user_opts["theme"] = param
else
user_opts.opts[key] = param
end
end
end
run_command(user_opts)
end
return command

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,948 @@
---@tag telescope.layout
---@config { ["module"] = "telescope.layout" }
---@brief [[
--- The layout of telescope pickers can be adjusted using the
--- |telescope.defaults.layout_strategy| and |telescope.defaults.layout_config| options.
--- For example, the following configuration changes the default layout strategy and the
--- default size of the picker:
--- <code>
--- require('telescope').setup{
--- defaults = {
--- layout_strategy = 'vertical',
--- layout_config = { height = 0.95 },
--- },
--- }
--- </code>
---
--- ────────────────────────────────────────────────────────────────────────────────
---
--- Layout strategies are different functions to position telescope.
---
--- All layout strategies are functions with the following signature:
---
--- <code>
--- function(picker, columns, lines, layout_config)
--- -- Do some calculations here...
--- return {
--- preview = preview_configuration
--- results = results_configuration,
--- prompt = prompt_configuration,
--- }
--- end
--- </code>
---
--- <pre>
--- Parameters: ~
--- - picker : A Picker object. (docs coming soon)
--- - columns : (number) Columns in the vim window
--- - lines : (number) Lines in the vim window
--- - layout_config : (table) The configuration values specific to the picker.
--- </pre>
---
--- This means you can create your own layout strategy if you want! Just be aware
--- for now that we may change some APIs or interfaces, so they may break if you create
--- your own.
---
--- A good method for creating your own would be to copy one of the strategies that most
--- resembles what you want from "./lua/telescope/pickers/layout_strategies.lua" in the
--- telescope repo.
---
---@brief ]]
local resolve = require "telescope.config.resolve"
local p_window = require "telescope.pickers.window"
local get_border_size = function(opts)
if opts.window.border == false then
return 0
end
return 1
end
local calc_tabline = function(max_lines)
local tbln = (vim.o.showtabline == 2) or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
if tbln then
max_lines = max_lines - 1
end
return max_lines, tbln
end
-- Helper function for capping over/undersized width/height, and calculating spacing
--@param cur_size number: size to be capped
--@param max_size any: the maximum size, e.g. max_lines or max_columns
--@param bs number: the size of the border
--@param w_num number: the maximum number of windows of the picker in the given direction
--@param b_num number: the number of border rows/column in the given direction (when border enabled)
--@param s_num number: the number of gaps in the given direction (when border disabled)
local calc_size_and_spacing = function(cur_size, max_size, bs, w_num, b_num, s_num)
local spacing = s_num * (1 - bs) + b_num * bs
cur_size = math.min(cur_size, max_size)
cur_size = math.max(cur_size, w_num + spacing)
return cur_size, spacing
end
local layout_strategies = {}
layout_strategies._configurations = {}
--@param strategy_config table: table with keys for each option for a strategy
--@return table: table with keys for each option (for this strategy) and with keys for each layout_strategy
local get_valid_configuration_keys = function(strategy_config)
local valid_configuration_keys = {
-- TEMP: There are a few keys we should say are valid to start with.
preview_cutoff = true,
prompt_position = true,
}
for key in pairs(strategy_config) do
valid_configuration_keys[key] = true
end
for name in pairs(layout_strategies) do
valid_configuration_keys[name] = true
end
return valid_configuration_keys
end
local adjust_pos = function(pos, ...)
for _, opts in ipairs { ... } do
opts.col = opts.col and opts.col + pos[1]
opts.line = opts.line and opts.line + pos[2]
end
end
--@param strategy_name string: the name of the layout_strategy we are validating for
--@param configuration table: table with keys for each option available
--@param values table: table containing all of the non-default options we want to set
--@param default_layout_config table: table with the default values to configure layouts
--@return table: table containing the combined options (defaults and non-defaults)
local function validate_layout_config(strategy_name, configuration, values, default_layout_config)
assert(strategy_name, "It is required to have a strategy name for validation.")
local valid_configuration_keys = get_valid_configuration_keys(configuration)
-- If no default_layout_config provided, check Telescope's config values
default_layout_config = vim.F.if_nil(default_layout_config, require("telescope.config").values.layout_config)
local result = {}
local get_value = function(k)
-- skip "private" items
if string.sub(k, 1, 1) == "_" then
return
end
local val
-- Prioritise options that are specific to this strategy
if values[strategy_name] ~= nil and values[strategy_name][k] ~= nil then
val = values[strategy_name][k]
end
-- Handle nested layout config values
if layout_strategies[k] and strategy_name ~= k and type(val) == "table" then
val = vim.tbl_deep_extend("force", default_layout_config[k], val)
end
if val == nil and values[k] ~= nil then
val = values[k]
end
if val == nil then
if default_layout_config[strategy_name] ~= nil and default_layout_config[strategy_name][k] ~= nil then
val = default_layout_config[strategy_name][k]
else
val = default_layout_config[k]
end
end
return val
end
-- Always set the values passed first.
for k in pairs(values) do
if not valid_configuration_keys[k] then
-- TODO: At some point we'll move to error here,
-- but it's a bit annoying to just straight up crash everyone's stuff.
vim.api.nvim_err_writeln(
string.format(
"Unsupported layout_config key for the %s strategy: %s\n%s",
strategy_name,
k,
vim.inspect(values)
)
)
end
result[k] = get_value(k)
end
-- And then set other valid keys via "inheritance" style extension
for k in pairs(valid_configuration_keys) do
if result[k] == nil then
result[k] = get_value(k)
end
end
return result
end
-- List of options that are shared by more than one layout.
local shared_options = {
width = { "How wide to make Telescope's entire layout", "See |resolver.resolve_width()|" },
height = { "How tall to make Telescope's entire layout", "See |resolver.resolve_height()|" },
mirror = "Flip the location of the results/prompt and preview windows",
scroll_speed = "The number of lines to scroll through the previewer",
prompt_position = { "Where to place prompt window.", "Available Values: 'bottom', 'top'" },
anchor = { "Which edge/corner to pin the picker to", "See |resolver.resolve_anchor_pos()|" },
}
-- Used for generating vim help documentation.
layout_strategies._format = function(name)
local strategy_config = layout_strategies._configurations[name]
if vim.tbl_isempty(strategy_config) then
return {}
end
local results = { "<pre>", "`picker.layout_config` shared options:" }
local strategy_keys = vim.tbl_keys(strategy_config)
table.sort(strategy_keys, function(a, b)
return a < b
end)
local add_value = function(k, val)
if type(val) == "string" then
table.insert(results, string.format(" - %s: %s", k, val))
elseif type(val) == "table" then
table.insert(results, string.format(" - %s:", k))
for _, line in ipairs(val) do
table.insert(results, string.format(" - %s", line))
end
else
error(string.format("expected string or table but found '%s'", type(val)))
end
end
for _, k in ipairs(strategy_keys) do
if shared_options[k] then
add_value(k, strategy_config[k])
end
end
table.insert(results, "")
table.insert(results, "`picker.layout_config` unique options:")
for _, k in ipairs(strategy_keys) do
if not shared_options[k] then
add_value(k, strategy_config[k])
end
end
table.insert(results, "</pre>")
return results
end
--@param name string: the name to be assigned to the layout
--@param layout_config table: table where keys are the available options for the layout
--@param layout function: function with signature
-- function(self, max_columns, max_lines, layout_config): table
-- the returned table is the sizing and location information for the parts of the picker
--@retun function: wrapped function that inputs a validated layout_config into the `layout` function
local function make_documented_layout(name, layout_config, layout)
-- Save configuration data to be used by documentation
layout_strategies._configurations[name] = layout_config
-- Return new function that always validates configuration
return function(self, max_columns, max_lines, override_layout)
return layout(
self,
max_columns,
max_lines,
validate_layout_config(
name,
layout_config,
vim.tbl_deep_extend("keep", vim.F.if_nil(override_layout, {}), vim.F.if_nil(self.layout_config, {}))
)
)
end
end
--- Horizontal layout has two columns, one for the preview
--- and one for the prompt and results.
---
--- <pre>
--- ┌──────────────────────────────────────────────────┐
--- │ │
--- │ ┌───────────────────┐┌───────────────────┐ │
--- │ │ ││ │ │
--- │ │ ││ │ │
--- │ │ ││ │ │
--- │ │ Results ││ │ │
--- │ │ ││ Preview │ │
--- │ │ ││ │ │
--- │ │ ││ │ │
--- │ └───────────────────┘│ │ │
--- │ ┌───────────────────┐│ │ │
--- │ │ Prompt ││ │ │
--- │ └───────────────────┘└───────────────────┘ │
--- │ │
--- └──────────────────────────────────────────────────┘
--- </pre>
---@eval { ["description"] = require('telescope.pickers.layout_strategies')._format("horizontal") }
---
layout_strategies.horizontal = make_documented_layout(
"horizontal",
vim.tbl_extend("error", shared_options, {
preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|" },
preview_cutoff = "When columns are less than this value, the preview will be disabled",
}),
function(self, max_columns, max_lines, layout_config)
local initial_options = p_window.get_initial_window_options(self)
local preview = initial_options.preview
local results = initial_options.results
local prompt = initial_options.prompt
local tbln
max_lines, tbln = calc_tabline(max_lines)
local width_opt = layout_config.width
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
local height_opt = layout_config.height
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
local bs = get_border_size(self)
local w_space
if self.previewer and max_columns >= layout_config.preview_cutoff then
-- Cap over/undersized width (with previewer)
width, w_space = calc_size_and_spacing(width, max_columns, bs, 2, 4, 1)
preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, function(_, cols)
if cols < 150 then
return math.floor(cols * 0.4)
elseif cols < 200 then
return 80
else
return 120
end
end))(self, width, max_lines)
results.width = width - preview.width - w_space
prompt.width = results.width
else
-- Cap over/undersized width (without previewer)
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
preview.width = 0
results.width = width - preview.width - w_space
prompt.width = results.width
end
local h_space
-- Cap over/undersized height
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 4, 1)
prompt.height = 1
results.height = height - prompt.height - h_space
if self.previewer then
preview.height = height - 2 * bs
else
preview.height = 0
end
local width_padding = math.floor((max_columns - width) / 2)
-- Default value is false, to use the normal horizontal layout
if not layout_config.mirror then
results.col = width_padding + bs + 1
prompt.col = results.col
preview.col = results.col + results.width + 1 + bs
else
preview.col = width_padding + bs + 1
prompt.col = preview.col + preview.width + 1 + bs
results.col = preview.col + preview.width + 1 + bs
end
preview.line = math.floor((max_lines - height) / 2) + bs + 1
if layout_config.prompt_position == "top" then
prompt.line = preview.line
results.line = prompt.line + prompt.height + 1 + bs
elseif layout_config.prompt_position == "bottom" then
results.line = preview.line
prompt.line = results.line + results.height + 1 + bs
else
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
end
local anchor_pos = resolve.resolve_anchor_pos(layout_config.anchor or "", width, height, max_columns, max_lines)
adjust_pos(anchor_pos, prompt, results, preview)
if tbln then
prompt.line = prompt.line + 1
results.line = results.line + 1
preview.line = preview.line + 1
end
return {
preview = self.previewer and preview.width > 0 and preview,
results = results,
prompt = prompt,
}
end
)
--- Centered layout with a combined block of the prompt
--- and results aligned to the middle of the screen.
--- The preview window is then placed in the remaining
--- space above or below, according to `anchor` or `mirror`.
--- Particularly useful for creating dropdown menus
--- (see |telescope.themes| and |themes.get_dropdown()|).
---
--- Note that vertical anchoring, i.e. `anchor` containing
--- `"N"` or `"S"`, will override `mirror` config. For `"N"`
--- anchoring preview will be placed below prompt/result
--- block. For `"S"` anchoring preview will be placed above
--- prompt/result block. For horizontal only anchoring preview
--- will be placed according to `mirror` config, default is
--- above the prompt/result block.
---
--- <pre>
--- ┌──────────────────────────────────────────────────┐
--- │ ┌────────────────────────────────────────┐ │
--- │ │ Preview │ │
--- │ │ Preview │ │
--- │ └────────────────────────────────────────┘ │
--- │ ┌────────────────────────────────────────┐ │
--- │ │ Prompt │ │
--- │ ├────────────────────────────────────────┤ │
--- │ │ Result │ │
--- │ │ Result │ │
--- │ └────────────────────────────────────────┘ │
--- │ │
--- │ │
--- │ │
--- │ │
--- └──────────────────────────────────────────────────┘
--- </pre>
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("center") }
---
layout_strategies.center = make_documented_layout(
"center",
vim.tbl_extend("error", shared_options, {
preview_cutoff = "When lines are less than this value, the preview will be disabled",
}),
function(self, max_columns, max_lines, layout_config)
local initial_options = p_window.get_initial_window_options(self)
local preview = initial_options.preview
local results = initial_options.results
local prompt = initial_options.prompt
local tbln
max_lines, tbln = calc_tabline(max_lines)
-- This sets the width for the whole layout
local width_opt = layout_config.width
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
-- This sets the height for the whole layout
local height_opt = layout_config.height
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
local bs = get_border_size(self)
local w_space
-- Cap over/undersized width
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
prompt.width = width - w_space
results.width = width - w_space
preview.width = width - w_space
local h_space
-- Cap over/undersized height
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 3, 0)
prompt.height = 1
results.height = height - prompt.height - h_space
local topline = math.floor((max_lines / 2) - ((results.height + (2 * bs)) / 2) + 1)
-- Align the prompt and results so halfway up the screen is
-- in the middle of this combined block
if layout_config.prompt_position == "top" then
prompt.line = topline
results.line = prompt.line + 1 + bs
elseif layout_config.prompt_position == "bottom" then
results.line = topline
prompt.line = results.line + results.height + bs
if type(prompt.title) == "string" then
prompt.title = { { pos = "S", text = prompt.title } }
end
else
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
end
local width_padding = math.floor((max_columns - width) / 2) + bs + 1
results.col, preview.col, prompt.col = width_padding, width_padding, width_padding
local anchor = layout_config.anchor or ""
local anchor_pos = resolve.resolve_anchor_pos(anchor, width, height, max_columns, max_lines)
adjust_pos(anchor_pos, prompt, results, preview)
-- Vertical anchoring (S or N variations) ignores layout_config.mirror
anchor = anchor:upper()
local mirror
if anchor:find "S" then
mirror = false
elseif anchor:find "N" then
mirror = true
else
mirror = layout_config.mirror
end
-- Set preview position
local block_line = math.min(results.line, prompt.line)
if not mirror then -- Preview at top
preview.line = 1 + bs
preview.height = block_line - (2 + 2 * bs)
else -- Preview at bottom
preview.line = block_line + results.height + 2 + 2 * bs
preview.height = max_lines - preview.line - bs + 1
end
if not (self.previewer and max_lines >= layout_config.preview_cutoff) then
preview.height = 0
end
if tbln then
prompt.line = prompt.line + 1
results.line = results.line + 1
preview.line = preview.line + 1
end
return {
preview = self.previewer and preview.height > 0 and preview,
results = results,
prompt = prompt,
}
end
)
--- Cursor layout dynamically positioned below the cursor if possible.
--- If there is no place below the cursor it will be placed above.
---
--- <pre>
--- ┌──────────────────────────────────────────────────┐
--- │ │
--- │ █ │
--- │ ┌──────────────┐┌─────────────────────┐ │
--- │ │ Prompt ││ Preview │ │
--- │ ├──────────────┤│ Preview │ │
--- │ │ Result ││ Preview │ │
--- │ │ Result ││ Preview │ │
--- │ └──────────────┘└─────────────────────┘ │
--- │ █ │
--- │ │
--- │ │
--- │ │
--- │ │
--- │ │
--- └──────────────────────────────────────────────────┘
--- </pre>
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("cursor") }
layout_strategies.cursor = make_documented_layout(
"cursor",
vim.tbl_extend("error", {
width = shared_options.width,
height = shared_options.height,
scroll_speed = shared_options.scroll_speed,
}, {
preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|" },
preview_cutoff = "When columns are less than this value, the preview will be disabled",
}),
function(self, max_columns, max_lines, layout_config)
local initial_options = p_window.get_initial_window_options(self)
local preview = initial_options.preview
local results = initial_options.results
local prompt = initial_options.prompt
local height_opt = layout_config.height
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
local width_opt = layout_config.width
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
local bs = get_border_size(self)
local h_space
-- Cap over/undersized height
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 3, 0)
prompt.height = 1
results.height = height - prompt.height - h_space
preview.height = height - 2 * bs
local w_space
if self.previewer and max_columns >= layout_config.preview_cutoff then
-- Cap over/undersized width (with preview)
width, w_space = calc_size_and_spacing(width, max_columns, bs, 2, 4, 0)
preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, 2 / 3))(self, width, max_lines)
prompt.width = width - preview.width - w_space
results.width = prompt.width
else
-- Cap over/undersized width (without preview)
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
preview.width = 0
prompt.width = width - w_space
results.width = prompt.width
end
local position = vim.api.nvim_win_get_position(0)
local winbar = (function()
if vim.fn.exists "&winbar" == 1 then
return vim.o.winbar == "" and 0 or 1
end
return 0
end)()
local top_left = {
line = vim.fn.winline() + position[1] + bs + winbar,
col = vim.fn.wincol() + position[2],
}
local bot_right = {
line = top_left.line + height - 1,
col = top_left.col + width - 1,
}
if bot_right.line > max_lines then
-- position above current line
top_left.line = top_left.line - height - 1
end
if bot_right.col >= max_columns then
-- cap to the right of the screen
top_left.col = max_columns - width
end
prompt.line = top_left.line + 1
results.line = prompt.line + bs + 1
preview.line = prompt.line
prompt.col = top_left.col + 1
results.col = prompt.col
preview.col = results.col + (bs * 2) + results.width
return {
preview = self.previewer and preview.width > 0 and preview,
results = results,
prompt = prompt,
}
end
)
--- Vertical layout stacks the items on top of each other.
--- Particularly useful with thinner windows.
---
--- <pre>
--- ┌──────────────────────────────────────────────────┐
--- │ │
--- │ ┌────────────────────────────────────────┐ │
--- │ │ Preview │ │
--- │ │ Preview │ │
--- │ │ Preview │ │
--- │ └────────────────────────────────────────┘ │
--- │ ┌────────────────────────────────────────┐ │
--- │ │ Result │ │
--- │ │ Result │ │
--- │ └────────────────────────────────────────┘ │
--- │ ┌────────────────────────────────────────┐ │
--- │ │ Prompt │ │
--- │ └────────────────────────────────────────┘ │
--- │ │
--- └──────────────────────────────────────────────────┘
--- </pre>
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("vertical") }
---
layout_strategies.vertical = make_documented_layout(
"vertical",
vim.tbl_extend("error", shared_options, {
preview_cutoff = "When lines are less than this value, the preview will be disabled",
preview_height = { "Change the height of Telescope's preview window", "See |resolver.resolve_height()|" },
}),
function(self, max_columns, max_lines, layout_config)
local initial_options = p_window.get_initial_window_options(self)
local preview = initial_options.preview
local results = initial_options.results
local prompt = initial_options.prompt
local tbln
max_lines, tbln = calc_tabline(max_lines)
local width_opt = layout_config.width
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
local height_opt = layout_config.height
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
local bs = get_border_size(self)
local w_space
-- Cap over/undersized width
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
prompt.width = width - w_space
results.width = prompt.width
preview.width = prompt.width
local h_space
if self.previewer and max_lines >= layout_config.preview_cutoff then
-- Cap over/undersized height (with previewer)
height, h_space = calc_size_and_spacing(height, max_lines, bs, 3, 6, 2)
preview.height =
resolve.resolve_height(vim.F.if_nil(layout_config.preview_height, 0.5))(self, max_columns, height)
else
-- Cap over/undersized height (without previewer)
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 4, 1)
preview.height = 0
end
prompt.height = 1
results.height = height - preview.height - prompt.height - h_space
local width_padding = math.floor((max_columns - width) / 2) + bs + 1
results.col, preview.col, prompt.col = width_padding, width_padding, width_padding
local height_padding = math.floor((max_lines - height) / 2)
if not layout_config.mirror then
preview.line = height_padding + (1 + bs)
if layout_config.prompt_position == "top" then
prompt.line = (preview.height == 0) and preview.line or preview.line + preview.height + (1 + bs)
results.line = prompt.line + prompt.height + (1 + bs)
elseif layout_config.prompt_position == "bottom" then
results.line = (preview.height == 0) and preview.line or preview.line + preview.height + (1 + bs)
prompt.line = results.line + results.height + (1 + bs)
else
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
end
else
if layout_config.prompt_position == "top" then
prompt.line = height_padding + (1 + bs)
results.line = prompt.line + prompt.height + (1 + bs)
preview.line = results.line + results.height + (1 + bs)
elseif layout_config.prompt_position == "bottom" then
results.line = height_padding + (1 + bs)
prompt.line = results.line + results.height + (1 + bs)
preview.line = prompt.line + prompt.height + (1 + bs)
else
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
end
end
local anchor_pos = resolve.resolve_anchor_pos(layout_config.anchor or "", width, height, max_columns, max_lines)
adjust_pos(anchor_pos, prompt, results, preview)
if tbln then
prompt.line = prompt.line + 1
results.line = results.line + 1
preview.line = preview.line + 1
end
return {
preview = self.previewer and preview.height > 0 and preview,
results = results,
prompt = prompt,
}
end
)
--- Flex layout swaps between `horizontal` and `vertical` strategies based on the window width
--- - Supports |layout_strategies.vertical| or |layout_strategies.horizontal| features
---
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("flex") }
---
layout_strategies.flex = make_documented_layout(
"flex",
vim.tbl_extend("error", shared_options, {
flip_columns = "The number of columns required to move to horizontal mode",
flip_lines = "The number of lines required to move to horizontal mode",
vertical = "Options to pass when switching to vertical layout",
horizontal = "Options to pass when switching to horizontal layout",
}),
function(self, max_columns, max_lines, layout_config)
local flip_columns = vim.F.if_nil(layout_config.flip_columns, 100)
local flip_lines = vim.F.if_nil(layout_config.flip_lines, 20)
if max_columns < flip_columns and max_lines > flip_lines then
self.__flex_strategy = "vertical"
self.layout_config.flip_columns = nil
self.layout_config.flip_lines = nil
return layout_strategies.vertical(self, max_columns, max_lines, layout_config.vertical)
else
self.__flex_strategy = "horizontal"
self.layout_config.flip_columns = nil
self.layout_config.flip_lines = nil
return layout_strategies.horizontal(self, max_columns, max_lines, layout_config.horizontal)
end
end
)
layout_strategies.current_buffer = make_documented_layout("current_buffer", {
-- No custom options.
-- height, width ignored
}, function(self, _, _, _)
local initial_options = p_window.get_initial_window_options(self)
local window_width = vim.api.nvim_win_get_width(0)
local window_height = vim.api.nvim_win_get_height(0)
local preview = initial_options.preview
local results = initial_options.results
local prompt = initial_options.prompt
local bs = get_border_size(self)
-- Width
local width_padding = (1 + bs) -- TODO(l-kershaw): make this configurable
prompt.width = window_width - 2 * width_padding
results.width = prompt.width
preview.width = prompt.width
-- Height
local height_padding = (1 + bs) -- TODO(l-kershaw): make this configurable
prompt.height = 1
if self.previewer then
results.height = 10 -- TODO(l-kershaw): make this configurable
preview.height = window_height - results.height - prompt.height - 2 * (1 + bs) - 2 * height_padding
else
results.height = window_height - prompt.height - (1 + bs) - 2 * height_padding
preview.height = 0
end
local win_position = vim.api.nvim_win_get_position(0)
local line = win_position[1]
if self.previewer then
preview.line = height_padding + line + 1
results.line = preview.line + preview.height + (1 + bs)
prompt.line = results.line + results.height + (1 + bs)
else
results.line = height_padding + line + 1
prompt.line = results.line + results.height + (1 + bs)
end
local col = win_position[2] + width_padding + 1
preview.col, results.col, prompt.col = col, col, col
return {
preview = preview.height > 0 and preview,
results = results,
prompt = prompt,
}
end)
--- Bottom pane can be used to create layouts similar to "ivy".
---
--- For an easy ivy configuration, see |themes.get_ivy()|
layout_strategies.bottom_pane = make_documented_layout(
"bottom_pane",
vim.tbl_extend("error", shared_options, {
preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|" },
preview_cutoff = "When columns are less than this value, the preview will be disabled",
}),
function(self, max_columns, max_lines, layout_config)
local initial_options = p_window.get_initial_window_options(self)
local results = initial_options.results
local prompt = initial_options.prompt
local preview = initial_options.preview
local tbln
max_lines, tbln = calc_tabline(max_lines)
local height = vim.F.if_nil(resolve.resolve_height(layout_config.height)(self, max_columns, max_lines), 25)
if type(layout_config.height) == "table" and type(layout_config.height.padding) == "number" then
-- Since bottom_pane only has padding at the top, we only need half as much padding in total
-- This doesn't match the vim help for `resolve.resolve_height`, but it matches expectations
height = math.floor((max_lines + height) / 2)
end
local bs = get_border_size(self)
-- Cap over/undersized height
height, _ = calc_size_and_spacing(height, max_lines, bs, 2, 3, 0)
-- Height
prompt.height = 1
results.height = height - prompt.height - (2 * bs)
preview.height = results.height - bs
-- Width
prompt.width = max_columns - 2 * bs
if self.previewer and max_columns >= layout_config.preview_cutoff then
-- Cap over/undersized width (with preview)
local width, w_space = calc_size_and_spacing(max_columns, max_columns, bs, 2, 4, 0)
preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, 0.5))(self, width, max_lines)
results.width = width - preview.width - w_space
else
results.width = prompt.width
preview.width = 0
end
-- Line
if layout_config.prompt_position == "top" then
prompt.line = max_lines - results.height - (1 + bs) + 1
results.line = prompt.line + 1
preview.line = results.line + bs
if results.border == true then
results.border = { 0, 1, 1, 1 }
end
if type(results.title) == "string" then
results.title = { { pos = "S", text = results.title } }
end
if type(preview.title) == "string" then
preview.title = { { pos = "S", text = preview.title } }
end
elseif layout_config.prompt_position == "bottom" then
results.line = max_lines - results.height - (1 + bs) + 1
preview.line = results.line
prompt.line = max_lines - bs
if type(prompt.title) == "string" then
prompt.title = { { pos = "S", text = prompt.title } }
end
if results.border == true then
results.border = { 1, 1, 0, 1 }
end
else
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
end
-- Col
prompt.col = 0 -- centered
if layout_config.mirror and preview.width > 0 then
results.col = preview.width + (3 * bs) + 1
preview.col = bs + 1
else
results.col = bs + 1
preview.col = results.width + (3 * bs) + 1
end
if tbln then
prompt.line = prompt.line + 1
results.line = results.line + 1
preview.line = preview.line + 1
end
return {
preview = self.previewer and preview.width > 0 and preview,
prompt = prompt,
results = results,
}
end
)
layout_strategies._validate_layout_config = validate_layout_config
return layout_strategies

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,339 @@
local conf = require("telescope.config").values
local utils = require "telescope.utils"
local Path = require "plenary.path"
local from_entry = require "telescope.from_entry"
local Previewer = require "telescope.previewers.previewer"
local defaulter = utils.make_default_callable
local previewers = {}
-- TODO: Should play with these some more, ty @clason
local bat_options = { "--style=plain", "--color=always", "--paging=always" }
local has_less = (vim.fn.executable "less" == 1) and conf.use_less
local get_file_stat = function(filename)
return vim.loop.fs_stat(vim.fn.expand(filename)) or {}
end
local list_dir = (function()
if vim.fn.has "win32" == 1 then
return function(dirname)
return { "cmd.exe", "/c", "dir", vim.fn.expand(dirname) }
end
else
return function(dirname)
return { "ls", "-la", vim.fn.expand(dirname) }
end
end
end)()
local bat_maker = function(filename, lnum, start, finish)
if get_file_stat(filename).type == "directory" then
return list_dir(filename)
end
local command = { "bat" }
if lnum then
table.insert(command, { "--highlight-line", lnum })
end
if has_less then
if start then
table.insert(command, { "--pager", string.format("less -RS +%s", start) })
else
table.insert(command, { "--pager", "less -RS" })
end
else
if start and finish then
table.insert(command, { "-r", string.format("%s:%s", start, finish) })
end
end
return vim.tbl_flatten {
command,
bat_options,
"--",
vim.fn.expand(filename),
}
end
local cat_maker = function(filename, _, start, _)
if get_file_stat(filename).type == "directory" then
return list_dir(filename)
end
if 1 == vim.fn.executable "file" then
local output = utils.get_os_command_output { "file", "--mime-type", "-b", filename }
local mime_type = vim.split(output[1], "/")[1]
if mime_type ~= "text" then
return { "echo", "Binary file found. These files cannot be displayed!" }
end
end
if has_less then
if start then
return { "less", "-RS", string.format("+%s", start), vim.fn.expand(filename) }
else
return { "less", "-RS", vim.fn.expand(filename) }
end
else
return {
"cat",
"--",
vim.fn.expand(filename),
}
end
end
local get_maker = function(opts)
local maker = opts.maker
if not maker and 1 == vim.fn.executable "bat" then
maker = bat_maker
elseif not maker and 1 == vim.fn.executable "cat" then
maker = cat_maker
end
if not maker then
error "Needs maker"
end
return maker
end
-- TODO: We shoudl make sure that all our terminals close all the way.
-- Otherwise it could be bad if they're just sitting around, waiting to be closed.
-- I don't think that's the problem, but it could be?
previewers.new_termopen_previewer = function(opts)
opts = opts or {}
assert(opts.get_command, "get_command is a required function")
assert(not opts.preview_fn, "preview_fn not allowed")
local opt_setup = opts.setup
local opt_teardown = opts.teardown
local old_bufs = {}
local bufentry_table = {}
local term_ids = {}
local function get_term_id(self)
if self.state then
return self.state.termopen_id
end
end
local function get_bufnr(self)
if self.state then
return self.state.termopen_bufnr
end
end
local function set_term_id(self, value)
if self.state and term_ids[self.state.termopen_bufnr] == nil then
term_ids[self.state.termopen_bufnr] = value
self.state.termopen_id = value
end
end
local function set_bufnr(self, value)
if get_bufnr(self) then
table.insert(old_bufs, get_bufnr(self))
end
if self.state then
self.state.termopen_bufnr = value
end
end
local function get_bufnr_by_bufentry(self, value)
if self.state then
return bufentry_table[value]
end
end
local function set_bufentry(self, value)
if self.state and value then
bufentry_table[value] = get_bufnr(self)
end
end
function opts.setup(self)
local state = {}
if opt_setup then
vim.tbl_deep_extend("force", state, opt_setup(self))
end
return state
end
function opts.teardown(self)
if opt_teardown then
opt_teardown(self)
end
set_bufnr(self, nil)
set_bufentry(self, nil)
for _, bufnr in ipairs(old_bufs) do
local term_id = term_ids[bufnr]
if term_id and utils.job_is_running(term_id) then
vim.fn.jobstop(term_id)
end
utils.buf_delete(bufnr)
end
bufentry_table = {}
end
function opts.preview_fn(self, entry, status)
if get_bufnr(self) == nil then
set_bufnr(self, vim.api.nvim_win_get_buf(status.preview_win))
end
local prev_bufnr = get_bufnr_by_bufentry(self, entry)
if prev_bufnr then
self.state.termopen_bufnr = prev_bufnr
utils.win_set_buf_noautocmd(status.preview_win, self.state.termopen_bufnr)
self.state.termopen_id = term_ids[self.state.termopen_bufnr]
else
local bufnr = vim.api.nvim_create_buf(false, true)
set_bufnr(self, bufnr)
utils.win_set_buf_noautocmd(status.preview_win, bufnr)
local term_opts = {
cwd = opts.cwd or vim.loop.cwd(),
env = conf.set_env,
}
local cmd = opts.get_command(entry, status)
if cmd then
vim.api.nvim_buf_call(bufnr, function()
set_term_id(self, vim.fn.termopen(cmd, term_opts))
end)
end
set_bufentry(self, entry)
end
end
if not opts.send_input then
function opts.send_input(self, input)
local termcode = vim.api.nvim_replace_termcodes(input, true, false, true)
local term_id = get_term_id(self)
if term_id then
vim.fn.chansend(term_id, termcode)
end
end
end
if not opts.scroll_fn then
function opts.scroll_fn(self, direction)
if not self.state then
return
end
local input = direction > 0 and "d" or "u"
local count = math.abs(direction)
self:send_input(count .. input)
end
end
return Previewer:new(opts)
end
previewers.cat = defaulter(function(opts)
opts = opts or {}
local maker = get_maker(opts)
local cwd = opts.cwd or vim.loop.cwd()
return previewers.new_termopen_previewer {
title = "File Preview",
dyn_title = function(_, entry)
return Path:new(from_entry.path(entry, false, false)):normalize(cwd)
end,
get_command = function(entry)
local p = from_entry.path(entry, true, false)
if p == nil or p == "" then
return
end
return maker(p)
end,
}
end, {})
previewers.vimgrep = defaulter(function(opts)
opts = opts or {}
local maker = get_maker(opts)
local cwd = opts.cwd or vim.loop.cwd()
return previewers.new_termopen_previewer {
title = "Grep Preview",
dyn_title = function(_, entry)
return Path:new(from_entry.path(entry, false, false)):normalize(cwd)
end,
get_command = function(entry, status)
local win_id = status.preview_win
local height = vim.api.nvim_win_get_height(win_id)
local p = from_entry.path(entry, true, false)
if p == nil or p == "" then
return
end
if entry.bufnr and (p == "[No Name]" or vim.api.nvim_buf_get_option(entry.bufnr, "buftype") ~= "") then
return
end
local lnum = entry.lnum or 0
local context = math.floor(height / 2)
local start = math.max(0, lnum - context)
local finish = lnum + context
return maker(p, lnum, start, finish)
end,
}
end, {})
previewers.qflist = defaulter(function(opts)
opts = opts or {}
local maker = get_maker(opts)
local cwd = opts.cwd or vim.loop.cwd()
return previewers.new_termopen_previewer {
title = "Grep Preview",
dyn_title = function(_, entry)
return Path:new(from_entry.path(entry, false, false)):normalize(cwd)
end,
get_command = function(entry, status)
local win_id = status.preview_win
local height = vim.api.nvim_win_get_height(win_id)
local p = from_entry.path(entry, true, false)
if p == nil or p == "" then
return
end
local lnum = entry.lnum
local start, finish
if entry.start and entry.finish then
start = entry.start
finish = entry.finish
else
local context = math.floor(height / 2)
start = math.max(0, lnum - context)
finish = lnum + context
end
return maker(p, lnum, start, finish)
end,
}
end, {})
return previewers

View File

@ -0,0 +1,31 @@
local state = {}
TelescopeGlobalState = TelescopeGlobalState or {}
TelescopeGlobalState.global = TelescopeGlobalState.global or {}
--- Set the status for a particular prompt bufnr
function state.set_status(prompt_bufnr, status)
TelescopeGlobalState[prompt_bufnr] = status
end
function state.set_global_key(key, value)
TelescopeGlobalState.global[key] = value
end
function state.get_global_key(key)
return TelescopeGlobalState.global[key]
end
function state.get_status(prompt_bufnr)
return TelescopeGlobalState[prompt_bufnr] or {}
end
function state.clear_status(prompt_bufnr)
state.set_status(prompt_bufnr, nil)
end
function state.get_existing_prompts()
return vim.tbl_keys(TelescopeGlobalState)
end
return state

View File

@ -0,0 +1,534 @@
---@tag telescope.utils
---@config { ["module"] = "telescope.utils" }
---@brief [[
--- Utilities for writing telescope pickers
---@brief ]]
local Path = require "plenary.path"
local Job = require "plenary.job"
local log = require "telescope.log"
local truncate = require("plenary.strings").truncate
local get_status = require("telescope.state").get_status
local utils = {}
utils.get_separator = function()
return Path.path.sep
end
utils.cycle = function(i, n)
return i % n == 0 and n or i % n
end
utils.get_lazy_default = function(x, defaulter, ...)
if x == nil then
return defaulter(...)
else
return x
end
end
utils.repeated_table = function(n, val)
local empty_lines = {}
for _ = 1, n do
table.insert(empty_lines, val)
end
return empty_lines
end
utils.filter_symbols = function(results, opts)
local has_ignore = opts.ignore_symbols ~= nil
local has_symbols = opts.symbols ~= nil
local filtered_symbols
if has_symbols and has_ignore then
utils.notify("filter_symbols", {
msg = "Either opts.symbols or opts.ignore_symbols, can't process opposing options at the same time!",
level = "ERROR",
})
return
elseif not (has_ignore or has_symbols) then
return results
elseif has_ignore then
if type(opts.ignore_symbols) == "string" then
opts.ignore_symbols = { opts.ignore_symbols }
end
if type(opts.ignore_symbols) ~= "table" then
utils.notify("filter_symbols", {
msg = "Please pass ignore_symbols as either a string or a list of strings",
level = "ERROR",
})
return
end
opts.ignore_symbols = vim.tbl_map(string.lower, opts.ignore_symbols)
filtered_symbols = vim.tbl_filter(function(item)
return not vim.tbl_contains(opts.ignore_symbols, string.lower(item.kind))
end, results)
elseif has_symbols then
if type(opts.symbols) == "string" then
opts.symbols = { opts.symbols }
end
if type(opts.symbols) ~= "table" then
utils.notify("filter_symbols", {
msg = "Please pass filtering symbols as either a string or a list of strings",
level = "ERROR",
})
return
end
opts.symbols = vim.tbl_map(string.lower, opts.symbols)
filtered_symbols = vim.tbl_filter(function(item)
return vim.tbl_contains(opts.symbols, string.lower(item.kind))
end, results)
end
-- TODO(conni2461): If you understand this correctly then we sort the results table based on the bufnr
-- If you ask me this should be its own function, that happens after the filtering part and should be
-- called in the lsp function directly
local current_buf = vim.api.nvim_get_current_buf()
if not vim.tbl_isempty(filtered_symbols) then
-- filter adequately for workspace symbols
local filename_to_bufnr = {}
for _, symbol in ipairs(filtered_symbols) do
if filename_to_bufnr[symbol.filename] == nil then
filename_to_bufnr[symbol.filename] = vim.uri_to_bufnr(vim.uri_from_fname(symbol.filename))
end
symbol["bufnr"] = filename_to_bufnr[symbol.filename]
end
table.sort(filtered_symbols, function(a, b)
if a.bufnr == b.bufnr then
return a.lnum < b.lnum
end
if a.bufnr == current_buf then
return true
end
if b.bufnr == current_buf then
return false
end
return a.bufnr < b.bufnr
end)
return filtered_symbols
end
-- print message that filtered_symbols is now empty
if has_symbols then
local symbols = table.concat(opts.symbols, ", ")
utils.notify("filter_symbols", {
msg = string.format("%s symbol(s) were not part of the query results", symbols),
level = "WARN",
})
elseif has_ignore then
local symbols = table.concat(opts.ignore_symbols, ", ")
utils.notify("filter_symbols", {
msg = string.format("%s ignore_symbol(s) have removed everything from the query result", symbols),
level = "WARN",
})
end
end
utils.path_smart = (function()
local paths = {}
return function(filepath)
local final = filepath
if #paths ~= 0 then
local dirs = vim.split(filepath, "/")
local max = 1
for _, p in pairs(paths) do
if #p > 0 and p ~= filepath then
local _dirs = vim.split(p, "/")
for i = 1, math.min(#dirs, #_dirs) do
if (dirs[i] ~= _dirs[i]) and i > max then
max = i
break
end
end
end
end
if #dirs ~= 0 then
if max == 1 and #dirs >= 2 then
max = #dirs - 2
end
final = ""
for k, v in pairs(dirs) do
if k >= max - 1 then
final = final .. (#final > 0 and "/" or "") .. v
end
end
end
end
if not paths[filepath] then
paths[filepath] = ""
table.insert(paths, filepath)
end
if final and final ~= filepath then
return "../" .. final
else
return filepath
end
end
end)()
utils.path_tail = (function()
local os_sep = utils.get_separator()
return function(path)
for i = #path, 1, -1 do
if path:sub(i, i) == os_sep then
return path:sub(i + 1, -1)
end
end
return path
end
end)()
utils.is_path_hidden = function(opts, path_display)
path_display = path_display or vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display)
return path_display == nil
or path_display == "hidden"
or type(path_display) == "table" and (vim.tbl_contains(path_display, "hidden") or path_display.hidden)
end
local is_uri = function(filename)
return string.match(filename, "^%w+://") ~= nil
end
local calc_result_length = function(truncate_len)
local status = get_status(vim.api.nvim_get_current_buf())
local len = vim.api.nvim_win_get_width(status.results_win) - status.picker.selection_caret:len() - 2
return type(truncate_len) == "number" and len - truncate_len or len
end
--- Transform path is a util function that formats a path based on path_display
--- found in `opts` or the default value from config.
--- It is meant to be used in make_entry to have a uniform interface for
--- builtins as well as extensions utilizing the same user configuration
--- Note: It is only supported inside `make_entry`/`make_display` the use of
--- this function outside of telescope might yield to undefined behavior and will
--- not be addressed by us
---@param opts table: The opts the users passed into the picker. Might contains a path_display key
---@param path string: The path that should be formatted
---@return string: The transformed path ready to be displayed
utils.transform_path = function(opts, path)
if path == nil then
return
end
if is_uri(path) then
return path
end
local path_display = vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display)
local transformed_path = path
if type(path_display) == "function" then
return path_display(opts, transformed_path)
elseif utils.is_path_hidden(nil, path_display) then
return ""
elseif type(path_display) == "table" then
if vim.tbl_contains(path_display, "tail") or path_display.tail then
transformed_path = utils.path_tail(transformed_path)
elseif vim.tbl_contains(path_display, "smart") or path_display.smart then
transformed_path = utils.path_smart(transformed_path)
else
if not vim.tbl_contains(path_display, "absolute") or path_display.absolute == false then
local cwd
if opts.cwd then
cwd = opts.cwd
if not vim.in_fast_event() then
cwd = vim.fn.expand(opts.cwd)
end
else
cwd = vim.loop.cwd()
end
transformed_path = Path:new(transformed_path):make_relative(cwd)
end
if vim.tbl_contains(path_display, "shorten") or path_display["shorten"] ~= nil then
if type(path_display["shorten"]) == "table" then
local shorten = path_display["shorten"]
transformed_path = Path:new(transformed_path):shorten(shorten.len, shorten.exclude)
else
transformed_path = Path:new(transformed_path):shorten(path_display["shorten"])
end
end
if vim.tbl_contains(path_display, "truncate") or path_display.truncate then
if opts.__length == nil then
opts.__length = calc_result_length(path_display.truncate)
end
if opts.__prefix == nil then
opts.__prefix = 0
end
transformed_path = truncate(transformed_path, opts.__length - opts.__prefix, nil, -1)
end
end
return transformed_path
else
log.warn("`path_display` must be either a function or a table.", "See `:help telescope.defaults.path_display.")
return transformed_path
end
end
-- local x = utils.make_default_callable(function(opts)
-- return function()
-- print(opts.example, opts.another)
-- end
-- end, { example = 7, another = 5 })
-- x()
-- x.new { example = 3 }()
function utils.make_default_callable(f, default_opts)
default_opts = default_opts or {}
return setmetatable({
new = function(opts)
opts = vim.tbl_extend("keep", opts, default_opts)
return f(opts)
end,
}, {
__call = function()
local ok, err = pcall(f(default_opts))
if not ok then
error(debug.traceback(err))
end
end,
})
end
function utils.job_is_running(job_id)
if job_id == nil then
return false
end
return vim.fn.jobwait({ job_id }, 0)[1] == -1
end
function utils.buf_delete(bufnr)
if bufnr == nil then
return
end
-- Suppress the buffer deleted message for those with &report<2
local start_report = vim.o.report
if start_report < 2 then
vim.o.report = 2
end
if vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr) then
vim.api.nvim_buf_delete(bufnr, { force = true })
end
if start_report < 2 then
vim.o.report = start_report
end
end
function utils.win_delete(name, win_id, force, bdelete)
if win_id == nil or not vim.api.nvim_win_is_valid(win_id) then
return
end
local bufnr = vim.api.nvim_win_get_buf(win_id)
if bdelete then
utils.buf_delete(bufnr)
end
if not vim.api.nvim_win_is_valid(win_id) then
return
end
if not pcall(vim.api.nvim_win_close, win_id, force) then
log.trace("Unable to close window: ", name, "/", win_id)
end
end
function utils.max_split(s, pattern, maxsplit)
pattern = pattern or " "
maxsplit = maxsplit or -1
local t = {}
local curpos = 0
while maxsplit ~= 0 and curpos < #s do
local found, final = string.find(s, pattern, curpos, false)
if found ~= nil then
local val = string.sub(s, curpos, found - 1)
if #val > 0 then
maxsplit = maxsplit - 1
table.insert(t, val)
end
curpos = final + 1
else
table.insert(t, string.sub(s, curpos))
break
-- curpos = curpos + 1
end
if maxsplit == 0 then
table.insert(t, string.sub(s, curpos))
end
end
return t
end
function utils.data_directory()
local sourced_file = require("plenary.debug_utils").sourced_filepath()
local base_directory = vim.fn.fnamemodify(sourced_file, ":h:h:h")
return Path:new({ base_directory, "data" }):absolute() .. Path.path.sep
end
function utils.buffer_dir()
return vim.fn.expand "%:p:h"
end
function utils.display_termcodes(str)
return str:gsub(string.char(9), "<TAB>"):gsub("", "<C-F>"):gsub(" ", "<Space>")
end
function utils.get_os_command_output(cmd, cwd)
if type(cmd) ~= "table" then
utils.notify("get_os_command_output", {
msg = "cmd has to be a table",
level = "ERROR",
})
return {}
end
local command = table.remove(cmd, 1)
local stderr = {}
local stdout, ret = Job:new({
command = command,
args = cmd,
cwd = cwd,
on_stderr = function(_, data)
table.insert(stderr, data)
end,
}):sync()
return stdout, ret, stderr
end
function utils.win_set_buf_noautocmd(win, buf)
local save_ei = vim.o.eventignore
vim.o.eventignore = "all"
vim.api.nvim_win_set_buf(win, buf)
vim.o.eventignore = save_ei
end
local load_once = function(f)
local resolved = nil
return function(...)
if resolved == nil then
resolved = f()
end
return resolved(...)
end
end
utils.file_extension = function(filename)
local parts = vim.split(filename, "%.")
-- this check enables us to get multi-part extensions, like *.test.js for example
if #parts > 2 then
return table.concat(vim.list_slice(parts, #parts - 1), ".")
else
return table.concat(vim.list_slice(parts, #parts), ".")
end
end
utils.transform_devicons = load_once(function()
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
if has_devicons then
if not devicons.has_loaded() then
devicons.setup()
end
return function(filename, display, disable_devicons)
local conf = require("telescope.config").values
if disable_devicons or not filename then
return display
end
local basename = utils.path_tail(filename)
local icon, icon_highlight = devicons.get_icon(basename, utils.file_extension(basename), { default = false })
if not icon then
icon, icon_highlight = devicons.get_icon(basename, nil, { default = true })
icon = icon or " "
end
local icon_display = icon .. " " .. (display or "")
if conf.color_devicons then
return icon_display, icon_highlight, icon
else
return icon_display, nil, icon
end
end
else
return function(_, display, _)
return display
end
end
end)
utils.get_devicons = load_once(function()
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
if has_devicons then
if not devicons.has_loaded() then
devicons.setup()
end
return function(filename, disable_devicons)
local conf = require("telescope.config").values
if disable_devicons or not filename then
return ""
end
local basename = utils.path_tail(filename)
local icon, icon_highlight = devicons.get_icon(basename, utils.file_extension(basename), { default = false })
if not icon then
icon, icon_highlight = devicons.get_icon(basename, nil, { default = true })
end
if conf.color_devicons then
return icon, icon_highlight
else
return icon, nil
end
end
else
return function(_, _)
return ""
end
end
end)
--- Telescope Wrapper around vim.notify
---@param funname string: name of the function that will be
---@param opts table: opts.level string, opts.msg string, opts.once bool
utils.notify = function(funname, opts)
opts.once = vim.F.if_nil(opts.once, false)
local level = vim.log.levels[opts.level]
if not level then
error("Invalid error level", 2)
end
local notify_fn = opts.once and vim.notify_once or vim.notify
notify_fn(string.format("[telescope.%s]: %s", funname, opts.msg), level, {
title = "telescope.nvim",
})
end
utils.__warn_no_selection = function(name)
utils.notify(name, {
msg = "Nothing currently selected",
level = "WARN",
})
end
return utils

Some files were not shown because too many files have changed in this diff Show More