Repository: nvim-telescope/telescope.nvim Branch: master Commit: 5255aa27c422 Files: 111 Total size: 1.4 MB Directory structure: gitextract_nywbmm0h/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ ├── docgen.yml │ ├── lint.yml │ └── release.yml ├── .gitignore ├── .luacheckrc ├── .stylua.toml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── data/ │ └── memes/ │ └── planets/ │ ├── earth │ ├── jupiter │ ├── mars │ ├── mercury │ ├── moon │ ├── neptune │ ├── pluto │ ├── saturn │ ├── uranus │ └── venus ├── developers.md ├── doc/ │ ├── secret.txt │ ├── telescope.txt │ └── telescope_changelog.txt ├── ftplugin/ │ ├── TelescopePrompt.lua │ └── TelescopeResults.lua ├── lua/ │ ├── telescope/ │ │ ├── _.lua │ │ ├── _extensions/ │ │ │ └── init.lua │ │ ├── actions/ │ │ │ ├── generate.lua │ │ │ ├── history.lua │ │ │ ├── init.lua │ │ │ ├── layout.lua │ │ │ ├── mt.lua │ │ │ ├── set.lua │ │ │ ├── state.lua │ │ │ └── utils.lua │ │ ├── algos/ │ │ │ ├── fzy.lua │ │ │ ├── linked_list.lua │ │ │ └── string_distance.lua │ │ ├── builtin/ │ │ │ ├── __diagnostics.lua │ │ │ ├── __files.lua │ │ │ ├── __git.lua │ │ │ ├── __internal.lua │ │ │ ├── __lsp.lua │ │ │ └── init.lua │ │ ├── command.lua │ │ ├── config/ │ │ │ └── resolve.lua │ │ ├── config.lua │ │ ├── debounce.lua │ │ ├── entry_manager.lua │ │ ├── finders/ │ │ │ ├── async_job_finder.lua │ │ │ ├── async_oneshot_finder.lua │ │ │ └── async_static_finder.lua │ │ ├── finders.lua │ │ ├── from_entry.lua │ │ ├── health.lua │ │ ├── init.lua │ │ ├── log.lua │ │ ├── make_entry.lua │ │ ├── mappings.lua │ │ ├── operators.lua │ │ ├── pickers/ │ │ │ ├── entry_display.lua │ │ │ ├── highlights.lua │ │ │ ├── layout.lua │ │ │ ├── layout_strategies.lua │ │ │ ├── multi.lua │ │ │ ├── scroller.lua │ │ │ └── window.lua │ │ ├── pickers.lua │ │ ├── previewers/ │ │ │ ├── buffer_previewer.lua │ │ │ ├── init.lua │ │ │ ├── previewer.lua │ │ │ ├── term_previewer.lua │ │ │ └── utils.lua │ │ ├── sorters.lua │ │ ├── state.lua │ │ ├── testharness/ │ │ │ ├── helpers.lua │ │ │ ├── init.lua │ │ │ └── runner.lua │ │ ├── themes.lua │ │ └── utils.lua │ └── tests/ │ ├── automated/ │ │ ├── action_spec.lua │ │ ├── command_spec.lua │ │ ├── entry_display_spec.lua │ │ ├── entry_manager_spec.lua │ │ ├── layout_strategies_spec.lua │ │ ├── linked_list_spec.lua │ │ ├── pickers/ │ │ │ ├── find_files_spec.lua │ │ │ └── live_grep_spec.lua │ │ ├── resolver_spec.lua │ │ ├── scroller_spec.lua │ │ ├── sorters_spec.lua │ │ ├── telescope_spec.lua │ │ └── utils_spec.lua │ ├── fixtures/ │ │ ├── find_files/ │ │ │ ├── file_a.txt │ │ │ └── file_abc.txt │ │ └── live_grep/ │ │ └── a.txt │ ├── helpers.lua │ └── pickers/ │ ├── find_files__readme.lua │ └── find_files__scrolling_descending_cycle.lua ├── plugin/ │ └── telescope.lua ├── scripts/ │ ├── gendocs.lua │ └── minimal_init.vim └── telescope.nvim-scm-1.rockspec ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ github: [tjdevries, Conni2461, fdschmidt93, jamestrew] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug report description: Report a problem with Telescope labels: [bug] body: - type: markdown attributes: value: | Before reporting: search [existing issues](https://github.com/nvim-telescope/telescope.nvim/issues) and make sure that both Telescope and its dependencies are updated to the latest version. - type: textarea attributes: label: "Description" description: "A short description of the problem you are reporting." validations: required: true - type: textarea attributes: label: "Neovim version" description: "Output of `nvim --version`" render: markdown placeholder: | NVIM v0.6.0-dev+209-g0603eba6e Build type: Release LuaJIT 2.1.0-beta3 validations: required: true - type: input attributes: label: "Operating system and version" placeholder: "macOS 11.5" validations: required: true - type: input attributes: label: "Telescope version / branch / rev" placeholder: "telescope 0.1.0" validations: required: true - type: textarea attributes: label: "checkhealth telescope" description: "Output of `:checkhealth telescope`" render: markdown placeholder: | health#telescope#check ======================================================================== ## Checking for required plugins - OK: plenary installed. - OK: nvim-treesitter installed. ## Checking external dependencies - OK: rg: found ripgrep 13.0.0 - OK: fd: found fd 8.2.1 ## ===== Installed extensions ===== validations: required: true - type: textarea attributes: label: "Steps to reproduce" description: "Steps to reproduce using the minimal config provided below." placeholder: | 1. `nvim -nu minimal.lua` 2. ... validations: required: true - type: textarea attributes: label: "Expected behavior" description: "A description of the behavior you expected:" - type: textarea attributes: label: "Actual behavior" description: "Observed behavior (may optionally include logs, images, or videos)." validations: required: true - type: textarea attributes: label: "Minimal config" description: "Minimal(!) configuration necessary to reproduce the issue. Save this as `minimal.lua` and run with `nvim -nu minimal.lua`. If _absolutely_ necessary, add plugins and config options from your `init.lua` at the indicated lines." render: Lua value: | local root = vim.fn.fnamemodify("./.repro", ":p") -- set stdpaths to use .repro for _, name in ipairs { "config", "data", "state", "cache" } do vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name end -- bootstrap lazy local lazypath = root .. "/plugins/lazy.nvim" if not vim.uv.fs_stat(lazypath) then vim.fn.system { "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath, } end vim.opt.runtimepath:prepend(lazypath) -- install plugins local plugins = { { "nvim-telescope/telescope.nvim", dependencies = { "nvim-lua/plenary.nvim", }, config = function() -- ADD INIT.LUA SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE require("telescope").setup {} end, }, } require("lazy").setup(plugins, { root = root .. "/plugins", }) validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Question url: https://https://github.com/nvim-telescope/telescope.nvim/discussions about: Usage questions, support requests, and general discussions ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ # Description Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context Fixes # (issue) ## Type of change Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - This change requires a documentation update # How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list relevant details about your configuration - [ ] Test A - [ ] Test B **Configuration**: * Neovim version (nvim --version): * Operating system and version: # Checklist: - [ ] My code follows the style guidelines of this project (stylua) - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation (lua annotations) ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" commit-message: prefix: "ci" ================================================ FILE: .github/workflows/ci.yml ================================================ name: Tests on: push: branches: - "master" pull_request: branches: - "master" workflow_dispatch: jobs: unit_tests: name: unit tests runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] rev: [nightly, stable, v0.10.4] include: - os: ubuntu-latest install-rg: sudo apt-get update && sudo apt-get install -y ripgrep - os: macos-latest install-rg: brew update && brew install ripgrep - os: windows-latest install-rg: choco install ripgrep steps: - uses: actions/checkout@v6 - uses: rhysd/action-setup-vim@v1 with: neovim: true version: ${{ matrix.rev }} - name: Prepare run: | ${{ matrix.install-rg }} rg --version git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ../plenary.nvim git clone --depth 1 https://github.com/nvim-tree/nvim-web-devicons ../nvim-web-devicons - name: Run tests run: | nvim --version make test ================================================ FILE: .github/workflows/docgen.yml ================================================ name: Generate docs on: pull_request: branches: - master workflow_dispatch: jobs: build-sources: name: Generate docs runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest url: https://github.com/neovim/neovim/releases/download/v0.9.5/nvim-linux64.tar.gz steps: - uses: actions/checkout@v6 - run: date +%F > todays-date - name: Restore cache for today's nightly. uses: actions/cache@v5 with: path: _neovim key: ${{ runner.os }}-${{ matrix.url }}-${{ hashFiles('todays-date') }} - name: Prepare run: | test -d _neovim || { mkdir -p _neovim curl -sL ${{ matrix.url }} | tar xzf - --strip-components=1 -C "${PWD}/_neovim" } mkdir -p ~/.local/share/nvim/site/pack/vendor/start git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim git clone https://github.com/tjdevries/tree-sitter-lua ~/.local/share/nvim/site/pack/vendor/start/tree-sitter-lua ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start - name: Build parser run: | # We have to build the parser every single time to keep up with parser changes cd ~/.local/share/nvim/site/pack/vendor/start/tree-sitter-lua git checkout 86f74dfb69c570f0749b241f8f5489f8f50adbea make dist cd - - name: Generating docs run: | export PATH="${PWD}/_neovim/bin:${PATH}" export VIM="${PWD}/_neovim/share/nvim/runtime" nvim --version make docgen # inspired by nvim-lspconfigs - name: Update documentation env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_MSG: | [docgen] Update doc/telescope.txt skip-checks: true run: | git config user.email "actions@github" git config user.name "Github Actions" git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git git add doc/ # Only commit and push if we have changes git diff --quiet && git diff --staged --quiet || (git commit -m "${COMMIT_MSG}"; git push origin HEAD:${GITHUB_REF}) ================================================ FILE: .github/workflows/lint.yml ================================================ name: Linting and style checking on: push: branches: - "master" pull_request: branches: - "master" workflow_dispatch: jobs: luacheck: name: Luacheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Prepare run: | sudo apt-get update sudo apt-get install -y luarocks sudo luarocks install luacheck - name: Lint run: sudo make lint stylua: name: stylua runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: JohnnyMorganz/stylua-action@v4 with: token: ${{ secrets.GITHUB_TOKEN }} version: latest # CLI arguments args: --color always --check lua/ ================================================ FILE: .github/workflows/release.yml ================================================ name: "release" on: push: tags: - '*' workflow_dispatch: jobs: luarocks-upload: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: LuaRocks Upload uses: lumen-oss/luarocks-tag-release@v7 env: LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} with: summary: "Find, Filter, Preview, Pick. All lua, all the time." detailed_description: | 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. dependencies: | plenary.nvim copy_directories: | doc ftplugin plugin scripts autoload data ================================================ FILE: .gitignore ================================================ build/ doc/tags .luacheckcache ================================================ FILE: .luacheckrc ================================================ -- Rerun tests only if their modification time changed. cache = true std = luajit codes = true self = false -- Glorious list of warnings: https://luacheck.readthedocs.io/en/stable/warnings.html ignore = { "212", -- Unused argument, In the case of callback function, _arg_name is easier to understand than _, so this option is set to off. "122", -- Indirectly setting a readonly global } globals = { "_", "TelescopeGlobalState", "_TelescopeConfigurationValues", "_TelescopeConfigurationPickers", } -- Global objects defined by the C code read_globals = { "vim", } files = { ["lua/telescope/builtin/init.lua"] = { ignore = { "631", -- allow line len > 120 } }, } ================================================ FILE: .stylua.toml ================================================ column_width = 120 line_endings = "Unix" indent_type = "Spaces" indent_width = 2 quote_style = "AutoPreferDouble" call_parentheses = "None" ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing 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. At this time, we are content with the number and functionality of the pickers we offer built in with Telescope and so we are currently not accepting new pickers (see this [issue](https://github.com/nvim-telescope/telescope.nvim/issues/1228) for a discussion on this). We are also conservative with integrating picker specific actions and features. If you're still interested in filling a particular picker need, we encourage packaging it up as its own Telescope extension. Read our [Bundling as extension](https://github.com/nvim-telescope/telescope.nvim/blob/master/developers.md#bundling-as-extension) guide here for more info on this. See other Telescope extensions (and add yours) [here](https://github.com/nvim-telescope/telescope.nvim/wiki/Extensions). That said, we welcome bug fixes, documentation improvements and non-picker specific features. If you're submitting a new feature, it is a good idea to create an issue first to gauge interest and feasibility. 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 [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 - 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 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020-2021 nvim-telescope Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .PHONY: test lint docgen test: nvim --headless --noplugin -u scripts/minimal_init.vim -c "PlenaryBustedDirectory lua/tests/automated/ { minimal_init = './scripts/minimal_init.vim' }" lint: luacheck lua/telescope docgen: nvim --headless --noplugin -u scripts/minimal_init.vim -c "luafile ./scripts/gendocs.lua" -c 'qa' ================================================ FILE: README.md ================================================ # telescope.nvim [![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) For more showcases of Telescope, please visit the [Showcase section](https://github.com/nvim-telescope/telescope.nvim/wiki/Showcase) in the Telescope Wiki ## 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.10.4)](https://github.com/neovim/neovim/releases/tag/v0.10.4) or the latest neovim nightly commit is required for `telescope.nvim` to work. The neovim version also needs to be compiled with LuaJIT; PUC Lua is not fully supported, both for performance reasons and because extensions may rely on FFI. ### 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` and is the first priority for `find_files`. We also strongly suggest installing a native telescope sorter to significantly improve sorting performance: * [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) - [devicons](https://github.com/nvim-tree/nvim-web-devicons) (icons) ### Installation We recommend pinning to the latest release [tag](https://github.com/nvim-telescope/telescope.nvim/tags), e.g. using [lazy.nvim](https://github.com/folke/lazy.nvim) ```lua { 'nvim-telescope/telescope.nvim', version = '*', dependencies = { 'nvim-lua/plenary.nvim', -- optional but recommended { 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' }, } } ``` ### 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` to see if `telescope.nvim` is installed correctly. ```lua local builtin = require('telescope.builtin') vim.keymap.set('n', 'ff', builtin.find_files, { desc = 'Telescope find files' }) vim.keymap.set('n', 'fg', builtin.live_grep, { desc = 'Telescope live grep' }) vim.keymap.set('n', 'fb', builtin.buffers, { desc = 'Telescope buffers' }) vim.keymap.set('n', 'fh', builtin.help_tags, { desc = 'Telescope 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 (default: ) -- actions.which_key shows the mappings for your picker, -- e.g. git_{create, delete, ...}_branch for the git_branches picker [""] = "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 | | -------------- | --------------------------------------------------------- | | `/` | Next item | | `/` | 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) | | `` | Confirm selection | | `` | Go to file selection as a split | | `` | Go to file selection as a vsplit | | `` | Go to a file in a new tab | | `` | Scroll up in preview window | | `` | Scroll down in preview window | | `` | Scroll left in preview window | | `` | Scroll right in preview window | | `` | Scroll left in results window | | `` | Scroll right in results window | | `` | Show mappings for picker actions (insert mode) | | `?` | Show mappings for picker actions (normal mode) | | `` | Close telescope (insert mode) | | `` | Close telescope (in normal mode) | | `` | Toggle selection and move to next selection | | `` | Toggle selection and move to prev selection | | `` | Send all items not filtered to quickfixlist (qflist) | | `` | Send all selected items to qflist | | `` | Insert cword in original window into prompt (insert mode) | | `` | Insert cWORD in original window into prompt (insert mode) | | `` | Insert cfile in original window into prompt (insert mode) | | `` | Insert cline in original window into prompt (insert mode) | To see the full list of mappings, check out `lua/telescope/mappings.lua` and the `default_mappings` table. **Tip**: you can use `` 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. ### 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 or selection 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 `` | | `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 `` | | `builtin.search_history` | Lists searches that were executed recently, and reruns them on `` | | `builtin.help_tags` | Lists available help tags and opens a new window with the relevant help info on `` | | `builtin.man_pages` | Lists manpage entries, opens them in a help window on `` | | `builtin.marks` | Lists vim marks and their value | | `builtin.colorscheme` | Lists available colorschemes and applies them on `` | | `builtin.quickfix` | Lists items in the quickfix list | | `builtin.quickfixhistory` | Lists all quickfix lists in your history and open them with `builtin.quickfix` or quickfix window | | `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 `` | | `builtin.registers` | Lists vim registers, pastes the contents of the register on `` | | `builtin.autocommands` | Lists vim autocommands and goes to their declaration on `` | | `builtin.spell_suggest` | Lists spelling suggestions for the current word under the cursor, replaces word with selected suggestion on `` | | `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 ``, reset mixed `m`, reset soft `s` and reset hard `h` | | `builtin.git_bcommits` | Lists buffer's git commits with diff preview and checks them out on `` | | `builtin.git_bcommits_range` | Lists buffer's git commits in a range of lines. Use options `from` and `to` to specify the range. In visual mode, lists commits for the selected lines | | `builtin.git_branches` | Lists all branches with log preview, checkout action ``, track action ``, rebase action``, create action ``, switch action ``, delete action `` and merge action `` | | `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 `` | ### Treesitter Picker | Functions | Description | | -------------------- | ------------------------------------------------- | | `builtin.treesitter` | Lists Function names, variables, ... using treesitter [`locals` queries](https://github.com/nvim-treesitter/nvim-treesitter/blob/main/CONTRIBUTING.md#locals) | ### Lists Picker | Functions | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `builtin.planets` | Use the telescope... | | `builtin.builtin` | Lists Built-in pickers and run them on ``. | | `builtin.reloader` | Lists Lua modules and reload them on ``. | | `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 using `vim.filetype` to guess the filetype for the selected file. The guessing is done by inspecting the filename, the head of the file(shebang) and the tail of the file (modeline). If you have trouble with filetype detection you should read `:help vim.filetype`. We need to do it manually because we can't determine the filetype in the traditional way: We don't do `bufload` and instead read the file asynchronously with `vim.uv.fs_` and attach only a highlighter; otherwise the speed of the previewer would slow down considerably. If you want to configure the `vim_buffer_` previewer (e.g. you want the line to wrap), do this: ```lua vim.api.nvim_create_autocmd("User", { pattern = "TelescopePreviewerLoaded", callback = function(args) if args.data.filetype ~= "help" then vim.wo.number = true elseif args.data.bufname:match("*.csv") then vim.wo.wrap = false end end, }) ``` A data field is passed to the callback, which contains the filetype and the buffer name. ```lua { title: string, # preview window title filetype: string, bufname: string, } ``` ## 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 f :lua require'telescope.builtin'.find_files(require('telescope.themes').get_dropdown({})) " Change an option nnoremap f :lua require'telescope.builtin'.find_files(require('telescope.themes').get_dropdown({ winblend = 10 })) ``` 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. ```vim " Show all builtin pickers :Telescope " Tab completion :Telescope | :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 | | `User TelescopeResumePost` | Do it after Telescope resume action is fully completed | ## 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: ```vim " 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) - [telescope.nvim 0.1 reflection (Video)](https://www.youtube.com/watch?v=3WEAjCXFiiM) - [Why Telescope? (Video)](https://www.youtube.com/watch?v=8SqFt5h2Lsg) - [Telescope and Nvim 0.5 Intro (Video)](https://www.youtube.com/watch?v=guxLXcG1kzQ) ## Contributing All contributions are welcome! Just open a pull request. Please read [CONTRIBUTING.md](./CONTRIBUTING.md) ## Related Projects - [snacks.picker](https://github.com/folke/snacks.nvim/blob/main/docs/picker.md) - [mini.pick](https://github.com/nvim-mini/mini.pick) - [fzf-lua](https://github.com/ibhagwan/fzf-lua) - [fzf.vim](https://github.com/junegunn/fzf.vim) - [denite.nvim](https://github.com/Shougo/denite.nvim) - [vim-clap](https://github.com/liuchengxu/vim-clap) ================================================ FILE: data/memes/planets/earth ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▓▓▓▓▓▓▓▓▓▓▓▒▒▒░  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒▓▓▓▓▓▒▒   ░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒▒▓▒▒░   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓ ░▓▓▓▒▒▒▒▒  ▒░ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▒▓▓▓▓▓▓▒▒ ▒▓▒░░▒▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓░▓▓▓▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓░░▒▓▓▓▓▓▓▓▓▓   ▒░░  ░ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▒▓▒▒▓▓▓▓▒▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▒▓▓▓▓▓▓▓▓ ▒░  ░▓  ░ ░ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▒▒▒▓▓▒░░▓▒▓▓▓▓▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓     ░▒▓      ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▒▒▓▒▒░░░▓▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░    ▓       ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▒▒▒▒▒▒▒░▒▓▓▓▓▒▓▒▒▓▒▓▓▓▒▓▓▓▓▓▓▓▓▓▓▒▒▓▒▒░▓▓▓░▓▓▓▓▓▒▓    ░▒▓▒▓░       ▓▓▓▓▓▓ ▓▓▓▓▓▓ ▒▒▒▒░▒▒▒░░▒▓▓▒▓░▓▓▓░▒▓▓▓▓▓▓▓▓▒▓▓▒▒▒▒▒░░░▓▓ ▓▓░▓▓▓▒▒░  ▓ ░▒▓         ▓▓▓▓▓ ▓▓▓▓▓▓▒▒▒▒░▒▒▒░▒▓▒▒▓▓▓▓▓▓▒▓▓ ▓░▓▓▓▒▒▒▓▓▓▓▒░░░░▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒         ▓▓▓▓▓ ▓▓▓▓▓ ▒▒▒▓▓▒▒▒▒▒▓▓▒▒▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▒▒ ▓▓▒▓▒▒░▒ ▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒░▓   ░░   ▒ ▓▓▓▓▓ ▓▓▓▓▓▒▒▒▒▒▒▒▒▒▓▒▓▓▒▓▓▓▓▓▓▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▒▒▒▓▓▒▓▓▓▓▓▓▓▓ ░▓▓▓▓▒▒░   ▒ ░   ▒░ ▓▓▓▓ ▓▓▓▓▓▒▒▒▒▒▒▒▒▒▓▓▒▓▓▓▒▓▓▓▓▒▓▓▓▓▒▓▒░ ▓▒▓▒▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓  ▒ ▒░▒░░    ░ ▒░     ▓▓▓▓ ▓▓▓▓▓▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▓▒▒▓▓▓▓▓▓▓▒▓▓▓▒▓▓▓▓▓▒▒▓░▓▓▓▒░▒   ░▒  ░ ░    ▒    ░▒░    ▓▓▓▓ ▓▓▓▓▓▒▒▒▒▒░░▒▓▒▒▒▒▒▒▒▓ ▓▓▓▒▓▓▓▓▓▓ ▓▒░░▓▓░▒▓▓            ░   ░ ░▒ ░░░░▒░ ░░▒ ▓▓▓▓ ▓▓▓▓▓▒▒▒▒▓▒▒▒▒░▒░▒▓  ▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓░          ▒▒▓▒░ ▒░░ ▒▓▓▓▒▒▒░▒ ▓ ▓▓▓▓ ▓▓▓▓▓ ▒░▒▓▒▒░░▒░░░▒▒▓ ░ ░▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▒ ▒▓   ▓▓  ░░░▒░▒   ▒▓▓▓▓▒░░    ▓▓▓▓▓ ▓▓▓▓▓▓▒▒░  ▒▓▒▒▒▒▓▒░ ▒ ░▒ ░▓▓▓▓▓▓▓▓▓▓▓▓░▒░░▓▓▓▓ ▓▓▓ ░▓▒░  ░ ░▒▒░▒▓▓░░░▒    ▓▓▓▓▓ ▓▓▓▓▓▓░▒▒▒▒ ▒  ▒▒░▓▒░▒ ▒░▒▓░▒▒▓▓▓▓▓▓▓▓▓▒▒░ ▒▓▓▒▓▓▓▓▓▓▓▓▓▒░▓▒ ▓▓▒▓▓▓▓▓▓▒░ ▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▒▒▒▒▒▒▒░▒▒▒░░▒▓░░▒▒░▒░▒░▒▓▓▓▒▒▒▒▓░ ░ ░       ▓▓▒▒▒░▓▓▓▓▓▓▓▓▓▓▓▓▒▒░ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▒▒░▒▒▒▒░▒▒░▒ ▒ ▓▓▓░▒▓░▒▓▓▒░░░░▒▒  ░░▓ ▒░ ▒░░▒░▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▒░▒▒▒▒▒░▒▒▒░▒▒▒▒  ▒▒░ ▒▒▒▓░▒▒   ▒▓▓░▒      ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒░▒▒░▒░▒▒▓░▒▓▓░▒▒░▒░▒▓▒▓▓▓▓▒▒▓░▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▒▒▒▒ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒░▒▒▒▒░▒░░▓▒▓▒▒▓░▓▓░▓▓ ▓▒▓ ▒  ░▓ ▒░▓░▓▓▓▓▓▓▓▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ ▒ ▒▒▒▒▒▒▒░▒░▒░░░▒▒░░░░ ▒ ░░▓▒░▓▓▓▓▓▒▓▓▓ ▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▒▒▒▒▒▓░▒▒░▒░▒▒░░▒▓▒▒▓▒▒▒▓▒░▓▒▒▓▓▓▓▓▓▓▓░░▓▓▓▓▓▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒░░▒▓▓▓▒▓▓▓▒▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒ ▒▒▒▒░░▒▒▒▓▒▒▓▒░▒▒▒▒░▒▒▓▓▒▒▓▓▒▓▓▓▓▓▓▓▓▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒░▒▒▒▒▒▒▒▒▒▒▓▒▒▒▓▓▒▓▒▒▒▒▓▓▒▒▓▓▓▓▓▒▓░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▓▒▓▓▓▒░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▓▒░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/jupiter ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ░░░ ░    ░  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒▒▒▒▒▒░░ ▒ ░▒░ ░░▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░▒▓▒▒░▒▒ ▒▒▒░▒▒░▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▓▓▒▓▒▓▓▓▓▓▓▓▓▒▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒   ▓ ░   ░▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▓▒▒▓▓▒▓▓▓▒▒▒▒▓▒▒▒▒▒▒▓▒▓▓▒▓▒▒▒▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▒▒▒▒▓▒▒▓▓▓▒▒▒▒▒▒▒▒▓░░░▒▒▒▒░░░░░▒▒▒░░░░░▒▒▒░░░▒░▒▒▒▒▒▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▓▓▓▓▓▓▒▓▓▓▓▒▒▓▓▒▒▒▓▓▒▒▒▒▓▓▓▒▓▓▓▓▓▒▓▒▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▒░▒▓▒▒▓▓▓▒▓▓▒▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓▒░▓▒▓▓   ▒ ▓▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▒▒░▓▓░░░  ▒   ▒▒▓▓▒  ▓▓░▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▒▓▓▓▒▒▓▓░▒░ ▓▓▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▓▓▓▓▒▓▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▒▓▓▓▓▓▒▒▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓░░▓▒▒▓▒▓ ▒▒▓▓▒▓▓▒▓▓▒▓▓▓▓▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▒▒▒▒▒▒▓▒▓▓▓▒▒▓▓▒▒    ░░▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▒▒▓▓▓▒▓▓▓▓▒      ░▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▓▓▓▒▓▒▓▓▓▓▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/mars ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░▒   ▓▒ ░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░  ░░░▒▒ ░ ▒░  ░░░░░░░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░ ░░  ░ ░░▒▓░░░░░░░ ░░ ░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░  ░░░░░░░░ ░▒░▒░░░▒▒░░░░░░░░░ ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░ ░░░░░░░░░░░░░░░▒▒▒▓▒▒▒▒▒▒▒░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ░░ ░  ░░░░░░░░░░ ░░░▒▒▒▒▒▒▒▓▒▒▒▒░▒▒░░░▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ░ ░░         ░  ░  ░ ░░▒▓▒▒▒▒▒▒▒▓▒▒░▒░▒▒░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░ ░░                  ░ ░░▒▒▒▒▒▓▒▒▓▒▒░░ ▒▒░ ░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ░                        ░░░▒▒▒▒▒▒▒▒▒░ ░░▒░░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                          ░▒░░▒▒▒▒▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                        ░░ ░░░░░▒▒░▒░▒▒▒░░░░░ ░ ░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                        ░ ░▒▒ ░ ░▒░░░░░░░░░  ░▒░░ ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓                        ░░  ░░ ░░ ░░░░░░░ ░░░░  ░░  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓        ░           ░░ ░              ░     ▒░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                      ░░░░     ░░     ░░ ░░░░░ ▒░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                       ░░░             ░ ░░▒░ ░▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                        ░░  ░  ░       ░░░  ░▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░                       ░ ▒        ░  ░░░▒▒░▒▒▒░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░        ░     ░ ░    ▒  ░░░ ░  ░░░░▒░░▓░▒░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░        ░          ░░ ▒▓▒░▒▒▒ ░░░░▒▒░▒▒ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░ ░   ░░░          ░▒ ▒▒▒▓▒▒░░░░░░░░░▒░▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ ░░░░░░░░░░ ░░░░ ▓▒▒▒▒▒▒▒▒▒▒░░░░░░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░░░░░░▒▒▒▒▒░▒▒▒░░▒▒▓▓▓▓▒▓▒▒▒▒▒░▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░   ░▒▒░░░░░▒▒░░▒▒▒▓▓▒▒▒▒▒▒░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ░ ░ ░░░░░░░░░▒▒░░░░░░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░░░░░░░░░░ ░ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/mercury ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓            ░  ░░    ░   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ░░            ░░   ░░  ░░ ░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓      ░░            ░   ░ ░░ ░ ░     ░   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ░░ ░          ░ ░        ░░ ░  ░░░     ░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                 ░░    ░          ░░  ░  ░ ░  ░ ░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓             ░░     ░░░░      ░ ░  ░ ░░  ░ ░░░░ ░░░░  ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓             ░░  ░     ░ ░░░ ░░ ░ ░░░    ░ ░░░░░░░ ░░░░░ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ░    ░   ░ ░░░  ░░░ ░░  ░░░░░░   ░░░░░  ░ ░ ░░ ░░░ ░░░░░░░ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓░   ░░ ░░  ░  ░  ░░░  ░░░ ░░ ░░  ░  ░░░░░░ ░░░ ░░░   ░░ ░░░░░░░▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓    ░░           ░░ ░   ░ ░░  ░░░░░░ ░░░░░░ ░    ░ ░  ░  ░░░░░ ░ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ░   ░ ░░░    ░  ░░░░ ░ ░ ░░░  ░░░░░░░░░░░░░░░░░░░ ░ ░ ░ ░░░░░░ ░░░▓▓▓▓▓▓ ▓▓▓▓▓▓    ░ ░     ░     ░░░░      ░   ░░░   ░ ░░░░░░░░░░░ ░░  ░░░  ░  ░ ░░▓▓▓▓▓▓ ▓▓▓▓▓▓ ░   ░  ░░░ ░   ░░░░░     ░   ░░  ░░   ░░░   ░░░░░░░░  ░░░░░░░░░░░░ ░▓▓▓▓▓ ▓▓▓▓▓░ ░  ░░        ░ ░░░ ░ ░░ ░  ░░░░░░░░ ░  ░░░ ░ ░░░░░░    ░░░ ░░░░░░░░  ▓▓▓▓ ▓▓▓▓▓   ░░░ ░░░░░  ░░  ░░░░░   ░░░░ ░░  ░ ░░░ ░░░░░░ ░ ░░░░ ░  ░░░░░░   ░░ ░▓▓▓▓ ▓▓▓▓▓ ░░░░░░░ ░░░     ░░░░ ░░░░░░░░     ░░░ ░      ░   ░░░░ ░░░░░░░░░░  ░░░ ▓▓▓▓ ▓▓▓▓ ░ ░░░░░ ░░░   ░░░░░ ░ ░░░░░░░░ ░   ░  ░  ░ ░░ ░░  ░░░░░░░░░░░░░░░░░░░░░▓▓▓▓ ▓▓▓▓░░░░░░░░░░░░░░░░░   ░    ░░ ░░░░░░  ░      ░ ░░░     ░ ░░░░░░░░░░░░░░░░ ▓▓▓▓ ▓▓▓▓ ░  ░░ ░░  ░░░░░░  ░ ░░    ░ ░░░░░         ░░ ░░░░░░░░░ ░░░░░░░░░░░░░ ░ ▓▓▓▓ ▓▓▓▓▓ ░░░       ░░░░░░ ░  ░     ░  ░░░░░ ░░░ ░░░░ ░░░░░░░░ ░░░░░░░░░░░░░░ ░░▓▓▓▓ ▓▓▓▓▓ ░░░░░░ ░  ░░░ ░░ ░ ░░░  ░     ░░░░ ░ ░░░░  ░░░ ░  ░░░  ░░░░░░░░   ░░  ▓▓▓▓ ▓▓▓▓▓░░░  ░░ ░░  ░░░ ░░░░░░░░░░░   ░  ░░░ ░░ ░ ░░░░░    ░ ░░ ░░ ░░░ ░ ░░ ░░░▓▓▓▓ ▓▓▓▓▓▓░░  ░░ ░  ░░░░░░░░░░░░░░░░░░░░░░░ ░  ░░░░       ░░░  ░░░░░░░░ ░░░ ░░ ▓▓▓▓▓ ▓▓▓▓▓▓▓  ░░ ░░░░░░░ ░ ░░░░ ░░░░░ ░░░░░   ░░ ░░░    ░  ░░ ░░░ ░ ░  ░░░░  ░░▓▓▓▓▓▓ ▓▓▓▓▓▓▓  ░░░░░░░░░░░░░░░░░░░ ░   ░░░░░░ ░  ░░ ░    ░  ░     ░  ░░░░  ░░░░░▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ░░░░ ░░░░░      ░░░░░  ░░         ░ ░ ░                  ░ ░ ░ ░▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ░░░░  ░░░░░░░░░░░░░   ░           ░░             ░    ░░     ░▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓      ░░░░  ░  ░░                ░ ░ ░  ░░        ░      ░░ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓    ░░░░░░░░        ░                                 ░░ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ░░░░░░░                     ░░░    ░              ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓       ░           ░         ░  ░     ░          ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░                      ░  ░    ░          ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ░                      ░           ░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ░   ░        ░              ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                   ░  ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/moon ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/neptune ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓       ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░▒▒▒░░░▒▒░░▒░▒░▒░░▒▒░░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░▒░░░░▒░▒░░▒▒▒░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░░░░   ░░░░░░░░░░░░▒░▒░▒░░░▒▒░░▒░▒░░  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒░░▒░▒░▒░░░  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒░▒░▒░░░░▒▒▒░░░░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒░░░░░░░░░░░░░      ░░░░░░░░░░░░░░░░░▒░░▒▒░▒░░░░   ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▒░░░▒░▒░░░░░░                 ░ ░░░░░░░░░░░▒░▒░░░░░░░   ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓░▒░▒░░░░░                           ░░░░░░▒░░▒▒░░░▒░░░░░  ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▒▒▒░░░░░░                              ░░░░▒░▒░▒▒░▒▒░░░░░   ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓░▒▒░░░░░░                                ░ ░░░░░░▒░░▒░░░░░░░   ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▒▒▒░░░░░                                   ░░░░░░▒░░▒░░▒▒░░░░   ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▒▒░░░░░░                                    ░░░░░▒░░░▒░░▒░▒░░░   ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓░░░▒░░░                                       ░░░░░▒░░▒░▒░▒░░░░░   ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▒░▒▒░░                         ░ ░░░░░░░░░     ░░░░░░░▒▒░░░▒░░░    ▓▓▓▓▓▓▓ ▓▓▓▓▓░▒░▒░░░░                          ░░░░░░░░░░░░░░░░░░░▒░▒░▒░░░░░░░░  ▓▓▓▓▓▓▓ ▓▓▓▓▓▒▒▒░░░░░                              ░ ░░░░░░░░░░░ ░▒▒░▒▒▒░░░░░░   ▓▓▓▓▓▓▓ ▓▓▓▓▓▒▒▒▒░░░░                                     ░░░░░░▒░▒▒▒▒▒░░░░░░    ▓▓▓▓▓▓▓ ▓▓▓▓▓░░▒░░░░░░                                   ░░░░░░  ░▒░▒░░░▒░░░░░   ▓▓▓▓▓▓▓ ▓▓▓▓▓░░▒░░░░░░                                   ░░░░░▒▒░░░░░▒▒░▒░▒░░░   ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▒▒▒▒░░░░░░                                ░░░░░░░░░░░▒░▒░░▒░░░░░   ▓▓▓▓▓▓▓ ▓▓▓▓▓▓░▒░░▒░░░░░                            ░ ░░░░░░░░░░░░▒░▒░░░░░░░░   ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓░▒▒░▒▒░░░░░ ░      ░     ░   ░   ░░░░░░░░░░░░▒░░░░░░▒░░░░░░░░     ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓░▒▒░▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░▒░░▒░░░░░░░░░   ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▒▒▒░▒ ░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░▒░░░▒░░░▒░▒░▒░░░░░▒░░ ░    ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░▒░░░▒░░░░░░░░░░░▒░░░░░░░░░░░░▒░▒░░░░░░░░░░    ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▒░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░░▒░▒░░▒░░░▒░▒░░░░░░░░░ ░░   ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓░▒░░░░▒░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░▒░░░░░░░░░░░░░    ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓░░▒░░░▒░░░░░░▒░░░░░░░░░░▒░░░░░░░▒░░░░░▒░░░░░░ ░      ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ░░░░░░░▒░░░▒░░░░░░▒▒░░░░▒░▒░░░▒▒▒░░░░░░░░░  ░     ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░░░░░░▒░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░      ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░▒░░░░░░░░░▒░░░░░░▒░░▒░░▒░░░░░░░ ░    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░ ░░░░░░░░░░░░░░░░░░░▒░░░░░░░       ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓     ░░░░░░░░░░░░░░░░        ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/pluto ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▒▒▒▒▒▒▒░░░░ ░░░▒░▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▒▒▒▒░░ ░░░░░░ ░░░▒▒▒░▒▒▒▒▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒  ░░  ░     ░░░ ░ ░▒▒▒▒▒▒▒▓▓▒▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▓▒▒▒░           ░        ░░▒▒▒▒▒▒▓▓▓▓▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒░                  ░░░▒▒░▒▒▒▒▒▓▓▓▒▓▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒▒▒░░     ▒   ░       ░░░░░░▒▒▒▒▒▓▓▓▓▓▓▓▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▒▒▒░░░▒ ░░▒▒▒░░      ░░░░░░▒▒▒▒▒▓▓▓▓▓▓▓▒▒▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▒▒░▒▒▒▒▒▒▒▒▒▒░░░░░░▒▒░▒▒▒▒▒▒▒▒▓▓▒▓▓▓▓▓▓▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▒▒▓░▒▒▒▒▒▒▒▒░░▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒▓▓▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▒▒▒▒▓▒▒▒▒░░▒ ░░▒▒▒▒▒▓▓▒▓▒▒▒▒▒▒▓▓▓▓▓▓▓▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▒▓▓▓▓▒▓▒▒▒▒▒       ░▒▒▒▓▓▒▓▓▒▒▒▒▒▓▒▒▓▓▓▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▓▓▒▓▓▒▒▒▒ ░         ░▒▓▒▒▒▒▒▒▒░▒▓▒▒▓▓▓▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▓▒▓▓▓▓▒▒▓▓▒░             ░▒░░░▒▒▒▒▒▒▒▓▓▓▒▒▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▓▓▒▒▓▓▓▓▓░░░░          ░░░░ ░▒▒▒▒░▓▒▒▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ░░▒ ▒▓▓▒▓▓▒░         ░░░░░░░▒▒▒▒▒▒▓▓▓▒▒  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒      ▒▒▒ ▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▒▒  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒          ▒▓▒▒▒░░░░░▒▒▒░▒▒▒▒▒▒▒▓▓▓▓▓▒▒  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ▒ ▒   ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓░░▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒  ▒       ▓▓▓▓▒▒▒▒▒▒▒▓▓▓▓▓▓░      ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▒░▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒░▒░     ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▒▒▒▒▒    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓▓▓▒▒▓▒░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/saturn ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒  ▒ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒░▒▒▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒ ▒▒▓▒▓▓▒▓░▓░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒    ░▒▒▒ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░▒▒  ▓▓  ▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░▒ ▓▓▓▓▓▓  ▒▓▓▓▓▒▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒ ▓▓▓▓▓▓▓▒ ▒▒▓▓▒▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ▒▒ ▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▓▒▒ ▒▓▓▒▒▒▓▒░▓▒▓▒▓▒▓░▒▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒░▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒▒░▒▓▓▓▓▓▓▓▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒  ▓▒▒▒ ▒░░░░░▒▒▒▒▒▒▒▒▒▒░░▓▓▓▓▓▓▓▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░  ▒▓▒▒▒▒░▒░░░░▒▒▒▒▒▒▒▒▒▒▒░▒▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▒  ▓▓▒▒▒▒▒░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒     ▓▓▒▒▒░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓  ░▒▒▓▒▒▒▓▒░░░░░▒▒▒▒▒▒▒▓▒▓▒▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▓▓▒  ▒▓▒▒▒▒▒░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓░░▒▒▓▒▒▒ ░▒▒▒▒░▒▒▒▒▒▓▒▒▒▒▒▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒░  ▓ ░▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▓▓▒▓   ▓▒▒▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓░▓░  ▓▓▓▓▓▒▒▒░▒▓▒▒▒▒▓▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒▒▒ ▒▓▒▒▒▓▒▒▒▒▒▓▒▒▒▒▒▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▒▒▒▒▒▓▒▓▓▒▒▒▒░░▓▓▓▓▓ ▓▓ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▒▒▒▒▒▓▓▒▓▒ ▓▓▓▓   ▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▓      ▒▒░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒   ░▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒▒▒▓▓▓▒▒░ ░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒░▒▒▒▒▒░▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▒▒░▒▒ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/uranus ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓       ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░▒▓▓▓▓▓▓▒▒░     ░░▒▒▓▓▓▓░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░▒▓▓▓▓▓▓▒░              ▒▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▓▓▓▓▓▓▓▓░                 ░▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▓▒▓▓▓▒▒▓▓▓░                   ░▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▒▒▓▒▒▒▒▒▒▒▓ ▓▓▓▓▓▓▓▓▓           ▒▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▓▓▒▒▒▒▒▒▒░░░░░  ▓▓▓▓▓▓▓▓▓▓          ▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▒▒▒░▒░░░░░░    ▓▓▓▓▓▓▓▓▓▓          ▒▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▓▓▓▒▒▒░░░░░░░      ▓▓▓▓▓▓▓▓▓▓▓       ▒▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▓▓▒▒▒▒░░░░░░░         ▓▓▓▓▓▓▓▓▓      ▒▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▓▓▒▒▒░░ ░░                ▓▓▓▓    ░▒▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒░░░                    ░▒▒▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒░░▒▒▒░░░░░            ░░░░░░░▒▒▒▒▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒░░  ░▒▒░░░░░░            ░░░░░░░▒▒▒▓▓▓▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒░░   ░▒▒░░░░    ░    ░  ░░░░░░▒▒▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░     ░▒░░░░░░░ ░░░░░░░░░░░░▒▒▒▒▓▓▒▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░      ░▒░░░░░░░░░░░░░░░░▒▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒░       ░░▒▒▒▒░▒░▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒░         ░▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒░░         ░░▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: data/memes/planets/venus ================================================                           ▓   ▓  ░░        ░░░░ ▓  ▓                                                        ▒   ░        ░░░░░░  ░   ▒                                              ▓ ▓▓ ░  ░      ░░░     ░  ░░   ░ ░ ░  ▒    ▓                                   ▓   ░░░  ░      ░     ░ ░             ░░   ░                                      ▓ ░░░   ░      ░  ░ ░ ░         ░ ░  ▒ ░ ░ ░░   ▓▓                            ▓    ░  ░░ ░       ░░   ░                        ░                              ▓░  ░░ ░ ░ ░  ░░  ░  ░ ░░  ░    ░  ░     ░          ▒                         ▓▓ ░   ░  ░    ░  ░ ░    ░      ░ ░ ░    ░     ░ ░░ ░                            ▓  ░  ░  ░░  ░ ▒  ░ ░░░░        ▒  ░░ ░  ░░  ░░   ░         ▓                     ░ ░  ░ ░░░   ░░  ░  ▒▒    ░  ░ ░   ░             ▒ ░  ░  ░                    ▓     ░ ░ ░    ░       ░░      ░░▒     ░░▒▒          ░ ░  ░    ▒                 ▓░   ░    ░  ░     ░  ░   ░   ░             ░  ░░       ▒  ░   ▓                  ░     ░ ░    ░ ░  ░            ░░      ░  ░░░░░         ░                       ░  ░     ░  ░░    ░ ░░   ░    ░ ░     ░░  ░ ▒  ░ ░  ░ ░  ░░    ░             ▓                   ░   ░ ▓░     ░           ░ ░         ░     ░  ░░             ▓ ▓░          ░  ░ ░░    ░  ░ ░  ░░░        ░░░             ░░                      ▒     ░░ ░ ░  ░░  ░ ░ ░░           ░        ░    ░           ░ ░  ▓                         ░   ░░░░ ░  ░ ░  ░               ░                ░░   ▓               ░ ░      ░     ░    ░           ░     ░         ░           ░░                ▓ ▓     ░  ░ ░         ░            ░          ░      ░ ░          ▓                 ░        ░    ░        ░    ░   ░         ░  ░                ▒                 ▓ ░ ░  ░░   ░  ░    ░   ░  ░░   ░       ░                      ▓                 ▓    ░  ░          ░   ░░ ░  ░░  ░ ░   ░░ ░              ░                             ░  ░░     ░ ░░          ░   ░   ░░                ░   ░                         ░    ░  ░  ░ ░    ░    ░ ░ ░░░░ ░                                                ░ ░       ░ ░      ░ ░░  ░   ░ ░   ░       ░░        ░▓                           ▒░░       ░           ░        ░   ░   ░          ░░  ▓                            ░                    ░░        ░    ░░ ░       ░                                   ▒ ░         ░        ░   ░      ░       ░  ░                                       ▒  ░ ░        ░    ░  ░     ░           ▓                                        ▓  ░░  ░        ░░  ░     ░  ░  ░   ░                                             ▓▓    ▒░      ░           ░   ░░   ▓                                                      ▓  ░░░░       ░░   ▓                                ================================================ FILE: developers.md ================================================ # 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 `` 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 `` 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 completely 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 open a [Discussion](https://github.com/nvim-telescope/telescope.nvim/discussions). ### 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/finders.lua sorter = FUNCTION, -- see lua/telescope/sorters.lua previewer = FUNCTION, -- see lua/telescope/previewers/previewer.lua selection_strategy = "reset", -- follow, reset, row border = {}, borderchars = {"─", "│", "─", "│", "┌", "┐", "┘", "└"}, default_selection_index = 1, -- Change the index of the initial selection row } ``` ### 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: doc/secret.txt ================================================ ================================================================================ *telescope.theprimeagen* To The Viewers: ~ Oh why hello, I didn't see you there. So nice of you to join us. The Primeagen must have sent you here. The places you want to look for help are: (you can do `:help ` below) - |telescope.nvim| - |telescope.setup| - |telescope.builtin| - |telescope.layout| - |telescope.actions| I hope you enjoy telescope & Neovim. May your programming always be fun and your vimming be quick. To The Primeagen: ~ Cyrnfr ernq guvf uryc znahny orsber pnyyvat zr ng 3 NZ jvgu gryrfpbcr rzretrapvrf. V xabj ynfg gvzr jr fnirq gur ragver fgernzvat vaqhfgel, ohg V unir n lbhat fba jub xrrcf zr hc ng avtug nyy ol uvzfrys. OGJ, unir lbh pbafvqrerq fraqvat culfvpny QIQf sbe znkvzhz dhnyvgl naq rneyl npprff gb arj pbagrag? Vg frrzf yvxr vg pbhyq or n cerggl pbby vqrn. #FunzryrffFrysCebzbgvba: uggcf://tvguho.pbz/fcbafbef/gwqrievrf vim:tw=78:ts=8:ft=help:norl: ================================================ FILE: doc/telescope.txt ================================================ ================================================================================ INTRODUCTION *telescope.nvim* Telescope.nvim is a plugin for fuzzy finding and neovim. It helps you search, filter, find and pick things in Lua. Getting started with telescope: 1. Run `:checkhealth telescope` to make sure everything is installed. 2. Evaluate it is working with `:Telescope find_files` or `:lua require("telescope.builtin").find_files()` 3. Put a `require("telescope").setup()` call somewhere in your neovim config. 4. Read |telescope.setup| to check what config keys are available and what you can put inside the setup call 5. Read |telescope.builtin| to check which builtin pickers are offered and what options these implement 6. Profit The below flow chart illustrates a simplified telescope architecture: ┌───────────────────────────────────────────────────────────┐ │ ┌────────┐ │ │ │ Multi │ ┌───────+ │ │ │ Select │ ┌───────┐ │ Entry │ │ │ └─────┬──* │ Entry │ ┌────────+ │ Maker │ │ │ │ ┌───│Manager│────│ Sorter │┐ └───┬───* │ │ ▼ ▼ └───────* └────────┘│ │ │ │ 1────────┐ 2───┴──┐ │ │ │ ┌─────│ Picker │ │Finder│◀────┘ │ │ ▼ └───┬────┘ └──────* │ │ ┌────────┐ │ 3────────+ ▲ │ │ │Selected│ └───────│ Prompt │─────────┘ │ │ │ Entry │ └───┬────┘ │ │ └────────* ┌───┴────┐ ┌────────┐ ┌────────┐ │ │ │ ▲ 4─────────┐│ Prompt │ │(Attach)│ │Actions │ │ │ ▼ └──▶ │ Results ││ Buffer │◀─┤Mappings│◀─┤User Fn │ │ │5─────────┐ └─────────┘└────────┘ └────────┘ └────────┘ │ ││Previewer│ │ │└─────────┘ telescope.nvim architecture │ └───────────────────────────────────────────────────────────┘ + The `Entry Maker` at least defines - value: "raw" result of the finder - ordinal: string to be sorted derived from value - display: line representation of entry in results buffer * The finder, entry manager, selected entry, and multi selections comprises `entries` constructed by the `Entry Maker` from raw results of the finder (`value`s) Primary components: 1 Picker: central UI dedicated to varying use cases (finding files, grepping, diagnostics, etc.) see :h telescope.builtin 2 Finder: pipe or interactively generates results to pick over 3 Prompt: user input that triggers the finder which sorts results in order into the entry manager 4 Results: listed entries scored by sorter from finder results 5 Previewer: preview of context of selected entry see :h telescope.previewers A practical introduction into telescope customization is our `developers.md` (top-level of repo) and `:h telescope.actions` that showcase how to access information about the state of the picker (current selection, etc.). To find out more: https://github.com/nvim-telescope/telescope.nvim :h telescope.setup :h telescope.command :h telescope.builtin :h telescope.themes :h telescope.layout :h telescope.resolve :h telescope.actions :h telescope.actions.state :h telescope.actions.set :h telescope.actions.utils :h telescope.actions.generate :h telescope.actions.history :h telescope.previewers telescope.setup({opts}) *telescope.setup()* Setup function to be run by user. Configures the defaults, pickers and extensions of telescope. Usage: > require('telescope').setup{ defaults = { -- Default configuration for telescope goes here: -- config_key = value, -- .. }, 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 } } < Valid keys for {opts.defaults} *telescope.defaults.sorting_strategy* sorting_strategy: ~ Determines the direction "better" results are sorted towards. Available options are: - "descending" (default) - "ascending" *telescope.defaults.selection_strategy* selection_strategy: ~ Determines how the cursor acts after each sort iteration. Available options are: - "reset" (default) - "follow" - "row" - "closest" - "none" *telescope.defaults.scroll_strategy* scroll_strategy: ~ Determines what happens if you try to scroll past the view of the picker. Available options are: - "cycle" (default) - "limit" *telescope.defaults.layout_strategy* layout_strategy: ~ Determines the default layout of Telescope pickers. See |telescope.layout| for details of the available strategies. Default: 'horizontal' *telescope.defaults.create_layout* create_layout: ~ Configure the layout of Telescope pickers. See |telescope.pickers.layout| for details. Default: 'nil' *telescope.defaults.layout_config* layout_config: ~ Determines the default configuration values for layout strategies. See |telescope.layout| for details of the configurations options for each strategy. Allows setting defaults for all strategies as top level options and for overriding for specific options. For example, the default values below set the default width to 80% of the screen width for all strategies except 'center', which has width of 50% of the screen width. Default: { bottom_pane = { height = 25, preview_cutoff = 120, prompt_position = "top" }, center = { height = 0.4, preview_cutoff = 40, prompt_position = "top", width = 0.5 }, cursor = { height = 0.9, preview_cutoff = 40, width = 0.8 }, horizontal = { height = 0.9, preview_cutoff = 120, prompt_position = "bottom", width = 0.8 }, vertical = { height = 0.9, preview_cutoff = 40, prompt_position = "bottom", width = 0.8 } } *telescope.defaults.cycle_layout_list* cycle_layout_list: ~ Determines the layouts to cycle through when using `actions.layout.cycle_layout_next` and `actions.layout.cycle_layout_prev`. Should be a list of "layout setups". Each "layout setup" can take one of two forms: 1. string This is interpreted as the name of a `layout_strategy` 2. table A table with possible keys `layout_strategy`, `layout_config` and `previewer` Default: { "horizontal", "vertical" } *telescope.defaults.winblend* winblend: ~ Configure winblend for telescope floating windows. See |winblend| for more information. Type can be a number or a function returning a number Default: function() return vim.o.winblend end *telescope.defaults.wrap_results* wrap_results: ~ Word wrap the search results Default: false *telescope.defaults.prompt_prefix* prompt_prefix: ~ The character(s) that will be shown in front of Telescope's prompt. Default: '> ' *telescope.defaults.selection_caret* selection_caret: ~ The character(s) that will be shown in front of the current selection. Default: '> ' *telescope.defaults.entry_prefix* entry_prefix: ~ Prefix in front of each result entry. Current selection not included. Default: ' ' *telescope.defaults.multi_icon* multi_icon: ~ Symbol to add in front of a multi-selected result entry. Replaces final character of |telescope.defaults.selection_caret| and |telescope.defaults.entry_prefix| as appropriate. To have no icon, set to the empty string. Default: '+' *telescope.defaults.initial_mode* initial_mode: ~ Determines in which mode telescope starts. Valid Keys: `insert` and `normal`. Default: "insert" *telescope.defaults.border* border: ~ Boolean defining if borders are added to Telescope windows. Default: true *telescope.defaults.path_display* path_display: ~ Determines how file paths are displayed. path_display can be set to an array with a combination of: - "hidden" hide file names - "tail" only display the file name, and not the path - "absolute" display absolute paths - "smart" remove as much from the path as possible to only show the difference between the displayed paths. Warning: The nature of the algorithm might have a negative performance impact! - "shorten" only display the first character of each directory in the path - "truncate" truncates the start of the path when the whole path will not fit. To increase the gap between the path and the edge, set truncate to number `truncate = 3` - "filename_first" shows filenames first and then the directories You can also specify the number of characters of each directory name to keep by setting `path_display.shorten = num`. e.g. for a path like `alpha/beta/gamma/delta.txt` setting `path_display.shorten = 1` will give a path like: `a/b/g/delta.txt` Similarly, `path_display.shorten = 2` will give a path like: `al/be/ga/delta.txt` You can also further customise the shortening behaviour by setting `path_display.shorten = { len = num, exclude = list }`, where `len` acts as above, and `exclude` is a list of positions that are not shortened. Negative numbers in the list are considered relative to the end of the path. e.g. for a path like `alpha/beta/gamma/delta.txt` setting `path_display.shorten = { len = 1, exclude = {1, -1} }` will give a path like: `alpha/b/g/delta.txt` setting `path_display.shorten = { len = 2, exclude = {2, -2} }` will give a path like: `al/beta/gamma/de` path_display can also be set to 'filename_first' to put the filename in front. path_display = { "filename_first" }, The directory structure can be reversed as follows: path_display = { filename_first = { reverse_directories = true } }, path_display can also be set to 'hidden' string to hide file names path_display can also be set to a function for custom formatting of the path display with the following signature Signature: fun(opts: table, path: string): string, table? The optional table is an list of positions and highlight groups to set the highlighting of the return path string. Example: -- Format path as "file.txt (path\to\file\)" path_display = function(opts, path) local tail = require("telescope.utils").path_tail(path) return string.format("%s (%s)", tail, path) end, -- Format path and add custom highlighting path_display = function(opts, path) local tail = require("telescope.utils").path_tail(path) path = string.format("%s (%s)", tail, path) local highlights = { { { 0, -- highlight start position #path, -- highlight end position }, "Comment", -- highlight group name }, } return path, highlights end Default: {} *telescope.defaults.borderchars* borderchars: ~ Set the borderchars of telescope floating windows. It has to be a table of 8 string values. Default: { "─", "│", "─", "│", "╭", "╮", "╯", "╰" } *telescope.defaults.get_status_text* get_status_text: ~ A function that determines what the virtual text looks like. Signature: function(picker) -> str Default: function that shows current count / all *telescope.defaults.hl_result_eol* hl_result_eol: ~ Changes if the highlight for the selected item in the results window is always the full width of the window Default: true *telescope.defaults.dynamic_preview_title* dynamic_preview_title: ~ Will change the title of the preview window dynamically, where it is supported. For example, the preview window's title could show up as the full filename. Default: false *telescope.defaults.results_title* results_title: ~ Defines the default title of the results window. A false value can be used to hide the title altogether. Default: "Results" *telescope.defaults.prompt_title* prompt_title: ~ Defines the default title of the prompt window. A false value can be used to hide the title altogether. Most of the times builtins define a prompt_title which will be preferred over this default. Default: "Prompt" *telescope.defaults.mappings* mappings: ~ Your mappings to override telescope's default mappings. See: ~ |telescope.mappings| *telescope.defaults.default_mappings* default_mappings: ~ Not recommended to use except for advanced users. Will allow you to completely remove all of telescope's default maps and use your own. Default: nil *telescope.defaults.history* history: ~ This field handles the configuration for prompt history. By default it is a table, with default values (more below). To disable history, set it to false. Currently mappings still need to be added, Example: mappings = { i = { [""] = require('telescope.actions').cycle_history_next, [""] = require('telescope.actions').cycle_history_prev, }, }, Fields: - path: The path to the telescope history as string. Default: stdpath("data")/telescope_history - limit: The amount of entries that will be written in the history. Warning: If limit is set to nil it will grow unbound. Default: 100 - handler: A lua function that implements the history. This is meant as a developer setting for extensions to override the history handling, e.g., https://github.com/nvim-telescope/telescope-smart-history.nvim, which allows context sensitive (cwd + picker) history. Default: require('telescope.actions.history').get_simple_history - cycle_wrap: Indicates whether the cycle_history_next and cycle_history_prev functions should wrap around to the beginning or end of the history entries on reaching their respective ends Default: false *telescope.defaults.cache_picker* cache_picker: ~ This field handles the configuration for picker caching. By default it is a table, with default values (more below). To disable caching, set it to false. Caching preserves all previous multi selections and results and therefore may result in slowdown or increased RAM occupation if too many pickers (`cache_picker.num_pickers`) or entries ('cache_picker.limit_entries`) are cached. Fields: - num_pickers: The number of pickers to be cached. Set to -1 to preserve all pickers of your session. If passed to a picker, the cached pickers with indices larger than `cache_picker.num_pickers` will be cleared. Default: 1 - limit_entries: The amount of entries that will be saved for each picker. Default: 1000 - ignore_empty_prompt: If true, the picker will not be cached if the prompt is empty (i.e., no text has been typed at the time of closing the prompt). Default: false *telescope.defaults.preview* preview: ~ This field handles the global configuration for previewers. By default it is a table, with default values (more below). To disable previewing, set it to false. If you have disabled previewers globally, but want to opt in to previewing for single pickers, you will have to pass `preview = true` or `preview = {...}` (your config) to the `opts` of your picker. Fields: - check_mime_type: Use `file` if available to try to infer whether the file to preview is a binary if filetype detection fails. Windows users get `file` from: https://github.com/julian-r/file-windows Set to false to attempt to preview any mime type. Default: true for all OS excl. Windows - filesize_limit: The maximum file size in MB attempted to be previewed. Set to false to attempt to preview any file size. Default: 25 - highlight_limit: The maximum file size in MB attempted to be highlighted. Set to false to attempt to highlight any file size. Default: 1 - timeout: Timeout the previewer if the preview did not complete within `timeout` milliseconds. Set to false to not timeout preview. Default: 250 - hook(s): Function(s) that takes `(filepath, bufnr, opts)`, where opts exposes winid and ft (filetype). Available hooks (in order of priority): {filetype, mime, filesize, timeout}_hook Important: the filetype_hook must return true or false to indicate whether to continue (true) previewing or not (false), respectively. Two examples: local putils = require("telescope.previewers.utils") ... -- preview is called in telescope.setup { ... } preview = { -- 1) Do not show previewer for certain files filetype_hook = function(filepath, bufnr, opts) -- you could analogously check opts.ft for filetypes local excluded = vim.tbl_filter(function(ending) return filepath:match(ending) end, { ".*%.csv", ".*%.toml", }) if not vim.tbl_isempty(excluded) then putils.set_preview_message( bufnr, opts.winid, string.format("I don't like %s files!", excluded[1]:sub(5, -1)) ) return false end return true end, -- 2) Truncate lines to preview window for too large files filesize_hook = function(filepath, bufnr, opts) local path = require("plenary.path"):new(filepath) -- opts exposes winid local height = vim.api.nvim_win_get_height(opts.winid) local lines = vim.split(path:head(height), "[\r]?\n") vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) end, } The configuration recipes for relevant examples. Note: we use vim.filetype filetype detection, so if you have troubles with files not highlighting correctly, please read |vim.filetype| Default: nil - treesitter: Determines whether the previewer performs treesitter highlighting, which falls back to regex-based highlighting. `true`: treesitter highlighting for all available filetypes `false`: regex-based highlighting for all filetypes `table`: may contain the following keys: - enable boolean|table: if boolean, enable ts highlighting for all supported filetypes. if table, ts highlighting is only enabled for given filetypes. - disable table: list of filetypes for which ts highlighting is not used if `enable = true`. Default: true - msg_bg_fillchar: Character to fill background of unpreviewable buffers with Default: "╱" - hide_on_startup: Hide previewer when picker starts. Previewer can be toggled with actions.layout.toggle_preview. Default: false - ls_short: Determines whether to use the `--short` flag for the `ls` command when previewing directories. Otherwise will result to using `--long`. Default: false *telescope.defaults.vimgrep_arguments* vimgrep_arguments: ~ Defines the command that will be used for `live_grep` and `grep_string` pickers. Hint: Make sure that color is currently set to `never` because we do not yet interpret color codes Hint 2: Make sure that these options are in your changes arguments: "--no-heading", "--with-filename", "--line-number", "--column" because we need them so the ripgrep output is in the correct format. Default: { "rg", "--color=never", "--no-heading", "--with-filename", "--line-number", "--column", "--smart-case" } *telescope.defaults.use_less* use_less: ~ Boolean if less should be enabled in term_previewer (deprecated and currently no longer used in the builtin pickers). Default: true *telescope.defaults.set_env* set_env: ~ Set an environment for term_previewer. A table of key values: Example: { COLORTERM = "truecolor", ... } Hint: Empty table is not allowed. Default: nil *telescope.defaults.color_devicons* color_devicons: ~ Boolean if devicons should be enabled or not. If set to false, the text highlight group is used. Hint: Coloring only works if |termguicolors| is enabled. Default: true *telescope.defaults.file_sorter* file_sorter: ~ A function pointer that specifies the file_sorter. This sorter will be used for find_files, git_files and similar. Hint: If you load a native sorter, you don't need to change this value, the native sorter will override it anyway. Default: require("telescope.sorters").get_fzy_sorter *telescope.defaults.generic_sorter* generic_sorter: ~ A function pointer to the generic sorter. The sorter that should be used for everything that is not a file. Hint: If you load a native sorter, you don't need to change this value, the native sorter will override it anyway. Default: require("telescope.sorters").get_fzy_sorter *telescope.defaults.prefilter_sorter* prefilter_sorter: ~ This points to a wrapper sorter around the generic_sorter that is able to do prefiltering. It's usually used for lsp_*_symbols and lsp_*_diagnostics Default: require("telescope.sorters").prefilter *telescope.defaults.tiebreak* tiebreak: ~ A function that determines how to break a tie when two entries have the same score. Having a function that always returns false would keep the entries in the order they are found, so existing_entry before current_entry. Vice versa always returning true would place the current_entry before the existing_entry. Signature: function(current_entry, existing_entry, prompt) -> boolean Default: function that breaks the tie based on the length of the entry's ordinal *telescope.defaults.file_ignore_patterns* file_ignore_patterns: ~ A table of lua regex that define the files that should be ignored. Example: { "^scratch/" } -- ignore all files in scratch directory Example: { "%.npz" } -- ignore all npz files See: https://www.lua.org/manual/5.1/manual.html#5.4.1 for more information about lua regex Note: `file_ignore_patterns` will be used in all pickers that have a file associated. This might lead to the problem that lsp_ pickers aren't displaying results because they might be ignored by `file_ignore_patterns`. For example, setting up node_modules as ignored will never show node_modules in any results, even if you are interested in lsp_ results. If you only want `file_ignore_patterns` for `find_files` and `grep_string`/`live_grep` it is suggested that you setup `gitignore` and have fd and or ripgrep installed because both tools will not show `gitignore`d files on default. Default: nil *telescope.defaults.get_selection_window* get_selection_window: ~ Function that takes function(picker, entry) and returns a window id. The window ID will be used to decide what window the chosen file will be opened in and the cursor placed in upon leaving the picker. Default: `function() return 0 end` *telescope.defaults.git_worktrees* git_worktrees: ~ A table of arrays of detached working trees with keys `gitdir` and `toplevel`. Used to pass `--git-dir` and `--work-tree` flags to git commands when telescope fails to infer the top-level directory of a given working tree based on cwd. Example: git_worktrees = { { toplevel = vim.env.HOME, gitdir = vim.env.HOME .. '/.cfg' } } Default: nil *telescope.defaults.file_previewer* file_previewer: ~ Function pointer to the default file_previewer. It is mostly used for find_files, git_files and similar. You can change this function pointer to either use your own previewer or use the command-line program bat as the previewer: require("telescope.previewers").cat.new Default: require("telescope.previewers").vim_buffer_cat.new *telescope.defaults.grep_previewer* grep_previewer: ~ Function pointer to the default vim_grep previewer. It is mostly used for live_grep, grep_string and similar. You can change this function pointer to either use your own previewer or use the command-line program bat as the previewer: require("telescope.previewers").vimgrep.new Default: require("telescope.previewers").vim_buffer_vimgrep.new *telescope.defaults.qflist_previewer* qflist_previewer: ~ Function pointer to the default qflist previewer. It is mostly used for qflist, loclist and lsp. You can change this function pointer to either use your own previewer or use the command-line program bat as the previewer: require("telescope.previewers").qflist.new Default: require("telescope.previewers").vim_buffer_qflist.new *telescope.defaults.buffer_previewer_maker* buffer_previewer_maker: ~ Developer option that defines the underlining functionality of the buffer previewer. For interesting configuration examples take a look at https://github.com/nvim-telescope/telescope.nvim/wiki/Configuration-Recipes Default: require("telescope.previewers").buffer_previewer_maker Parameters: ~ {opts} (table) Configuration opts. Keys: defaults, pickers, extensions telescope.load_extension({name}) *telescope.load_extension()* Load an extension. - Notes: - Loading triggers ext setup via the config passed in |telescope.setup| Parameters: ~ {name} (string) Name of the extension telescope.register_extension({mod}) *telescope.register_extension()* Register an extension. To be used by plugin authors. Parameters: ~ {mod} (table) Module telescope.extensions() *telescope.extensions()* Use telescope.extensions to reference any extensions within your configuration. While the docs currently generate this as a function, it's actually a table. Sorry. ================================================================================ COMMAND *telescope.command* 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: `require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})` If you want to use this api from a vim file you should prepend `lua` to the command, as below: `lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})` If you want to use this api from a neovim command line you should prepend `:lua` to the command, as below: `:lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})` 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: `:Telescope find_files hidden=true layout_config={"prompt_position":"top"}` 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: `:Telescope find_files layout_config={ "prompt_position" : "top" }` as there are spaces in the option. ================================================================================ BUILTIN *telescope.builtin* 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: > :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 }) < builtin.live_grep({opts}) *telescope.builtin.live_grep()* Search for a string and get results live as you type, respects .gitignore Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) {grep_open_files} (boolean) if true, restrict search to open files only, mutually exclusive with `search_dirs` {search_dirs} (table) directory/directories/files to search, mutually exclusive with `grep_open_files` {glob_pattern} (string|table) argument to be used with `--glob`, e.g. "*.toml", can use the opposite "!*.toml" {type_filter} (string) argument to be used with `--type`, e.g. "rust", see `rg --type-list` {additional_args} (function|table) additional arguments to be passed on. Can be fn(opts) -> tbl {max_results} (number) define a upper result value {disable_coordinates} (boolean) don't show the line & row numbers (default: false) {file_encoding} (string) file encoding for the entry & previewer builtin.grep_string({opts}) *telescope.builtin.grep_string()* Searches for the string under your cursor or the visual selection in your current working directory Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) {search} (string) the query to search {grep_open_files} (boolean) if true, restrict search to open files only, mutually exclusive with `search_dirs` {search_dirs} (table) directory/directories/files to search, mutually exclusive with `grep_open_files` {use_regex} (boolean) if true, special characters won't be escaped, allows for using regex (default: false) {word_match} (string) can be set to `-w` to enable exact word matches {additional_args} (function|table) additional arguments to be passed on. Can be fn(opts) -> tbl {disable_coordinates} (boolean) don't show the line and row numbers (default: false) {only_sort_text} (boolean) only sort the text, not the file, line or row (default: false) {file_encoding} (string) file encoding for the entry & previewer builtin.find_files({opts}) *telescope.builtin.find_files()* Search for files (respecting .gitignore) Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) {find_command} (function|table) cmd to use for the search. Can be a fn(opts) -> tbl (default: autodetect) {file_entry_encoding} (string) encoding of output of `find_command` {follow} (boolean) if true, follows symlinks (i.e. uses `-L` flag for the `find` command) (default: false) {hidden} (boolean) determines whether to show hidden files or not (default: false) {no_ignore} (boolean) show files ignored by .gitignore, .ignore, etc. (default: false) {no_ignore_parent} (boolean) show files ignored by .gitignore, .ignore, etc. in parent dirs. (default: false) {search_dirs} (table) directory/directories/files to search {search_file} (string) specify a filename to search for {file_encoding} (string) file encoding for the previewer builtin.fd() *telescope.builtin.fd()* This is an alias for the `find_files` picker builtin.treesitter() *telescope.builtin.treesitter()* Lists function names, variables, and other symbols from treesitter queries Requires parser and `locals` queries for the current buffer in `runtimepath`, see |vim.treesitter-parsers|, |vim.treesitter.query|, and https://github.com/nvim-treesitter/nvim-treesitter/blob/main/CONTRIBUTING.md#locals - Default keymaps: - ``: show autocompletion menu to prefilter your query by kind of ts node you want to see (i.e. `:var:`) Options: ~ {show_line} (boolean) if true, shows the row:column that the result is found at (default: true) {bufnr} (number) specify the buffer number where treesitter should run. (default: current buffer) {symbol_width} (number) defines the width of the symbol section (default: 25) {symbols} (string|table) filter results by symbol kind(s) {ignore_symbols} (string|table) list of symbols to ignore {symbol_highlights} (table) string -> string. Matches symbol with hl_group {file_encoding} (string) file encoding for the previewer builtin.current_buffer_fuzzy_find({opts}) *telescope.builtin.current_buffer_fuzzy_find()* Live fuzzy search inside of the currently open buffer Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {skip_empty_lines} (boolean) if true we don't display empty lines (default: false) {results_ts_highlight} (boolean) highlight result entries with treesitter (default: true) {file_encoding} (string) file encoding for the previewer builtin.tags({opts}) *telescope.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) Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) {ctags_file} (string) specify a particular ctags file to use {show_line} (boolean) if true, shows the content of the line the tag is found on in the picker (default: true) {show_kind} (boolean) if true and kind info is available, show the kind of the tag (default: true) {only_sort_tags} (boolean) if true we will only sort tags (default: false) {fname_width} (number) defines the width of the filename section (default: 30) builtin.current_buffer_tags({opts}) *telescope.builtin.current_buffer_tags()* Lists all of the tags for the currently open buffer, with a preview Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) {ctags_file} (string) specify a particular ctags file to use {show_line} (boolean) if true, shows the content of the line the tag is found on in the picker (default: true) {show_kind} (boolean) if true and kind info is available, show the kind of the tag (default: true) {only_sort_tags} (boolean) if true we will only sort tags (default: false) {fname_width} (number) defines the width of the filename section (default: 30) builtin.git_files({opts}) *telescope.builtin.git_files()* Fuzzy search for files tracked by Git. This command lists the output of the `git ls-files` command, respects .gitignore - Default keymaps: - ``: opens the currently selected file Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify the path of the repo {use_file_path} (boolean) if we should use the current buffer git root (default: false) {use_git_root} (boolean) if we should use git root as cwd or the cwd (important for submodule) (default: true) {show_untracked} (boolean) if true, adds `--others` flag to command and shows untracked files (default: false) {recurse_submodules} (boolean) if true, adds the `--recurse-submodules` flag to command (default: false) {git_command} (table) command that will be executed. {"git","ls-files","--exclude-standard","--cached"} {file_encoding} (string) file encoding for the previewer builtin.git_commits({opts}) *telescope.builtin.git_commits()* Lists commits for current directory with diff preview - Default keymaps: - ``: checks out the currently selected commit - `m`: resets current branch to selected commit using mixed mode - `s`: resets current branch to selected commit using soft mode - `h`: resets current branch to selected commit using hard mode Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify the path of the repo {use_file_path} (boolean) if we should use the current buffer git root (default: false) {use_git_root} (boolean) if we should use git root as cwd or the cwd (important for submodule) (default: true) {git_command} (table) command that will be executed. {"git","log","--pretty=oneline","--abbrev-commit","--","."} builtin.git_bcommits({opts}) *telescope.builtin.git_bcommits()* Lists commits for current buffer with diff preview - Default keymaps or your overridden `select_` keys: - ``: checks out the currently selected commit - ``: opens a diff in a vertical split - ``: opens a diff in a horizontal split - ``: opens a diff in a new tab Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify the path of the repo {use_file_path} (boolean) if we should use the current buffer git root (default: false) {use_git_root} (boolean) if we should use git root as cwd or the cwd (important for submodule) (default: true) {current_file} (string) specify the current file that should be used for bcommits (default: current buffer) {git_command} (table) command that will be executed. {"git","log","--pretty=oneline","--abbrev-commit"} builtin.git_bcommits_range({opts}) *telescope.builtin.git_bcommits_range()* Lists commits for a range of lines in the current buffer with diff preview In visual mode, lists commits for the selected lines With operator mode enabled, lists commits inside the text object/motion - Default keymaps or your overridden `select_` keys: - ``: checks out the currently selected commit - ``: opens a diff in a vertical split - ``: opens a diff in a horizontal split - ``: opens a diff in a new tab Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify the path of the repo {use_git_root} (boolean) if we should use git root as cwd or the cwd (important for submodule) (default: true) {current_file} (string) specify the current file that should be used for bcommits (default: current buffer) {git_command} (table) command that will be executed. the last element must be "-L". {"git","log","--pretty=oneline","--abbrev-commit","--no-patch","-L"} {from} (number) the first line number in the range (default: current line) {to} (number) the last line number in the range (default: the value of `from`) {operator} (boolean) select lines in operator-pending mode (default: false) builtin.git_branches({opts}) *telescope.builtin.git_branches()* List branches for current directory, with output from `git log --oneline` shown in the preview window - Default keymaps: - ``: checks out the currently selected branch - ``: tracks currently selected branch - ``: rebases currently selected branch - ``: creates a new branch, with confirmation prompt before creation - ``: deletes the currently selected branch, with confirmation prompt before deletion - ``: merges the currently selected branch, with confirmation prompt before deletion Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify the path of the repo {use_file_path} (boolean) if we should use the current buffer git root (default: false) {use_git_root} (boolean) if we should use git root as cwd or the cwd (important for submodule) (default: true) {show_remote_tracking_branches} (boolean) show remote tracking branches like origin/main (default: true) {pattern} (string) specify the pattern to match all refs builtin.git_status({opts}) *telescope.builtin.git_status()* Lists git status for current directory - Default keymaps: - ``: stages or unstages the currently selected file - ``: opens the currently selected file Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify the path of the repo {use_file_path} (boolean) if we should use the current buffer git root (default: false) {use_git_root} (boolean) if we should use git root as cwd or the cwd (important for submodule) (default: true) {git_icons} (table) string -> string. Matches name with icon (see source code, make_entry.lua git_icon_defaults) {expand_dir} (boolean) pass flag `-uall` to show files in untracked directories (default: true) builtin.git_stash({opts}) *telescope.builtin.git_stash()* Lists stash items in current repository - Default keymaps: - ``: runs `git apply` for currently selected stash Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify the path of the repo {use_file_path} (boolean) if we should use the current buffer git root (default: false) {use_git_root} (boolean) if we should use git root as cwd or the cwd (important for submodule) (default: true) {show_branch} (boolean) if we should display the branch name for git stash entries (default: true) builtin.builtin({opts}) *telescope.builtin.builtin()* Lists all of the community maintained pickers built into Telescope Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {include_extensions} (boolean) if true will show the pickers of the installed extensions (default: false) {use_default_opts} (boolean) if the selected picker should use its default options (default: false) builtin.resume({opts}) *telescope.builtin.resume()* 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| Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cache_index} (number) what picker to resume, where 1 denotes most recent (default: 1) builtin.pickers({opts}) *telescope.builtin.pickers()* Opens a picker over previously cached pickers in their preserved states (incl. multi selections) - Default keymaps: - ``: delete the selected cached picker - Notes: - Requires `cache_picker` in setup or when having invoked pickers, see |telescope.defaults.cache_picker| Parameters: ~ {opts} (table) options to pass to the picker builtin.planets({opts}) *telescope.builtin.planets()* Use the telescope... Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_pluto} (boolean) we love Pluto (default: false, because its a hidden feature) {show_moon} (boolean) we love the Moon (default: false, because its a hidden feature) builtin.symbols({opts}) *telescope.builtin.symbols()* 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. Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {symbol_path} (string) specify the second path. Default: `stdpath("data")/telescope/symbols/*.json` {sources} (table) specify a table of sources you want to load this time builtin.commands({opts}) *telescope.builtin.commands()* Lists available plugin/user commands and runs them on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_buf_command} (boolean) show buf local command (Default: true) builtin.quickfix({opts}) *telescope.builtin.quickfix()* Lists items in the quickfix list, jumps to location on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) {nr} (number) specify the quickfix list number builtin.quickfixhistory({opts}) *telescope.builtin.quickfixhistory()* 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 Parameters: ~ {opts} (table) options to pass to the picker builtin.loclist({opts}) *telescope.builtin.loclist()* Lists items from the current window's location list, jumps to location on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) builtin.oldfiles({opts}) *telescope.builtin.oldfiles()* Lists previously open files, opens on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify a working directory to filter oldfiles by {only_cwd} (boolean) show only files in the cwd (default: false) {cwd_only} (boolean) alias for only_cwd {file_encoding} (string) file encoding for the previewer builtin.command_history({opts}) *telescope.builtin.command_history()* Lists commands that were executed recently, and reruns them on `` - Default keymaps: - ``: open the command line with the text of the currently selected result populated in it Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {filter_fn} (function) filter fn(cmd:string). true if the history command should be presented. builtin.search_history({opts}) *telescope.builtin.search_history()* Lists searches that were executed recently, and reruns them on `` - Default keymaps: - ``: open a search window with the text of the currently selected search result populated in it Parameters: ~ {opts} (table) options to pass to the picker builtin.vim_options({opts}) *telescope.builtin.vim_options()* Lists vim options, allows you to edit the current value on `` Parameters: ~ {opts} (table) options to pass to the picker builtin.help_tags({opts}) *telescope.builtin.help_tags()* Lists available help tags and opens a new window with the relevant help info on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {lang} (string) specify language (default: vim.o.helplang) {fallback} (boolean) fallback to en if language isn't installed (default: true) builtin.man_pages({opts}) *telescope.builtin.man_pages()* Lists manpage entries, opens them in a help window on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {sections} (table) a list of sections to search, use `{ "ALL" }` to search in all sections (default: { "1" }) {man_cmd} (function) that returns the man command. (Default: `apropos ""` on linux, `apropos " "` on macos) builtin.reloader({opts}) *telescope.builtin.reloader()* Lists lua modules and reloads them on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {column_len} (number) define the max column len for the module name (default: dynamic, longest module name) builtin.buffers({opts}) *telescope.builtin.buffers()* Lists open buffers in current neovim instance, opens selected buffer on `` - Default keymaps: - ``: delete the currently selected buffer Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {cwd} (string) specify a working directory to filter buffers list by {show_all_buffers} (boolean) if true, show all buffers, including unloaded buffers (default: true) {ignore_current_buffer} (boolean) if true, don't show the current buffer in the list (default: false) {only_cwd} (boolean) if true, only show buffers in the current working directory (default: false) {cwd_only} (boolean) alias for only_cwd {sort_lastused} (boolean) Sorts current and last buffer to the top and selects the lastused (default: false) {sort_mru} (boolean) Sorts all buffers after most recent used. Not just the current and last one (default: false) {bufnr_width} (number) Defines the width of the buffer numbers in front of the filenames (default: dynamic) {file_encoding} (string) file encoding for the previewer {sort_buffers} (function) sort fn(bufnr_a, bufnr_b). true if bufnr_a should go first. Runs after sorting by most recent (if specified) {select_current} (boolean) select current buffer (default: false) builtin.colorscheme({opts}) *telescope.builtin.colorscheme()* Lists available colorschemes and applies them on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {colors} (table) a list of additional colorschemes to explicitly make available to telescope (default: {}) {enable_preview} (boolean) if true, will preview the selected color {ignore_builtins} (boolean) if true, builtin colorschemes are not listed builtin.marks({opts}) *telescope.builtin.marks()* Lists vim marks and their value, jumps to the mark on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {file_encoding} (string) file encoding for the previewer {mark_type} (string) filter marks by type (default: "all", options: "all"|"global"|"local") builtin.registers({opts}) *telescope.builtin.registers()* Lists vim registers, pastes the contents of the register on `` - Default keymaps: - ``: edit the contents of the currently selected register Parameters: ~ {opts} (table) options to pass to the picker builtin.keymaps({opts}) *telescope.builtin.keymaps()* Lists normal mode keymappings, runs the selected keymap on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {modes} (table) a list of short-named keymap modes to search (default: { "n", "i", "c", "x" }) {show_plug} (boolean) if true, the keymaps for which the lhs contains "" are also shown (default: true) {only_buf} (boolean) if true, only show the buffer-local keymaps (default: false) {lhs_filter} (function) filter(lhs:string) -> boolean. true for keymap.lhs if the keymap should be shown (optional) {filter} (function) filter(km:keymap) -> boolean. true for the keymap if it should be shown (optional) builtin.filetypes({opts}) *telescope.builtin.filetypes()* Lists all available filetypes, sets currently open buffer's filetype to selected filetype in Telescope on `` Parameters: ~ {opts} (table) options to pass to the picker builtin.highlights({opts}) *telescope.builtin.highlights()* Lists all available highlights Parameters: ~ {opts} (table) options to pass to the picker builtin.autocommands({opts}) *telescope.builtin.autocommands()* Lists vim autocommands and goes to their declaration on `` Parameters: ~ {opts} (table) options to pass to the picker builtin.spell_suggest({opts}) *telescope.builtin.spell_suggest()* Lists spelling suggestions for the current word under the cursor, replaces word with selected suggestion on `` Parameters: ~ {opts} (table) options to pass to the picker builtin.tagstack({opts}) *telescope.builtin.tagstack()* Lists the tag stack for the current window, jumps to tag on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) builtin.jumplist({opts}) *telescope.builtin.jumplist()* Lists items from Vim's jumplist, jumps to location on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) builtin.lsp_references({opts}) *telescope.builtin.lsp_references()* Lists LSP references for word under the cursor, jumps to reference on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {include_declaration} (boolean) include symbol declaration in the lsp references (default: true) {include_current_line} (boolean) include current line (default: false) {jump_type} (string) how to goto reference if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never" {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) {reuse_win} (boolean) jump to existing window if buffer is already opened (default: false) {file_encoding} (string) file encoding for the previewer builtin.lsp_incoming_calls({opts}) *telescope.builtin.lsp_incoming_calls()* Lists LSP incoming calls for word under the cursor, jumps to reference on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) {file_encoding} (string) file encoding for the previewer builtin.lsp_outgoing_calls({opts}) *telescope.builtin.lsp_outgoing_calls()* Lists LSP outgoing calls for word under the cursor, jumps to reference on `` Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) {file_encoding} (string) file encoding for the previewer builtin.lsp_definitions({opts}) *telescope.builtin.lsp_definitions()* Goto the definition of the word under the cursor, if there's only one, otherwise show all options in Telescope Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {jump_type} (string) how to goto definition if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never" {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) {reuse_win} (boolean) jump to existing window if buffer is already opened (default: false) {file_encoding} (string) file encoding for the previewer builtin.lsp_type_definitions({opts}) *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 Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {jump_type} (string) how to goto definition if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never" {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) {reuse_win} (boolean) jump to existing window if buffer is already opened (default: false) {file_encoding} (string) file encoding for the previewer builtin.lsp_implementations({opts}) *telescope.builtin.lsp_implementations()* Goto the implementation of the word under the cursor if there's only one, otherwise show all options in Telescope Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {jump_type} (string) how to goto implementation if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never" {show_line} (boolean) show results text (default: true) {trim_text} (boolean) trim results text (default: false) {reuse_win} (boolean) jump to existing window if buffer is already opened (default: false) {file_encoding} (string) file encoding for the previewer builtin.lsp_document_symbols({opts}) *telescope.builtin.lsp_document_symbols()* Lists LSP document symbols in the current buffer - Default keymaps: - ``: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`) Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {fname_width} (number) defines the width of the filename section (default: 30) {symbol_width} (number) defines the width of the symbol section (default: 25) {symbol_type_width} (number) defines the width of the symbol type section (default: 8) {show_line} (boolean) if true, shows the content of the line the tag is found on (default: false) {symbols} (string|table) filter results by symbol kind(s) {ignore_symbols} (string|table) list of symbols to ignore {symbol_highlights} (table) string -> string. Matches symbol with hl_group {file_encoding} (string) file encoding for the previewer builtin.lsp_workspace_symbols({opts}) *telescope.builtin.lsp_workspace_symbols()* Lists LSP document symbols in the current workspace - Default keymaps: - ``: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`) Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {query} (string) for what to query the workspace (default: "") {fname_width} (number) defines the width of the filename section (default: 30) {symbol_width} (number) defines the width of the symbol section (default: 25) {symbol_type_width} (number) defines the width of the symbol type section (default: 8) {show_line} (boolean) if true, shows the content of the line the tag is found on (default: false) {symbols} (string|table) filter results by symbol kind(s) {ignore_symbols} (string|table) list of symbols to ignore {symbol_highlights} (table) string -> string. Matches symbol with hl_group {file_encoding} (string) file encoding for the previewer builtin.lsp_dynamic_workspace_symbols({opts}) *telescope.builtin.lsp_dynamic_workspace_symbols()* Dynamically lists LSP for all workspace symbols - Default keymaps: - ``: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`), only works after refining to fuzzy search using Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {fname_width} (number) defines the width of the filename section (default: 30) {show_line} (boolean) if true, shows the content of the line the symbol is found on (default: false) {symbols} (string|table) filter results by symbol kind(s) {ignore_symbols} (string|table) list of symbols to ignore {symbol_highlights} (table) string -> string. Matches symbol with hl_group {file_encoding} (string) file encoding for the previewer builtin.diagnostics({opts}) *telescope.builtin.diagnostics()* Lists diagnostics - Fields: - `All severity flags can be passed as `string` or `number` as per `:vim.diagnostic.severity:` - Default keymaps: - ``: show autocompletion menu to prefilter your query with the diagnostic you want to see (i.e. `:warning:`) - sort_by option: - "buffer": order by bufnr (prioritizing current bufnr), severity, lnum - "severity": order by severity, bufnr (prioritizing current bufnr), lnum Parameters: ~ {opts} (table) options to pass to the picker Options: ~ {bufnr} (number|nil) Buffer number to get diagnostics from. Use 0 for current buffer or nil for all buffers {severity} (string|number) filter diagnostics by severity name (string) or id (number) {severity_limit} (string|number) keep diagnostics equal or more severe wrt severity name (string) or id (number) {severity_bound} (string|number) keep diagnostics equal or less severe wrt severity name (string) or id (number) {root_dir} (string|boolean) if set to string, get diagnostics only for buffers under this dir otherwise cwd {no_unlisted} (boolean) if true, get diagnostics only for listed buffers {no_sign} (boolean) hide DiagnosticSigns from Results (default: false) {line_width} (string|number) set length of diagnostic entry text in Results. Use 'full' for full untruncated text {namespace} (number) limit your diagnostics to a specific namespace {disable_coordinates} (boolean) don't show the line & row numbers (default: false) {sort_by} (string) sort order of the diagnostics results; see above notes (default: "buffer") ================================================================================ THEMES *telescope.themes* Themes are ways to combine several elements of styling together. They are helpful for managing the several different UI aspects for telescope and provide a simple interface for users to get a particular "style" of picker. themes.get_dropdown() *telescope.themes.get_dropdown()* Dropdown style theme. Usage: > local opts = {...} -- picker options local builtin = require('telescope.builtin') local themes = require('telescope.themes') builtin.find_files(themes.get_dropdown(opts)) < themes.get_cursor() *telescope.themes.get_cursor()* Cursor style theme. Usage: > local opts = {...} -- picker options local builtin = require('telescope.builtin') local themes = require('telescope.themes') builtin.find_files(themes.get_cursor(opts)) < themes.get_ivy() *telescope.themes.get_ivy()* Ivy style theme. Usage: > local opts = {...} -- picker options local builtin = require('telescope.builtin') local themes = require('telescope.themes') builtin.find_files(themes.get_ivy(opts)) < ================================================================================ MAPPINGS *telescope.mappings* |telescope.mappings| is used to configure the keybindings within a telescope picker. These key binds are only local to the picker window and will be cleared once you exit the picker. We provide multiple configuration options to make it easy for you to adjust telescope's default key bindings and create your own custom key binds. To see many of the builtin actions that you can use as values for this table, see |telescope.actions| Format is: > { mode = { ..keys } } < where {mode} is the one character letter for a mode ('i' for insert, 'n' for normal). For example: > mappings = { i = { [""] = require('telescope.actions').close, }, } < To disable a keymap, put `[map] = false` For example: > { ..., [""] = false, ..., } < To override behavior of a key, simply set the value to be a function (either by requiring an action or by writing your own function) > { ..., [""] = require('telescope.actions').select_default, ..., } < If the function you want is part of `telescope.actions`, then you can simply supply the function name as a string. For example, the previous option is equivalent to: > { ..., [""] = "select_default", ..., } < You can also add other mappings using tables with `type = "command"`. For example: > { ..., ["jj"] = { "", type = "command" }, ["kk"] = { "echo \"Hello, World!\"", type = "command" },) ..., } < You can also add additional options for mappings of any type ("action" and "command"). For example: > { ..., [""] = { actions.move_selection_next, type = "action", opts = { nowait = true, silent = true } }, ..., } < There are three main places you can configure |telescope.mappings|. These are ordered from the lowest priority to the highest priority. 1. |telescope.defaults.mappings| 2. In the |telescope.setup()| table, inside a picker with a given name, use the `mappings` key > require("telescope").setup { pickers = { find_files = { mappings = { n = { ["kj"] = "close", }, }, }, }, } < 3. `attach_mappings` function for a particular picker. > require("telescope.builtin").find_files { attach_mappings = function(_, map) map("i", "asdf", function(_prompt_bufnr) print "You typed asdf" end) map({"i", "n"}, "", function(_prompt_bufnr) print "You typed " end, { desc = "desc for which key"}) -- needs to return true if you want to map default_mappings and -- false if not return true end, } < ================================================================================ LAYOUT *telescope.pickers.layout* The telescope pickers layout can be configured using the |telescope.defaults.create_layout| option. Parameters: ~ - picker : A Picker object. Return: ~ - layout : instance of `TelescopeLayout` class. Example: ~ > local Layout = require "telescope.pickers.layout" require("telescope").setup { create_layout = function(picker) local function create_window(enter, width, height, row, col, title) local bufnr = vim.api.nvim_create_buf(false, true) local winid = vim.api.nvim_open_win(bufnr, enter, { style = "minimal", relative = "editor", width = width, height = height, row = row, col = col, border = "single", title = title, }) vim.wo[winid].winhighlight = "Normal:Normal" return Layout.Window { bufnr = bufnr, winid = winid, } end local function destory_window(window) if window then if vim.api.nvim_win_is_valid(window.winid) then vim.api.nvim_win_close(window.winid, true) end if vim.api.nvim_buf_is_valid(window.bufnr) then vim.api.nvim_buf_delete(window.bufnr, { force = true }) end end end local layout = Layout { picker = picker, mount = function(self) self.results = create_window(false, 40, 20, 0, 0, "Results") self.preview = create_window(false, 40, 23, 0, 42, "Preview") self.prompt = create_window(true, 40, 1, 22, 0, "Prompt") end, unmount = function(self) destory_window(self.results) destory_window(self.preview) destory_window(self.prompt) end, update = function(self) end, } return layout end, } < TelescopeWindowBorder.config *TelescopeWindowBorder.config* Fields: ~ {bufnr} (integer) {winid} (integer|nil) {change_title} (nil|function) (self: TelescopeWindowBorder, title: string, pos?: "NW"|"N"|"NE"|"SW"|"S"|"SE"):nil TelescopeWindowBorder *TelescopeWindowBorder* Fields: ~ {bufnr} (integer|nil) {winid} (integer|nil) TelescopeWindow.config *TelescopeWindow.config* Fields: ~ {bufnr} (integer) {winid} (integer|nil) {border} (TelescopeWindowBorder.config|nil) TelescopeWindow *TelescopeWindow* Fields: ~ {border} (TelescopeWindowBorder) {bufnr} (integer) {winid} (integer) TelescopeLayout.config *TelescopeLayout.config* Fields: ~ {mount} (function) (self: TelescopeLayout):nil {unmount} (function) (self: TelescopeLayout):nil {update} (function) (self: TelescopeLayout):nil {prompt} (TelescopeWindow|nil) {results} (TelescopeWindow|nil) {preview} (TelescopeWindow|nil) TelescopeLayout *TelescopeLayout* Fields: ~ {prompt} (TelescopeWindow) {results} (TelescopeWindow) {preview} (TelescopeWindow|nil) Layout:mount() *telescope.pickers.layout:mount()* Create the layout. This needs to ensure the required properties are populated. Layout:unmount() *telescope.pickers.layout:unmount()* Destroy the layout. This is responsible for performing clean-up, for example: - deleting buffers - closing windows - clearing autocmds Layout:update() *telescope.pickers.layout:update()* Refresh the layout. This is called when, for example, vim is resized. ================================================================================ LAYOUT *telescope.layout* 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: > require('telescope').setup{ defaults = { layout_strategy = 'vertical', layout_config = { height = 0.95 }, }, } < ──────────────────────────────────────────────────────────────────────────────── Layout strategies are different functions to position telescope. All layout strategies are functions with the following signature: > function(picker, columns, lines, layout_config) -- Do some calculations here... return { preview = preview_configuration, results = results_configuration, prompt = prompt_configuration, } end < 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. 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. layout_strategies.horizontal() *telescope.layout.horizontal()* Horizontal layout has two columns, one for the preview and one for the prompt and results. ┌──────────────────────────────────────────────────┐ │ │ │ ┌───────────────────┐┌───────────────────┐ │ │ │ ││ │ │ │ │ ││ │ │ │ │ ││ │ │ │ │ Results ││ │ │ │ │ ││ Preview │ │ │ │ ││ │ │ │ │ ││ │ │ │ └───────────────────┘│ │ │ │ ┌───────────────────┐│ │ │ │ │ Prompt ││ │ │ │ └───────────────────┘└───────────────────┘ │ │ │ └──────────────────────────────────────────────────┘ `picker.layout_config` shared options: - anchor: - Which edge/corner to pin the picker to - See |resolver.resolve_anchor_pos()| - anchor_padding: - Specifies an amount of additional padding around the anchor - Values should be a positive integer - height: - How tall to make Telescope's entire layout - See |resolver.resolve_height()| - mirror: Flip the location of the results/prompt and preview windows - prompt_position: - Where to place prompt window. - Available Values: 'bottom', 'top' - scroll_speed: The number of lines to scroll through the previewer - width: - How wide to make Telescope's entire layout - See |resolver.resolve_width()| `picker.layout_config` unique options: - preview_cutoff: When columns are less than this value, the preview will be disabled - preview_width: - Change the width of Telescope's preview window - See |resolver.resolve_width()| layout_strategies.center() *telescope.layout.center()* 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. ┌──────────────────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ │ │ Preview │ │ │ │ Preview │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ Prompt │ │ │ ├────────────────────────────────────────┤ │ │ │ Result │ │ │ │ Result │ │ │ └────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────┘ `picker.layout_config` shared options: - anchor: - Which edge/corner to pin the picker to - See |resolver.resolve_anchor_pos()| - anchor_padding: - Specifies an amount of additional padding around the anchor - Values should be a positive integer - height: - How tall to make Telescope's entire layout - See |resolver.resolve_height()| - mirror: Flip the location of the results/prompt and preview windows - prompt_position: - Where to place prompt window. - Available Values: 'bottom', 'top' - scroll_speed: The number of lines to scroll through the previewer - width: - How wide to make Telescope's entire layout - See |resolver.resolve_width()| `picker.layout_config` unique options: - preview_cutoff: When lines are less than this value, the preview will be disabled layout_strategies.cursor() *telescope.layout.cursor()* Cursor layout dynamically positioned below the cursor if possible. If there is no place below the cursor it will be placed above. ┌──────────────────────────────────────────────────┐ │ │ │ █ │ │ ┌──────────────┐┌─────────────────────┐ │ │ │ Prompt ││ Preview │ │ │ ├──────────────┤│ Preview │ │ │ │ Result ││ Preview │ │ │ │ Result ││ Preview │ │ │ └──────────────┘└─────────────────────┘ │ │ █ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────┘ `picker.layout_config` shared options: - height: - How tall to make Telescope's entire layout - See |resolver.resolve_height()| - scroll_speed: The number of lines to scroll through the previewer - width: - How wide to make Telescope's entire layout - See |resolver.resolve_width()| `picker.layout_config` unique options: - preview_cutoff: When columns are less than this value, the preview will be disabled - preview_width: - Change the width of Telescope's preview window - See |resolver.resolve_width()| layout_strategies.vertical() *telescope.layout.vertical()* Vertical layout stacks the items on top of each other. Particularly useful with thinner windows. ┌──────────────────────────────────────────────────┐ │ │ │ ┌────────────────────────────────────────┐ │ │ │ Preview │ │ │ │ Preview │ │ │ │ Preview │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ Result │ │ │ │ Result │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ Prompt │ │ │ └────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────┘ `picker.layout_config` shared options: - anchor: - Which edge/corner to pin the picker to - See |resolver.resolve_anchor_pos()| - anchor_padding: - Specifies an amount of additional padding around the anchor - Values should be a positive integer - height: - How tall to make Telescope's entire layout - See |resolver.resolve_height()| - mirror: Flip the location of the results/prompt and preview windows - prompt_position: - Where to place prompt window. - Available Values: 'bottom', 'top' - scroll_speed: The number of lines to scroll through the previewer - width: - How wide to make Telescope's entire layout - See |resolver.resolve_width()| `picker.layout_config` unique 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()| layout_strategies.flex() *telescope.layout.flex()* Flex layout swaps between `horizontal` and `vertical` strategies based on the window width - Supports |layout_strategies.vertical| or |layout_strategies.horizontal| features `picker.layout_config` shared options: - anchor: - Which edge/corner to pin the picker to - See |resolver.resolve_anchor_pos()| - anchor_padding: - Specifies an amount of additional padding around the anchor - Values should be a positive integer - height: - How tall to make Telescope's entire layout - See |resolver.resolve_height()| - mirror: Flip the location of the results/prompt and preview windows - prompt_position: - Where to place prompt window. - Available Values: 'bottom', 'top' - scroll_speed: The number of lines to scroll through the previewer - width: - How wide to make Telescope's entire layout - See |resolver.resolve_width()| `picker.layout_config` unique 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 - horizontal: Options to pass when switching to horizontal layout - vertical: Options to pass when switching to vertical layout layout_strategies.bottom_pane() *telescope.layout.bottom_pane()* Bottom pane can be used to create layouts similar to "ivy". For an easy ivy configuration, see |themes.get_ivy()| ================================================================================ RESOLVE *telescope.resolve* Provides "resolver functions" to allow more customisable inputs for options. resolver.resolve_height() *telescope.resolve.resolve_height()* Converts input to a function that returns the height. The input must take one of five forms: 1. 0 <= number < 1 This means total height as a percentage. 2. 1 <= number This means total height as a fixed number. 3. function Must have signature: function(self, max_columns, max_lines): number 4. table of the form: { val, max = ..., min = ... } val has to be in the first form 0 <= val < 1 and only one is given, `min` or `max` as fixed number 5. table of the form: {padding = `foo`} where `foo` has one of the previous three forms. The height is then set to be the remaining space after padding. For example, if the window has height 50, and the input is {padding = 5}, the height returned will be `40 = 50 - 2*5` The returned function will have signature: function(self, max_columns, max_lines): number resolver.resolve_width() *telescope.resolve.resolve_width()* Converts input to a function that returns the width. The input must take one of five forms: 1. 0 <= number < 1 This means total width as a percentage. 2. 1 <= number This means total width as a fixed number. 3. function Must have signature: function(self, max_columns, max_lines): number 4. table of the form: { val, max = ..., min = ... } val has to be in the first form 0 <= val < 1 and only one is given, `min` or `max` as fixed number 5. table of the form: {padding = `foo`} where `foo` has one of the previous three forms. The width is then set to be the remaining space after padding. For example, if the window has width 100, and the input is {padding = 5}, the width returned will be `90 = 100 - 2*5` The returned function will have signature: function(self, max_columns, max_lines): number resolver.resolve_anchor_pos() *telescope.resolve.resolve_anchor_pos()* Calculates the adjustment required to move the picker from the middle of the screen to an edge or corner. The `anchor` can be any of the following strings: - "", "CENTER", "NW", "N", "NE", "E", "SE", "S", "SW", "W" The anchors have the following meanings: - "" or "CENTER": the picker will remain in the middle of the screen. - Compass directions: the picker will move to the corresponding edge/corner e.g. "NW" -> "top left corner", "E" -> "right edge", "S" -> "bottom edge" ================================================================================ MAKE_ENTRY *telescope.make_entry* Each picker has a finder made up of two parts, the results which are the data to be displayed, and the entry_maker. These entry_makers are functions returned from make_entry functions. These will be referred to as entry_makers in the following documentation. Every entry maker returns a function that accepts the data to be used for an entry. This function will return an entry table (or nil, meaning skip this entry) which contains the following important keys: - value any: value key can be anything but still required - valid bool (optional): is an optional key because it defaults to true but if the key is set to false it will not be displayed by the picker - ordinal string: is the text that is used for filtering - display string|function: is either a string of the text that is being displayed or a function receiving the entry at a later stage, when the entry is actually being displayed. A function can be useful here if a complex calculation has to be done. `make_entry` can also return a second value - a highlight array which will then apply to the line. Highlight entry in this array has the following signature `{ { start_col, end_col }, hl_group }` - filename string (optional): will be interpreted by the default `` action as open this file - bufnr number (optional): will be interpreted by the default `` action as open this buffer - lnum number (optional): lnum value which will be interpreted by the default `` action as a jump to this line - col number (optional): col value which will be interpreted by the default `` action as a jump to this column For more information on easier displaying, see |telescope.pickers.entry_display| TODO: Document something we call `entry_index` ================================================================================ ENTRY_DISPLAY *telescope.pickers.entry_display* Entry Display is used to format each entry shown in the result panel. Entry Display create() will give us a function based on the configuration of column widths we pass into it. We then can use this function n times to return a string based on structured input. Note that if you call `create()` inside `make_display` it will be called for every single entry. So it is suggested to do this outside of `make_display` for the best performance. The create function will use the column widths passed to it in configuration.items. Each item in that table is the number of characters in the column. It's also possible for the final column to not have a fixed width, this will be shown in the configuration as 'remaining = true'. An example of this configuration is shown for the buffers picker: > local displayer = entry_display.create { separator = " ", items = { { width = opts.bufnr_width }, { width = 4 }, { width = icon_width }, { remaining = true }, }, } < This shows 4 columns, the first is defined in the opts as the width we'll use when display_string is the number of the buffer. The second has a fixed width of 4 and the third column's width will be decided by the width of the icons we use. The fourth column will use the remaining space. Finally, we have also defined the separator between each column will be the space " ". An example of how the display reference will be used is shown, again for the buffers picker: > return displayer { { entry.bufnr, "TelescopeResultsNumber" }, { entry.indicator, "TelescopeResultsComment" }, { icon, hl_group }, display_bufname .. ":" .. entry.lnum, } < There are two types of values each column can have. Either a simple String or a table containing the String as well as the hl_group. The displayer can return values, string and an optional highlights. The string is all the text to be displayed for this entry as a single string. If parts of the string are to be highlighted they will be described in the highlights table. For a better understanding of how create() and displayer are used it's best to look at the code in make_entry.lua. ================================================================================ UTILS *telescope.utils* Utilities for writing telescope pickers utils.str_byteindex() *telescope.utils.str_byteindex()* Return: ~ integer utils.path_expand({path}) *telescope.utils.path_expand()* Hybrid of `vim.fn.expand()` and custom `vim.fs.normalize()` Paths starting with '%', '#' or '<' are expanded with `vim.fn.expand()`. Otherwise avoids using `vim.fn.expand()` due to its overly aggressive expansion behavior which can sometimes lead to errors or the creation of non-existent paths when dealing with valid absolute paths. Other paths will have '~' and environment variables expanded. Unlike `vim.fs.normalize()`, backslashes are preserved. This has better compatibility with `plenary.path` and also avoids mangling valid Unix paths with literal backslashes. Trailing slashes are trimmed. With the exception of root paths. eg. `/` on Unix or `C:\` on Windows Parameters: ~ {path} (string) Return: ~ string utils.transform_path({opts}, {path}) *telescope.utils.transform_path()* 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 Parameters: ~ {opts} (table) The opts the users passed into the picker. Might contains a path_display key {path} (string|nil) The path that should be formatted Return: ~ string: path to be displayed table: The transformed path ready to be displayed with the styling utils.has_ts_parser({lang}) *telescope.utils.has_ts_parser()* Checks if treesitter parser for language is installed Parameters: ~ {lang} (string) utils.notify({funname}, {opts}) *telescope.utils.notify()* Telescope Wrapper around vim.notify Parameters: ~ {funname} (string) name of the function that will be {opts} (table) opts.level string, opts.msg string, opts.once bool ================================================================================ ACTIONS *telescope.actions* These functions are useful for people creating their own mappings. Actions can be either normal functions that expect the `prompt_bufnr` as first argument (1) or they can be a custom telescope type called "action" (2). (1) The `prompt_bufnr` of a normal function denotes the identifier of your picker which can be used to access the picker state. In practice, users most commonly access from both picker and global state via the following: > -- for utility functions local action_state = require "telescope.actions.state" local actions = {} actions.do_stuff = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) -- picker state local entry = action_state.get_selected_entry() end < See |telescope.actions.state| for more information. (2) To transform a module of functions into a module of "action"s, you need to do the following: > local transform_mod = require("telescope.actions.mt").transform_mod local mod = {} mod.a1 = function(prompt_bufnr) -- your code goes here -- You can access the picker/global state as described above in (1). end mod.a2 = function(prompt_bufnr) -- your code goes here end mod = transform_mod(mod) -- Now the following is possible. This means that actions a2 will be executed -- after action a1. You can chain as many actions as you want. local action = mod.a1 + mod.a2 action(bufnr) < Another interesting thing to do is that these actions now have functions you can call. These functions include `:replace(f)`, `:replace_if(f, c)`, `replace_map(tbl)` and `enhance(tbl)`. More information on these functions can be found in the `developers.md` and `lua/tests/automated/action_spec.lua` file. actions.move_selection_next({prompt_bufnr}) *telescope.actions.move_selection_next()* Move the selection to the next entry Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.move_selection_previous({prompt_bufnr}) *telescope.actions.move_selection_previous()* Move the selection to the previous entry Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.move_selection_worse({prompt_bufnr}) *telescope.actions.move_selection_worse()* Move the selection to the entry that has a worse score Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.move_selection_better({prompt_bufnr}) *telescope.actions.move_selection_better()* Move the selection to the entry that has a better score Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.move_to_top({prompt_bufnr}) *telescope.actions.move_to_top()* Move to the top of the picker Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.move_to_middle({prompt_bufnr}) *telescope.actions.move_to_middle()* Move to the middle of the picker Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.move_to_bottom({prompt_bufnr}) *telescope.actions.move_to_bottom()* Move to the bottom of the picker Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.add_selection({prompt_bufnr}) *telescope.actions.add_selection()* Add current entry to multi select Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.remove_selection({prompt_bufnr}) *telescope.actions.remove_selection()* Remove current entry from multi select Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.toggle_selection({prompt_bufnr}) *telescope.actions.toggle_selection()* Toggle current entry status for multi select Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.select_all({prompt_bufnr}) *telescope.actions.select_all()* Multi select all entries. - Note: selected entries may include results not visible in the results pop up. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.drop_all({prompt_bufnr}) *telescope.actions.drop_all()* Drop all entries from the current multi selection. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.toggle_all({prompt_bufnr}) *telescope.actions.toggle_all()* Toggle multi selection for all entries. - Note: toggled entries may include results not visible in the results pop up. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.preview_scrolling_up({prompt_bufnr}) *telescope.actions.preview_scrolling_up()* Scroll the preview window up Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.preview_scrolling_down({prompt_bufnr}) *telescope.actions.preview_scrolling_down()* Scroll the preview window down Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.preview_scrolling_left({prompt_bufnr}) *telescope.actions.preview_scrolling_left()* Scroll the preview window to the left Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.preview_scrolling_right({prompt_bufnr}) *telescope.actions.preview_scrolling_right()* Scroll the preview window to the right Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.results_scrolling_up({prompt_bufnr}) *telescope.actions.results_scrolling_up()* Scroll the results window up Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.results_scrolling_down({prompt_bufnr}) *telescope.actions.results_scrolling_down()* Scroll the results window down Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.results_scrolling_left({prompt_bufnr}) *telescope.actions.results_scrolling_left()* Scroll the results window to the left Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.results_scrolling_right({prompt_bufnr}) *telescope.actions.results_scrolling_right()* Scroll the results window to the right Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.center({prompt_bufnr}) *telescope.actions.center()* Center the cursor in the window, can be used after selecting a file to edit You can just map `actions.select_default + actions.center` Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.select_default({prompt_bufnr}) *telescope.actions.select_default()* Perform default action on selection, usually something like `:edit ` i.e. open the selection in the current buffer Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.select_horizontal({prompt_bufnr}) *telescope.actions.select_horizontal()* Perform 'horizontal' action on selection, usually something like `:new ` i.e. open the selection in a new horizontal split Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.select_vertical({prompt_bufnr}) *telescope.actions.select_vertical()* Perform 'vertical' action on selection, usually something like `:vnew ` i.e. open the selection in a new vertical split Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.select_tab({prompt_bufnr}) *telescope.actions.select_tab()* Perform 'tab' action on selection, usually something like `:tabedit ` i.e. open the selection in a new tab Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.select_drop({prompt_bufnr}) *telescope.actions.select_drop()* Perform 'drop' action on selection, usually something like `:drop ` i.e. open the selection in a window Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.select_tab_drop({prompt_bufnr}) *telescope.actions.select_tab_drop()* Perform 'tab drop' action on selection, usually something like `:tab drop ` i.e. open the selection in a new tab Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.file_edit({prompt_bufnr}) *telescope.actions.file_edit()* Perform file edit on selection, usually something like `:edit ` Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.file_split({prompt_bufnr}) *telescope.actions.file_split()* Perform file split on selection, usually something like `:new ` Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.file_vsplit({prompt_bufnr}) *telescope.actions.file_vsplit()* Perform file vsplit on selection, usually something like `:vnew ` Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.file_tab({prompt_bufnr}) *telescope.actions.file_tab()* Perform file tab on selection, usually something like `:tabedit ` Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.close({prompt_bufnr}) *telescope.actions.close()* Close the Telescope window, usually used within an action Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions._close({prompt_bufnr}) *telescope.actions._close()* Close the Telescope window, usually used within an action Deprecated and no longer needed, does the same as |telescope.actions.close|. Might be removed in the future Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.edit_command_line({prompt_bufnr}) *telescope.actions.edit_command_line()* Set a value in the command line and don't run it, making it editable. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.set_command_line({prompt_bufnr}) *telescope.actions.set_command_line()* Set a value in the command line and run it Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.edit_search_line({prompt_bufnr}) *telescope.actions.edit_search_line()* Set a value in the search line and don't search for it, making it editable. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.set_search_line({prompt_bufnr}) *telescope.actions.set_search_line()* Set a value in the search line and search for it Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.edit_register({prompt_bufnr}) *telescope.actions.edit_register()* Edit a register Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.paste_register({prompt_bufnr}) *telescope.actions.paste_register()* Paste the selected register into the buffer Note: only meant to be used inside builtin.registers Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.insert_symbol({prompt_bufnr}) *telescope.actions.insert_symbol()* Insert a symbol into the current buffer (while switching to normal mode) Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.insert_symbol_i({prompt_bufnr}) *telescope.actions.insert_symbol_i()* Insert a symbol into the current buffer and keeping the insert mode. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_create_branch({prompt_bufnr}) *telescope.actions.git_create_branch()* Create and checkout a new git branch if it doesn't already exist Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_apply_stash({prompt_bufnr}) *telescope.actions.git_apply_stash()* Applies an existing git stash Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_checkout({prompt_bufnr}) *telescope.actions.git_checkout()* Checkout an existing git branch Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_switch_branch({prompt_bufnr}) *telescope.actions.git_switch_branch()* Switch to git branch. If the branch already exists in local, switch to that. If the branch is only in remote, create new branch tracking remote and switch to new one. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_rename_branch() *telescope.actions.git_rename_branch()* Action to rename selected git branch actions.git_track_branch({prompt_bufnr}) *telescope.actions.git_track_branch()* Tell git to track the currently selected remote branch in Telescope Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_delete_branch({prompt_bufnr}) *telescope.actions.git_delete_branch()* Delete all currently selected branches Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_merge_branch({prompt_bufnr}) *telescope.actions.git_merge_branch()* Merge the currently selected branch Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_rebase_branch({prompt_bufnr}) *telescope.actions.git_rebase_branch()* Rebase to selected git branch Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_reset_mixed({prompt_bufnr}) *telescope.actions.git_reset_mixed()* Reset to selected git commit using mixed mode Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_reset_soft({prompt_bufnr}) *telescope.actions.git_reset_soft()* Reset to selected git commit using soft mode Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_reset_hard({prompt_bufnr}) *telescope.actions.git_reset_hard()* Reset to selected git commit using hard mode Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_checkout_current_buffer({prompt_bufnr}) *telescope.actions.git_checkout_current_buffer()* Checkout a specific file for a given sha Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.git_staging_toggle({prompt_bufnr}) *telescope.actions.git_staging_toggle()* Stage/unstage selected file Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.send_selected_to_qflist({prompt_bufnr}) *telescope.actions.send_selected_to_qflist()* Sends the selected entries to the quickfix list, replacing the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.add_selected_to_qflist({prompt_bufnr}) *telescope.actions.add_selected_to_qflist()* Adds the selected entries to the quickfix list, keeping the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.send_to_qflist({prompt_bufnr}) *telescope.actions.send_to_qflist()* Sends all entries to the quickfix list, replacing the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.add_to_qflist({prompt_bufnr}) *telescope.actions.add_to_qflist()* Adds all entries to the quickfix list, keeping the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.send_selected_to_loclist({prompt_bufnr}) *telescope.actions.send_selected_to_loclist()* Sends the selected entries to the location list, replacing the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.add_selected_to_loclist({prompt_bufnr}) *telescope.actions.add_selected_to_loclist()* Adds the selected entries to the location list, keeping the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.send_to_loclist({prompt_bufnr}) *telescope.actions.send_to_loclist()* Sends all entries to the location list, replacing the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.add_to_loclist({prompt_bufnr}) *telescope.actions.add_to_loclist()* Adds all entries to the location list, keeping the previous entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.smart_send_to_qflist({prompt_bufnr}) *telescope.actions.smart_send_to_qflist()* Sends the selected entries to the quickfix list, replacing the previous entries. If no entry was selected, sends all entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.smart_add_to_qflist({prompt_bufnr}) *telescope.actions.smart_add_to_qflist()* Adds the selected entries to the quickfix list, keeping the previous entries. If no entry was selected, adds all entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.smart_send_to_loclist({prompt_bufnr}) *telescope.actions.smart_send_to_loclist()* Sends the selected entries to the location list, replacing the previous entries. If no entry was selected, sends all entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.smart_add_to_loclist({prompt_bufnr}) *telescope.actions.smart_add_to_loclist()* Adds the selected entries to the location list, keeping the previous entries. If no entry was selected, adds all entries. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.complete_tag({prompt_bufnr}) *telescope.actions.complete_tag()* Open completion menu containing the tags which can be used to filter the results in a faster way Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.cycle_history_next({prompt_bufnr}) *telescope.actions.cycle_history_next()* Cycle to the next search prompt in the history Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.cycle_history_prev({prompt_bufnr}) *telescope.actions.cycle_history_prev()* Cycle to the previous search prompt in the history Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.open_qflist({prompt_bufnr}) *telescope.actions.open_qflist()* Open the quickfix list. It makes sense to use this in combination with one of the send_to_qflist actions `actions.smart_send_to_qflist + actions.open_qflist` Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.open_loclist({prompt_bufnr}) *telescope.actions.open_loclist()* Open the location list. It makes sense to use this in combination with one of the send_to_loclist actions `actions.smart_send_to_qflist + actions.open_qflist` Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.delete_buffer({prompt_bufnr}) *telescope.actions.delete_buffer()* Delete the selected buffer or all the buffers selected using multi selection. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.cycle_previewers_next({prompt_bufnr}) *telescope.actions.cycle_previewers_next()* Cycle to the next previewer if there is one available. This action is not mapped on default. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.cycle_previewers_prev({prompt_bufnr}) *telescope.actions.cycle_previewers_prev()* Cycle to the previous previewer if there is one available. This action is not mapped on default. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.remove_selected_picker({prompt_bufnr}) *telescope.actions.remove_selected_picker()* Removes the selected picker in |builtin.pickers|. This action is not mapped by default and only intended for |builtin.pickers|. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.which_key({prompt_bufnr}) *telescope.actions.which_key()* Display the keymaps of registered actions similar to which-key.nvim. - Notes: - The defaults can be overridden via |action_generate.which_key|. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.to_fuzzy_refine({prompt_bufnr}) *telescope.actions.to_fuzzy_refine()* Move from a none fuzzy search to a fuzzy one This action is meant to be used in live_grep and lsp_dynamic_workspace_symbols Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.delete_mark({prompt_bufnr}) *telescope.actions.delete_mark()* Delete the selected mark or all the marks selected using multi selection. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.insert_original_cword({prompt_bufnr}) *telescope.actions.insert_original_cword()* Insert the word under the cursor of the original (pre-Telescope) window Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.insert_original_cWORD({prompt_bufnr}) *telescope.actions.insert_original_cWORD()* Insert the WORD under the cursor of the original (pre-Telescope) window Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.insert_original_cfile({prompt_bufnr}) *telescope.actions.insert_original_cfile()* Insert the file under the cursor of the original (pre-Telescope) window Parameters: ~ {prompt_bufnr} (number) The prompt bufnr actions.insert_original_cline({prompt_bufnr}) *telescope.actions.insert_original_cline()* Insert the line under the cursor of the original (pre-Telescope) window Parameters: ~ {prompt_bufnr} (number) The prompt bufnr ================================================================================ ACTIONS_STATE *telescope.actions.state* Functions to be used to determine the current state of telescope. Generally used from within other |telescope.actions| action_state.get_selected_entry() *telescope.actions.state.get_selected_entry()* Get the current entry action_state.get_current_line() *telescope.actions.state.get_current_line()* Gets the current line in the search prompt action_state.get_current_picker({prompt_bufnr}) *telescope.actions.state.get_current_picker()* Gets the current picker Parameters: ~ {prompt_bufnr} (number) The prompt bufnr ================================================================================ ACTIONS_SET *telescope.actions.set* 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. action_set.shift_selection({prompt_bufnr}, {change}) *telescope.actions.set.shift_selection()* Move the current selection of a picker {change} rows. Handles not overflowing / underflowing the list. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {change} (number) The amount to shift the selection by action_set.select({prompt_bufnr}, {type}) *telescope.actions.set.select()* Select the current entry. This is the action set to overwrite common actions by the user. By default maps to editing a file. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {type} (string) The type of selection to make action_set.edit({prompt_bufnr}, {command}) *telescope.actions.set.edit()* Edit a file based on the current selection. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {command} (string) The command to use to open the file. action_set.scroll_previewer({prompt_bufnr}, {direction}) *telescope.actions.set.scroll_previewer()* 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. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {direction} (number) The direction of the scrolling action_set.scroll_horizontal_previewer({prompt_bufnr}, {direction}) *telescope.actions.set.scroll_horizontal_previewer()* Scrolls the previewer to the left or right. Defaults to a half page scroll, but can be overridden using the `scroll_speed` option in `layout_config`. See |telescope.layout| for more details. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {direction} (number) The direction of the scrolling action_set.scroll_results({prompt_bufnr}, {direction}) *telescope.actions.set.scroll_results()* 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. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {direction} (number) The direction of the scrolling action_set.scroll_horizontal_results({prompt_bufnr}, {direction}) *telescope.actions.set.scroll_horizontal_results()* Scrolls the results to the left or right. Defaults to a half page scroll, but can be overridden using the `scroll_speed` option in `layout_config`. See |telescope.layout| for more details. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {direction} (number) The direction of the scrolling ================================================================================ ACTIONS_LAYOUT *telescope.actions.layout* The layout actions are actions to be used to change the layout of a picker. action_layout.toggle_preview({prompt_bufnr}) *telescope.actions.layout.toggle_preview()* Toggle preview window. - Note: preview window can be toggled even if preview is set to false. This action is not mapped by default. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr action_layout.toggle_prompt_position({prompt_bufnr}) *telescope.actions.layout.toggle_prompt_position()* Toggles the `prompt_position` option between "top" and "bottom". Checks if `prompt_position` is an option for the current layout. This action is not mapped by default. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr action_layout.toggle_mirror({prompt_bufnr}) *telescope.actions.layout.toggle_mirror()* Toggles the `mirror` option between `true` and `false`. Checks if `mirror` is an option for the current layout. This action is not mapped by default. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr action_layout.cycle_layout_next({prompt_bufnr}) *telescope.actions.layout.cycle_layout_next()* Cycles to the next layout in `cycle_layout_list`. This action is not mapped by default. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr action_layout.cycle_layout_prev({prompt_bufnr}) *telescope.actions.layout.cycle_layout_prev()* Cycles to the previous layout in `cycle_layout_list`. This action is not mapped by default. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr ================================================================================ ACTIONS_UTILS *telescope.actions.utils* Utilities to wrap functions around picker selections and entries. Generally used from within other |telescope.actions| utils.map_entries({prompt_bufnr}, {f}) *telescope.actions.utils.map_entries()* Apply `f` to the entries of the current picker. - Notes: - Mapped entries include all currently filtered results, not just the visible ones. - Indices are 1-indexed, whereas rows are 0-indexed. - Warning: `map_entries` has no return value. - The below example showcases how to collect results Usage: > local action_state = require "telescope.actions.state" local action_utils = require "telescope.actions.utils" function entry_value_by_row() local prompt_bufnr = vim.api.nvim_get_current_buf() local current_picker = action_state.get_current_picker(prompt_bufnr) local results = {} action_utils.map_entries(prompt_bufnr, function(entry, index, row) results[row] = entry.value end) return results end < Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {f} (function) Function to map onto entries of picker that takes (entry, index, row) as viable arguments utils.map_selections({prompt_bufnr}, {f}) *telescope.actions.utils.map_selections()* Apply `f` to the multi selections of the current picker and return a table of mapped selections. - Notes: - Mapped selections may include results not visible in the results pop up. - Selected entries are returned in order of their selection. - Warning: `map_selections` has no return value. - The below example showcases how to collect results Usage: > local action_state = require "telescope.actions.state" local action_utils = require "telescope.actions.utils" function selection_by_index() local prompt_bufnr = vim.api.nvim_get_current_buf() local current_picker = action_state.get_current_picker(prompt_bufnr) local results = {} action_utils.map_selections(prompt_bufnr, function(entry, index) results[index] = entry.value end) return results end < Parameters: ~ {prompt_bufnr} (number) The prompt bufnr {f} (function) Function to map onto selection of picker that takes (selection) as a viable argument utils.get_registered_mappings({prompt_bufnr}) *telescope.actions.utils.get_registered_mappings()* Utility to collect mappings of prompt buffer in array of `{mode, keybind, name}`. Parameters: ~ {prompt_bufnr} (number) The prompt bufnr ================================================================================ ACTIONS_GENERATE *telescope.actions.generate* Module for convenience to override defaults of corresponding |telescope.actions| at |telescope.setup()|. General usage: > require("telescope").setup { defaults = { mappings = { n = { ["?"] = action_generate.which_key { name_width = 20, -- typically leads to smaller floats max_height = 0.5, -- increase potential maximum height separator = " > ", -- change sep between mode, keybind, and name close_with_action = false, -- do not close float on action }, }, }, }, } < action_generate.which_key({opts}) *telescope.actions.generate.which_key()* Display the keymaps of registered actions similar to which-key.nvim. - Floating window: - Appears on the opposite side of the prompt. - Resolves to minimum required number of lines to show hints with `opts` or truncates entries at `max_height`. - Closes automatically on action call and can be disabled with by setting `close_with_action` to false. Parameters: ~ {opts} (table) options to pass to toggling registered actions Fields: ~ {max_height} (number) % of max. height or no. of rows for hints (default: 0.4), see |resolver.resolve_height()| {only_show_current_mode} (boolean) only show keymaps for the current mode (default: true) {mode_width} (number) fixed width of mode to be shown (default: 1) {keybind_width} (number) fixed width of keybind to be shown (default: 7) {name_width} (number) fixed width of action name to be shown (default: 30) {column_padding} (string) string to split; can be used for vertical separator (default: " ") {mode_hl} (string) hl group of mode (default: TelescopeResultsConstant) {keybind_hl} (string) hl group of keybind (default: TelescopeResultsVariable) {name_hl} (string) hl group of action name (default: TelescopeResultsFunction) {column_indent} (number) number of left-most spaces before keybinds are shown (default: 4) {line_padding} (number) row padding in top and bottom of float (default: 1) {separator} (string) separator string between mode, key bindings, and action (default: " -> ") {close_with_action} (boolean) registered action will close keymap float (default: true) {normal_hl} (string) winhl of "Normal" for keymap hints floating window (default: "TelescopePrompt") {border_hl} (string) winhl of "Normal" for keymap borders (default: "TelescopePromptBorder") {winblend} (number) pseudo-transparency of keymap hints floating window {zindex} (number) z-index of keymap hints floating window (default: 100) ================================================================================ PREVIEWERS *telescope.previewers* Provides a Previewer table that has to be implemented by each previewer. To achieve this, this module also provides two wrappers that abstract most of the work and make it really easy to create new previewers. - `previewers.new_termopen_previewer` - `previewers.new_buffer_previewer` Furthermore, there are a collection of previewers already defined which can be used for every picker, as long as the entries of the picker provide the necessary fields. The more important ones are - `previewers.cat` - `previewers.vimgrep` - `previewers.qflist` - `previewers.vim_buffer_cat` - `previewers.vim_buffer_vimgrep` - `previewers.vim_buffer_qflist` Previewers can be disabled for any builtin or custom picker by doing :Telescope find_files previewer=false previewers.Previewer() *telescope.previewers.Previewer()* This is the base table all previewers have to implement. It's possible to write a wrapper for this because most previewers need to have the same keys set. Examples of wrappers are: - `new_buffer_previewer` - `new_termopen_previewer` To create a new table do following: - `local new_previewer = Previewer:new(opts)` What `:new` expects is listed below The interface provides the following set of functions. All of them, besides `new`, will be handled by telescope pickers. - `:new(opts)` - `:preview(entry, status)` - `:teardown()` - `:send_input(input)` - `:scroll_fn(direction)` - `:scroll_horizontal_fn(direction)` `Previewer:new()` expects a table as input with following keys: - `setup` function(self): Will be called the first time preview will be called. - `teardown` function(self): Will be called on clean up. - `preview_fn` function(self, entry, status): Will be called each time a new entry was selected. - `title` function(self): Will return the static title of the previewer. - `dynamic_title` function(self, entry): Will return the dynamic title of the previewer. Will only be called when config value dynamic_preview_title is true. - `send_input` function(self, input): This is meant for `termopen_previewer` and it can be used to send input to the terminal application, like less. - `scroll_fn` function(self, direction): Used to make scrolling work. - `scroll_horizontal_fn` function(self, direction): Used to make horizontal scrolling work. previewers.new() *telescope.previewers.new()* A shorthand for creating a new Previewer. The provided table will be forwarded to `Previewer:new(...)` previewers.new_termopen_previewer() *telescope.previewers.new_termopen_previewer()* Is a wrapper around Previewer and helps with creating a new `termopen_previewer`. It requires you to specify one table entry `get_command(entry, status)`. This `get_command` function has to return the terminal command that will be executed for each entry. Example: > get_command = function(entry, status) return { 'bat', entry.path } end < Additionally you can define: - `title` a static title for example "File Preview" - `dyn_title(self, entry)` a dynamic title function which gets called when config value `dynamic_preview_title = true` - `env` table: define environment variables to forward to the terminal process. Example: - `{ ['PAGER'] = '', ['MANWIDTH'] = 50 }` It's an easy way to get your first previewer going and it integrates well with `bat` and `less`. Providing out of the box scrolling if the command uses less. Furthermore, if `env` is not set, it will forward all `config.set_env` environment variables to that terminal process. previewers.cat() *telescope.previewers.cat()* Provides a `termopen_previewer` which has the ability to display files. It will always show the top of the file and has support for `bat`(prioritized) and `cat`. Each entry has to provide either the field `path` or `filename` in order to make this previewer work. The preferred way of using this previewer is like this `require('telescope.config').values.cat_previewer` This will respect user configuration and will use `buffer_previewers` in case it's configured that way. previewers.vimgrep() *telescope.previewers.vimgrep()* Provides a `termopen_previewer` which has the ability to display files at the provided line. It has support for `bat`(prioritized) and `cat`. Each entry has to provide either the field `path` or `filename` and a `lnum` field in order to make this previewer work. The preferred way of using this previewer is like this `require('telescope.config').values.grep_previewer` This will respect user configuration and will use `buffer_previewers` in case it's configured that way. previewers.qflist() *telescope.previewers.qflist()* Provides a `termopen_previewer` which has the ability to display files at the provided line or range. It has support for `bat`(prioritized) and `cat`. Each entry has to provide either the field `path` or `filename`, `lnum` and a `start` and `finish` range in order to make this previewer work. The preferred way of using this previewer is like this `require('telescope.config').values.qflist_previewer` This will respect user configuration and will use buffer previewers in case it's configured that way. previewers.new_buffer_previewer() *telescope.previewers.new_buffer_previewer()* An interface to instantiate a new `buffer_previewer`. That means that the content actually lives inside a vim buffer which enables us more control over the actual content. For example, we can use `vim.fn.search` to jump to a specific line or reuse buffers/already opened files more easily. This interface is more complex than `termopen_previewer` but offers more flexibility over your content. It was designed to display files but was extended to also display the output of terminal commands. In the following options, state table and general tips are mentioned to make your experience with this previewer more seamless. options: - `define_preview = function(self, entry, status)` (required) Is called for each selected entry, after each selection_move (up or down) and is meant to handle things like reading file, jump to line or attach a highlighter. - `setup = function(self)` (optional) Is called once at the beginning, before the preview for the first entry is displayed. You can return a table of vars that will be available in `self.state` in each `define_preview` call. - `teardown = function(self)` (optional) Will be called at the end, when the picker is being closed and is meant to clean up everything that was allocated by the previewer. The `buffer_previewer` will automatically clean up all created buffers. So you only need to handle things that were introduced by you. - `keep_last_buf = true` (optional) Will not delete the last selected buffer. This would allow you to reuse that buffer in the select action. For example, that buffer can be opened in a new split, rather than recreating that buffer in an action. To access the last buffer number: `require('telescope.state').get_global_key("last_preview_bufnr")` - `get_buffer_by_name = function(self, entry)` Allows you to set a unique name for each buffer. This is used for caching purposes. `self.state.bufname` will be nil if the entry was never loaded or the unique name when it was loaded once. For example, useful if you have one file but multiple entries. This happens for grep and lsp builtins. So to make the cache work only load content if `self.state.bufname ~= entry.your_unique_key` - `title` a static title for example "File Preview" - `dyn_title(self, entry)` a dynamic title function which gets called when config value `dynamic_preview_title = true` `self.state` table: - `self.state.bufnr` Is the current buffer number, in which you have to write the loaded content. Don't create a buffer yourself, otherwise it's not managed by the buffer_previewer interface and you will probably be better off writing your own interface. - self.state.winid Current window id. Useful if you want to set the cursor to a provided line number. - self.state.bufname Will return the current buffer name, if `get_buffer_by_name` is defined. nil will be returned if the entry was never loaded or when `get_buffer_by_name` is not set. Tips: - If you want to display content of a terminal job, use: `require('telescope.previewers.utils').job_maker(cmd, bufnr, opts)` - `cmd` table: for example { 'git', 'diff', entry.value } - `bufnr` number: in which the content will be written - `opts` table: with following keys - `bufname` string: used for cache - `value` string: used for cache - `mode` string: either "insert" or "append". "insert" is default - `env` table: define environment variables. Example: - `{ ['PAGER'] = '', ['MANWIDTH'] = 50 }` - `cwd` string: define current working directory for job - `callback` function(bufnr, content): will be called when job is done. Content will be nil if job is already loaded. So you can do highlighting only the first time the previewer is created for that entry. Use the returned `bufnr` and not `self.state.bufnr` in callback, because state can already be changed at this point in time. - If you want to attach a highlighter use: - `require('telescope.previewers.utils').highlighter(bufnr, ft)` - This will prioritize tree sitter highlighting if available for environment and language. - `require('telescope.previewers.utils').regex_highlighter(bufnr, ft)` - `require('telescope.previewers.utils').ts_highlighter(bufnr, ft)` - If you want to use `vim.fn.search` or similar you need to run it in that specific buffer context. Do > vim.api.nvim_buf_call(bufnr, function() -- for example `search` and `matchadd` end) < to achieve that. - If you want to read a file into the buffer it's best to use `buffer_previewer_maker`. But access this function with `require('telescope.config').values.buffer_previewer_maker` because it can be redefined by users. previewers.buffer_previewer_maker({filepath}, {bufnr}, {opts}) *telescope.previewers.buffer_previewer_maker()* A universal way of reading a file into a buffer previewer. It handles async reading, cache, highlighting, displaying directories and provides a callback which can be used, to jump to a line in the buffer. Parameters: ~ {filepath} (string) String to the filepath, will be expanded {bufnr} (number) Where the content will be written {opts} (table) keys: `use_ft_detect`, `bufname` and `callback` previewers.vim_buffer_cat() *telescope.previewers.vim_buffer_cat()* A previewer that is used to display a file. It uses the `buffer_previewer` interface and won't jump to the line. To integrate this one into your own picker make sure that the field `path` or `filename` is set for each entry. The preferred way of using this previewer is like this `require('telescope.config').values.file_previewer` This will respect user configuration and will use `termopen_previewer` in case it's configured that way. previewers.vim_buffer_vimgrep() *telescope.previewers.vim_buffer_vimgrep()* A previewer that is used to display a file and jump to the provided line. It uses the `buffer_previewer` interface. To integrate this one into your own picker make sure that the field `path` or `filename` and `lnum` is set in each entry. If the latter is not present, it will default to the first line. Additionally, `lnend`, `col` and `colend` can be set to highlight a text range instead of a single line. All line/column values are 1-indexed. The preferred way of using this previewer is like this `require('telescope.config').values.grep_previewer` This will respect user configuration and will use `termopen_previewer` in case it's configured that way. previewers.vim_buffer_qflist() *telescope.previewers.vim_buffer_qflist()* Is the same as `vim_buffer_vimgrep` and only exists for consistency with `term_previewers`. The preferred way of using this previewer is like this `require('telescope.config').values.qflist_previewer` This will respect user configuration and will use `termopen_previewer` in case it's configured that way. previewers.git_branch_log() *telescope.previewers.git_branch_log()* A previewer that shows a log of a branch as graph previewers.git_stash_diff() *telescope.previewers.git_stash_diff()* A previewer that shows a diff of a stash previewers.git_commit_diff_to_parent() *telescope.previewers.git_commit_diff_to_parent()* A previewer that shows a diff of a commit to a parent commit. The run command is `git --no-pager diff SHA^! -- $CURRENT_FILE` The current file part is optional. So is only uses it with bcommits. previewers.git_commit_diff_to_head() *telescope.previewers.git_commit_diff_to_head()* A previewer that shows a diff of a commit to head. The run command is `git --no-pager diff --cached $SHA -- $CURRENT_FILE` The current file part is optional. So is only uses it with bcommits. previewers.git_commit_diff_as_was() *telescope.previewers.git_commit_diff_as_was()* A previewer that shows a diff of a commit as it was. The run command is `git --no-pager show $SHA:$CURRENT_FILE` or `git --no-pager show $SHA` previewers.git_commit_message() *telescope.previewers.git_commit_message()* A previewer that shows the commit message of a diff. The run command is `git --no-pager log -n 1 $SHA` previewers.git_file_diff() *telescope.previewers.git_file_diff()* A previewer that shows the current diff of a file. Used in git_status. The run command is `git --no-pager diff $FILE` previewers.display_content() *telescope.previewers.display_content()* A deprecated way of displaying content more easily. Was written at a time, where the buffer_previewer interface wasn't present. Nowadays it's easier to just use this. We will keep it around for backwards compatibility because some extensions use it. It doesn't use cache or some other clever tricks. ================================================================================ HISTORY *telescope.actions.history* A base implementation of a prompt history that provides a simple history and can be replaced with a custom implementation. For example: We provide an extension for a smart history that uses sql.nvim to map histories to metadata, like the calling picker or cwd. So you have a history for: - find_files project_1 - grep_string project_1 - live_grep project_1 - find_files project_2 - grep_string project_2 - live_grep project_2 - etc See https://github.com/nvim-telescope/telescope-smart-history.nvim histories.History() *telescope.actions.history.History()* Manages prompt history Fields: ~ {enabled} (boolean) Will indicate if History is enabled or disabled {path} (string) Will point to the location of the history file {limit} (string) Will have the limit of the history. Can be nil, if limit is disabled. {content} (table) History table. Needs to be filled by your own History implementation {index} (number) Used to keep track of the next or previous index. Default is #content + 1 {cycle_wrap} (boolean) Controls if history will wrap on reaching beginning or end histories.History:new({opts}) *telescope.actions.history.History:new()* Create a new History Parameters: ~ {opts} (table) Defines the behavior of History Fields: ~ {init} (function) Will be called after handling configuration (required) {append} (function) How to append a new prompt item (required) {reset} (function) What happens on reset. Will be called when telescope closes (required) {pre_get} (function) Will be called before a next or previous item will be returned (optional) histories.new() *telescope.actions.history.new()* Shorthand to create a new history histories.History:reset() *telescope.actions.history.History:reset()* Will reset the history index to the default initial state. Will happen after the picker closed histories.History:append({line}, {picker}, {no_reset}) *telescope.actions.history.History:append()* Append a new line to the history Parameters: ~ {line} (string) current line that will be appended {picker} (table) the current picker object {no_reset} (boolean) On default it will reset the state at the end. If you don't want to do this set to true histories.History:get_next({line}, {picker}) *telescope.actions.history.History:get_next()* Will return the next history item. Can be nil if there are no next items Parameters: ~ {line} (string) the current line {picker} (table) the current picker object Return: ~ string: the next history item histories.History:get_prev({line}, {picker}) *telescope.actions.history.History:get_prev()* Will return the previous history item. Can be nil if there are no previous items Parameters: ~ {line} (string) the current line {picker} (table) the current picker object Return: ~ string: the previous history item histories.get_simple_history() *telescope.actions.history.get_simple_history()* A simple implementation of history. It will keep one unified history across all pickers. vim:tw=78:ts=8:ft=help:norl: ================================================ FILE: doc/telescope_changelog.txt ================================================ ================================================================================ *telescope.changelog* # Changelog *telescope.changelog-922* Date: May 17, 2021 PR: https://github.com/nvim-telescope/telescope.nvim/pull/922 This is one of our largest breaking changes thus far, so I (TJ) am adding some information here so that you can more easily update (without having to track down the commit, etc.). The goal of these breaking changes is to greatly simplify the way configuration for layouts happen. This should make it much easier to configure each picker, layout_strategy, and more. Please report any bugs or behavior that is broken / confusing upstream and we can try and make the configuration better. |telescope.setup()| has changed `layout_defaults` -> `layout_config`. This makes it so that the setup and the pickers share the same key, otherwise it is too confusing which key is for which. `picker:find()` now has different values available for configuring the UI. All configuration for the layout must be passed in the key: `layout_config`. Previously, these keys were passed via `picker:find(opts)`, but should be passed via `opts.layout_config` now. - {height} - {width} - {prompt_position} - {preview_cutoff} These keys are removed: - {results_height}: This key is no longer valid. Instead, use `height` and the corresponding `preview_*` options for the layout strategy to get the correct results height. This simplifies the configuration for many of the existing strategies. - {results_width}: This key actually never did anything. It was leftover from some hacking that I had attempted before. Instead you should be using something like the `preview_width` configuration option for |layout_strategies.horizontal()| You should get error messages when you try and use any of the above keys now. *telescope.changelog-839* Date: July 7, 2021 PR: https://github.com/nvim-telescope/telescope.nvim/pull/839 Small breaking change regarding `shorten_path` and `hide_filename`. This allows to configure path displays on a global level and offers a way for extension developers to make use of the same configuration, offering a better overall experience. The new way to configure to configure path displays is with: `path_display`: It is a table and accepts multiple values: - "hidden" hide file names - "tail" only display the file name, and not the path - "absolute" display absolute paths - "shorten" only display the first character of each directory in the path see |telescope.defaults.path_display| Example would be for a global configuration: require("telescope").setup{ defaults = { path_display = { "shorten", "absolute", }, } } You can also still pass this to a single builtin call: require("telescope.builtin").find_files { path_display = { "shorten" } } For extension developers there is a new util function that can be used to display a path: local filename = utils.transform_path(opts, entry.filename) *telescope.changelog-473* Date: July 14, 2021 PR: https://github.com/nvim-telescope/telescope.nvim/pull/473 Deprecation of telescope.path Extension developers need to move to plenary.path, because we will remove the telescope.path module soon. Guide to switch over to plenary.path - separator before: require("telescope.path").separator now: require("plenary.path").path.sep - home before: require("telescope.path").home now: require("plenary.path").path.home - make_relative before: require("telescope.path").make_relative(filepath, cwd) now: require("plenary.path"):new(filepath):make_relative(cwd) - shorten before: require("telescope.path").shorten(filepath) now: require("plenary.path"):new(filepath):shorten() with optional len, default is 1 - normalize before: require("telescope.path").normalize(filepath, cwd) now: require("plenary.path"):new(filepath):normalize(cwd) - read_file before: require("telescope.path").read_file(filepath) now: require("plenary.path"):new(filepath):read() - read_file_async before: require("telescope.path").read_file_async(filepath, callback) now: require("plenary.path"):new(filepath):read(callback) *telescope.changelog-1406* Date: November 4, 2021 PR: https://github.com/nvim-telescope/telescope.nvim/pull/1406 Telescope requires Neovim release 0.5.1 or a recent nightly Due to making use of newly implemented extmark features, Telescope now requires users to be on Neovim 0.5.1 (the most recent stable version) or on the LATEST version of Neovim nightly. *telescope.changelog-1549* Date: December 10, 2021 PR: https://github.com/nvim-telescope/telescope.nvim/pull/1549 Telescope requires now Neovim release 0.6.0 or a more recent nightly. If you are running neovim nightly, you need to make sure that you are on the LATEST version. Every other commit is not supported. So make sure you build the newest nightly before reporting issues. *telescope.changelog-1553* Date: December 10, 2021 PR: https://github.com/nvim-telescope/telescope.nvim/pull/1553 Move from `vim.lsp.diagnostic` to `vim.diagnostic`. Because the newly added `vim.diagnostic` has no longer anything to do with lsp we also decided to rename our diagnostic functions: Telescope lsp_document_diagnostics -> Telescope diagnostics bufnr=0 Telescope lsp_workspace_diagnostics -> Telescope diagnostics Because of that the `lsp_*_diagnostics` inside Telescope will be deprecated and removed soon. The new `diagnostics` works almost identical to the previous functions. Note that there is no longer a workspace diagnostics. You can only get all diagnostics for all open buffers. *telescope.changelog-1851* Date: April 22, 2022 PR: https://github.com/nvim-telescope/telescope.nvim/pull/1851 Telescope requires now Neovim release 0.7.0 or a more recent nightly. If you are running Neovim nightly, you need to make sure that you are on the LATEST version. Every other commit is not supported. So make sure you build the newest nightly before reporting issues. In the future, we will adopt a different release strategy. This release strategy follows the approach that the latest telescope.nvim master will only work with latest Neovim nightly and we will provide tags for specific Neovim versions. You can read more about this strategy here: https://github.com/nvim-telescope/telescope.nvim/issues/1772 *telescope.changelog-1866* Date: April 25, 2022 PR: https://github.com/nvim-telescope/telescope.nvim/pull/1866 We decided to remove both `lsp_code_actions` and `lsp_range_code_actions`. Currently, both functions are highly duplicated code from neovim, with fewer features, because it's out of date. So rather that we copy over the required changes to fix some bugs or implement client side code actions, we decided to remove both of them and suggest you use `vim.lsp.buf.code_action` and `vim.lsp.buf.range_code_action`. The transition to it is easy thanks to `vim.ui.select` which allows you to override the select UI. We provide a small extension for quite some time that make it easy to use telescope for `vim.ui.select`. You can found the code here https://github.com/nvim-telescope/telescope-ui-select.nvim. It offers the same displaying as the current version of `lsp_code_actions`. An alternative is https://github.com/stevearc/dressing.nvim which has support for multiple different backends including telescope. *telescope.changelog-1945* Date: July 01, 2022 PR: https://github.com/nvim-telescope/telescope.nvim/pull/1945 This is our dev branch which contains a lot of PRs, a lot of fixes, refactoring and general quality of life improvements. It also contains new features, the most noteworthy are the following (mostly developed by the community): - feat: none strategy & control attachment (#1867) - feat: force buffer delete for terminal and improvements for Picker:delete_selection (#1943) - feat(tags): process tagfiles on the fly (#1989) - feat(builtin.lsp): implement builtin handlers for lsp.(incoming|outgoing)_calls (#1484) - feat: clear previewer if no item is selected (#2004) - feat: add min max boundary to width, height resolver (#2002) - feat: Add entry_index for entry_makers (#1850) - feat: refine with new_table (#1115) The last one is one of the most exciting new features, because it allows you to go from live_grep into a fuzzy environment with the following mapping ``. It's a general interface we now implemented for `live_grep` and `lsp_dynamic_workspace_symbols` but it could also be easily implemented for other builtins, by us or the user. It's now available for extension developers. We will add documentation in the next couple of days and improve it by adding more options to configure it after the initial 0.1 release. But as with all longer development phases, there are also some breaking changes. This is the main reason we moved development to a separate branch, for the past two months. We can't promise that there won't be more breaking changes, but it is the plan that this is the last set of breaking changes prior to the 0.1 release on July, 12. We are deeply sorry for the inconvenience. The following breaking changes are included in this PR: - break(git_files): change `show_untracked` default to false. Can be changed back with `:Telescope git_files show_untracked=true` - break: deprecate `utils.get_default` `utils.if_nil`, will be removed prior to 0.1, so if you use it in your config, please move to `vim.F.if_nil` - break: drops `ignore_filename` option, use `path_display= { "hidden" }` instead - break: prefix internal interfaces with __ so `require("telescope.builtin.files").find_files` will show a notify error but still works for now. The error will be removed prior to 0.1! You should use `require("telescope.builtin").find_files` because we wrap all the functions that are exposed in this module. - break: defaults.preview.treesitter rework that allows you to either enable a list of languages, or enable all and disable some. Please read `:help telescope.defaults.preview` for more information. Something like this is now possible: > treesitter = { enable = false, -- or enable = { "c" }, -- disable can be set if enable isn't set disable = { "perl", "javascript" }, }, < *telescope.changelog-2499* Date: May 24, 2023 PR: https://github.com/nvim-telescope/telescope.nvim/pull/2499 We decided to bump the minimum Neovim version to 0.9.0, in order to remove a couple of no longer required workarounds. That includes using upstream treesitter implementation in favor of nvim-treesitter. If you still have a requirement for Neovim 0.7 or 0.8, we also have a stable branch 0.1.x (or version, currently 0.1.1) which will not receive this version bump and will continue to offer support for older Neovim versions. *telescope.changelog-2529* Date: June 09, 2023 PR: https://github.com/nvim-telescope/telescope.nvim/pull/2529 We finally removed usage of `plenary.filetype` to determine filetypes for previewing and replaced it with `vim.filetype`. So if you have highlighting issues you no longer have to configure `plenary`, but rather read |vim.filetype|. vim:tw=78:ts=8:ft=help:norl: ================================================ FILE: ftplugin/TelescopePrompt.lua ================================================ -- Don't wrap textwidth things vim.opt_local.formatoptions:remove "t" vim.opt_local.formatoptions:remove "c" -- Don't include `showbreak` when calculating strdisplaywidth vim.opt_local.wrap = false -- There's also no reason to enable textwidth here anyway vim.opt_local.textwidth = 0 vim.opt_local.scrollbind = false vim.opt_local.signcolumn = "no" ================================================ FILE: ftplugin/TelescopeResults.lua ================================================ -- Don't have scrolloff, it makes things weird. vim.opt_local.scrolloff = 0 vim.opt_local.scrollbind = false vim.opt_local.signcolumn = "no" ================================================ FILE: lua/telescope/_.lua ================================================ local uv = vim.uv local Object = require "plenary.class" local log = require "plenary.log" local async = require "plenary.async" local channel = require("plenary.async").control.channel local utils = require "telescope.utils" 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 = utils.path_expand(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, opts) schedule = vim.F.if_nil(schedule, true) local split_char = vim.F.if_nil(opts and opts.split_char, "\n") 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 function next_value() 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, split_char, 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: lua/telescope/_extensions/init.lua ================================================ local extensions = {} extensions._loaded = {} extensions._config = {} extensions._health = {} local load_extension = function(name) local ok, ext = pcall(require, "telescope._extensions." .. name) if not ok then error(string.format("'%s' extension doesn't exist or isn't installed: %s", name, ext)) end return ext end extensions.manager = setmetatable({}, { __index = function(t, k) local ext = load_extension(k) t[k] = ext.exports or {} if ext.setup then ext.setup(extensions._config[k] or {}, require("telescope.config").values) end extensions._health[k] = ext.health return t[k] end, }) --- Register an extension module. --- --- Extensions have several important keys. --- - setup: --- function(ext_config, config) -> nil --- --- Called when first loading the extension. --- The first parameter is the config passed by the user --- in telescope setup. The second parameter is the resulting --- config.values after applying the users setup defaults. --- --- It is acceptable for a plugin to override values in config, --- as some plugins will be installed simply to manage some setup, --- install some sorter, etc. --- --- - exports: --- table --- --- Only the items in `exports` will be exposed on the resulting --- module that users can access via require('telescope').extensions.foo --- Also, any top-level key-value pairs in exports where the value is a function and the --- key doesn't start with an underscore will be included when calling the `builtin` picker --- with the `include_extensions` option enabled. --- --- Other things in the module will not be accessible. This is the public API --- for your extension. Consider not breaking it a lot :laugh: --- --- TODO: --- - actions extensions.register = function(mod) return mod end extensions.load = function(name) local ext = load_extension(name) if ext.setup then ext.setup(extensions._config[name] or {}, require("telescope.config").values) end return extensions.manager[name] end extensions.set_config = function(extensions_config) extensions._config = extensions_config or {} end return extensions ================================================ FILE: lua/telescope/actions/generate.lua ================================================ ---@tag telescope.actions.generate ---@config { ["module"] = "telescope.actions.generate", ["name"] = "ACTIONS_GENERATE" } ---@brief [[ --- Module for convenience to override defaults of corresponding |telescope.actions| at |telescope.setup()|. --- --- General usage: --- --- require("telescope").setup { --- defaults = { --- mappings = { --- n = { --- ["?"] = action_generate.which_key { --- name_width = 20, -- typically leads to smaller floats --- max_height = 0.5, -- increase potential maximum height --- separator = " > ", -- change sep between mode, keybind, and name --- close_with_action = false, -- do not close float on action --- }, --- }, --- }, --- }, --- } --- ---@brief ]] local actions = require "telescope.actions" local config = require "telescope.config" local action_state = require "telescope.actions.state" local finders = require "telescope.finders" local action_generate = {} --- Display the keymaps of registered actions similar to which-key.nvim.
--- - Floating window: --- - Appears on the opposite side of the prompt. --- - Resolves to minimum required number of lines to show hints with `opts` or truncates entries at `max_height`. --- - Closes automatically on action call and can be disabled with by setting `close_with_action` to false. ---@param opts table: options to pass to toggling registered actions ---@field max_height number: % of max. height or no. of rows for hints (default: 0.4), see |resolver.resolve_height()| ---@field only_show_current_mode boolean: only show keymaps for the current mode (default: true) ---@field mode_width number: fixed width of mode to be shown (default: 1) ---@field keybind_width number: fixed width of keybind to be shown (default: 7) ---@field name_width number: fixed width of action name to be shown (default: 30) ---@field column_padding string: string to split; can be used for vertical separator (default: " ") ---@field mode_hl string: hl group of mode (default: TelescopeResultsConstant) ---@field keybind_hl string: hl group of keybind (default: TelescopeResultsVariable) ---@field name_hl string: hl group of action name (default: TelescopeResultsFunction) ---@field column_indent number: number of left-most spaces before keybinds are shown (default: 4) ---@field line_padding number: row padding in top and bottom of float (default: 1) ---@field separator string: separator string between mode, key bindings, and action (default: " -> ") ---@field close_with_action boolean: registered action will close keymap float (default: true) ---@field normal_hl string: winhl of "Normal" for keymap hints floating window (default: "TelescopePrompt") ---@field border_hl string: winhl of "Normal" for keymap borders (default: "TelescopePromptBorder") ---@field winblend number: pseudo-transparency of keymap hints floating window ---@field zindex number: z-index of keymap hints floating window (default: 100) action_generate.which_key = function(opts) local which_key = function(prompt_bufnr) actions.which_key(prompt_bufnr, opts) end return which_key end action_generate.refine = function(prompt_bufnr, opts) opts = opts or {} opts.prompt_to_prefix = vim.F.if_nil(opts.prompt_to_prefix, false) opts.prefix_hl_group = vim.F.if_nil(opts.prompt_hl_group, "TelescopePromptPrefix") opts.prompt_prefix = vim.F.if_nil(opts.prompt_prefix, config.values.prompt_prefix) opts.reset_multi_selection = vim.F.if_nil(opts.reset_multi_selection, false) opts.reset_prompt = vim.F.if_nil(opts.reset_prompt, true) opts.sorter = vim.F.if_nil(opts.sorter, config.values.generic_sorter {}) local push_history = vim.F.if_nil(opts.push_history, true) local current_picker = action_state.get_current_picker(prompt_bufnr) local current_line = action_state.get_current_line() if push_history then action_state.get_current_history():append(current_line, current_picker) end -- title if opts.prompt_title and current_picker.layout.prompt.border then current_picker.layout.prompt.border:change_title(opts.prompt_title) end if opts.results_title and current_picker.layout.results.border then current_picker.layout.results.border:change_title(opts.results_title) end local results = {} for entry in current_picker.manager:iter() do table.insert(results, entry) end -- if opts.sorter == false, keep older sorter if opts.sorter then current_picker.sorter:_destroy() current_picker.sorter = opts.sorter current_picker.sorter:_init() end local new_finder = finders.new_table { results = results, entry_maker = function(x) return x end, } if not opts.reset_multi_selection and current_line ~= "" then opts.multi = current_picker._multi end if opts.prompt_to_prefix then local current_prefix = current_picker.prompt_prefix local suffix = current_prefix ~= opts.prompt_prefix and current_prefix or "" opts.new_prefix = suffix .. current_line .. " " .. opts.prompt_prefix end current_picker:refresh(new_finder, opts) end return action_generate ================================================ FILE: lua/telescope/actions/history.lua ================================================ local conf = require("telescope.config").values local Path = require "plenary.path" local utils = require "telescope.utils" local uv = vim.uv ---@tag telescope.actions.history ---@config { ["module"] = "telescope.actions.history" } ---@brief [[ --- A base implementation of a prompt history that provides a simple history --- and can be replaced with a custom implementation. --- --- For example: We provide an extension for a smart history that uses sql.nvim --- to map histories to metadata, like the calling picker or cwd. --- --- So you have a history for: --- - find_files project_1 --- - grep_string project_1 --- - live_grep project_1 --- - find_files project_2 --- - grep_string project_2 --- - live_grep project_2 --- - etc --- --- See https://github.com/nvim-telescope/telescope-smart-history.nvim ---@brief ]] -- TODO(conni2461): currently not present in plenary path only sync. -- But sync is just unnecessary here local write_async = function(path, txt, flag) uv.fs_open(path, flag, 438, function(open_err, fd) assert(not open_err, open_err) uv.fs_write(fd, txt, -1, function(write_err) assert(not write_err, write_err) uv.fs_close(fd, function(close_err) assert(not close_err, close_err) end) end) end) end local append_async = function(path, txt) write_async(path, txt, "a") end local histories = {} --- Manages prompt history ---@class History @Manages prompt history ---@field enabled boolean: Will indicate if History is enabled or disabled ---@field path string: Will point to the location of the history file ---@field limit string: Will have the limit of the history. Can be nil, if limit is disabled. ---@field content table: History table. Needs to be filled by your own History implementation ---@field index number: Used to keep track of the next or previous index. Default is #content + 1 ---@field cycle_wrap boolean: Controls if history will wrap on reaching beginning or end histories.History = {} histories.History.__index = histories.History --- Create a new History ---@param opts table: Defines the behavior of History ---@field init function: Will be called after handling configuration (required) ---@field append function: How to append a new prompt item (required) ---@field reset function: What happens on reset. Will be called when telescope closes (required) ---@field pre_get function: Will be called before a next or previous item will be returned (optional) function histories.History:new(opts) local obj = {} if conf.history == false or type(conf.history) ~= "table" then obj.enabled = false return setmetatable(obj, self) end obj.enabled = true if conf.history.limit then obj.limit = conf.history.limit end obj.path = utils.path_expand(conf.history.path) obj.content = {} obj.index = 1 obj.cycle_wrap = conf.history.cycle_wrap opts.init(obj) obj._reset = opts.reset obj._append = opts.append obj._pre_get = opts.pre_get return setmetatable(obj, self) end --- Shorthand to create a new history function histories.new(...) return histories.History:new(...) end --- Will reset the history index to the default initial state. Will happen after the picker closed function histories.History:reset() if not self.enabled then return end self._reset(self) end --- Append a new line to the history ---@param line string: current line that will be appended ---@param picker table: the current picker object ---@param no_reset boolean: On default it will reset the state at the end. If you don't want to do this set to true function histories.History:append(line, picker, no_reset) if not self.enabled then return end self._append(self, line, picker, no_reset) end --- Will return the next history item. Can be nil if there are no next items ---@param line string: the current line ---@param picker table: the current picker object ---@return string: the next history item function histories.History:get_next(line, picker) if not self.enabled then utils.notify("History:get_next", { msg = "You are cycling to next the history item but history is disabled. Read ':help telescope.defaults.history'", level = "WARN", }) return false end if self._pre_get then self._pre_get(self, line, picker) end local next_idx = self.index + 1 if next_idx > #self.content and self.cycle_wrap then next_idx = 1 end if next_idx <= #self.content then self.index = next_idx return self.content[next_idx] end self.index = #self.content + 1 return nil end --- Will return the previous history item. Can be nil if there are no previous items ---@param line string: the current line ---@param picker table: the current picker object ---@return string: the previous history item function histories.History:get_prev(line, picker) if not self.enabled then utils.notify("History:get_prev", { msg = "You are cycling to next the history item but history is disabled. Read ':help telescope.defaults.history'", level = "WARN", }) return false end if self._pre_get then self._pre_get(self, line, picker) end local next_idx = self.index - 1 if next_idx < 1 and self.cycle_wrap then next_idx = #self.content end if self.index == #self.content + 1 then if line ~= "" then self:append(line, picker, true) end end if next_idx >= 1 then self.index = next_idx return self.content[next_idx] end return nil end --- A simple implementation of history. --- --- It will keep one unified history across all pickers. histories.get_simple_history = function() return histories.new { init = function(obj) local p = Path:new(obj.path) if not p:exists() then p:touch { parents = true } end obj.content = Path:new(obj.path):readlines() obj.index = #obj.content table.remove(obj.content, obj.index) end, reset = function(self) self.index = #self.content + 1 end, append = function(self, line, _, no_reset) if line ~= "" then if self.content[#self.content] ~= line then table.insert(self.content, line) local len = #self.content if self.limit and len > self.limit then local diff = len - self.limit for i = diff, 1, -1 do table.remove(self.content, i) end write_async(self.path, table.concat(self.content, "\n") .. "\n", "w") else append_async(self.path, line .. "\n") end end end if not no_reset then self:reset() end end, } end return histories ================================================ FILE: lua/telescope/actions/init.lua ================================================ ---@tag telescope.actions ---@config { ["module"] = "telescope.actions" } ---@brief [[ --- These functions are useful for people creating their own mappings. --- --- Actions can be either normal functions that expect the `prompt_bufnr` as --- first argument (1) or they can be a custom telescope type called "action" (2). --- --- (1) The `prompt_bufnr` of a normal function denotes the identifier of your --- picker which can be used to access the picker state. In practice, users --- most commonly access from both picker and global state via the following: --- --- -- for utility functions --- local action_state = require "telescope.actions.state" --- --- local actions = {} --- actions.do_stuff = function(prompt_bufnr) --- local current_picker = action_state.get_current_picker(prompt_bufnr) -- picker state --- local entry = action_state.get_selected_entry() --- end --- --- --- See |telescope.actions.state| for more information. --- --- (2) To transform a module of functions into a module of "action"s, you need --- to do the following: --- --- local transform_mod = require("telescope.actions.mt").transform_mod --- --- local mod = {} --- mod.a1 = function(prompt_bufnr) --- -- your code goes here --- -- You can access the picker/global state as described above in (1). --- end --- --- mod.a2 = function(prompt_bufnr) --- -- your code goes here --- end --- mod = transform_mod(mod) --- --- -- Now the following is possible. This means that actions a2 will be executed --- -- after action a1. You can chain as many actions as you want. --- local action = mod.a1 + mod.a2 --- action(bufnr) --- --- --- Another interesting thing to do is that these actions now have functions you --- can call. These functions include `:replace(f)`, `:replace_if(f, c)`, --- `replace_map(tbl)` and `enhance(tbl)`. More information on these functions --- can be found in the `developers.md` and `lua/tests/automated/action_spec.lua` --- file. ---@brief ]] local api = vim.api local conf = require("telescope.config").values local state = require "telescope.state" local utils = require "telescope.utils" local popup = require "plenary.popup" local p_scroller = require "telescope.pickers.scroller" local action_state = require "telescope.actions.state" local action_utils = require "telescope.actions.utils" local action_set = require "telescope.actions.set" local entry_display = require "telescope.pickers.entry_display" local from_entry = require "telescope.from_entry" local transform_mod = require("telescope.actions.mt").transform_mod local resolver = require "telescope.config.resolve" local actions = setmetatable({}, { __index = function(_, k) error("Key does not exist for 'telescope.actions': " .. tostring(k)) end, }) local append_to_history = function(prompt_bufnr) action_state .get_current_history() :append(action_state.get_current_line(), action_state.get_current_picker(prompt_bufnr)) end --- Move the selection to the next entry ---@param prompt_bufnr number: The prompt bufnr actions.move_selection_next = function(prompt_bufnr) action_set.shift_selection(prompt_bufnr, 1) end --- Move the selection to the previous entry ---@param prompt_bufnr number: The prompt bufnr actions.move_selection_previous = function(prompt_bufnr) action_set.shift_selection(prompt_bufnr, -1) end --- Move the selection to the entry that has a worse score ---@param prompt_bufnr number: The prompt bufnr actions.move_selection_worse = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) action_set.shift_selection(prompt_bufnr, p_scroller.worse(picker.sorting_strategy)) end --- Move the selection to the entry that has a better score ---@param prompt_bufnr number: The prompt bufnr actions.move_selection_better = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) action_set.shift_selection(prompt_bufnr, p_scroller.better(picker.sorting_strategy)) end --- Move to the top of the picker ---@param prompt_bufnr number: The prompt bufnr actions.move_to_top = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:set_selection( p_scroller.top(current_picker.sorting_strategy, current_picker.max_results, current_picker.manager:num_results()) ) end --- Move to the middle of the picker ---@param prompt_bufnr number: The prompt bufnr actions.move_to_middle = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:set_selection( p_scroller.middle(current_picker.sorting_strategy, current_picker.max_results, current_picker.manager:num_results()) ) end --- Move to the bottom of the picker ---@param prompt_bufnr number: The prompt bufnr actions.move_to_bottom = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:set_selection( p_scroller.bottom(current_picker.sorting_strategy, current_picker.max_results, current_picker.manager:num_results()) ) end --- Add current entry to multi select ---@param prompt_bufnr number: The prompt bufnr actions.add_selection = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:add_selection(current_picker:get_selection_row()) end --- Remove current entry from multi select ---@param prompt_bufnr number: The prompt bufnr actions.remove_selection = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:remove_selection(current_picker:get_selection_row()) end --- Toggle current entry status for multi select ---@param prompt_bufnr number: The prompt bufnr actions.toggle_selection = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:toggle_selection(current_picker:get_selection_row()) end --- Multi select all entries. --- - Note: selected entries may include results not visible in the results pop up. ---@param prompt_bufnr number: The prompt bufnr actions.select_all = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) action_utils.map_entries(prompt_bufnr, function(entry, _, row) if not current_picker._multi:is_selected(entry) then current_picker._multi:add(entry) if current_picker:can_select_row(row) then local caret = current_picker:update_prefix(entry, row) if current_picker._selection_entry == entry and current_picker._selection_row == row then current_picker.highlighter:hi_selection(row, caret:match "(.*%S)") end current_picker.highlighter:hi_multiselect(row, current_picker._multi:is_selected(entry)) end end end) current_picker:get_status_updater(current_picker.prompt_win, current_picker.prompt_bufnr)() end --- Drop all entries from the current multi selection. ---@param prompt_bufnr number: The prompt bufnr actions.drop_all = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) action_utils.map_entries(prompt_bufnr, function(entry, _, row) current_picker._multi:drop(entry) if current_picker:can_select_row(row) then local caret = current_picker:update_prefix(entry, row) if current_picker._selection_entry == entry and current_picker._selection_row == row then current_picker.highlighter:hi_selection(row, caret:match "(.*%S)") end current_picker.highlighter:hi_multiselect(row, current_picker._multi:is_selected(entry)) end end) current_picker:get_status_updater(current_picker.prompt_win, current_picker.prompt_bufnr)() end --- Toggle multi selection for all entries. --- - Note: toggled entries may include results not visible in the results pop up. ---@param prompt_bufnr number: The prompt bufnr actions.toggle_all = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) action_utils.map_entries(prompt_bufnr, function(entry, _, row) current_picker._multi:toggle(entry) if current_picker:can_select_row(row) then local caret = current_picker:update_prefix(entry, row) if current_picker._selection_entry == entry and current_picker._selection_row == row then current_picker.highlighter:hi_selection(row, caret:match "(.*%S)") end current_picker.highlighter:hi_multiselect(row, current_picker._multi:is_selected(entry)) end end) current_picker:get_status_updater(current_picker.prompt_win, current_picker.prompt_bufnr)() end --- Scroll the preview window up ---@param prompt_bufnr number: The prompt bufnr actions.preview_scrolling_up = function(prompt_bufnr) action_set.scroll_previewer(prompt_bufnr, -1) end --- Scroll the preview window down ---@param prompt_bufnr number: The prompt bufnr actions.preview_scrolling_down = function(prompt_bufnr) action_set.scroll_previewer(prompt_bufnr, 1) end --- Scroll the preview window to the left ---@param prompt_bufnr number: The prompt bufnr actions.preview_scrolling_left = function(prompt_bufnr) action_set.scroll_horizontal_previewer(prompt_bufnr, -1) end --- Scroll the preview window to the right ---@param prompt_bufnr number: The prompt bufnr actions.preview_scrolling_right = function(prompt_bufnr) action_set.scroll_horizontal_previewer(prompt_bufnr, 1) end --- Scroll the results window up ---@param prompt_bufnr number: The prompt bufnr actions.results_scrolling_up = function(prompt_bufnr) action_set.scroll_results(prompt_bufnr, -1) end --- Scroll the results window down ---@param prompt_bufnr number: The prompt bufnr actions.results_scrolling_down = function(prompt_bufnr) action_set.scroll_results(prompt_bufnr, 1) end --- Scroll the results window to the left ---@param prompt_bufnr number: The prompt bufnr actions.results_scrolling_left = function(prompt_bufnr) action_set.scroll_horizontal_results(prompt_bufnr, -1) end --- Scroll the results window to the right ---@param prompt_bufnr number: The prompt bufnr actions.results_scrolling_right = function(prompt_bufnr) action_set.scroll_horizontal_results(prompt_bufnr, 1) end --- Center the cursor in the window, can be used after selecting a file to edit --- You can just map `actions.select_default + actions.center` ---@param prompt_bufnr number: The prompt bufnr actions.center = function(prompt_bufnr) vim.cmd ":normal! zz" end --- Perform default action on selection, usually something like
--- `:edit ` --- --- i.e. open the selection in the current buffer ---@param prompt_bufnr number: The prompt bufnr actions.select_default = { pre = append_to_history, action = function(prompt_bufnr) return action_set.select(prompt_bufnr, "default") end, } --- Perform 'horizontal' action on selection, usually something like
---`:new ` --- --- i.e. open the selection in a new horizontal split ---@param prompt_bufnr number: The prompt bufnr actions.select_horizontal = { pre = append_to_history, action = function(prompt_bufnr) return action_set.select(prompt_bufnr, "horizontal") end, } --- Perform 'vertical' action on selection, usually something like
---`:vnew ` --- --- i.e. open the selection in a new vertical split ---@param prompt_bufnr number: The prompt bufnr actions.select_vertical = { pre = append_to_history, action = function(prompt_bufnr) return action_set.select(prompt_bufnr, "vertical") end, } --- Perform 'tab' action on selection, usually something like
---`:tabedit ` --- --- i.e. open the selection in a new tab ---@param prompt_bufnr number: The prompt bufnr actions.select_tab = { pre = append_to_history, action = function(prompt_bufnr) return action_set.select(prompt_bufnr, "tab") end, } --- Perform 'drop' action on selection, usually something like
---`:drop ` --- --- i.e. open the selection in a window ---@param prompt_bufnr number: The prompt bufnr actions.select_drop = { pre = append_to_history, action = function(prompt_bufnr) return action_set.select(prompt_bufnr, "drop") end, } --- Perform 'tab drop' action on selection, usually something like
---`:tab drop ` --- --- i.e. open the selection in a new tab ---@param prompt_bufnr number: The prompt bufnr actions.select_tab_drop = { pre = append_to_history, action = function(prompt_bufnr) return action_set.select(prompt_bufnr, "tab drop") end, } -- TODO: consider adding float! -- https://github.com/nvim-telescope/telescope.nvim/issues/365 --- Perform file edit on selection, usually something like
--- `:edit ` ---@param prompt_bufnr number: The prompt bufnr actions.file_edit = function(prompt_bufnr) return action_set.edit(prompt_bufnr, "edit") end --- Perform file split on selection, usually something like
--- `:new ` ---@param prompt_bufnr number: The prompt bufnr actions.file_split = function(prompt_bufnr) return action_set.edit(prompt_bufnr, "new") end --- Perform file vsplit on selection, usually something like
--- `:vnew ` ---@param prompt_bufnr number: The prompt bufnr actions.file_vsplit = function(prompt_bufnr) return action_set.edit(prompt_bufnr, "vnew") end --- Perform file tab on selection, usually something like
--- `:tabedit ` ---@param prompt_bufnr number: The prompt bufnr actions.file_tab = function(prompt_bufnr) return action_set.edit(prompt_bufnr, "tabedit") end actions.close_pum = function(_) if 0 ~= vim.fn.pumvisible() then api.nvim_feedkeys(api.nvim_replace_termcodes("", true, true, true), "n", true) end end --- Close the Telescope window, usually used within an action ---@param prompt_bufnr number: The prompt bufnr actions.close = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) local original_win_id = picker.original_win_id local cursor_valid, original_cursor = pcall(api.nvim_win_get_cursor, original_win_id) actions.close_pum(prompt_bufnr) require("telescope.pickers").on_close_prompt(prompt_bufnr) pcall(api.nvim_set_current_win, original_win_id) if cursor_valid and api.nvim_get_mode().mode == "i" and picker._original_mode ~= "i" then pcall(api.nvim_win_set_cursor, original_win_id, { original_cursor[1], original_cursor[2] + 1 }) end end --- Close the Telescope window, usually used within an action
--- Deprecated and no longer needed, does the same as |telescope.actions.close|. Might be removed in the future ---@deprecated ---@param prompt_bufnr number: The prompt bufnr actions._close = function(prompt_bufnr) actions.close(prompt_bufnr) end local set_edit_line = function(prompt_bufnr, fname, prefix, postfix) postfix = vim.F.if_nil(postfix, "") postfix = api.nvim_replace_termcodes(postfix, true, false, true) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection(fname) return end actions.close(prompt_bufnr) api.nvim_feedkeys(prefix .. selection.value .. postfix, "n", true) end --- Set a value in the command line and don't run it, making it editable. ---@param prompt_bufnr number: The prompt bufnr actions.edit_command_line = function(prompt_bufnr) set_edit_line(prompt_bufnr, "actions.edit_command_line", ":") end --- Set a value in the command line and run it ---@param prompt_bufnr number: The prompt bufnr actions.set_command_line = function(prompt_bufnr) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.set_command_line" return end actions.close(prompt_bufnr) vim.fn.histadd("cmd", selection.value) vim.cmd(selection.value) end --- Set a value in the search line and don't search for it, making it editable. ---@param prompt_bufnr number: The prompt bufnr actions.edit_search_line = function(prompt_bufnr) set_edit_line(prompt_bufnr, "actions.edit_search_line", "/") end --- Set a value in the search line and search for it ---@param prompt_bufnr number: The prompt bufnr actions.set_search_line = function(prompt_bufnr) set_edit_line(prompt_bufnr, "actions.set_search_line", "/", "") end --- Edit a register ---@param prompt_bufnr number: The prompt bufnr actions.edit_register = function(prompt_bufnr) local selection = action_state.get_selected_entry() local picker = action_state.get_current_picker(prompt_bufnr) vim.fn.inputsave() local updated_value = vim.fn.input("Edit [" .. selection.value .. "] ❯ ", selection.content) vim.fn.inputrestore() if updated_value ~= selection.content then vim.fn.setreg(selection.value, updated_value) selection.content = updated_value end -- update entry in results table -- TODO: find way to redraw finder content for _, v in pairs(picker.finder.results) do if v == selection then v.content = updated_value end end end --- Paste the selected register into the buffer --- --- Note: only meant to be used inside builtin.registers ---@param prompt_bufnr number: The prompt bufnr actions.paste_register = function(prompt_bufnr) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.paste_register" return end actions.close(prompt_bufnr) -- ensure that the buffer can be written to if vim.bo[0].modifiable then api.nvim_paste(selection.content, true, -1) end end --- Insert a symbol into the current buffer (while switching to normal mode) ---@param prompt_bufnr number: The prompt bufnr actions.insert_symbol = function(prompt_bufnr) local symbol = action_state.get_selected_entry().value[1] actions.close(prompt_bufnr) vim.schedule(function() api.nvim_put({ symbol }, "", true, true) end) end --- Insert a symbol into the current buffer and keeping the insert mode. ---@param prompt_bufnr number: The prompt bufnr actions.insert_symbol_i = function(prompt_bufnr) local symbol = action_state.get_selected_entry().value[1] actions.close(prompt_bufnr) vim.schedule(function() vim.cmd [[startinsert]] api.nvim_put({ symbol }, "", true, true) end) end -- TODO: Think about how to do this. actions.insert_value = function(prompt_bufnr) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.insert_value" return end vim.schedule(function() actions.close(prompt_bufnr) end) return selection.value end --- Ask user to confirm an action ---@param prompt string: The prompt for confirmation ---@param default_value string: The default value of user input ---@param yes_values table: List of positive user confirmations ({"y", "yes"} by default) ---@return boolean: Whether user confirmed the prompt local function ask_to_confirm(prompt, default_value, yes_values) yes_values = yes_values or { "y", "yes" } default_value = default_value or "" local confirmation = vim.fn.input(prompt, default_value) confirmation = string.lower(confirmation) if string.len(confirmation) == 0 then return false end for _, v in pairs(yes_values) do if v == confirmation then return true end end return false end --- Create and checkout a new git branch if it doesn't already exist ---@param prompt_bufnr number: The prompt bufnr actions.git_create_branch = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local new_branch = action_state.get_current_line() if new_branch == "" then utils.notify("actions.git_create_branch", { msg = "Missing the new branch name", level = "ERROR", }) else local confirmation = ask_to_confirm(string.format("Create new branch '%s'? [y/n]: ", new_branch)) if not confirmation then utils.notify("actions.git_create_branch", { msg = string.format("branch creation canceled: '%s'", new_branch), level = "INFO", }) return end actions.close(prompt_bufnr) local _, ret, stderr = utils.get_os_command_output({ "git", "checkout", "-b", new_branch }, cwd) if ret == 0 then utils.notify("actions.git_create_branch", { msg = string.format("Switched to a new branch: %s", new_branch), level = "INFO", }) else utils.notify("actions.git_create_branch", { msg = string.format( "Error when creating new branch: '%s' Git returned '%s'", new_branch, table.concat(stderr, " ") ), level = "INFO", }) end end end --- Applies an existing git stash ---@param prompt_bufnr number: The prompt bufnr actions.git_apply_stash = function(prompt_bufnr) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.git_apply_stash" return end actions.close(prompt_bufnr) local _, ret, stderr = utils.get_os_command_output { "git", "stash", "apply", "--index", selection.value } if ret == 0 then utils.notify("actions.git_apply_stash", { msg = string.format("applied: '%s' ", selection.value), level = "INFO", }) else utils.notify("actions.git_apply_stash", { msg = string.format("Error when applying: %s. Git returned: '%s'", selection.value, table.concat(stderr, " ")), level = "ERROR", }) end end --- Checkout an existing git branch ---@param prompt_bufnr number: The prompt bufnr actions.git_checkout = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.git_checkout" return end actions.close(prompt_bufnr) local _, ret, stderr = utils.get_os_command_output({ "git", "checkout", selection.value }, cwd) if ret == 0 then utils.notify("actions.git_checkout", { msg = string.format("Checked out: %s", selection.value), level = "INFO", }) vim.cmd "checktime" else utils.notify("actions.git_checkout", { msg = string.format( "Error when checking out: %s. Git returned: '%s'", selection.value, table.concat(stderr, " ") ), level = "ERROR", }) end end --- Switch to git branch.
--- If the branch already exists in local, switch to that. --- If the branch is only in remote, create new branch tracking remote and switch to new one. ---@param prompt_bufnr number: The prompt bufnr actions.git_switch_branch = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.git_switch_branch" return end actions.close(prompt_bufnr) local pattern = "^refs/remotes/%w+/" local branch = selection.value if string.match(selection.refname, pattern) then branch = string.gsub(selection.refname, pattern, "") end local _, ret, stderr = utils.get_os_command_output({ "git", "switch", branch }, cwd) if ret == 0 then utils.notify("actions.git_switch_branch", { msg = string.format("Switched to: '%s'", branch), level = "INFO", }) else utils.notify("actions.git_switch_branch", { msg = string.format( "Error when switching to: %s. Git returned: '%s'", selection.value, table.concat(stderr, " ") ), level = "ERROR", }) end end --- Action to rename selected git branch --- @param prompt_bufnr number: The prompt bufnr actions.git_rename_branch = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.git_rename_branch" return end -- Keeps the selected branch name for the input that asks for the new branch name local new_branch = vim.fn.input("New branch name: ", selection.value) if new_branch == "" then utils.notify("actions.git_rename_branch", { msg = "Missing the new branch name", level = "ERROR", }) else actions.close(prompt_bufnr) local _, ret, stderr = utils.get_os_command_output({ "git", "branch", "-m", selection.value, new_branch }, cwd) if ret == 0 then utils.notify("actions.git_rename_branch", { msg = string.format("Renamed branch: '%s'", selection.value), level = "INFO", }) else utils.notify("actions.git_rename_branch", { msg = string.format( "Error when renaming branch: %s. Git returned: '%s'", selection.value, table.concat(stderr, " ") ), level = "ERROR", }) end end end local function make_git_branch_action(opts) return function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection(opts.action_name) return end local should_confirm = opts.should_confirm if should_confirm then local confirmation = ask_to_confirm(string.format(opts.confirmation_question, selection.value), "y") if not confirmation then utils.notify(opts.action_name, { msg = "action canceled", level = "INFO", }) return end end actions.close(prompt_bufnr) local _, ret, stderr = utils.get_os_command_output(opts.command(selection.value), cwd) if ret == 0 then utils.notify(opts.action_name, { msg = string.format(opts.success_message, selection.value), level = "INFO", }) else utils.notify(opts.action_name, { msg = string.format(opts.error_message, selection.value, table.concat(stderr, " ")), level = "ERROR", }) end end end --- Tell git to track the currently selected remote branch in Telescope ---@param prompt_bufnr number: The prompt bufnr actions.git_track_branch = make_git_branch_action { should_confirm = false, action_name = "actions.git_track_branch", success_message = "Tracking branch: %s", error_message = "Error when tracking branch: %s. Git returned: '%s'", command = function(branch_name) return { "git", "checkout", "--track", branch_name } end, } --- Delete all currently selected branches ---@param prompt_bufnr number: The prompt bufnr actions.git_delete_branch = function(prompt_bufnr) local confirmation = ask_to_confirm("Do you really want to delete the selected branches? [Y/n] ", "y") if not confirmation then utils.notify("actions.git_delete_branch", { msg = "action canceled", level = "INFO", }) return end local picker = action_state.get_current_picker(prompt_bufnr) local action_name = "actions.git_delete_branch" picker:delete_selection(function(selection) local branch = selection.value print("Deleting branch " .. branch) local _, ret, stderr = utils.get_os_command_output({ "git", "branch", "-D", branch }, picker.cwd) if ret == 0 then utils.notify(action_name, { msg = string.format("Deleted branch: %s", branch), level = "INFO", }) else utils.notify(action_name, { msg = string.format("Error when deleting branch: %s. Git returned: '%s'", branch, table.concat(stderr, " ")), level = "ERROR", }) end return ret == 0 end) end --- Merge the currently selected branch ---@param prompt_bufnr number: The prompt bufnr actions.git_merge_branch = make_git_branch_action { should_confirm = true, action_name = "actions.git_merge_branch", confirmation_question = "Do you really want to merge branch %s? [Y/n] ", success_message = "Merged branch: %s", error_message = "Error when merging branch: %s. Git returned: '%s'", command = function(branch_name) return { "git", "merge", branch_name } end, } --- Rebase to selected git branch ---@param prompt_bufnr number: The prompt bufnr actions.git_rebase_branch = make_git_branch_action { should_confirm = true, action_name = "actions.git_rebase_branch", confirmation_question = "Do you really want to rebase branch %s? [Y/n] ", success_message = "Rebased branch: %s", error_message = "Error when rebasing branch: %s. Git returned: '%s'", command = function(branch_name) return { "git", "rebase", branch_name } end, } local git_reset_branch = function(prompt_bufnr, mode) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.git_reset_branch" return end local confirmation = ask_to_confirm("Do you really want to " .. mode .. " reset to " .. selection.value .. "? [Y/n] ", "y") if not confirmation then utils.notify("actions.git_reset_branch", { msg = "action canceled", level = "INFO", }) return end actions.close(prompt_bufnr) local _, ret, stderr = utils.get_os_command_output({ "git", "reset", mode, selection.value }, cwd) if ret == 0 then utils.notify("actions.git_rebase_branch", { msg = string.format("Reset to: '%s'", selection.value), level = "INFO", }) else utils.notify("actions.git_rebase_branch", { msg = string.format("Rest to: %s. Git returned: '%s'", selection.value, table.concat(stderr, " ")), level = "ERROR", }) end end --- Reset to selected git commit using mixed mode ---@param prompt_bufnr number: The prompt bufnr actions.git_reset_mixed = function(prompt_bufnr) git_reset_branch(prompt_bufnr, "--mixed") end --- Reset to selected git commit using soft mode ---@param prompt_bufnr number: The prompt bufnr actions.git_reset_soft = function(prompt_bufnr) git_reset_branch(prompt_bufnr, "--soft") end --- Reset to selected git commit using hard mode ---@param prompt_bufnr number: The prompt bufnr actions.git_reset_hard = function(prompt_bufnr) git_reset_branch(prompt_bufnr, "--hard") end --- Checkout a specific file for a given sha ---@param prompt_bufnr number: The prompt bufnr actions.git_checkout_current_buffer = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.git_checkout_current_buffer" return end actions.close(prompt_bufnr) utils.get_os_command_output({ "git", "checkout", selection.value, "--", selection.current_file }, cwd) vim.cmd "checktime" end --- Stage/unstage selected file ---@param prompt_bufnr number: The prompt bufnr actions.git_staging_toggle = function(prompt_bufnr) local cwd = action_state.get_current_picker(prompt_bufnr).cwd local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "actions.git_staging_toggle" return end if selection.status:sub(2) == " " then utils.get_os_command_output({ "git", "restore", "--staged", selection.value }, cwd) else utils.get_os_command_output({ "git", "add", selection.value }, cwd) end end local entry_to_qf = function(entry) local text = entry.text if not text then if type(entry.value) == "table" then text = entry.value.text else text = entry.value end end return { bufnr = entry.bufnr, filename = from_entry.path(entry, false, false), lnum = vim.F.if_nil(entry.lnum, 1), col = vim.F.if_nil(entry.col, 1), text = text, type = entry.qf_type, } end local send_selected_to_qf = function(prompt_bufnr, mode, target) local picker = action_state.get_current_picker(prompt_bufnr) local qf_entries = {} for _, entry in ipairs(picker:get_multi_selection()) do table.insert(qf_entries, entry_to_qf(entry)) end local prompt = picker:_get_prompt() actions.close(prompt_bufnr) api.nvim_exec_autocmds("QuickFixCmdPre", {}) if target == "loclist" then vim.fn.setloclist(picker.original_win_id, qf_entries, mode) else local qf_title = string.format([[%s (%s)]], picker.prompt_title, prompt) vim.fn.setqflist(qf_entries, mode) vim.fn.setqflist({}, "a", { title = qf_title }) end api.nvim_exec_autocmds("QuickFixCmdPost", {}) end local send_all_to_qf = function(prompt_bufnr, mode, target) local picker = action_state.get_current_picker(prompt_bufnr) local manager = picker.manager local qf_entries = {} for entry in manager:iter() do table.insert(qf_entries, entry_to_qf(entry)) end local prompt = picker:_get_prompt() actions.close(prompt_bufnr) api.nvim_exec_autocmds("QuickFixCmdPre", {}) local qf_title = string.format([[%s (%s)]], picker.prompt_title, prompt) if target == "loclist" then vim.fn.setloclist(picker.original_win_id, qf_entries, mode) vim.fn.setloclist(picker.original_win_id, {}, "a", { title = qf_title }) else vim.fn.setqflist(qf_entries, mode) vim.fn.setqflist({}, "a", { title = qf_title }) end api.nvim_exec_autocmds("QuickFixCmdPost", {}) end --- Sends the selected entries to the quickfix list, replacing the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.send_selected_to_qflist = { pre = append_to_history, action = function(prompt_bufnr) send_selected_to_qf(prompt_bufnr, " ") end, } --- Adds the selected entries to the quickfix list, keeping the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.add_selected_to_qflist = { pre = append_to_history, action = function(prompt_bufnr) send_selected_to_qf(prompt_bufnr, "a") end, } --- Sends all entries to the quickfix list, replacing the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.send_to_qflist = { pre = append_to_history, action = function(prompt_bufnr) send_all_to_qf(prompt_bufnr, " ") end, } --- Adds all entries to the quickfix list, keeping the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.add_to_qflist = { pre = append_to_history, action = function(prompt_bufnr) send_all_to_qf(prompt_bufnr, "a") end, } --- Sends the selected entries to the location list, replacing the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.send_selected_to_loclist = { pre = append_to_history, action = function(prompt_bufnr) send_selected_to_qf(prompt_bufnr, " ", "loclist") end, } --- Adds the selected entries to the location list, keeping the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.add_selected_to_loclist = { pre = append_to_history, action = function(prompt_bufnr) send_selected_to_qf(prompt_bufnr, "a", "loclist") end, } --- Sends all entries to the location list, replacing the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.send_to_loclist = { pre = append_to_history, action = function(prompt_bufnr) send_all_to_qf(prompt_bufnr, " ", "loclist") end, } --- Adds all entries to the location list, keeping the previous entries. ---@param prompt_bufnr number: The prompt bufnr actions.add_to_loclist = { pre = append_to_history, action = function(prompt_bufnr) send_all_to_qf(prompt_bufnr, "a", "loclist") end, } local smart_send = function(prompt_bufnr, mode, target) local picker = action_state.get_current_picker(prompt_bufnr) if #picker:get_multi_selection() > 0 then send_selected_to_qf(prompt_bufnr, mode, target) else send_all_to_qf(prompt_bufnr, mode, target) end end --- Sends the selected entries to the quickfix list, replacing the previous entries. --- If no entry was selected, sends all entries. ---@param prompt_bufnr number: The prompt bufnr actions.smart_send_to_qflist = { pre = append_to_history, action = function(prompt_bufnr) smart_send(prompt_bufnr, " ") end, } --- Adds the selected entries to the quickfix list, keeping the previous entries. --- If no entry was selected, adds all entries. ---@param prompt_bufnr number: The prompt bufnr actions.smart_add_to_qflist = { pre = append_to_history, action = function(prompt_bufnr) smart_send(prompt_bufnr, "a") end, } --- Sends the selected entries to the location list, replacing the previous entries. --- If no entry was selected, sends all entries. ---@param prompt_bufnr number: The prompt bufnr actions.smart_send_to_loclist = { pre = append_to_history, action = function(prompt_bufnr) smart_send(prompt_bufnr, " ", "loclist") end, } --- Adds the selected entries to the location list, keeping the previous entries. --- If no entry was selected, adds all entries. ---@param prompt_bufnr number: The prompt bufnr actions.smart_add_to_loclist = { pre = append_to_history, action = function(prompt_bufnr) smart_send(prompt_bufnr, "a", "loclist") end, } --- Open completion menu containing the tags which can be used to filter the results in a faster way ---@param prompt_bufnr number: The prompt bufnr actions.complete_tag = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) local tags = current_picker.sorter.tags local delimiter = current_picker.sorter._delimiter if not tags then utils.notify("actions.complete_tag", { msg = "No tag pre-filtering set for this picker", level = "ERROR", }) return end -- format tags to match filter_function local prefilter_tags = {} for tag, _ in pairs(tags) do table.insert(prefilter_tags, string.format("%s%s%s ", delimiter, tag:lower(), delimiter)) end local line = action_state.get_current_line() local filtered_tags = {} -- retrigger completion with already selected tag anew -- trim and add space since we can match [[:pattern: ]] with or without space at the end if vim.tbl_contains(prefilter_tags, vim.trim(line) .. " ") then filtered_tags = prefilter_tags else -- match tag by substring for _, tag in pairs(prefilter_tags) do local start, _ = tag:find(line) if start then table.insert(filtered_tags, tag) end end end if vim.tbl_isempty(filtered_tags) then utils.notify("complete_tag", { msg = "No matches found", level = "INFO", }) return end -- incremental completion by substituting string starting from col - #line byte offset local col = api.nvim_win_get_cursor(0)[2] + 1 vim.fn.complete(col - #line, filtered_tags) end --- Cycle to the next search prompt in the history ---@param prompt_bufnr number: The prompt bufnr actions.cycle_history_next = function(prompt_bufnr) local history = action_state.get_current_history() local current_picker = action_state.get_current_picker(prompt_bufnr) local line = action_state.get_current_line() local entry = history:get_next(line, current_picker) if entry == false then return end current_picker:reset_prompt() if entry ~= nil then current_picker:set_prompt(entry) end end --- Cycle to the previous search prompt in the history ---@param prompt_bufnr number: The prompt bufnr actions.cycle_history_prev = function(prompt_bufnr) local history = action_state.get_current_history() local current_picker = action_state.get_current_picker(prompt_bufnr) local line = action_state.get_current_line() local entry = history:get_prev(line, current_picker) if entry == false then return end if entry ~= nil then current_picker:reset_prompt() current_picker:set_prompt(entry) end end --- Open the quickfix list. It makes sense to use this in combination with one of the send_to_qflist actions --- `actions.smart_send_to_qflist + actions.open_qflist` ---@param prompt_bufnr number: The prompt bufnr actions.open_qflist = function(prompt_bufnr) vim.cmd [[botright copen]] end --- Open the location list. It makes sense to use this in combination with one of the send_to_loclist actions --- `actions.smart_send_to_qflist + actions.open_qflist` ---@param prompt_bufnr number: The prompt bufnr actions.open_loclist = function(prompt_bufnr) vim.cmd [[lopen]] end --- Delete the selected buffer or all the buffers selected using multi selection. ---@param prompt_bufnr number: The prompt bufnr actions.delete_buffer = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:delete_selection(function(selection) local force = vim.bo[selection.bufnr].buftype == "terminal" local ok = pcall(api.nvim_buf_delete, selection.bufnr, { force = force }) -- If the current buffer is deleted, switch to the previous buffer -- according to bdelete behavior if ok and selection.bufnr == current_picker.original_bufnr then if api.nvim_win_is_valid(current_picker.original_win_id) then local jumplist = vim.fn.getjumplist(current_picker.original_win_id)[1] for i = #jumplist, 1, -1 do if jumplist[i].bufnr ~= selection.bufnr and vim.fn.bufloaded(jumplist[i].bufnr) == 1 then api.nvim_win_set_buf(current_picker.original_win_id, jumplist[i].bufnr) current_picker.original_bufnr = jumplist[i].bufnr return ok end end -- no more valid buffers in jumplist, create an empty buffer local empty_buf = api.nvim_create_buf(true, true) api.nvim_win_set_buf(current_picker.original_win_id, empty_buf) current_picker.original_bufnr = empty_buf api.nvim_buf_delete(selection.bufnr, { force = true }) return ok end -- window of the selected buffer got wiped, switch to first valid window local win_id = vim.fn.win_getid(1, current_picker.original_tabpage) current_picker.original_win_id = win_id current_picker.original_bufnr = api.nvim_win_get_buf(win_id) end return ok end) end --- Cycle to the next previewer if there is one available.
--- This action is not mapped on default. ---@param prompt_bufnr number: The prompt bufnr actions.cycle_previewers_next = function(prompt_bufnr) action_state.get_current_picker(prompt_bufnr):cycle_previewers(1) end --- Cycle to the previous previewer if there is one available.
--- This action is not mapped on default. ---@param prompt_bufnr number: The prompt bufnr actions.cycle_previewers_prev = function(prompt_bufnr) action_state.get_current_picker(prompt_bufnr):cycle_previewers(-1) end --- Removes the selected picker in |builtin.pickers|.
--- This action is not mapped by default and only intended for |builtin.pickers|. ---@param prompt_bufnr number: The prompt bufnr actions.remove_selected_picker = function(prompt_bufnr) local curr_picker = action_state.get_current_picker(prompt_bufnr) local curr_entry = action_state.get_selected_entry() local cached_pickers = state.get_global_key "cached_pickers" if not curr_entry then return end local selection_index, _ = utils.list_find(function(v) if curr_entry.value == v.value then return true end return false end, curr_picker.finder.results) curr_picker:delete_selection(function() table.remove(cached_pickers, selection_index) end) if #cached_pickers == 0 then actions.close(prompt_bufnr) end end --- Display the keymaps of registered actions similar to which-key.nvim.
--- - Notes: --- - The defaults can be overridden via |action_generate.which_key|. ---@param prompt_bufnr number: The prompt bufnr actions.which_key = function(prompt_bufnr, opts) opts = opts or {} opts.max_height = vim.F.if_nil(opts.max_height, 0.4) opts.only_show_current_mode = vim.F.if_nil(opts.only_show_current_mode, true) opts.mode_width = vim.F.if_nil(opts.mode_width, 1) opts.keybind_width = vim.F.if_nil(opts.keybind_width, 7) opts.name_width = vim.F.if_nil(opts.name_width, 30) opts.line_padding = vim.F.if_nil(opts.line_padding, 1) opts.separator = vim.F.if_nil(opts.separator, " -> ") opts.close_with_action = vim.F.if_nil(opts.close_with_action, true) opts.normal_hl = vim.F.if_nil(opts.normal_hl, "TelescopePrompt") opts.border_hl = vim.F.if_nil(opts.border_hl, "TelescopePromptBorder") opts.winblend = vim.F.if_nil(opts.winblend, conf.winblend) if type(opts.winblend) == "function" then opts.winblend = opts.winblend() end opts.zindex = vim.F.if_nil(opts.zindex, 100) opts.column_padding = vim.F.if_nil(opts.column_padding, " ") -- Assigning into 'opts.column_indent' would override a number with a string and -- cause issues with subsequent calls, keep a local copy of the string instead local column_indent = table.concat(utils.repeated_table(vim.F.if_nil(opts.column_indent, 4), " ")) -- close on repeated keypress local km_bufs = (function() local ret = {} local bufs = api.nvim_list_bufs() for _, buf in ipairs(bufs) do for _, bufname in ipairs { "_TelescopeWhichKey", "_TelescopeWhichKeyBorder" } do if string.find(api.nvim_buf_get_name(buf), bufname) then table.insert(ret, buf) end end end return ret end)() if not vim.tbl_isempty(km_bufs) then for _, buf in ipairs(km_bufs) do utils.buf_delete(buf) local win_ids = vim.fn.win_findbuf(buf) for _, win_id in ipairs(win_ids) do pcall(api.nvim_win_close, win_id, true) end end return end local displayer = entry_display.create { separator = opts.separator, items = { { width = opts.mode_width }, { width = opts.keybind_width }, { width = opts.name_width }, }, } local make_display = function(mapping) return displayer { { mapping.mode, vim.F.if_nil(opts.mode_hl, "TelescopeResultsConstant") }, { mapping.keybind, vim.F.if_nil(opts.keybind_hl, "TelescopeResultsVariable") }, { mapping.name, vim.F.if_nil(opts.name_hl, "TelescopeResultsFunction") }, } end local mappings = {} local mode = api.nvim_get_mode().mode for _, v in pairs(action_utils.get_registered_mappings(prompt_bufnr)) do if v.desc and v.desc ~= "which_key" and v.desc ~= "nop" then if not opts.only_show_current_mode or mode == v.mode then table.insert(mappings, { mode = v.mode, keybind = v.keybind, name = v.desc }) if v.desc == "" then utils.notify("actions.which_key", { msg = "No name available for anonymous functions.", level = "INFO", once = true, }) end end end end table.sort(mappings, function(x, y) if x.name < y.name then return true elseif x.name == y.name then -- show normal mode as the standard mode first if x.mode > y.mode then return true else return false end else return false end end) local entry_width = #opts.column_padding + opts.mode_width + opts.keybind_width + opts.name_width + (3 * #opts.separator) local num_total_columns = math.floor((vim.o.columns - #column_indent) / entry_width) opts.num_rows = math.min(math.ceil(#mappings / num_total_columns), resolver.resolve_height(opts.max_height)(_, _, vim.o.lines)) local total_available_entries = opts.num_rows * num_total_columns local winheight = opts.num_rows + 2 * opts.line_padding -- place hints at top or bottom relative to prompt local win_central_row = function(win_nr) return api.nvim_win_get_position(win_nr)[1] + 0.5 * api.nvim_win_get_height(win_nr) end -- TODO(fdschmidt93|l-kershaw): better generalization of where to put which key float local picker = action_state.get_current_picker(prompt_bufnr) local prompt_row = win_central_row(picker.prompt_win) local results_row = win_central_row(picker.results_win) local preview_row = picker.preview_win and win_central_row(picker.preview_win) or results_row local prompt_pos = prompt_row < 0.4 * vim.o.lines or prompt_row < 0.6 * vim.o.lines and results_row + preview_row < vim.o.lines local modes = { n = "Normal", i = "Insert" } local title_mode = opts.only_show_current_mode and modes[mode] .. " Mode " or "" local title_text = title_mode .. "Keymaps" local popup_opts = { relative = "editor", enter = false, minwidth = vim.o.columns, maxwidth = vim.o.columns, minheight = winheight, maxheight = winheight, line = prompt_pos == true and vim.o.lines - winheight + 1 or 1, col = 0, border = { prompt_pos and 1 or 0, 0, not prompt_pos and 1 or 0, 0 }, borderchars = { prompt_pos and "─" or " ", "", not prompt_pos and "─" or " ", "", "", "", "", "" }, noautocmd = true, title = { { text = title_text, pos = prompt_pos and "N" or "S" } }, zindex = opts.zindex, } local km_win_id, km_opts = popup.create("", popup_opts) local km_buf = api.nvim_win_get_buf(km_win_id) api.nvim_buf_set_name(km_buf, "_TelescopeWhichKey") api.nvim_buf_set_name(km_opts.border.bufnr, "_TelescopeTelescopeWhichKeyBorder") vim.wo[km_win_id].winhl = "Normal:" .. opts.normal_hl vim.wo[km_opts.border.win_id].winhl = "Normal:" .. opts.border_hl vim.wo[km_win_id].winblend = opts.winblend vim.wo[km_win_id].foldenable = false api.nvim_create_autocmd("BufLeave", { buffer = km_buf, once = true, callback = function() pcall(api.nvim_win_close, km_win_id, true) pcall(api.nvim_win_close, km_opts.border.win_id, true) require("telescope.utils").buf_delete(km_buf) end, }) api.nvim_buf_set_lines( km_buf, 0, -1, false, utils.repeated_table(opts.num_rows + 2 * opts.line_padding, column_indent) ) local keymap_highlights = api.nvim_create_namespace "telescope_whichkey" local highlights = {} for index, mapping in ipairs(mappings) do local row = utils.cycle(index, opts.num_rows) - 1 + opts.line_padding local prev_line = api.nvim_buf_get_lines(km_buf, row, row + 1, false)[1] if index == total_available_entries and total_available_entries > #mappings then local new_line = prev_line .. "..." api.nvim_buf_set_lines(km_buf, row, row + 1, false, { new_line }) break end local display, display_hl = make_display(mapping) local new_line = prev_line .. display .. opts.column_padding -- incl. padding api.nvim_buf_set_lines(km_buf, row, row + 1, false, { new_line }) table.insert(highlights, { hl = display_hl, row = row, col = #prev_line }) end -- highlighting only after line setting as a.nvim_buf_set_lines removes hl otherwise for _, highlight_tbl in pairs(highlights) do local highlight = highlight_tbl.hl local row_ = highlight_tbl.row local col = highlight_tbl.col for _, hl_block in ipairs(highlight) do utils.hl_range( km_buf, keymap_highlights, hl_block[2], { row_, col + hl_block[1][1] }, { row_, col + hl_block[1][2] } ) end end -- if close_with_action is true, close the which_key window when any action is triggered -- otherwise close the window when the prompt buffer is closed local close_event, close_pattern, close_buffer if opts.close_with_action then close_event, close_pattern, close_buffer = "User", "TelescopeKeymap", nil else close_event, close_pattern, close_buffer = "BufWinLeave", nil, prompt_bufnr end -- only set up autocommand after showing preview completed vim.schedule(function() api.nvim_create_autocmd(close_event, { pattern = close_pattern, buffer = close_buffer, once = true, callback = function() vim.schedule(function() pcall(api.nvim_win_close, km_win_id, true) pcall(api.nvim_win_close, km_opts.border.win_id, true) utils.buf_delete(km_buf) end) end, }) end) end --- Move from a none fuzzy search to a fuzzy one
--- This action is meant to be used in live_grep and lsp_dynamic_workspace_symbols ---@param prompt_bufnr number: The prompt bufnr actions.to_fuzzy_refine = function(prompt_bufnr) local line = action_state.get_current_line() local opts = (function() local opts = { sorter = conf.generic_sorter {}, } local title = action_state.get_current_picker(prompt_bufnr).prompt_title if title == "Live Grep" then opts.prefix = "Find Word" elseif title == "LSP Dynamic Workspace Symbols" then opts.prefix = "LSP Workspace Symbols" opts.sorter = conf.prefilter_sorter { tag = "symbol_type", sorter = opts.sorter, } else opts.prefix = "Fuzzy over" end return opts end)() require("telescope.actions.generate").refine(prompt_bufnr, { prompt_title = string.format("%s (%s)", opts.prefix, line), sorter = opts.sorter, }) end --- Delete the selected mark or all the marks selected using multi selection. ---@param prompt_bufnr number: The prompt bufnr actions.delete_mark = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:delete_selection(function(selection) local bufname = selection.filename local bufnr = vim.fn.bufnr(bufname) local mark = selection.ordinal:sub(1, 1) local success if mark:match "%u" then success = pcall(api.nvim_del_mark, mark) else success = pcall(api.nvim_buf_del_mark, bufnr, mark) end return success end) end --- Insert the word under the cursor of the original (pre-Telescope) window ---@param prompt_bufnr number: The prompt bufnr actions.insert_original_cword = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:set_prompt(current_picker.original_cword, false) end --- Insert the WORD under the cursor of the original (pre-Telescope) window ---@param prompt_bufnr number: The prompt bufnr actions.insert_original_cWORD = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:set_prompt(current_picker.original_cWORD, false) end --- Insert the file under the cursor of the original (pre-Telescope) window ---@param prompt_bufnr number: The prompt bufnr actions.insert_original_cfile = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:set_prompt(current_picker.original_cfile, false) end --- Insert the line under the cursor of the original (pre-Telescope) window ---@param prompt_bufnr number: The prompt bufnr actions.insert_original_cline = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:set_prompt(current_picker.original_cline, false) end actions.nop = function(_) end actions.mouse_click = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) local pos = vim.fn.getmousepos() if pos.winid == picker.results_win then vim.schedule(function() picker:set_selection(pos.line - 1) end) elseif pos.winid == picker.preview_win then vim.schedule(function() actions.select_default(prompt_bufnr) end) end return "" end actions.double_mouse_click = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) local pos = vim.fn.getmousepos() if pos.winid == picker.results_win then vim.schedule(function() picker:set_selection(pos.line - 1) actions.select_default(prompt_bufnr) end) end return "" end -- ================================================== -- Transforms modules and sets the correct metatables. -- ================================================== actions = transform_mod(actions) return actions ================================================ FILE: lua/telescope/actions/layout.lua ================================================ ---@tag telescope.actions.layout ---@config { ["module"] = "telescope.actions.layout", ["name"] = "ACTIONS_LAYOUT" } ---@brief [[ --- The layout actions are actions to be used to change the layout of a picker. ---@brief ]] local action_state = require "telescope.actions.state" local state = require "telescope.state" local layout_strats = require "telescope.pickers.layout_strategies" local transform_mod = require("telescope.actions.mt").transform_mod local action_layout = setmetatable({}, { __index = function(_, k) error("'telescope.actions.layout' does not have a value: " .. tostring(k)) end, }) --- Toggle preview window. --- - Note: preview window can be toggled even if preview is set to false. --- --- This action is not mapped by default. ---@param prompt_bufnr number: The prompt bufnr action_layout.toggle_preview = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) local status = state.get_status(picker.prompt_bufnr) local preview_winid = status.layout.preview and status.layout.preview.winid if picker.previewer and preview_winid then picker.hidden_previewer = picker.previewer picker.previewer = nil elseif picker.hidden_previewer and not preview_winid then picker.previewer = picker.hidden_previewer picker.hidden_previewer = nil else return end picker:full_layout_update() end -- TODO IMPLEMENT (mentored project available, contact @l-kershaw) action_layout.toggle_padding = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) -- if padding ~= 0 -- 1. Save `height` and `width` of picker -- 2. Set both to `{padding = 0}` -- else -- 1. Lookup previous `height` and `width` of picker -- 2. Set both to previous values picker:full_layout_update() end --- Toggles the `prompt_position` option between "top" and "bottom". --- Checks if `prompt_position` is an option for the current layout. --- --- This action is not mapped by default. ---@param prompt_bufnr number: The prompt bufnr action_layout.toggle_prompt_position = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) picker.layout_config = picker.layout_config or {} picker.layout_config[picker.layout_strategy] = picker.layout_config[picker.layout_strategy] or {} -- flex layout is weird and needs handling separately if picker.layout_strategy == "flex" then picker.layout_config.flex.horizontal = picker.layout_config.flex.horizontal or {} picker.layout_config.flex.vertical = picker.layout_config.flex.vertical or {} local old_pos = vim.F.if_nil( picker.layout_config.flex[picker.__flex_strategy].prompt_position, picker.layout_config[picker.__flex_strategy].prompt_position ) local new_pos = old_pos == "top" and "bottom" or "top" picker.layout_config[picker.__flex_strategy].prompt_position = new_pos picker.layout_config.flex[picker.__flex_strategy].prompt_position = new_pos picker:full_layout_update() elseif layout_strats._configurations[picker.layout_strategy].prompt_position then if picker.layout_config.prompt_position == "top" then picker.layout_config.prompt_position = "bottom" picker.layout_config[picker.layout_strategy].prompt_position = "bottom" else picker.layout_config.prompt_position = "top" picker.layout_config[picker.layout_strategy].prompt_position = "top" end picker:full_layout_update() end end --- Toggles the `mirror` option between `true` and `false`. --- Checks if `mirror` is an option for the current layout. --- --- This action is not mapped by default. ---@param prompt_bufnr number: The prompt bufnr action_layout.toggle_mirror = function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) -- flex layout is weird and needs handling separately if picker.layout_strategy == "flex" then picker.layout_config.flex.horizontal = picker.layout_config.flex.horizontal or {} picker.layout_config.flex.vertical = picker.layout_config.flex.vertical or {} local new_mirror = not picker.layout_config.flex[picker.__flex_strategy].mirror picker.layout_config[picker.__flex_strategy].mirror = new_mirror picker.layout_config.flex[picker.__flex_strategy].mirror = new_mirror picker:full_layout_update() elseif layout_strats._configurations[picker.layout_strategy].mirror then picker.layout_config = picker.layout_config or {} local new_mirror = not picker.layout_config.mirror picker.layout_config.mirror = new_mirror picker.layout_config[picker.layout_strategy] = picker.layout_config[picker.layout_strategy] or {} picker.layout_config[picker.layout_strategy].mirror = new_mirror picker:full_layout_update() end end -- Helper function for `cycle_layout_next` and `cycle_layout_prev`. local get_cycle_layout = function(dir) return function(prompt_bufnr) local picker = action_state.get_current_picker(prompt_bufnr) if picker.__layout_index then picker.__layout_index = ((picker.__layout_index + dir - 1) % #picker.__cycle_layout_list) + 1 else picker.__layout_index = 1 end local new_layout = picker.__cycle_layout_list[picker.__layout_index] if type(new_layout) == "string" then picker.layout_strategy = new_layout picker.layout_config = {} picker.previewer = picker.all_previewers and picker.all_previewers[1] or nil elseif type(new_layout) == "table" then picker.layout_strategy = new_layout.layout_strategy picker.layout_config = new_layout.layout_config or {} picker.previewer = (new_layout.previewer == nil and picker.all_previewers[picker.current_previewer_index]) or new_layout.previewer else error("Not a valid layout setup: " .. vim.inspect(new_layout) .. "\nShould be a string or a table") end picker:full_layout_update() end end --- Cycles to the next layout in `cycle_layout_list`. --- --- This action is not mapped by default. ---@param prompt_bufnr number: The prompt bufnr action_layout.cycle_layout_next = get_cycle_layout(1) --- Cycles to the previous layout in `cycle_layout_list`. --- --- This action is not mapped by default. ---@param prompt_bufnr number: The prompt bufnr action_layout.cycle_layout_prev = get_cycle_layout(-1) action_layout = transform_mod(action_layout) return action_layout ================================================ FILE: lua/telescope/actions/mt.lua ================================================ local action_mt = {} --- Checks all replacement combinations to determine which function to run. --- If no replacement can be found, then it will run the original function local run_replace_or_original = function(replacements, original_func, ...) for _, replacement_map in ipairs(replacements or {}) do for condition, replacement in pairs(replacement_map) do if condition == true or condition(...) then return replacement(...) end end end return original_func(...) end local append_action_copy = function(new, v, old) table.insert(new, v) new._func[v] = old._func[v] new._static_pre[v] = old._static_pre[v] new._pre[v] = old._pre[v] new._replacements[v] = old._replacements[v] new._static_post[v] = old._static_post[v] new._post[v] = old._post[v] end -- TODO(conni2461): Not a fan of this solution/hack. Needs to be addressed local all_mts = {} --TODO(conni2461): It gets worse. This is so bad but because we have now n mts for n actions -- We have to check all actions for relevant mts to set replace and before, after -- Its not bad for performance because its being called on startup when we attach mappings. -- Its just a bad solution local find_all_relevant_mts = function(action_name, f) for _, mt in ipairs(all_mts) do for fun, _ in pairs(mt._func) do if fun == action_name then f(mt) end end end end --- an action is metatable which allows replacement(prepend or append) of the function ---@class Action ---@field _func table: the original action function ---@field _static_pre table: will allways run before the function even if its replaced ---@field _pre table: the functions that will run before the action ---@field _replacements table: the function that replaces this action ---@field _static_post table: will allways run after the function even if its replaced ---@field _post table: the functions that will run after the action action_mt.create = function() local mt = { __call = function(t, ...) local values = {} for _, action_name in ipairs(t) do if t._static_pre[action_name] then t._static_pre[action_name](...) end if vim.tbl_isempty(t._replacements) and t._pre[action_name] then t._pre[action_name](...) end local result = { run_replace_or_original(t._replacements[action_name], t._func[action_name], ...), } for _, res in ipairs(result) do table.insert(values, res) end if t._static_post[action_name] then t._static_post[action_name](...) end if vim.tbl_isempty(t._replacements) and t._post[action_name] then t._post[action_name](...) end end return unpack(values) end, __add = function(lhs, rhs) local new_action = setmetatable({}, action_mt.create()) for _, v in ipairs(lhs) do append_action_copy(new_action, v, lhs) end for _, v in ipairs(rhs) do append_action_copy(new_action, v, rhs) end new_action.clear = function() lhs.clear() rhs.clear() end return new_action end, _func = {}, _static_pre = {}, _pre = {}, _replacements = {}, _static_post = {}, _post = {}, } mt.__index = mt mt.clear = function() mt._pre = {} mt._replacements = {} mt._post = {} end --- Replace the reference to the function with a new one temporarily function mt:replace(v) assert(#self == 1, "Cannot replace an already combined action") return self:replace_map { [true] = v } end function mt:replace_if(condition, replacement) assert(#self == 1, "Cannot replace an already combined action") return self:replace_map { [condition] = replacement } end --- Replace table with -- Example: -- -- actions.select:replace_map { -- [function() return filetype == 'lua' end] = actions.file_split, -- [function() return filetype == 'other' end] = actions.file_split_edit, -- } function mt:replace_map(tbl) assert(#self == 1, "Cannot replace an already combined action") local action_name = self[1] find_all_relevant_mts(action_name, function(another) if not another._replacements[action_name] then another._replacements[action_name] = {} end table.insert(another._replacements[action_name], 1, tbl) end) return self end function mt:enhance(opts) assert(#self == 1, "Cannot enhance already combined actions") local action_name = self[1] find_all_relevant_mts(action_name, function(another) if opts.pre then another._pre[action_name] = opts.pre end if opts.post then another._post[action_name] = opts.post end end) return self end table.insert(all_mts, mt) return mt end action_mt.transform = function(k, mt, _, v) local res = setmetatable({ k }, mt) if type(v) == "table" then res._static_pre[k] = v.pre res._static_post[k] = v.post res._func[k] = v.action else res._func[k] = v end return res end action_mt.transform_mod = function(mod) -- Pass the metatable of the module if applicable. -- This allows for custom errors, lookups, etc. local redirect = setmetatable({}, getmetatable(mod) or {}) for k, v in pairs(mod) do local mt = action_mt.create() redirect[k] = action_mt.transform(k, mt, _, v) end redirect._clear = function() for k, v in pairs(redirect) do if k ~= "_clear" then pcall(v.clear) end end end return redirect end action_mt.clear_all = function() for _, v in ipairs(all_mts) do pcall(v.clear) end end return action_mt ================================================ FILE: lua/telescope/actions/set.lua ================================================ ---@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 api = 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 = api.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 = { drop = "drop", ["tab drop"] = "tab drop", edit = "buffer", new = "sbuffer", vnew = "vert sbuffer", ["leftabove new"] = "leftabove sbuffer", ["leftabove vnew"] = "leftabove vert sbuffer", ["rightbelow new"] = "rightbelow sbuffer", ["rightbelow vnew"] = "rightbelow vert sbuffer", ["topleft new"] = "topleft sbuffer", ["topleft vnew"] = "topleft vert sbuffer", ["botright new"] = "botright sbuffer", ["botright vnew"] = "botright vert sbuffer", tabedit = "tab sb", } edit_buffer = function(command, bufnr) local buf_command = map[command] if buf_command == nil then local valid_commands = vim.tbl_map(function(cmd) return string.format("%q", cmd) end, vim.tbl_keys(map)) table.sort(valid_commands) error( string.format( "There was no associated buffer command for %q.\nValid commands are: %s.", command, table.concat(valid_commands, ", ") ) ) end if buf_command ~= "drop" and buf_command ~= "tab drop" then vim.cmd(string.format("%s %d", buf_command, bufnr)) else vim.cmd(string.format("%s %s", buf_command, vim.fn.fnameescape(api.nvim_buf_get_name(bufnr)))) end 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 are: -- - "edit" -- - "new" -- - "vedit" -- - "tabedit" -- - "drop" -- - "tab drop" -- - "leftabove new" -- - "leftabove vnew" -- - "rightbelow new" -- - "rightbelow vnew" -- - "topleft new" -- - "topleft vnew" -- - "botright new" -- - "botright vnew" 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(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 "", from = from } } vim.fn.settagstack(vim.fn.win_getid(), { items = items }, "t") end if win_id ~= 0 and api.nvim_get_current_win() ~= win_id then api.nvim_set_current_win(win_id) end if entry_bufnr then if not vim.bo[entry_bufnr].buflisted then vim.bo[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 api.nvim_buf_get_name(0) ~= filename or command ~= "edit" then filename = Path:new(filename):normalize(vim.uv.cwd()) pcall(vim.cmd, string.format("%s %s", command, vim.fn.fnameescape(filename))) end end -- HACK: fixes folding: https://github.com/nvim-telescope/telescope.nvim/issues/699 if vim.wo[0][0].foldmethod == "expr" then vim.schedule(function() vim.wo[0][0].foldmethod = "expr" end) end local pos = 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 if api.nvim_buf_get_name(0) == filename then vim.cmd [[normal! m']] end local ok, err_msg = pcall(api.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 ---@param prompt_bufnr integer ---@return table? previewer ---@return number? speed local __scroll_previewer = function(prompt_bufnr) local previewer = action_state.get_current_picker(prompt_bufnr).previewer local status = state.get_status(prompt_bufnr) local preview_winid = status.layout.preview and status.layout.preview.winid -- Check if we actually have a previewer and a preview window if type(previewer) ~= "table" or not preview_winid then return end local default_speed = api.nvim_win_get_height(preview_winid) / 2 local speed = status.picker.layout_config.scroll_speed or default_speed return previewer, speed 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, speed = __scroll_previewer(prompt_bufnr) if previewer and previewer.scroll_fn then previewer:scroll_fn(math.floor(speed * direction)) end end --- Scrolls the previewer to the left or right. --- 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_horizontal_previewer = function(prompt_bufnr, direction) local previewer, speed = __scroll_previewer(prompt_bufnr) if previewer and previewer.scroll_horizontal_fn then previewer:scroll_horizontal_fn(math.floor(speed * direction)) end 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 = api.nvim_win_get_height(status.layout.results.winid) / 2 local speed = status.picker.layout_config.scroll_speed or default_speed local input = direction > 0 and [[]] or [[]] api.nvim_win_call(status.layout.results.winid, function() vim.cmd([[normal! ]] .. math.floor(speed) .. input) end) action_set.shift_selection(prompt_bufnr, math.floor(speed) * direction) end --- Scrolls the results to the left or right. --- 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_horizontal_results = function(prompt_bufnr, direction) local status = state.get_status(prompt_bufnr) local default_speed = 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 [[zl]] or [[zh]] api.nvim_win_call(status.results_win, function() vim.cmd([[normal! ]] .. math.floor(speed) .. input) end) end -- ================================================== -- Transforms modules and sets the corect metatables. -- ================================================== action_set = transform_mod(action_set) return action_set ================================================ FILE: lua/telescope/actions/state.lua ================================================ ---@tag telescope.actions.state ---@config { ["module"] = "telescope.actions.state", ["name"] = "ACTIONS_STATE" } ---@brief [[ --- Functions to be used to determine the current state of telescope. --- --- Generally used from within other |telescope.actions| ---@brief ]] local global_state = require "telescope.state" local conf = require("telescope.config").values local action_state = {} --- Get the current entry function action_state.get_selected_entry() return global_state.get_global_key "selected_entry" end --- Gets the current line in the search prompt function action_state.get_current_line() return global_state.get_global_key "current_line" or "" end --- Gets the current picker ---@param prompt_bufnr number: The prompt bufnr function action_state.get_current_picker(prompt_bufnr) return global_state.get_status(prompt_bufnr).picker end local select_to_edit_map = { default = "edit", horizontal = "new", vertical = "vnew", tab = "tabedit", drop = "drop", ["tab drop"] = "tab drop", } function action_state.select_key_to_edit_key(type) return select_to_edit_map[type] end function action_state.get_current_history() local history = global_state.get_global_key "history" if not history then if conf.history == false or type(conf.history) ~= "table" then history = require("telescope.actions.history").get_simple_history() global_state.set_global_key("history", history) else history = conf.history.handler() global_state.set_global_key("history", history) end end return history end return action_state ================================================ FILE: lua/telescope/actions/utils.lua ================================================ ---@tag telescope.actions.utils ---@config { ["module"] = "telescope.actions.utils", ["name"] = "ACTIONS_UTILS" } ---@brief [[ --- Utilities to wrap functions around picker selections and entries. --- --- Generally used from within other |telescope.actions| ---@brief ]] local action_state = require "telescope.actions.state" local validate = require("telescope.utils").validate local utils = {} --- Apply `f` to the entries of the current picker. --- - Notes: --- - Mapped entries include all currently filtered results, not just the visible ones. --- - Indices are 1-indexed, whereas rows are 0-indexed. --- - Warning: `map_entries` has no return value. --- - The below example showcases how to collect results --- --- Usage: --- --- local action_state = require "telescope.actions.state" --- local action_utils = require "telescope.actions.utils" --- function entry_value_by_row() --- local prompt_bufnr = vim.api.nvim_get_current_buf() --- local current_picker = action_state.get_current_picker(prompt_bufnr) --- local results = {} --- action_utils.map_entries(prompt_bufnr, function(entry, index, row) --- results[row] = entry.value --- end) --- return results --- end --- ---@param prompt_bufnr number: The prompt bufnr ---@param f function: Function to map onto entries of picker that takes (entry, index, row) as viable arguments function utils.map_entries(prompt_bufnr, f) validate("f", f, "function") local current_picker = action_state.get_current_picker(prompt_bufnr) local index = 1 -- indices are 1-indexed, rows are 0-indexed for entry in current_picker.manager:iter() do local row = current_picker:get_row(index) f(entry, index, row) index = index + 1 end end --- Apply `f` to the multi selections of the current picker and return a table of mapped selections. --- - Notes: --- - Mapped selections may include results not visible in the results pop up. --- - Selected entries are returned in order of their selection. --- - Warning: `map_selections` has no return value. --- - The below example showcases how to collect results --- --- Usage: --- --- local action_state = require "telescope.actions.state" --- local action_utils = require "telescope.actions.utils" --- function selection_by_index() --- local prompt_bufnr = vim.api.nvim_get_current_buf() --- local current_picker = action_state.get_current_picker(prompt_bufnr) --- local results = {} --- action_utils.map_selections(prompt_bufnr, function(entry, index) --- results[index] = entry.value --- end) --- return results --- end --- ---@param prompt_bufnr number: The prompt bufnr ---@param f function: Function to map onto selection of picker that takes (selection) as a viable argument function utils.map_selections(prompt_bufnr, f) validate("f", f, "function") local current_picker = action_state.get_current_picker(prompt_bufnr) for _, selection in ipairs(current_picker:get_multi_selection()) do f(selection) end end --- Utility to collect mappings of prompt buffer in array of `{mode, keybind, name}`. ---@param prompt_bufnr number: The prompt bufnr function utils.get_registered_mappings(prompt_bufnr) local ret = {} for _, mode in ipairs { "n", "i" } do for _, mapping in ipairs(vim.api.nvim_buf_get_keymap(prompt_bufnr, mode)) do -- ensure only telescope mappings if mapping.desc then if mapping.desc:sub(1, 10) == "telescope|" then table.insert(ret, { mode = mode, keybind = mapping.lhs, desc = mapping.desc:sub(11) }) elseif mapping.desc:sub(1, 11) == "telescopej|" then local fname = utils._get_anon_function_name(vim.json.decode(mapping.desc:sub(12))) fname = fname:lower() == mapping.lhs:lower() and "" or fname table.insert(ret, { mode = mode, keybind = mapping.lhs, desc = fname, }) end end end end return ret end -- Best effort to infer function names for actions.which_key function utils._get_anon_function_name(info) local Path = require "plenary.path" local fname -- if fn defined in string (ie loadstring) source is string -- if fn defined in file, source is file name prefixed with a `@´ local path = Path:new((info.source:gsub("@", ""))) if not path:exists() then return "" end for i, line in ipairs(path:readlines()) do if i == info.linedefined then fname = line break end end -- test if assignment or named function, otherwise anon if (fname:match "=" == nil) and (fname:match "function %S+%(" == nil) then return "" else local patterns = { { "function", "" }, -- remove function { "local", "" }, -- remove local { "[%s=]", "" }, -- remove whitespace and = { [=[%[["']]=], "" }, -- remove left-hand bracket of table assignment { [=[["']%]]=], "" }, -- remove right-ahnd bracket of table assignment { "%((.+)%)", "" }, -- remove function arguments { "(.+)%.", "" }, -- remove TABLE. prefix if available } for _, tbl in ipairs(patterns) do fname = (fname:gsub(tbl[1], tbl[2])) -- make sure only string is returned end -- not sure if this can happen, catch all just in case if fname == nil or fname == "" then return "" end return fname end end return utils ================================================ FILE: lua/telescope/algos/fzy.lua ================================================ -- The fzy matching algorithm -- -- by Seth Warn -- a lua port of John Hawthorn's fzy -- -- > fzy tries to find the result the user intended. It does this by favouring -- > matches on consecutive letters and starts of words. This allows matching -- > using acronyms or different parts of the path." - J Hawthorn local has_path, Path = pcall(require, "plenary.path") if not has_path then Path = { path = { separator = "/", }, } end local SCORE_GAP_LEADING = -0.005 local SCORE_GAP_TRAILING = -0.005 local SCORE_GAP_INNER = -0.01 local SCORE_MATCH_CONSECUTIVE = 1.0 local SCORE_MATCH_SLASH = 0.9 local SCORE_MATCH_WORD = 0.8 local SCORE_MATCH_CAPITAL = 0.7 local SCORE_MATCH_DOT = 0.6 local SCORE_MAX = math.huge local SCORE_MIN = -math.huge local MATCH_MAX_LENGTH = 1024 local fzy = {} function fzy.has_match(needle, haystack) needle = string.lower(needle) haystack = string.lower(haystack) local j = 1 for i = 1, string.len(needle) do j = string.find(haystack, needle:sub(i, i), j, true) if not j then return false else j = j + 1 end end return true end local function is_lower(c) return c:match "%l" end local function is_upper(c) return c:match "%u" end local function precompute_bonus(haystack) local match_bonus = {} local last_char = Path.path.sep for i = 1, string.len(haystack) do local this_char = haystack:sub(i, i) if last_char == Path.path.sep then match_bonus[i] = SCORE_MATCH_SLASH elseif last_char == "-" or last_char == "_" or last_char == " " then match_bonus[i] = SCORE_MATCH_WORD elseif last_char == "." then match_bonus[i] = SCORE_MATCH_DOT elseif is_lower(last_char) and is_upper(this_char) then match_bonus[i] = SCORE_MATCH_CAPITAL else match_bonus[i] = 0 end last_char = this_char end return match_bonus end local function compute(needle, haystack, D, M) local match_bonus = precompute_bonus(haystack) local n = string.len(needle) local m = string.len(haystack) local lower_needle = string.lower(needle) local lower_haystack = string.lower(haystack) -- Because lua only grants access to chars through substring extraction, -- get all the characters from the haystack once now, to reuse below. local haystack_chars = {} for i = 1, m do haystack_chars[i] = lower_haystack:sub(i, i) end for i = 1, n do D[i] = {} M[i] = {} local prev_score = SCORE_MIN local gap_score = i == n and SCORE_GAP_TRAILING or SCORE_GAP_INNER local needle_char = lower_needle:sub(i, i) for j = 1, m do if needle_char == haystack_chars[j] then local score = SCORE_MIN if i == 1 then score = ((j - 1) * SCORE_GAP_LEADING) + match_bonus[j] elseif j > 1 then local a = M[i - 1][j - 1] + match_bonus[j] local b = D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE score = math.max(a, b) end D[i][j] = score prev_score = math.max(score, prev_score + gap_score) M[i][j] = prev_score else D[i][j] = SCORE_MIN prev_score = prev_score + gap_score M[i][j] = prev_score end end end end function fzy.score(needle, haystack) local n = string.len(needle) local m = string.len(haystack) if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > MATCH_MAX_LENGTH then return SCORE_MIN elseif n == m then return SCORE_MAX else local D = {} local M = {} compute(needle, haystack, D, M) return M[n][m] end end function fzy.positions(needle, haystack) local n = string.len(needle) local m = string.len(haystack) if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > MATCH_MAX_LENGTH then return {} elseif n == m then local consecutive = {} for i = 1, n do consecutive[i] = i end return consecutive end local D = {} local M = {} compute(needle, haystack, D, M) local positions = {} local match_required = false local j = m for i = n, 1, -1 do while j >= 1 do if D[i][j] ~= SCORE_MIN and (match_required or D[i][j] == M[i][j]) then match_required = (i ~= 1) and (j ~= 1) and (M[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE) positions[i] = j j = j - 1 break else j = j - 1 end end end return positions end -- If strings a or b are empty or too long, `fzy.score(a, b) == fzy.get_score_min()`. function fzy.get_score_min() return SCORE_MIN end -- For exact matches, `fzy.score(s, s) == fzy.get_score_max()`. function fzy.get_score_max() return SCORE_MAX end -- For all strings a and b that -- - are not covered by either `fzy.get_score_min()` or fzy.get_score_max()`, and -- - are matched, such that `fzy.has_match(a, b) == true`, -- then `fzy.score(a, b) > fzy.get_score_floor()` will be true. function fzy.get_score_floor() return (MATCH_MAX_LENGTH + 1) * SCORE_GAP_INNER end return fzy ================================================ FILE: lua/telescope/algos/linked_list.lua ================================================ local LinkedList = {} LinkedList.__index = LinkedList function LinkedList:new(opts) opts = opts or {} local track_at = opts.track_at return setmetatable({ size = 0, head = false, tail = false, -- track_at: Track at can track a particular node -- Use to keep a node tracked at a particular index -- This greatly decreases looping for checking values at this location. track_at = track_at, _tracked_node = nil, tracked = nil, }, self) end function LinkedList:_increment() self.size = self.size + 1 return self.size end local create_node = function(item) return { item = item, } end function LinkedList:append(item) local final_size = self:_increment() local node = create_node(item) if not self.head then self.head = node end if self.tail then self.tail.next = node node.prev = self.tail end self.tail = node if self.track_at then if final_size == self.track_at then self.tracked = item self._tracked_node = node end end end function LinkedList:prepend(item) local final_size = self:_increment() local node = create_node(item) if not self.tail then self.tail = node end if self.head then self.head.prev = node node.next = self.head end self.head = node if self.track_at then if final_size == self.track_at then self._tracked_node = self.tail elseif final_size > self.track_at then self._tracked_node = self._tracked_node.prev else return end self.tracked = self._tracked_node.item end end -- [a, b, c] -- b.prev = a -- b.next = c -- -- a.next = b -- c.prev = c -- -- insert d after b -- [a, b, d, c] -- -- b.next = d -- b.prev = a -- -- Place "item" after "node" (which is at index `index`) function LinkedList:place_after(index, node, item) local new_node = create_node(item) assert(node.prev ~= node) assert(node.next ~= node) local final_size = self:_increment() -- Update tail to be the next node. if self.tail == node then self.tail = new_node end new_node.prev = node new_node.next = node.next node.next = new_node if new_node.prev then new_node.prev.next = new_node end if new_node.next then new_node.next.prev = new_node end if self.track_at then if index == self.track_at then self._tracked_node = new_node elseif index < self.track_at then if final_size == self.track_at then self._tracked_node = self.tail elseif final_size > self.track_at then self._tracked_node = self._tracked_node.prev else return end end self.tracked = self._tracked_node.item end end function LinkedList:place_before(index, node, item) local new_node = create_node(item) assert(node.prev ~= node) assert(node.next ~= node) local final_size = self:_increment() -- Update head to be the node we are inserting. if self.head == node then self.head = new_node end new_node.prev = node.prev new_node.next = node node.prev = new_node -- node.next = node.next if new_node.prev then new_node.prev.next = new_node end if new_node.next then new_node.next.prev = new_node end if self.track_at then if index == self.track_at - 1 then self._tracked_node = node elseif index < self.track_at then if final_size == self.track_at then self._tracked_node = self.tail elseif final_size > self.track_at then self._tracked_node = self._tracked_node.prev else return end end self.tracked = self._tracked_node.item end end -- Do you even do this in linked lists...? -- function LinkedList:remove(item) -- end function LinkedList:iter() local current_node = self.head return function() local node = current_node if not node then return nil end current_node = current_node.next return node.item end end function LinkedList:ipairs() local index = 0 local current_node = self.head return function() local node = current_node if not node then return nil end current_node = current_node.next index = index + 1 return index, node.item, node end end function LinkedList:truncate(max_results) if max_results >= self.size then return end local current_node if max_results < self.size - max_results then local index = 1 current_node = self.head while index < max_results do local node = current_node if not node.next then break end current_node = current_node.next index = index + 1 end self.size = max_results else current_node = self.tail while self.size > max_results do if current_node.prev == nil then break end current_node = current_node.prev self.size = self.size - 1 end end self.tail = current_node self.tail.next = nil if max_results < self.track_at then self.track_at = max_results self.tracked = current_node.item self._tracked_node = current_node end end return LinkedList ================================================ FILE: lua/telescope/algos/string_distance.lua ================================================ local function min(a, b, c) local min_val = a if b < min_val then min_val = b end if c < min_val then min_val = c end return min_val end ---------------------------------- --- Levenshtein distance function. -- @tparam string s1 -- @tparam string s2 -- @treturn number the levenshtein distance -- @within Metrics return function(s1, s2) if s1 == s2 then return 0 end if s1:len() == 0 then return s2:len() end if s2:len() == 0 then return s1:len() end if s1:len() < s2:len() then s1, s2 = s2, s1 end local t = {} for i = 1, #s1 + 1 do t[i] = { i - 1 } end for i = 1, #s2 + 1 do t[1][i] = i - 1 end local cost for i = 2, #s1 + 1 do for j = 2, #s2 + 1 do cost = (s1:sub(i - 1, i - 1) == s2:sub(j - 1, j - 1) and 0) or 1 t[i][j] = min(t[i - 1][j] + 1, t[i][j - 1] + 1, t[i - 1][j - 1] + cost) end end return t[#s1 + 1][#s2 + 1] end ================================================ FILE: lua/telescope/builtin/__diagnostics.lua ================================================ 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 sorting_comparator = function(opts) local current_buf = vim.api.nvim_get_current_buf() local comparators = { -- sort results by bufnr (prioritize cur buf), severity, lnum buffer = 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 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, severity = function(a, b) if a.type < b.type then return true elseif a.type > b.type then return false end if a.bufnr == b.bufnr then return a.lnum < b.lnum elseif a.bufnr == current_buf then return true elseif b.bufnr == current_buf then return false else return a.bufnr < b.bufnr end end, } local sort_by = vim.F.if_nil(opts.sort_by, "buffer") return comparators[sort_by] end 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 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 if vim.version().minor > 9 and vim.tbl_isempty(diagnosis_opts.severity) then diagnosis_opts.severity = nil end end opts.root_dir = opts.root_dir == true and vim.uv.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.bo[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 table.sort(items, sorting_comparator(opts)) return items end diagnostics.get = function(opts) if opts.bufnr ~= 0 then opts.bufnr = nil end if type(opts.bufnr) == "string" then opts.bufnr = tonumber(opts.bufnr) end if opts.bufnr ~= nil then opts.path_display = vim.F.if_nil(opts.path_display, "hidden") 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 if type(opts.line_width) == "string" and opts.line_width ~= "full" then utils.notify("builtin.diagnostics", { msg = string.format("'%s' is not a valid value for line_width", opts.line_width), level = "ERROR", }) return end 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) ================================================ FILE: lua/telescope/builtin/__files.lua ================================================ local api = vim.api 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 = utils.flatten local filter = vim.tbl_filter local files = {} ---@param s string ---@return string local escape_chars = function(s) return ( s:gsub("[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%.]", { ["\\"] = "\\\\", ["-"] = "\\-", ["("] = "\\(", [")"] = "\\)", ["["] = "\\[", ["]"] = "\\]", ["{"] = "\\{", ["}"] = "\\}", ["?"] = "\\?", ["+"] = "\\+", ["*"] = "\\*", ["^"] = "\\^", ["$"] = "\\$", ["."] = "\\.", }) ) end local has_rg_program = function(picker_name, program) if vim.fn.executable(program) == 1 then return true end utils.notify(picker_name, { msg = string.format( "'ripgrep', or similar alternative, is a required dependency for the %s picker. " .. "Visit https://github.com/BurntSushi/ripgrep#installation for installation instructions.", picker_name ), level = "ERROR", }) return false 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, api.nvim_list_bufs()) if not next(bufnrs) then return end local filelist = {} for _, bufnr in ipairs(bufnrs) do local file = 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 if not has_rg_program("live_grep", vimgrep_arguments[1]) then return end local search_dirs = opts.search_dirs local grep_open_files = opts.grep_open_files opts.cwd = opts.cwd and utils.path_expand(opts.cwd) or vim.uv.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] = utils.path_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 if opts.file_encoding then additional_args[#additional_args + 1] = "--encoding=" .. opts.file_encoding 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", "", actions.to_fuzzy_refine) return true end, push_cursor_on_edit = true, }) :find() end files.grep_string = function(opts) local vimgrep_arguments = vim.F.if_nil(opts.vimgrep_arguments, conf.vimgrep_arguments) if not has_rg_program("grep_string", vimgrep_arguments[1]) then return end local word local visual = vim.fn.mode() == "v" if visual == true then local saved_reg = vim.fn.getreg "v" vim.cmd [[noautocmd sil norm! "vy]] local sele = vim.fn.getreg "v" vim.fn.setreg("v", saved_reg) word = vim.F.if_nil(opts.search, sele) else word = vim.F.if_nil(opts.search, vim.fn.expand "") end word = tostring(word) local search = opts.use_regex and word or escape_chars(word) local search_args = search == "" and { "-v", "--", "^[[:space:]]*$" } or { "--", search } 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.file_encoding then additional_args[#additional_args + 1] = "--encoding=" .. opts.file_encoding end local args if visual == true then args = flatten { vimgrep_arguments, additional_args, search_args, } else args = flatten { vimgrep_arguments, additional_args, opts.word_match, search_args, } end 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) or {}) do table.insert(args, file) end elseif opts.search_dirs then for _, path in ipairs(opts.search_dirs) do table.insert(args, utils.path_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), push_cursor_on_edit = true, }) :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] = utils.path_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 = utils.path_expand(opts.cwd) end opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts) pickers .new(opts, { prompt_title = "Find Files", __locations_input = true, finder = finders.new_oneshot_job(find_command, opts), previewer = conf.grep_previewer(opts), sorter = conf.file_sorter(opts), }) :find() end -- TODO: finish docs for opts.show_line files.treesitter = function(opts) opts.show_line = vim.F.if_nil(opts.show_line, true) local ts = vim.treesitter local ft = vim.bo[opts.bufnr].filetype local lang = ts.language.get_lang(ft) if not (lang and ts.language.add(lang)) then utils.notify("builtin.treesitter", { msg = "No parser for the current buffer", level = "ERROR", }) return end local query = ts.query.get(lang, "locals") if not query then utils.notify("builtin.treesitter", { msg = "No locals query for the current buffer", level = "ERROR", }) return end local parser = assert(ts.get_parser(opts.bufnr)) parser:parse() local root = parser:trees()[1]:root() local results = {} for id, node, _ in query:iter_captures(root, opts.bufnr) do local kind = query.captures[id] if node and vim.startswith(kind, "local.definition") then table.insert(results, { kind = kind:gsub("^local%.definition", ""):gsub("^%.", ""), node = node }) end end results = utils.filter_symbols(results, opts) if vim.tbl_isempty(results) then -- error message already printed in `utils.filter_symbols` return 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), }, push_cursor_on_edit = true, }) :find() end files.current_buffer_fuzzy_find = function(opts) -- All actions are on the current buffer local filename = api.nvim_buf_get_name(opts.bufnr) local filetype = vim.bo[opts.bufnr].filetype local lines = 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 -- TODO(clason): refactor when dropping support for Nvim 0.10 (no option, no filetype fallback) opts.results_ts_highlight = vim.F.if_nil(opts.results_ts_highlight, true) local lang = vim.treesitter.language.get_lang(filetype) or filetype if opts.results_ts_highlight and lang and utils.has_ts_parser(lang) then local parser = vim.treesitter.get_parser(opts.bufnr, lang) local query = vim.treesitter.query.get(lang, "highlights") if query then local root = parser:parse()[1]:root() local line_highlights = setmetatable({}, { __index = function(t, k) local obj = {} rawset(t, k, obj) return obj end, }) for id, node in query:iter_captures(root, opts.bufnr, 0, -1) do local hl = "@" .. query.captures[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 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() actions.select_default:replace(function(prompt_bufnr) local selection = action_state.get_selected_entry() if not selection then utils.__warn_no_selection "builtin.current_buffer_fuzzy_find" return end local current_picker = action_state.get_current_picker(prompt_bufnr) local searched_for = require("telescope.actions.state").get_current_line() ---@type number[] | {start:number, end:number?, highlight:string?}[] local highlights = current_picker.sorter:highlighter(searched_for, selection.ordinal) or {} highlights = vim.tbl_map(function(hl) if type(hl) == "table" and hl.start then return hl.start elseif type(hl) == "number" then return hl end error "Invalid higlighter fn" end, highlights) local first_col = 0 if #highlights > 0 then first_col = math.min(unpack(highlights)) - 1 end actions.close(prompt_bufnr) vim.schedule(function() vim.cmd "normal! m'" api.nvim_win_set_cursor(0, { selection.lnum, first_col }) end) end) return true end, }) :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 "keepjumps norm! gg" vim.fn.search(scode) vim.cmd "norm! zz" else 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: lua/telescope/builtin/__git.lua ================================================ local api = vim.api local actions = require "telescope.actions" local action_state = require "telescope.actions.state" local finders = require "telescope.finders" local make_entry = require "telescope.make_entry" local operators = require "telescope.operators" local pickers = require "telescope.pickers" local previewers = require "telescope.previewers" local utils = require "telescope.utils" local entry_display = require "telescope.pickers.entry_display" local strings = require "plenary.strings" local Path = require "plenary.path" local conf = require("telescope.config").values local git_command = utils.__git_command local git = {} local get_git_command_output = function(args, opts) return utils.get_os_command_output(git_command(args, opts), opts.cwd) end git.files = function(opts) if opts.is_bare then utils.notify("builtin.git_files", { msg = "This operation must be run in a work tree", level = "ERROR", }) return end local show_untracked = vim.F.if_nil(opts.show_untracked, false) local recurse_submodules = vim.F.if_nil(opts.recurse_submodules, false) if show_untracked and recurse_submodules then utils.notify("builtin.git_files", { msg = "Git does not support both --others and --recurse-submodules", level = "ERROR", }) return end -- By creating the entry maker after the cwd options, -- we ensure the maker uses the cwd options when being created. opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_file(opts)) opts.git_command = vim.F.if_nil( opts.git_command, git_command({ "-c", "core.quotepath=false", "ls-files", "--exclude-standard", "--cached" }, opts) ) pickers .new(opts, { prompt_title = "Git Files", __locations_input = true, finder = finders.new_oneshot_job( utils.flatten { opts.git_command, show_untracked and "--others" or nil, recurse_submodules and "--recurse-submodules" or nil, }, opts ), previewer = conf.grep_previewer(opts), sorter = conf.file_sorter(opts), }) :find() end git.commits = function(opts) opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts)) opts.git_command = vim.F.if_nil(opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--", "." }, opts)) pickers .new(opts, { prompt_title = "Git Commits", finder = finders.new_oneshot_job(opts.git_command, opts), previewer = { previewers.git_commit_diff_to_parent.new(opts), previewers.git_commit_diff_to_head.new(opts), previewers.git_commit_diff_as_was.new(opts), previewers.git_commit_message.new(opts), }, sorter = conf.file_sorter(opts), attach_mappings = function(_, map) actions.select_default:replace(actions.git_checkout) map({ "i", "n" }, "m", actions.git_reset_mixed) map({ "i", "n" }, "s", actions.git_reset_soft) map({ "i", "n" }, "h", actions.git_reset_hard) return true end, }) :find() end git.stash = function(opts) opts.show_branch = vim.F.if_nil(opts.show_branch, true) opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_stash(opts)) opts.git_command = vim.F.if_nil(opts.git_command, git_command({ "--no-pager", "stash", "list" }, opts)) pickers .new(opts, { prompt_title = "Git Stash", finder = finders.new_oneshot_job(opts.git_command, opts), previewer = previewers.git_stash_diff.new(opts), sorter = conf.file_sorter(opts), attach_mappings = function() actions.select_default:replace(actions.git_apply_stash) return true end, }) :find() end local get_current_buf_line = function(winnr) local lnum = api.nvim_win_get_cursor(winnr)[1] return vim.trim(api.nvim_buf_get_lines(api.nvim_win_get_buf(winnr), lnum - 1, lnum, false)[1]) end local bcommits_picker = function(opts, title, finder) return pickers.new(opts, { prompt_title = title, finder = finder, previewer = { previewers.git_commit_diff_to_parent.new(opts), previewers.git_commit_diff_to_head.new(opts), previewers.git_commit_diff_as_was.new(opts), previewers.git_commit_message.new(opts), }, sorter = conf.file_sorter(opts), attach_mappings = function() actions.select_default:replace(actions.git_checkout_current_buffer) local transfrom_file = function() return opts.current_file and Path:new(opts.current_file):make_relative(opts.cwd) or "" end local get_buffer_of_orig = function(selection) local value = selection.value .. ":" .. transfrom_file() local content = utils.get_os_command_output({ "git", "--no-pager", "show", value }, opts.cwd) local bufnr = api.nvim_create_buf(false, true) api.nvim_buf_set_lines(bufnr, 0, -1, false, content) api.nvim_buf_set_name(bufnr, "Original") return bufnr end local vimdiff = function(selection, command) local ft = vim.bo.filetype vim.cmd "diffthis" local bufnr = get_buffer_of_orig(selection) vim.cmd(string.format("%s %s", command, bufnr)) vim.bo.filetype = ft vim.cmd "diffthis" api.nvim_create_autocmd("WinClosed", { buffer = bufnr, nested = true, once = true, callback = function() api.nvim_buf_delete(bufnr, { force = true }) end, }) end actions.select_vertical:replace(function(prompt_bufnr) actions.close(prompt_bufnr) local selection = action_state.get_selected_entry() vimdiff(selection, "leftabove vert sbuffer") end) actions.select_horizontal:replace(function(prompt_bufnr) actions.close(prompt_bufnr) local selection = action_state.get_selected_entry() vimdiff(selection, "belowright sbuffer") end) actions.select_tab:replace(function(prompt_bufnr) actions.close(prompt_bufnr) local selection = action_state.get_selected_entry() vim.cmd("tabedit " .. transfrom_file()) vimdiff(selection, "leftabove vert sbuffer") end) return true end, }) end git.bcommits = function(opts) opts.current_line = (opts.current_file == nil) and get_current_buf_line(opts.winnr) or nil opts.current_file = vim.F.if_nil(opts.current_file, api.nvim_buf_get_name(opts.bufnr)) opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts)) opts.git_command = vim.F.if_nil(opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--follow" }, opts)) local title = "Git BCommits" local finder = finders.new_oneshot_job( utils.flatten { opts.git_command, opts.current_file, }, opts ) bcommits_picker(opts, title, finder):find() end git.bcommits_range = function(opts) opts.current_line = (opts.current_file == nil) and get_current_buf_line(opts.winnr) or nil opts.current_file = vim.F.if_nil(opts.current_file, api.nvim_buf_get_name(opts.bufnr)) opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts)) opts.git_command = vim.F.if_nil( opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--no-patch", "-L" }, opts) ) local visual = string.find(vim.fn.mode(), "[vV]") ~= nil local line_number_first = opts.from local line_number_last = vim.F.if_nil(opts.to, line_number_first) if visual then line_number_first = vim.F.if_nil(line_number_first, vim.fn.line "v") line_number_last = vim.F.if_nil(line_number_last, vim.fn.line ".") elseif opts.operator then opts.operator = false opts.operator_callback = true operators.run_operator(git.bcommits_range, opts) return elseif opts.operator_callback then line_number_first = vim.fn.line "'[" line_number_last = vim.fn.line "']" elseif line_number_first == nil then line_number_first = vim.F.if_nil(line_number_first, vim.fn.line ".") line_number_last = vim.F.if_nil(line_number_last, vim.fn.line ".") end local line_range = string.format("%d,%d:%s", line_number_first, line_number_last, Path:new(opts.current_file):make_relative(opts.cwd)) local title = "Git BCommits in range" local finder = finders.new_oneshot_job( utils.flatten { opts.git_command, line_range, }, opts ) bcommits_picker(opts, title, finder):find() end git.branches = function(opts) local format = "%(HEAD)" .. "%(refname)" .. "%(authorname)" .. "%(upstream:lstrip=2)" .. "%(committerdate:format-local:%Y/%m/%d %H:%M:%S)" local output = get_git_command_output( { "for-each-ref", "--perl", "--format", format, "--sort", "-authordate", opts.pattern }, opts ) local show_remote_tracking_branches = vim.F.if_nil(opts.show_remote_tracking_branches, true) local results = {} local widths = { name = 0, authorname = 0, upstream = 0, committerdate = 0, } local unescape_single_quote = function(v) return string.gsub(v, "\\([\\'])", "%1") end local parse_line = function(line) local fields = vim.split(string.sub(line, 2, -2), "''") local entry = { head = fields[1], refname = unescape_single_quote(fields[2]), authorname = unescape_single_quote(fields[3]), upstream = unescape_single_quote(fields[4]), committerdate = fields[5], } local prefix if vim.startswith(entry.refname, "refs/remotes/") then if show_remote_tracking_branches then prefix = "refs/remotes/" else return end elseif vim.startswith(entry.refname, "refs/heads/") then prefix = "refs/heads/" else return end local index = 1 if entry.head ~= "*" then index = #results + 1 end entry.name = string.sub(entry.refname, string.len(prefix) + 1) for key, value in pairs(widths) do widths[key] = math.max(value, strings.strdisplaywidth(entry[key] or "")) end if string.len(entry.upstream) > 0 then widths.upstream_indicator = 2 end table.insert(results, index, entry) end for _, line in ipairs(output) do parse_line(line) end if #results == 0 then return end local displayer = entry_display.create { separator = " ", items = { { width = 1 }, { width = widths.name }, { width = widths.authorname }, { width = widths.upstream_indicator }, { width = widths.upstream }, { width = widths.committerdate }, }, } local make_display = function(entry) return displayer { { entry.head }, { entry.name, "TelescopeResultsIdentifier" }, { entry.authorname }, { string.len(entry.upstream) > 0 and "=>" or "" }, { entry.upstream, "TelescopeResultsIdentifier" }, { entry.committerdate }, } end pickers .new(opts, { prompt_title = "Git Branches", finder = finders.new_table { results = results, entry_maker = function(entry) entry.value = entry.name entry.ordinal = entry.name entry.display = make_display return make_entry.set_default_entry_mt(entry, opts) end, }, previewer = previewers.git_branch_log.new(opts), sorter = conf.file_sorter(opts), attach_mappings = function(_, map) actions.select_default:replace(actions.git_checkout) map({ "i", "n" }, "", actions.git_track_branch) map({ "i", "n" }, "", actions.git_rebase_branch) map({ "i", "n" }, "", actions.git_create_branch) map({ "i", "n" }, "", actions.git_switch_branch) map({ "i", "n" }, "", actions.git_delete_branch) map({ "i", "n" }, "", actions.git_merge_branch) return true end, }) :find() end git.status = function(opts) if opts.is_bare then utils.notify("builtin.git_status", { msg = "This operation must be run in a work tree", level = "ERROR", }) return end local args = { "status", "-z", "--", "." } local gen_new_finder = function() if vim.F.if_nil(opts.expand_dir, true) then table.insert(args, #args - 1, "-uall") end local git_cmd = git_command(args, opts) opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_status(opts)) opts.split_char = "\0" return finders.new_oneshot_job(git_cmd, opts) end local initial_finder = gen_new_finder() if not initial_finder then return end pickers .new(opts, { prompt_title = "Git Status", finder = initial_finder, previewer = previewers.git_file_diff.new(opts), sorter = conf.file_sorter(opts), on_complete = { function(self) local prompt = action_state.get_current_line() -- HACK: self.manager:num_results() can return 0 despite having results -- due to some async/event loop shenanigans (#3316) local count = 0 for _, entry in pairs(self.finder.results) do if entry and entry.valid ~= false then count = count + 1 end end if count == 0 and prompt == "" then utils.notify("builtin.git_status", { msg = "No changes found", level = "INFO", }) end end, }, attach_mappings = function(prompt_bufnr, map) actions.git_staging_toggle:enhance { post = function() local picker = action_state.get_current_picker(prompt_bufnr) -- temporarily register a callback which keeps selection on refresh local selection = picker:get_selection_row() local callbacks = { unpack(picker._completion_callbacks) } -- shallow copy picker:register_completion_callback(function(self) self:set_selection(selection) self._completion_callbacks = callbacks end) -- refresh picker:refresh(gen_new_finder(), { reset_prompt = false }) end, } map({ "i", "n" }, "", actions.git_staging_toggle) return true end, }) :find() end local try_worktrees = function(opts) local worktrees = conf.git_worktrees if vim.islist(worktrees) then for _, wt in ipairs(worktrees) do if vim.startswith(opts.cwd, wt.toplevel) then opts.toplevel = wt.toplevel opts.gitdir = wt.gitdir if opts.use_git_root then opts.cwd = wt.toplevel end return end end end error(opts.cwd .. " is not a git directory") end local current_path_toplevel = function() local gitdir = vim.fn.finddir(".git", vim.fn.expand "%:p" .. ";") if gitdir == "" then return nil end return Path:new(gitdir):parent():absolute() end local set_opts_cwd = function(opts) opts.use_git_root = vim.F.if_nil(opts.use_git_root, true) if opts.cwd then opts.cwd = utils.path_expand(opts.cwd) elseif opts.use_file_path then opts.cwd = current_path_toplevel() if not opts.cwd then opts.cwd = vim.fn.expand "%:p:h" try_worktrees(opts) return end else opts.cwd = vim.uv.cwd() end local toplevel, ret = utils.get_os_command_output({ "git", "rev-parse", "--show-toplevel" }, opts.cwd) if ret ~= 0 then local in_worktree = utils.get_os_command_output({ "git", "rev-parse", "--is-inside-work-tree" }, opts.cwd) local in_bare = utils.get_os_command_output({ "git", "rev-parse", "--is-bare-repository" }, opts.cwd) if in_worktree[1] ~= "true" and in_bare[1] ~= "true" then try_worktrees(opts) elseif in_worktree[1] ~= "true" and in_bare[1] == "true" then opts.is_bare = true end else if opts.use_git_root then opts.cwd = toplevel[1] end end end local function apply_checks(mod) for k, v in pairs(mod) do mod[k] = function(opts) opts = vim.F.if_nil(opts, {}) set_opts_cwd(opts) v(opts) end end return mod end return apply_checks(git) ================================================ FILE: lua/telescope/builtin/__internal.lua ================================================ local api = vim.api local actions = require "telescope.actions" local action_set = require "telescope.actions.set" local action_state = require "telescope.actions.state" local finders = require "telescope.finders" local make_entry = require "telescope.make_entry" local Path = require "plenary.path" local pickers = require "telescope.pickers" local previewers = require "telescope.previewers" local p_window = require "telescope.pickers.window" local state = require "telescope.state" local utils = require "telescope.utils" local conf = require("telescope.config").values -- Makes sure aliased options are set correctly local function apply_cwd_only_aliases(opts) local has_cwd_only = opts.cwd_only ~= nil local has_only_cwd = opts.only_cwd ~= nil if has_only_cwd and not has_cwd_only then -- Internally, use cwd_only opts.cwd_only = opts.only_cwd opts.only_cwd = nil end return opts end ---@return boolean local function buf_in_cwd(bufname, cwd) if cwd:sub(-1) ~= Path.path.sep then cwd = cwd .. Path.path.sep end local bufname_prefix = bufname:sub(1, #cwd) return bufname_prefix == cwd end local internal = {} internal.builtin = function(opts) opts.include_extensions = vim.F.if_nil(opts.include_extensions, false) opts.use_default_opts = vim.F.if_nil(opts.use_default_opts, false) local objs = {} for k, v in pairs(require "telescope.builtin") do local debug_info = debug.getinfo(v) table.insert(objs, { filename = string.sub(debug_info.source, 2), text = k, }) end local title = "Telescope Builtin" if opts.include_extensions then title = "Telescope Pickers" for ext, funcs in pairs(require("telescope").extensions) do for func_name, func_obj in pairs(funcs) do -- Only include exported functions whose name doesn't begin with an underscore if type(func_obj) == "function" and string.sub(func_name, 0, 1) ~= "_" then local debug_info = debug.getinfo(func_obj) table.insert(objs, { filename = string.sub(debug_info.source, 2), text = string.format("%s : %s", ext, func_name), }) end end end end table.sort(objs, function(a, b) return a.text < b.text end) opts.bufnr = api.nvim_get_current_buf() opts.winnr = api.nvim_get_current_win() pickers .new(opts, { prompt_title = title, finder = finders.new_table { results = objs, entry_maker = function(entry) return make_entry.set_default_entry_mt({ value = entry, text = entry.text, display = entry.text, ordinal = entry.text, filename = entry.filename, }, opts) end, }, previewer = previewers.builtin.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(_) actions.select_default:replace(function(prompt_bufnr) local selection = action_state.get_selected_entry() if not selection then utils.__warn_no_selection "builtin.builtin" return end -- we do this to avoid any surprises opts.include_extensions = nil local picker_opts if not opts.use_default_opts then picker_opts = opts end actions.close(prompt_bufnr) vim.schedule(function() if string.match(selection.text, " : ") then -- Call appropriate function from extensions local split_string = vim.split(selection.text, " : ") local ext = split_string[1] local func = split_string[2] require("telescope").extensions[ext][func](picker_opts) else -- Call appropriate telescope builtin require("telescope.builtin")[selection.text](picker_opts) end end) end) return true end, }) :find() end internal.resume = function(opts) opts = opts or {} opts.cache_index = vim.F.if_nil(opts.cache_index, 1) local cached_pickers = state.get_global_key "cached_pickers" if cached_pickers == nil or vim.tbl_isempty(cached_pickers) then utils.notify("builtin.resume", { msg = "No cached picker(s).", level = "INFO", }) return end local picker = cached_pickers[opts.cache_index] if picker == nil then utils.notify("builtin.resume", { msg = string.format("Index too large as there are only '%s' pickers cached", #cached_pickers), level = "ERROR", }) return end -- reset layout strategy and get_window_options if default as only one is valid -- and otherwise unclear which was actually set if picker.layout_strategy == conf.layout_strategy then picker.layout_strategy = nil end if picker.get_window_options == p_window.get_window_options then picker.get_window_options = nil end picker.cache_picker.index = opts.cache_index -- avoid partial `opts.cache_picker` at picker creation if opts.cache_picker ~= false then picker.cache_picker = vim.tbl_extend("keep", opts.cache_picker or {}, picker.cache_picker) else picker.cache_picker.disabled = true end opts.cache_picker = nil picker.previewer = picker.all_previewers if picker.hidden_previewer then picker.hidden_previewer = nil opts.previewer = vim.F.if_nil(opts.previewer, false) end opts.resumed_picker = true pickers.new(opts, picker):find() end internal.pickers = function(opts) local cached_pickers = state.get_global_key "cached_pickers" if cached_pickers == nil or vim.tbl_isempty(cached_pickers) then utils.notify("builtin.pickers", { msg = "No cached picker(s).", level = "INFO", }) return end opts = opts or {} -- clear cache picker for immediate pickers.new and pass option to resumed picker if opts.cache_picker ~= nil then opts._cache_picker = opts.cache_picker opts.cache_picker = nil end pickers .new(opts, { prompt_title = "Pickers", finder = finders.new_table { results = cached_pickers, entry_maker = make_entry.gen_from_picker(opts), }, previewer = previewers.pickers.new(opts), sorter = conf.generic_sorter(opts), cache_picker = false, attach_mappings = function(_, map) actions.select_default:replace(function(prompt_bufnr) local curr_picker = action_state.get_current_picker(prompt_bufnr) local curr_entry = action_state.get_selected_entry() if not curr_entry then return end actions.close(prompt_bufnr) local selection_index, _ = utils.list_find(function(v) if curr_entry.value == v.value then return true end return false end, curr_picker.finder.results) opts.cache_picker = opts._cache_picker opts["cache_index"] = selection_index opts["initial_mode"] = cached_pickers[selection_index].initial_mode internal.resume(opts) end) map({ "i", "n" }, "", actions.remove_selected_picker) return true end, }) :find() end internal.planets = function(opts) local show_pluto = opts.show_pluto or false local show_moon = opts.show_moon or false local sourced_file = require("plenary.debug_utils").sourced_filepath() local base_directory = vim.fn.fnamemodify(sourced_file, ":h:h:h:h") local globbed_files = vim.fn.globpath(base_directory .. "/data/memes/planets/", "*", true, true) local acceptable_files = {} for _, v in ipairs(globbed_files) do if (show_pluto or not v:find "pluto") and (show_moon or not v:find "moon") then table.insert(acceptable_files, vim.fn.fnamemodify(v, ":t")) end end pickers .new(opts, { prompt_title = "Planets", finder = finders.new_table { results = acceptable_files, entry_maker = function(line) return make_entry.set_default_entry_mt({ ordinal = line, display = line, filename = base_directory .. "/data/memes/planets/" .. line, }, opts) end, }, previewer = previewers.cat.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.planets" return end actions.close(prompt_bufnr) print("Enjoy astronomy! You viewed:", selection.display) end) return true end, }) :find() end internal.symbols = function(opts) local initial_mode = vim.fn.mode() local files = api.nvim_get_runtime_file("data/telescope-sources/*.json", true) local data_path = (function() if not opts.symbol_path then return Path:new { vim.fn.stdpath "data", "telescope", "symbols" } else return Path:new { opts.symbol_path } end end)() if data_path:exists() then for _, v in ipairs(vim.fs.find(function(name, _) return name:match "%.json$" end, { path = data_path:absolute(), limit = math.huge, type = "file" })) do table.insert(files, v) end end if #files == 0 then utils.notify("builtin.symbols", { msg = "No sources found! Check out https://github.com/nvim-telescope/telescope-symbols.nvim " .. "for some prebuild symbols or how to create you own symbol source.", level = "ERROR", }) return end local sources = {} if opts.sources then for _, v in ipairs(files) do for _, s in ipairs(opts.sources) do if v:find(s) then table.insert(sources, v) end end end else sources = files end local results = {} for _, source in ipairs(sources) do local data = vim.json.decode(Path:new(source):read()) for _, entry in ipairs(data) do table.insert(results, entry) end end pickers .new(opts, { prompt_title = "Symbols", finder = finders.new_table { results = results, entry_maker = function(entry) return make_entry.set_default_entry_mt({ value = entry, ordinal = entry[1] .. " " .. entry[2], display = entry[1] .. " " .. entry[2], }, opts) end, }, sorter = conf.generic_sorter(opts), attach_mappings = function(_) if initial_mode == "i" then actions.select_default:replace(actions.insert_symbol_i) else actions.select_default:replace(actions.insert_symbol) end return true end, }) :find() end internal.commands = function(opts) pickers .new(opts, { prompt_title = "Commands", finder = finders.new_table { results = (function() local command_iter = api.nvim_get_commands {} local commands = {} for _, cmd in pairs(command_iter) do table.insert(commands, cmd) end local need_buf_command = vim.F.if_nil(opts.show_buf_command, true) if need_buf_command then local buf_command_iter = api.nvim_buf_get_commands(0, {}) buf_command_iter[true] = nil -- remove the redundant entry for _, cmd in pairs(buf_command_iter) do table.insert(commands, cmd) end end return commands end)(), entry_maker = opts.entry_maker or make_entry.gen_from_commands(opts), }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.commands" return end actions.close(prompt_bufnr) local val = selection.value local cmd = string.format([[:%s ]], val.name) if val.nargs == "0" then local cr = api.nvim_replace_termcodes("", true, false, true) cmd = cmd .. cr end vim.cmd [[stopinsert]] api.nvim_feedkeys(cmd, "nt", false) end) return true end, }) :find() end internal.quickfix = function(opts) local qf_identifier = opts.id or vim.F.if_nil(opts.nr, "$") local locations = vim.fn.getqflist({ [opts.id and "id" or "nr"] = qf_identifier, items = true }).items if vim.tbl_isempty(locations) then utils.notify("builtin.quickfix", { msg = "No quickfix items", level = "INFO" }) return end pickers .new(opts, { prompt_title = "Quickfix", 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), }) :find() end internal.quickfixhistory = function(opts) local qflists = {} for i = 1, 10 do -- (n)vim keeps at most 10 quickfix lists in full -- qf weirdness: id = 0 gets id of quickfix list nr local qflist = vim.fn.getqflist { nr = i, id = 0, title = true, items = true } if not vim.tbl_isempty(qflist.items) then table.insert(qflists, qflist) end end local entry_maker = opts.make_entry or function(entry) return make_entry.set_default_entry_mt({ value = entry.title or "Untitled", ordinal = entry.title or "Untitled", display = entry.title or "Untitled", nr = entry.nr, id = entry.id, items = entry.items, }, opts) end local qf_entry_maker = make_entry.gen_from_quickfix(opts) pickers .new(opts, { prompt_title = "Quickfix History", finder = finders.new_table { results = qflists, entry_maker = entry_maker, }, previewer = previewers.new_buffer_previewer { title = "Quickfix List Preview", dyn_title = function(_, entry) return entry.title end, get_buffer_by_name = function(_, entry) return "quickfixlist_" .. tostring(entry.nr) end, define_preview = function(self, entry) if self.state.bufname then return end local entries = vim.tbl_map(function(i) return qf_entry_maker(i):display() end, entry.items) api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, entries) end, }, sorter = conf.generic_sorter(opts), attach_mappings = function(_, map) action_set.select:replace(function(prompt_bufnr) local nr = action_state.get_selected_entry().nr actions.close(prompt_bufnr) internal.quickfix { nr = nr } end) map({ "i", "n" }, "", function(prompt_bufnr) local nr = action_state.get_selected_entry().nr actions.close(prompt_bufnr) vim.cmd(nr .. "chistory") vim.cmd "botright copen" end) return true end, }) :find() end internal.loclist = function(opts) local locations = vim.fn.getloclist(0) local filenames = {} for _, value in pairs(locations) do local bufnr = value.bufnr if filenames[bufnr] == nil then filenames[bufnr] = api.nvim_buf_get_name(bufnr) end value.filename = filenames[bufnr] end if vim.tbl_isempty(locations) then utils.notify("builtin.loclist", { msg = "No loclist items", level = "INFO" }) return end pickers .new(opts, { prompt_title = "Loclist", 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), }) :find() end internal.oldfiles = function(opts) opts = apply_cwd_only_aliases(opts) opts.include_current_session = vim.F.if_nil(opts.include_current_session, true) local current_buffer = api.nvim_get_current_buf() local current_file = api.nvim_buf_get_name(current_buffer) local results = {} if utils.iswin then -- for slash problem in windows current_file = current_file:gsub("/", "\\") end if opts.include_current_session then for _, buffer in ipairs(utils.split_lines(vim.fn.execute ":buffers! t")) do local match = tonumber(string.match(buffer, "%s*(%d+)")) local open_by_lsp = string.match(buffer, "line 0$") if match and not open_by_lsp then local file = api.nvim_buf_get_name(match) if utils.iswin then file = file:gsub("/", "\\") end if vim.uv.fs_stat(file) and match ~= current_buffer then table.insert(results, file) end end end end for _, file in ipairs(vim.v.oldfiles) do if utils.iswin then file = file:gsub("/", "\\") end local file_stat = vim.uv.fs_stat(file) if file_stat and file_stat.type == "file" and not vim.tbl_contains(results, file) and file ~= current_file then table.insert(results, file) end end if opts.cwd_only or opts.cwd then local cwd = opts.cwd_only and vim.uv.cwd() or opts.cwd results = vim.tbl_filter(function(file) return buf_in_cwd(file, cwd) end, results) end pickers .new(opts, { prompt_title = "Oldfiles", __locations_input = true, finder = finders.new_table { results = results, entry_maker = opts.entry_maker or make_entry.gen_from_file(opts), }, sorter = conf.file_sorter(opts), previewer = conf.grep_previewer(opts), }) :find() end internal.command_history = function(opts) local history_string = vim.fn.execute "history cmd" local history_list = utils.split_lines(history_string) local results = {} local filter_fn = opts.filter_fn for i = #history_list, 3, -1 do local item = history_list[i] local _, finish = string.find(item, "%d+ +") local cmd = string.sub(item, finish + 1) if filter_fn then if filter_fn(cmd) then table.insert(results, cmd) end else table.insert(results, cmd) end end pickers .new(opts, { prompt_title = "Command History", finder = finders.new_table(results), sorter = conf.generic_sorter(opts), attach_mappings = function(_, map) actions.select_default:replace(actions.set_command_line) map({ "i", "n" }, "", actions.edit_command_line) -- TODO: Find a way to insert the text... it seems hard. -- map('i', '', actions.insert_value, { expr = true }) return true end, }) :find() end internal.search_history = function(opts) local search_string = vim.fn.execute "history search" local search_list = utils.split_lines(search_string) local results = {} for i = #search_list, 3, -1 do local item = search_list[i] local _, finish = string.find(item, "%d+ +") table.insert(results, string.sub(item, finish + 1)) end pickers .new(opts, { prompt_title = "Search History", finder = finders.new_table(results), sorter = conf.generic_sorter(opts), attach_mappings = function(_, map) actions.select_default:replace(actions.set_search_line) map({ "i", "n" }, "", actions.edit_search_line) -- TODO: Find a way to insert the text... it seems hard. -- map('i', '', actions.insert_value, { expr = true }) return true end, }) :find() end internal.vim_options = function(opts) local res = {} for _, v in pairs(api.nvim_get_all_options_info()) do local ok, value = pcall(api.nvim_get_option_value, v.name, {}) if ok then v.value = value table.insert(res, v) end end table.sort(res, function(left, right) return left.name < right.name end) pickers .new(opts, { prompt_title = "options", finder = finders.new_table { results = res, entry_maker = opts.entry_maker or make_entry.gen_from_vimoptions(opts), }, sorter = conf.generic_sorter(opts), attach_mappings = function() actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.vim_options" return end local esc = "" if vim.fn.mode() == "i" then esc = api.nvim_replace_termcodes("", true, false, true) end api.nvim_feedkeys( selection.value.type == "boolean" and string.format("%s:set %s!", esc, selection.value.name) or string.format("%s:set %s=%s", esc, selection.value.name, selection.value.value), "m", true ) end) return true end, }) :find() end internal.help_tags = function(opts) opts.lang = vim.F.if_nil(opts.lang, vim.o.helplang) opts.fallback = vim.F.if_nil(opts.fallback, true) opts.file_ignore_patterns = {} local langs = vim.split(opts.lang, ",", { trimempty = true }) if opts.fallback and not vim.tbl_contains(langs, "en") then table.insert(langs, "en") end local langs_map = {} for _, lang in ipairs(langs) do langs_map[lang] = true end local tag_files = {} local function add_tag_file(lang, file) if langs_map[lang] then if tag_files[lang] then table.insert(tag_files[lang], file) else tag_files[lang] = { file } end end end local help_files = {} local rtp = vim.o.runtimepath -- extend the runtime path with all plugins not loaded by lazy.nvim local lazy = package.loaded["lazy.core.util"] if lazy and lazy.get_unloaded_rtp then local paths = lazy.get_unloaded_rtp "" if #paths > 0 then rtp = rtp .. "," .. table.concat(paths, ",") end end local all_files = vim.fn.globpath(rtp, "doc/*", 1, 1) for _, fullpath in ipairs(all_files) do local file = utils.path_tail(fullpath) if file == "tags" then add_tag_file("en", fullpath) elseif file:match "^tags%-..$" then local lang = file:sub(-2) add_tag_file(lang, fullpath) else help_files[file] = fullpath end end local tags = {} local tags_map = {} local delimiter = string.char(9) for _, lang in ipairs(langs) do for _, file in ipairs(tag_files[lang] or {}) do local lines = utils.split_lines(Path:new(file):read(), { trimempty = true }) for _, line in ipairs(lines) do -- TODO: also ignore tagComment starting with ';' if not line:match "^!_TAG_" then local fields = vim.split(line, delimiter, { trimempty = true }) if #fields == 3 and not tags_map[fields[1]] then if fields[1] ~= "help-tags" or fields[2] ~= "tags" then table.insert(tags, { name = fields[1], filename = help_files[fields[2]], cmd = fields[3], lang = lang, }) tags_map[fields[1]] = true end end end end end end pickers .new(opts, { prompt_title = "Help", finder = finders.new_table { results = tags, entry_maker = function(entry) return make_entry.set_default_entry_mt({ value = entry.name .. "@" .. entry.lang, display = entry.name, ordinal = entry.name, filename = entry.filename, cmd = entry.cmd, }, opts) end, }, previewer = previewers.help.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) action_set.select:replace(function(_, cmd) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.help_tags" return end actions.close(prompt_bufnr) if cmd == "default" or cmd == "horizontal" then vim.cmd("help " .. selection.value) elseif cmd == "vertical" then vim.cmd("vert help " .. selection.value) elseif cmd == "tab" then vim.cmd("tab help " .. selection.value) end end) return true end, }) :find() end internal.man_pages = function(opts) opts.sections = vim.F.if_nil(opts.sections, { "1" }) assert(vim.islist(opts.sections), "sections should be a list") opts.man_cmd = utils.get_lazy_default(opts.man_cmd, function() local uname = vim.uv.os_uname() local sysname = string.lower(uname.sysname) if sysname == "darwin" then local major_version = tonumber(vim.fn.matchlist(uname.release, [[^\(\d\+\)\..*]])[2]) or 0 return major_version >= 22 and { "apropos", "." } or { "apropos", " " } elseif sysname == "freebsd" then return { "apropos", "." } else return { "apropos", "" } end end) opts.entry_maker = opts.entry_maker or make_entry.gen_from_apropos(opts) opts.env = { PATH = vim.env.PATH, MANPATH = vim.env.MANPATH } pickers .new(opts, { prompt_title = "Man", finder = finders.new_oneshot_job(opts.man_cmd, opts), previewer = previewers.man.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) action_set.select:replace(function(_, cmd) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.man_pages" return end local args = selection.section .. " " .. selection.value actions.close(prompt_bufnr) if cmd == "default" or cmd == "horizontal" then vim.cmd("Man " .. args) elseif cmd == "vertical" then vim.cmd("vert Man " .. args) elseif cmd == "tab" then vim.cmd("tab Man " .. args) end end) return true end, }) :find() end internal.reloader = function(opts) local package_list = vim.tbl_keys(package.loaded) -- filter out packages we don't want and track the longest package name local column_len = 0 for index, module_name in pairs(package_list) do if type(require(module_name)) ~= "table" or module_name:sub(1, 1) == "_" or package.searchpath(module_name, package.path) == nil then table.remove(package_list, index) elseif #module_name > column_len then column_len = #module_name end end opts.column_len = vim.F.if_nil(opts.column_len, column_len) pickers .new(opts, { prompt_title = "Packages", finder = finders.new_table { results = package_list, entry_maker = opts.entry_maker or make_entry.gen_from_packages(opts), }, -- previewer = previewers.vim_buffer.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.reloader" return end actions.close(prompt_bufnr) require("plenary.reload").reload_module(selection.value) utils.notify("builtin.reloader", { msg = string.format("[%s] - module reloaded", selection.value), level = "INFO", }) end) return true end, }) :find() end internal.buffers = function(opts) opts = apply_cwd_only_aliases(opts) local bufnrs = vim.tbl_filter(function(bufnr) if 1 ~= vim.fn.buflisted(bufnr) then return false end -- only hide unloaded buffers if opts.show_all_buffers is false, keep them listed if true or nil if opts.show_all_buffers == false and not api.nvim_buf_is_loaded(bufnr) then return false end if opts.ignore_current_buffer and bufnr == api.nvim_get_current_buf() then return false end local bufname = api.nvim_buf_get_name(bufnr) if opts.cwd_only and not buf_in_cwd(bufname, vim.uv.cwd()) then return false end if not opts.cwd_only and opts.cwd and not buf_in_cwd(bufname, opts.cwd) then return false end return true end, api.nvim_list_bufs()) if not next(bufnrs) then utils.notify("builtin.buffers", { msg = "No buffers found with the provided options", level = "INFO" }) return end if opts.sort_mru then table.sort(bufnrs, function(a, b) return vim.fn.getbufinfo(a)[1].lastused > vim.fn.getbufinfo(b)[1].lastused end) end if type(opts.sort_buffers) == "function" then table.sort(bufnrs, opts.sort_buffers) end local buffers = {} local default_selection_idx = 1 for i, bufnr in ipairs(bufnrs) do local flag = bufnr == vim.fn.bufnr "" and "%" or (bufnr == vim.fn.bufnr "#" and "#" or " ") if opts.sort_lastused and not opts.ignore_current_buffer and flag == "#" then default_selection_idx = 2 end local element = { bufnr = bufnr, flag = flag, info = vim.fn.getbufinfo(bufnr)[1], } if opts.sort_lastused and (flag == "#" or flag == "%") then local idx = ((buffers[1] ~= nil and buffers[1].flag == "%") and 2 or 1) table.insert(buffers, idx, element) else if opts.select_current and flag == "%" then default_selection_idx = i end table.insert(buffers, element) end end if not opts.bufnr_width then local max_bufnr = math.max(unpack(bufnrs)) opts.bufnr_width = #tostring(max_bufnr) end pickers .new(opts, { prompt_title = "Buffers", finder = finders.new_table { results = buffers, entry_maker = opts.entry_maker or make_entry.gen_from_buffer(opts), }, previewer = conf.grep_previewer(opts), sorter = conf.generic_sorter(opts), default_selection_index = default_selection_idx, attach_mappings = function(_, map) map({ "i", "n" }, "", actions.delete_buffer) return true end, }) :find() end internal.colorscheme = function(opts) local before_background = vim.o.background local before_color = api.nvim_exec2("colorscheme", { output = true }).output local need_restore = not not opts.enable_preview local colors = opts.colors or { before_color } if not vim.tbl_contains(colors, before_color) then table.insert(colors, 1, before_color) end colors = vim.list_extend( colors, vim.tbl_filter(function(color) return not vim.tbl_contains(colors, color) end, vim.fn.getcompletion("", "color")) ) -- if lazy is available, extend the colors list with unloaded colorschemes local lazy = package.loaded["lazy.core.util"] if lazy and lazy.get_unloaded_rtp then local paths = lazy.get_unloaded_rtp "" local all_files = vim.fn.globpath(table.concat(paths, ","), "colors/*", 1, 1) for _, f in ipairs(all_files) do local color = vim.fn.fnamemodify(f, ":t:r") if not vim.tbl_contains(colors, color) then table.insert(colors, color) end end end if opts.ignore_builtins then -- stylua: ignore local builtins = { "blue", "darkblue", "default", "delek", "desert", "elflord", "evening", "habamax", "industry", "koehler", "lunaperche", "morning", "murphy", "pablo", "peachpuff", "quiet", "retrobox", "ron", "shine", "slate", "sorbet", "torte", "unokai", "vim", "wildcharm", "zaibatsu", "zellner", } colors = vim.tbl_filter(function(color) return not vim.tbl_contains(builtins, color) end, colors) end local previewer if opts.enable_preview then -- define previewer local bufnr = api.nvim_get_current_buf() local p = api.nvim_buf_get_name(bufnr) -- show current buffer content in previewer previewer = previewers.new_buffer_previewer { get_buffer_by_name = function() return p end, define_preview = function(self) if vim.uv.fs_stat(p) then conf.buffer_previewer_maker(p, self.state.bufnr, { bufname = self.state.bufname }) else local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines) end end, } end local picker = pickers.new(opts, { prompt_title = "Change Colorscheme", finder = finders.new_table { results = colors, }, sorter = conf.generic_sorter(opts), previewer = previewer, attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.colorscheme" return end need_restore = false actions.close(prompt_bufnr) vim.cmd.colorscheme(selection.value) end) return true end, on_complete = { function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.colorscheme" return end if opts.enable_preview then vim.cmd.colorscheme(selection.value) end end, }, }) if opts.enable_preview then -- rewrite picker.close_windows. restore color if needed local close_windows = picker.close_windows picker.close_windows = function(status) close_windows(status) if need_restore then vim.o.background = before_background vim.cmd.colorscheme(before_color) end end -- rewrite picker.set_selection so that color schemes can be previewed when the current -- selection is shifted using the keyboard or if an item is clicked with the mouse local set_selection = picker.set_selection picker.set_selection = function(self, row) set_selection(self, row) local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.colorscheme" return end if opts.enable_preview then vim.cmd.colorscheme(selection.value) end end end picker:find() end internal.marks = function(opts) local local_marks = { items = vim.fn.getmarklist(opts.bufnr), name_func = function(_, line) return api.nvim_buf_get_lines(opts.bufnr, line - 1, line, false)[1] end, } local global_marks = { items = vim.fn.getmarklist(), name_func = function(mark, _) -- get buffer name if it is opened, otherwise get file name return api.nvim_get_mark(mark, {})[4] end, } local marks_table = {} local marks_others = {} local bufname = api.nvim_buf_get_name(opts.bufnr) local all_marks = {} opts.mark_type = vim.F.if_nil(opts.mark_type, "all") if opts.mark_type == "all" then all_marks = { local_marks, global_marks } elseif opts.mark_type == "local" then all_marks = { local_marks } elseif opts.mark_type == "global" then all_marks = { global_marks } end for _, cnf in ipairs(all_marks) do for _, v in ipairs(cnf.items) do -- strip the first single quote character local mark = string.sub(v.mark, 2, 3) local _, lnum, col, _ = unpack(v.pos) local name = cnf.name_func(mark, lnum) -- same format to :marks command local line = string.format("%s %6d %4d %s", mark, lnum, col - 1, name) local row = { line = line, lnum = lnum, col = col, filename = utils.path_expand(v.file or bufname), } -- non alphanumeric marks goes to last if mark:match "%w" then table.insert(marks_table, row) else table.insert(marks_others, row) end end end marks_table = vim.fn.extend(marks_table, marks_others) pickers .new(opts, { prompt_title = "Marks", finder = finders.new_table { results = marks_table, entry_maker = opts.entry_maker or make_entry.gen_from_marks(opts), }, previewer = conf.grep_previewer(opts), sorter = conf.generic_sorter(opts), push_cursor_on_edit = true, push_tagstack_on_edit = true, }) :find() end internal.registers = function(opts) local registers_table = { '"', "-", "#", "=", "/", "*", "+", ":", ".", "%" } -- named for i = 0, 9 do table.insert(registers_table, tostring(i)) end -- alphabetical for i = 65, 90 do table.insert(registers_table, string.char(i)) end pickers .new(opts, { prompt_title = "Registers", finder = finders.new_table { results = registers_table, entry_maker = opts.entry_maker or make_entry.gen_from_registers(opts), }, sorter = conf.generic_sorter(opts), attach_mappings = function(_, map) actions.select_default:replace(actions.paste_register) map({ "i", "n" }, "", actions.edit_register) return true end, }) :find() end internal.keymaps = function(opts) opts.modes = vim.F.if_nil(opts.modes, { "n", "i", "c", "x" }) opts.show_plug = vim.F.if_nil(opts.show_plug, true) opts.only_buf = vim.F.if_nil(opts.only_buf, false) local keymap_encountered = {} -- used to make sure no duplicates are inserted into keymaps_table local keymaps_table = {} local max_len_lhs = 0 -- helper function to populate keymaps_table and determine max_len_lhs local function extract_keymaps(keymaps) for _, keymap in pairs(keymaps) do local keymap_key = keymap.buffer .. keymap.mode .. keymap.lhs -- should be distinct for every keymap if not keymap_encountered[keymap_key] then keymap_encountered[keymap_key] = true if (opts.show_plug or not string.find(keymap.lhs, "")) and (not opts.lhs_filter or opts.lhs_filter(keymap.lhs)) and (not opts.filter or opts.filter(keymap)) then table.insert(keymaps_table, keymap) max_len_lhs = math.max(max_len_lhs, #utils.display_termcodes(keymap.lhs)) end end end end for _, mode in pairs(opts.modes) do local global = api.nvim_get_keymap(mode) local buf_local = api.nvim_buf_get_keymap(0, mode) if not opts.only_buf then extract_keymaps(global) end extract_keymaps(buf_local) end opts.width_lhs = max_len_lhs + 1 pickers .new(opts, { prompt_title = "Key Maps", finder = finders.new_table { results = keymaps_table, entry_maker = opts.entry_maker or make_entry.gen_from_keymaps(opts), }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.keymaps" return end api.nvim_feedkeys(api.nvim_replace_termcodes(selection.value.lhs, true, false, true), "t", true) return actions.close(prompt_bufnr) end) return true end, }) :find() end internal.filetypes = function(opts) local filetypes = vim.fn.getcompletion("", "filetype") pickers .new(opts, { prompt_title = "Filetypes", finder = finders.new_table { results = filetypes, }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then print "[telescope] Nothing currently selected" return end actions.close(prompt_bufnr) vim.cmd("setfiletype " .. selection[1]) end) return true end, }) :find() end internal.highlights = function(opts) local highlights = vim.fn.getcompletion("", "highlight") pickers .new(opts, { prompt_title = "Highlights", finder = finders.new_table { results = highlights, entry_maker = opts.entry_maker or make_entry.gen_from_highlights(opts), }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.highlights" return end actions.close(prompt_bufnr) vim.cmd("hi " .. selection.value) end) return true end, previewer = previewers.highlights.new(opts), }) :find() end internal.autocommands = function(opts) local autocmds = api.nvim_get_autocmds {} table.sort(autocmds, function(lhs, rhs) return lhs.event < rhs.event end) pickers .new(opts, { prompt_title = "autocommands", finder = finders.new_table { results = autocmds, entry_maker = opts.entry_maker or make_entry.gen_from_autocommands(opts), }, previewer = previewers.autocommands.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) action_set.select:replace_if(function() local selection = action_state.get_selected_entry() if selection == nil then return false end local val = selection.value local cb = val.callback if vim.is_callable(cb) then if type(cb) ~= "string" then local f = type(cb) == "function" and cb or rawget(getmetatable(cb), "__call") local info = debug.getinfo(f, "S") local file = info.source:match "^@(.+)" local lnum = info.linedefined if file and (lnum or 0) > 0 then selection.filename, selection.lnum, selection.col = file, lnum, 1 return false end end end local group_name = val.group_name ~= "" and val.group_name or "" local output = vim.fn.execute("verb autocmd " .. group_name .. " " .. val.event .. " " .. val.pattern, "silent") for line in output:gmatch "[^\r\n]+" do local source_file = line:match "Last set from (.*) line %d*$" or line:match "Last set from (.*)$" if source_file and source_file ~= "Lua" then selection.filename = source_file local source_lnum = line:match "line (%d*)$" or "1" selection.lnum = tonumber(source_lnum) selection.col = 1 return false end end return true end, function() local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) print("You selected autocmd: " .. vim.inspect(selection.value)) end) return true end, }) :find() end internal.spell_suggest = function(opts) local cursor_word = vim.fn.expand "" local suggestions = vim.fn.spellsuggest(cursor_word) pickers .new(opts, { prompt_title = "Spelling Suggestions", finder = finders.new_table { results = suggestions, }, sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions.select_default:replace(function() local selection = action_state.get_selected_entry() if selection == nil then utils.__warn_no_selection "builtin.spell_suggest" return end action_state.get_current_picker(prompt_bufnr)._original_mode = "i" actions.close(prompt_bufnr) vim.cmd('normal! "_ciw' .. selection[1]) vim.cmd "stopinsert" end) return true end, }) :find() end internal.tagstack = function(opts) opts = opts or {} local tagstack = vim.fn.gettagstack().items local tags = {} for i = #tagstack, 1, -1 do local tag = tagstack[i] tag.bufnr = tag.from[1] if api.nvim_buf_is_valid(tag.bufnr) then tags[#tags + 1] = tag tag.filename = vim.fn.bufname(tag.bufnr) tag.lnum = tag.from[2] tag.col = tag.from[3] tag.text = api.nvim_buf_get_lines(tag.bufnr, tag.lnum - 1, tag.lnum, false)[1] or "" end end if vim.tbl_isempty(tags) then utils.notify("builtin.tagstack", { msg = "No tagstack available", level = "WARN", }) return end pickers .new(opts, { prompt_title = "TagStack", finder = finders.new_table { results = tags, entry_maker = make_entry.gen_from_quickfix(opts), }, previewer = conf.qflist_previewer(opts), sorter = conf.generic_sorter(opts), }) :find() end internal.jumplist = function(opts) opts = opts or {} local jumplist = vim.fn.getjumplist()[1] -- reverse the list local sorted_jumplist = {} for i = #jumplist, 1, -1 do if api.nvim_buf_is_valid(jumplist[i].bufnr) then jumplist[i].text = api.nvim_buf_get_lines(jumplist[i].bufnr, jumplist[i].lnum - 1, jumplist[i].lnum, false)[1] or "" table.insert(sorted_jumplist, jumplist[i]) end end pickers .new(opts, { prompt_title = "Jumplist", finder = finders.new_table { results = sorted_jumplist, entry_maker = make_entry.gen_from_quickfix(opts), }, previewer = conf.qflist_previewer(opts), 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(internal) ================================================ FILE: lua/telescope/builtin/__lsp.lua ================================================ ---@alias TextDocumentFunction fun(client: lsp.Client): lsp.TextDocumentPositionParams local api = vim.api local lsp = vim.lsp 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 M = {} local nvim011 = utils.nvim011 local function call_hierarchy(opts, method, title, direction, item) lsp.buf_request(opts.bufnr, method, { item = item }, function(err, result) if err then utils.notify("lsp.call_hierarchy", { msg = title .. ": " .. err.message, level = "ERROR" }) 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 _, rng in pairs(ch_call.fromRanges) do table.insert(locations, { filename = vim.uri_to_fname(ch_item.uri), text = ch_item.name, lnum = rng.start.line + 1, col = rng.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 or vim.tbl_isempty(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 call_hierarchy_items[choice] end ---@param win number|nil: Window handler ---@param extra lsp.TextDocumentPositionParams|nil: Extra fields in params ---@return lsp.TextDocumentPositionParams|TextDocumentFunction: Params to send to the server local function client_position_params(win, extra) win = win or api.nvim_get_current_win() if not nvim011 then local params = lsp.util.make_position_params(win) if extra then params = vim.tbl_extend("force", params, extra) end return params end return function(client) local params = lsp.util.make_position_params(win, client.offset_encoding) if extra then params = vim.tbl_extend("force", params, extra) end return params end end local function calls(opts, direction) local params = client_position_params() lsp.buf_request(opts.bufnr, "textDocument/prepareCallHierarchy", params, function(err, result) if err then utils.notify("lsp.calls", { msg = err.message, level = "ERROR" }) 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 M.incoming_calls = function(opts) calls(opts, "from") end M.outgoing_calls = function(opts) calls(opts, "to") end ---@alias telescope.lsp.list_or_jump_action ---| "textDocument/references" ---| "textDocument/definition" ---| "textDocument/typeDefinition" ---| "textDocument/implementation" ---@param action telescope.lsp.list_or_jump_action ---@param items vim.quickfix.entry[] ---@param opts table ---@return vim.quickfix.entry[] local apply_action_handler = function(action, items, opts) if action == "textDocument/references" and not opts.include_current_line then local lnum = api.nvim_win_get_cursor(opts.winnr)[1] items = vim.tbl_filter(function(v) return not (v.filename == opts.curr_filepath and v.lnum == lnum) end, items) end return items end ---@param items vim.quickfix.entry[] ---@param opts table ---@return vim.quickfix.entry[] local function filter_file_ignore_patterns(items, opts) local file_ignore_patterns = vim.F.if_nil(opts.file_ignore_patterns, conf.file_ignore_patterns) file_ignore_patterns = file_ignore_patterns or {} if vim.tbl_isempty(file_ignore_patterns) then return items end return vim.tbl_filter(function(item) for _, patt in ipairs(file_ignore_patterns) do if string.match(item.filename, patt) then return false end end return true end, items) end ---@param action telescope.lsp.list_or_jump_action ---@param title string prompt title ---@param funname string: name of the calling function ---@param params lsp.TextDocumentPositionParams|(fun(client: lsp.Client, bufnr: integer): table?) ---@param opts table local function list_or_jump(action, title, funname, params, opts) opts.reuse_win = vim.F.if_nil(opts.reuse_win, false) opts.curr_filepath = api.nvim_buf_get_name(opts.bufnr) lsp.buf_request_all(opts.bufnr, action, params, function(results_per_client) local items = {} local first_encoding local errors = {} for client_id, result_or_error in pairs(results_per_client) do local error, result = result_or_error.err, result_or_error.result if error then errors[client_id] = error else if result ~= nil then local locations = {} if not vim.islist(result) then vim.list_extend(locations, { result }) else vim.list_extend(locations, result) end local offset_encoding = lsp.get_client_by_id(client_id).offset_encoding if not vim.tbl_isempty(result) then first_encoding = offset_encoding end vim.list_extend(items, lsp.util.locations_to_items(locations, offset_encoding)) end end end for _, error in pairs(errors) do utils.notify(funname, { msg = action .. " : " .. error.message, level = "ERROR" }) end items = apply_action_handler(action, items, opts) items = filter_file_ignore_patterns(items, opts) if vim.tbl_isempty(items) then utils.notify(funname, { msg = string.format("No %s found", title), level = "INFO", }) return end if #items == 1 and opts.jump_type ~= "never" then local item = items[1] if opts.curr_filepath ~= item.filename or not opts.reuse_win then local cmd if opts.jump_type == "tab" then cmd = "tabedit" elseif opts.jump_type == "split" then cmd = "new" elseif opts.jump_type == "vsplit" then cmd = "vnew" elseif opts.jump_type == "tab drop" then cmd = "tab drop" end if cmd then vim.cmd(string.format("%s %s", cmd, item.filename)) end end lsp.util.show_document(item.user_data, first_encoding, { reuse_win = opts.reuse_win }) else pickers .new(opts, { prompt_title = title, finder = finders.new_table { results = items, 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 M.references = function(opts) opts.include_current_line = vim.F.if_nil(opts.include_current_line, false) local params = client_position_params(opts.winnr, { context = { includeDeclaration = vim.F.if_nil(opts.include_declaration, true) }, }) return list_or_jump("textDocument/references", "LSP References", "builtin.lsp_references", params, opts) end M.definitions = function(opts) local params = client_position_params(opts.winnr) return list_or_jump("textDocument/definition", "LSP Definitions", "builtin.lsp_definitions", params, opts) end M.type_definitions = function(opts) local params = client_position_params(opts.winnr) return list_or_jump( "textDocument/typeDefinition", "LSP Type Definitions", "builtin.lsp_type_definitions", params, opts ) end M.implementations = function(opts) local params = client_position_params(opts.winnr) return list_or_jump("textDocument/implementation", "LSP Implementations", "builtin.lsp_implementations", params, opts) end local symbols_sorter = function(symbols) if vim.tbl_isempty(symbols) then return symbols end local current_buf = api.nvim_get_current_buf() -- sort adequately for workspace symbols local filename_to_bufnr = {} for _, symbol in ipairs(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(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 symbols end M.document_symbols = function(opts) local params = client_position_params(opts.winnr) lsp.buf_request(opts.bufnr, "textDocument/documentSymbol", params, function(err, result, ctx, _) if err then utils.notify("lsp.document_symbols", { msg = err.message, level = "ERROR" }) 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 if nvim011 then local client = assert(lsp.get_client_by_id(ctx.client_id)) locations = lsp.util.symbols_to_items(result or {}, opts.bufnr, client.offset_encoding) or {} else locations = lsp.util.symbols_to_items(result or {}, opts.bufnr) or {} end locations = utils.filter_symbols(locations, opts, symbols_sorter) if vim.tbl_isempty(locations) 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 M.workspace_symbols = function(opts) local params = { query = opts.query or "" } lsp.buf_request(opts.bufnr, "workspace/symbol", params, function(err, server_result, ctx, _) if err then utils.notify("lsp.workspace_symbols", { msg = err.message, level = "ERROR" }) return end local locations if nvim011 then local client = assert(lsp.get_client_by_id(ctx.client_id)) locations = lsp.util.symbols_to_items(server_result or {}, opts.bufnr, client.offset_encoding) or {} else locations = lsp.util.symbols_to_items(server_result or {}, opts.bufnr) or {} end locations = utils.filter_symbols(locations, opts, symbols_sorter) if vim.tbl_isempty(locations) 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 = lsp.buf_request_all(bufnr, "workspace/symbol", { query = prompt }, tx) local results = rx() ---@type table local locations = {} ---@type vim.quickfix.entry[] for client_id, client_res in pairs(results) do if client_res.error then utils.notify("lsp.workspace_symbols", { msg = client_res.error.message, level = "ERROR" }) elseif client_res.result ~= nil then if nvim011 then local client = assert(lsp.get_client_by_id(client_id)) vim.list_extend(locations, lsp.util.symbols_to_items(client_res.result, bufnr, client.offset_encoding)) else vim.list_extend(locations, lsp.util.symbols_to_items(client_res.result, bufnr)) end end end if not vim.tbl_isempty(locations) then locations = utils.filter_symbols(locations, opts, symbols_sorter) or {} end return locations end end M.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", "", actions.to_fuzzy_refine) return true end, }) :find() end local function check_capabilities(method, bufnr) local clients = lsp.get_clients { bufnr = bufnr } for _, client in pairs(clients) do if nvim011 then if client:supports_method(method, bufnr) then return true end else if client.supports_method(method, { bufnr = bufnr }) then return true end end end if #clients == 0 then utils.notify("builtin.lsp_*", { msg = "no client attached", level = "INFO", }) else utils.notify("builtin.lsp_*", { msg = "server does not support " .. method, level = "INFO", }) end return false end local feature_map = { ["document_symbols"] = "textDocument/documentSymbol", ["references"] = "textDocument/references", ["definitions"] = "textDocument/definition", ["type_definitions"] = "textDocument/typeDefinition", ["implementations"] = "textDocument/implementation", ["workspace_symbols"] = "workspace/symbol", ["incoming_calls"] = "callHierarchy/incomingCalls", ["outgoing_calls"] = "callHierarchy/outgoingCalls", } local function apply_checks(mod) for k, v in pairs(mod) do mod[k] = function(opts) opts = opts or {} local method = feature_map[k] if method and not check_capabilities(method, opts.bufnr) then return end v(opts) end end return mod end return apply_checks(M) ================================================ FILE: lua/telescope/builtin/init.lua ================================================ ---@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: --- --- --- :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 --- }) --- ---@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 or the visual selection 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 file_entry_encoding string: encoding of output of `find_command` ---@field follow boolean: if true, follows symlinks (i.e. uses `-L` flag for the `find` command) (default: false) ---@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: --- - ``: 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_width number: defines the width of the symbol section (default: 25) ---@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.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 results_ts_highlight boolean: highlight result entries with treesitter (default: true) ---@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 show_kind boolean: if true and kind info is available, show the kind of the tag (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 show_kind boolean: if true and kind info is available, show the kind of the tag (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: --- - ``: 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_file_path boolean: if we should use the current buffer git root (default: false) ---@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: --- - ``: checks out the currently selected commit --- - `m`: resets current branch to selected commit using mixed mode --- - `s`: resets current branch to selected commit using soft mode --- - `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_file_path boolean: if we should use the current buffer git root (default: false) ---@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: --- - ``: checks out the currently selected commit --- - ``: opens a diff in a vertical split --- - ``: opens a diff in a horizontal split --- - ``: 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_file_path boolean: if we should use the current buffer git root (default: false) ---@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 --- Lists commits for a range of lines in the current buffer with diff preview --- In visual mode, lists commits for the selected lines --- With operator mode enabled, lists commits inside the text object/motion --- - Default keymaps or your overridden `select_` keys: --- - ``: checks out the currently selected commit --- - ``: opens a diff in a vertical split --- - ``: opens a diff in a horizontal split --- - ``: 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. the last element must be "-L". {"git","log","--pretty=oneline","--abbrev-commit","--no-patch","-L"} ---@field from number: the first line number in the range (default: current line) ---@field to number: the last line number in the range (default: the value of `from`) ---@field operator boolean: select lines in operator-pending mode (default: false) builtin.git_bcommits_range = require_on_exported_call("telescope.builtin.__git").bcommits_range --- List branches for current directory, with output from `git log --oneline` shown in the preview window --- - Default keymaps: --- - ``: checks out the currently selected branch --- - ``: tracks currently selected branch --- - ``: rebases currently selected branch --- - ``: creates a new branch, with confirmation prompt before creation --- - ``: deletes the currently selected branch, with confirmation prompt before deletion --- - ``: 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_file_path boolean: if we should use the current buffer git root (default: false) ---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true) ---@field show_remote_tracking_branches boolean: show remote tracking branches like origin/main (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: --- - ``: stages or unstages the currently selected file --- - ``: 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_file_path boolean: if we should use the current buffer git root (default: false) ---@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) ---@field expand_dir boolean: pass flag `-uall` to show files in untracked directories (default: true) builtin.git_status = require_on_exported_call("telescope.builtin.__git").status --- Lists stash items in current repository --- - Default keymaps: --- - ``: 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_file_path boolean: if we should use the current buffer git root (default: false) ---@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: --- - ``: 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 `` ---@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 `` ---@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 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 `` ---@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) builtin.loclist = require_on_exported_call("telescope.builtin.__internal").loclist --- Lists previously open files, opens on `` ---@param opts table: options to pass to the picker ---@field cwd string: specify a working directory to filter oldfiles by ---@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 `` --- - Default keymaps: --- - ``: open the command line with the text of the currently selected result populated in it ---@param opts table: options to pass to the picker ---@field filter_fn function: filter fn(cmd:string). true if the history command should be presented. builtin.command_history = require_on_exported_call("telescope.builtin.__internal").command_history --- Lists searches that were executed recently, and reruns them on `` --- - Default keymaps: --- - ``: 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 `` ---@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 `` ---@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 `` ---@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 `` ---@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 `` --- - Default keymaps: --- - ``: delete the currently selected buffer ---@param opts table: options to pass to the picker ---@field cwd string: specify a working directory to filter buffers list by ---@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 ---@field sort_buffers function: sort fn(bufnr_a, bufnr_b). true if bufnr_a should go first. Runs after sorting by most recent (if specified) ---@field select_current boolean: select current buffer (default: false) builtin.buffers = require_on_exported_call("telescope.builtin.__internal").buffers --- Lists available colorschemes and applies them on `` ---@param opts table: options to pass to the picker ---@field colors table: a list of additional colorschemes to explicitly make available to telescope (default: {}) ---@field enable_preview boolean: if true, will preview the selected color ---@field ignore_builtins boolean: if true, builtin colorschemes are not listed builtin.colorscheme = require_on_exported_call("telescope.builtin.__internal").colorscheme --- Lists vim marks and their value, jumps to the mark on `` ---@param opts table: options to pass to the picker ---@field file_encoding string: file encoding for the previewer ---@field mark_type string: filter marks by type (default: "all", options: "all"|"global"|"local") builtin.marks = require_on_exported_call("telescope.builtin.__internal").marks --- Lists vim registers, pastes the contents of the register on `` --- - Default keymaps: --- - ``: 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 `` ---@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 "" are also shown (default: true) ---@field only_buf boolean: if true, only show the buffer-local keymaps (default: false) ---@field lhs_filter function: filter(lhs:string) -> boolean. true for keymap.lhs if the keymap should be shown (optional) ---@field filter function: filter(km:keymap) -> boolean. true for the keymap if it should be shown (optional) 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 `` ---@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 `` ---@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 `` ---@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 `` ---@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) builtin.tagstack = require_on_exported_call("telescope.builtin.__internal").tagstack --- Lists items from Vim's jumplist, jumps to location on `` ---@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) 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 `` ---@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 jump_type string: how to goto reference if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never" ---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) ---@field reuse_win boolean: jump to existing window if buffer is already opened (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 `` ---@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 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 `` ---@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 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", "tab drop", "split", "vsplit", "never" ---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) ---@field reuse_win boolean: jump to existing window if buffer is already opened (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", "tab drop", "split", "vsplit", "never" ---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) ---@field reuse_win boolean: jump to existing window if buffer is already opened (default: false) ---@field file_encoding string: file encoding for the previewer builtin.lsp_type_definitions = require_on_exported_call("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", "tab drop", "split", "vsplit", "never" ---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) ---@field reuse_win boolean: jump to existing window if buffer is already opened (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: --- - ``: 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: --- - ``: 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: --- - ``: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`), only works after refining to fuzzy search using ---@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: --- - ``: show autocompletion menu to prefilter your query with the diagnostic you want to see (i.e. `:warning:`) --- - sort_by option: --- - "buffer": order by bufnr (prioritizing current bufnr), severity, lnum --- - "severity": order by severity, bufnr (prioritizing current bufnr), lnum ---@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 string|number: set length of diagnostic entry text in Results. Use 'full' for full untruncated text ---@field namespace number: limit your diagnostics to a specific namespace ---@field disable_coordinates boolean: don't show the line & row numbers (default: false) ---@field sort_by string: sort order of the diagnostics results; see above notes (default: "buffer") 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 if defaults.attach_mappings and opts.attach_mappings then local opts_attach = opts.attach_mappings opts.attach_mappings = function(prompt_bufnr, map) defaults.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 ================================================ FILE: lua/telescope/command.lua ================================================ ---@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: ---
--- `require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
--- 
--- If you want to use this api from a vim file you should prepend `lua` to the command, as below: ---
--- `lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
--- 
--- If you want to use this api from a neovim command line you should prepend `:lua` to --- the command, as below: ---
--- `:lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
--- 
--- --- 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: ---
--- `:Telescope find_files hidden=true layout_config={"prompt_position":"top"}`
--- 
--- 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: ---
--- `:Telescope find_files layout_config={ "prompt_position" : "top" }`
--- 
--- 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) -- BUG: this doesn't allow things like `preview=false`, where an -- option supports other types other than the default type -- (`preview` has a default type of table) 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 local ok = pcall(require("telescope").load_extension, cmd) if ok 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 ` -- 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: lua/telescope/config/resolve.lua ================================================ ---@tag telescope.resolve ---@config { ["module"] = "telescope.resolve" } ---@brief [[ --- Provides "resolver functions" to allow more customisable inputs for options. ---@brief ]] --[[ Ultimately boils down to getting `height` and `width` for: - prompt - preview - results No matter what you do, I will not make prompt have more than one line (atm) Result of `resolve` should be a table with: { preview = { get_width = function(self, max_columns, max_lines) end get_height = function(self, max_columns, max_lines) end }, result = { get_width = function(self, max_columns, max_lines) end get_height = function(self, max_columns, max_lines) end }, prompt = { get_width = function(self, max_columns, max_lines) end get_height = function(self, max_columns, max_lines) end }, total ? } !!NOT IMPLEMENTED YET!! height = 1. 0 <= number < 1 This means total height as a percentage 2. 1 <= number This means total height as a fixed number 3. function(picker, columns, lines) -> returns one of the above options return math.min(110, max_rows * .5) if columns > 120 then return 110 else return 0.6 end 3. { previewer = x, results = x, prompt = x, }, this means I do my best guess I can for these, given your options width = exactly the same, but switch to width { height = 0.5, width = { previewer = 0.25, results = 30, } } https://github.com/nvim-lua/telescope.nvim/pull/43 After we get layout, we should try and make top-down sorting work. That's the next step to scrolling. { vertical = { }, horizontal = { }, height = ... width = ... } --]] local resolver = {} local _resolve_map = {} local throw_invalid_config_option = function(key, value) error(string.format("Invalid configuration option for '%s': '%s'", key, tostring(value)), 2) end -- Booleans _resolve_map[function(val) return val == false end] = function(_, val) return function(...) return val end end -- Percentages _resolve_map[function(val) return type(val) == "number" and val >= 0 and val < 1 end] = function(selector, val) return function(...) local selected = select(selector, ...) return math.floor(val * selected) end end -- Numbers _resolve_map[function(val) return type(val) == "number" and val >= 1 end] = function(selector, val) return function(...) local selected = select(selector, ...) return math.min(val, selected) end end -- function: -- Function must have same signature as get_window_layout -- function(self, max_columns, max_lines): number -- -- Resulting number is used for this configuration value. _resolve_map[function(val) return type(val) == "function" end] = function(_, val) return val end _resolve_map[function(val) return type(val) == "table" and val["max"] ~= nil and val[1] ~= nil and val[1] >= 0 and val[1] < 1 end] = function( selector, val ) return function(...) local selected = select(selector, ...) return math.min(math.floor(val[1] * selected), val["max"]) end end _resolve_map[function(val) return type(val) == "table" and val["min"] ~= nil and val[1] ~= nil and val[1] >= 0 and val[1] < 1 end] = function( selector, val ) return function(...) local selected = select(selector, ...) return math.max(math.floor(val[1] * selected), val["min"]) end end -- Add padding option _resolve_map[function(val) return type(val) == "table" and val["padding"] ~= nil end] = function(selector, val) local resolve_pad = function(value) for k, v in pairs(_resolve_map) do if k(value) then return v(selector, value) end end throw_invalid_config_option("padding", value) end return function(...) local selected = select(selector, ...) local padding = resolve_pad(val["padding"]) return math.floor(selected - 2 * padding(...)) end end --- Converts input to a function that returns the height. --- The input must take one of five forms: --- 1. 0 <= number < 1
--- This means total height as a percentage. --- 2. 1 <= number
--- This means total height as a fixed number. --- 3. function
--- Must have signature: --- function(self, max_columns, max_lines): number --- 4. table of the form: { val, max = ..., min = ... }
--- val has to be in the first form 0 <= val < 1 and only one is given, --- `min` or `max` as fixed number --- 5. table of the form: {padding = `foo`}
--- where `foo` has one of the previous three forms.
--- The height is then set to be the remaining space after padding. --- For example, if the window has height 50, and the input is {padding = 5}, --- the height returned will be `40 = 50 - 2*5` --- --- The returned function will have signature: --- function(self, max_columns, max_lines): number resolver.resolve_height = function(val) for k, v in pairs(_resolve_map) do if k(val) then return v(3, val) end end throw_invalid_config_option("height", val) end --- Converts input to a function that returns the width. --- The input must take one of five forms: --- 1. 0 <= number < 1
--- This means total width as a percentage. --- 2. 1 <= number
--- This means total width as a fixed number. --- 3. function
--- Must have signature: --- function(self, max_columns, max_lines): number --- 4. table of the form: { val, max = ..., min = ... }
--- val has to be in the first form 0 <= val < 1 and only one is given, --- `min` or `max` as fixed number --- 5. table of the form: {padding = `foo`}
--- where `foo` has one of the previous three forms.
--- The width is then set to be the remaining space after padding. --- For example, if the window has width 100, and the input is {padding = 5}, --- the width returned will be `90 = 100 - 2*5` --- --- The returned function will have signature: --- function(self, max_columns, max_lines): number resolver.resolve_width = function(val) for k, v in pairs(_resolve_map) do if k(val) then return v(2, val) end end throw_invalid_config_option("width", val) end --- Calculates the adjustment required to move the picker from the middle of the screen to --- an edge or corner.
--- The `anchor` can be any of the following strings: --- - "", "CENTER", "NW", "N", "NE", "E", "SE", "S", "SW", "W" --- The anchors have the following meanings: --- - "" or "CENTER":
--- the picker will remain in the middle of the screen. --- - Compass directions:
--- the picker will move to the corresponding edge/corner --- e.g. "NW" -> "top left corner", "E" -> "right edge", "S" -> "bottom edge" resolver.resolve_anchor_pos = function(anchor, p_width, p_height, max_columns, max_lines, anchor_padding) anchor = anchor:upper() local pos = { 0, 0 } if anchor == "CENTER" then return pos end if anchor:find "W" then pos[1] = math.ceil((p_width - max_columns) / 2) + anchor_padding elseif anchor:find "E" then pos[1] = math.ceil((max_columns - p_width) / 2) - anchor_padding end if anchor:find "N" then pos[2] = math.ceil((p_height - max_lines) / 2) + anchor_padding elseif anchor:find "S" then pos[2] = math.ceil((max_lines - p_height) / 2) - anchor_padding end return pos end -- duplicate from utils.lua to keep self-contained -- Win option always returns a table with preview, results, and prompt. -- It handles many different ways. Some examples are as follows: -- -- -- Disable -- borderchars = false -- -- -- All three windows share the same -- borderchars = { '─', '│', '─', '│', '┌', '┐', '┘', '└'}, -- -- -- Each window gets it's own configuration -- borderchars = { -- preview = {...}, -- results = {...}, -- prompt = {...}, -- } -- -- -- Default to [1] but override with specific items -- borderchars = { -- {...} -- prompt = {...}, -- } resolver.win_option = function(val, default) if type(val) ~= "table" or vim.islist(val) then if val == nil then val = default end return { preview = val, results = val, prompt = val, } elseif type(val) == "table" then assert(not vim.islist(val)) local val_to_set = val[1] if val_to_set == nil then val_to_set = default end return { preview = vim.F.if_nil(val.preview, val_to_set), results = vim.F.if_nil(val.results, val_to_set), prompt = vim.F.if_nil(val.prompt, val_to_set), } end end return resolver ================================================ FILE: lua/telescope/config.lua ================================================ local strings = require "plenary.strings" local sorters = require "telescope.sorters" local os_sep = require("plenary.path").path.sep local has_win = vim.fn.has "win32" == 1 -- Keep the values around between reloads _TelescopeConfigurationValues = _TelescopeConfigurationValues or {} _TelescopeConfigurationPickers = _TelescopeConfigurationPickers or {} local function first_non_null(...) local n = select("#", ...) for i = 1, n do local value = select(i, ...) if value ~= nil then return value end end end -- A function that creates an amended copy of the `base` table, -- by replacing keys at "level 2" that match keys in "level 1" in `priority`, -- and then performs a deep_extend. -- May give unexpected results if used with tables of "depth" -- greater than 2. local smarter_depth_2_extend = function(priority, base) local result = {} for key, val in pairs(base) do if type(val) ~= "table" then result[key] = first_non_null(priority[key], val) else result[key] = {} for k, v in pairs(val) do result[key][k] = first_non_null(priority[k], v) end end end for key, val in pairs(priority) do if type(val) ~= "table" then result[key] = first_non_null(val, result[key]) else result[key] = vim.tbl_extend("keep", val, result[key] or {}) end end return result end local resolve_table_opts = function(priority, base) if priority == false or (priority == nil and base == false) then return false end if priority == nil and type(base) == "table" then return base end return smarter_depth_2_extend(priority, base) end -- TODO: Add other major configuration points here. -- selection_strategy local config = {} config.smarter_depth_2_extend = smarter_depth_2_extend config.resolve_table_opts = resolve_table_opts config.values = _TelescopeConfigurationValues config.descriptions = {} config.pickers = _TelescopeConfigurationPickers function config.set_pickers(pickers) pickers = vim.F.if_nil(pickers, {}) for k, v in pairs(pickers) do config.pickers[k] = v end end local layout_config_defaults = { horizontal = { width = 0.8, height = 0.9, prompt_position = "bottom", preview_cutoff = 120, }, vertical = { width = 0.8, height = 0.9, prompt_position = "bottom", preview_cutoff = 40, }, center = { width = 0.5, height = 0.4, preview_cutoff = 40, prompt_position = "top", }, cursor = { width = 0.8, height = 0.9, preview_cutoff = 40, }, bottom_pane = { height = 25, prompt_position = "top", preview_cutoff = 120, }, } local layout_config_description = string.format( [[ Determines the default configuration values for layout strategies. See |telescope.layout| for details of the configurations options for each strategy. Allows setting defaults for all strategies as top level options and for overriding for specific options. For example, the default values below set the default width to 80%% of the screen width for all strategies except 'center', which has width of 50%% of the screen width. Default: %s ]], vim.inspect(layout_config_defaults, { newline = "\n ", indent = " " }) ) -- A table of all the usual defaults for telescope. -- Keys will be the name of the default, -- values will be a list where: -- - first entry is the value -- - second entry is the description of the option local telescope_defaults = {} config.descriptions_order = {} local append = function(name, val, doc) telescope_defaults[name] = { val, doc } table.insert(config.descriptions_order, name) end append( "sorting_strategy", "descending", [[ Determines the direction "better" results are sorted towards. Available options are: - "descending" (default) - "ascending"]] ) append( "selection_strategy", "reset", [[ Determines how the cursor acts after each sort iteration. Available options are: - "reset" (default) - "follow" - "row" - "closest" - "none"]] ) append( "scroll_strategy", "cycle", [[ Determines what happens if you try to scroll past the view of the picker. Available options are: - "cycle" (default) - "limit"]] ) append( "layout_strategy", "horizontal", [[ Determines the default layout of Telescope pickers. See |telescope.layout| for details of the available strategies. Default: 'horizontal']] ) append( "create_layout", nil, [[ Configure the layout of Telescope pickers. See |telescope.pickers.layout| for details. Default: 'nil']] ) append("layout_config", layout_config_defaults, layout_config_description) append( "cycle_layout_list", { "horizontal", "vertical" }, [[ Determines the layouts to cycle through when using `actions.layout.cycle_layout_next` and `actions.layout.cycle_layout_prev`. Should be a list of "layout setups". Each "layout setup" can take one of two forms: 1. string This is interpreted as the name of a `layout_strategy` 2. table A table with possible keys `layout_strategy`, `layout_config` and `previewer` Default: { "horizontal", "vertical" } ]] ) append( "winblend", function() return vim.o.winblend end, [[ Configure winblend for telescope floating windows. See |winblend| for more information. Type can be a number or a function returning a number Default: function() return vim.o.winblend end]] ) append( "wrap_results", false, [[ Word wrap the search results Default: false]] ) append( "prompt_prefix", "> ", [[ The character(s) that will be shown in front of Telescope's prompt. Default: '> ']] ) append( "selection_caret", "> ", [[ The character(s) that will be shown in front of the current selection. Default: '> ']] ) append( "entry_prefix", " ", [[ Prefix in front of each result entry. Current selection not included. Default: ' ']] ) append( "multi_icon", "+", [[ Symbol to add in front of a multi-selected result entry. Replaces final character of |telescope.defaults.selection_caret| and |telescope.defaults.entry_prefix| as appropriate. To have no icon, set to the empty string. Default: '+']] ) append( "initial_mode", "insert", [[ Determines in which mode telescope starts. Valid Keys: `insert` and `normal`. Default: "insert"]] ) append( "border", true, [[ Boolean defining if borders are added to Telescope windows. Default: true]] ) append( "path_display", {}, [[ Determines how file paths are displayed. path_display can be set to an array with a combination of: - "hidden" hide file names - "tail" only display the file name, and not the path - "absolute" display absolute paths - "smart" remove as much from the path as possible to only show the difference between the displayed paths. Warning: The nature of the algorithm might have a negative performance impact! - "shorten" only display the first character of each directory in the path - "truncate" truncates the start of the path when the whole path will not fit. To increase the gap between the path and the edge, set truncate to number `truncate = 3` - "filename_first" shows filenames first and then the directories You can also specify the number of characters of each directory name to keep by setting `path_display.shorten = num`. e.g. for a path like `alpha/beta/gamma/delta.txt` setting `path_display.shorten = 1` will give a path like: `a/b/g/delta.txt` Similarly, `path_display.shorten = 2` will give a path like: `al/be/ga/delta.txt` You can also further customise the shortening behaviour by setting `path_display.shorten = { len = num, exclude = list }`, where `len` acts as above, and `exclude` is a list of positions that are not shortened. Negative numbers in the list are considered relative to the end of the path. e.g. for a path like `alpha/beta/gamma/delta.txt` setting `path_display.shorten = { len = 1, exclude = {1, -1} }` will give a path like: `alpha/b/g/delta.txt` setting `path_display.shorten = { len = 2, exclude = {2, -2} }` will give a path like: `al/beta/gamma/de` path_display can also be set to 'filename_first' to put the filename in front. path_display = { "filename_first" }, The directory structure can be reversed as follows: path_display = { filename_first = { reverse_directories = true } }, path_display can also be set to 'hidden' string to hide file names path_display can also be set to a function for custom formatting of the path display with the following signature Signature: fun(opts: table, path: string): string, table? The optional table is an list of positions and highlight groups to set the highlighting of the return path string. Example: -- Format path as "file.txt (path\to\file\)" path_display = function(opts, path) local tail = require("telescope.utils").path_tail(path) return string.format("%s (%s)", tail, path) end, -- Format path and add custom highlighting path_display = function(opts, path) local tail = require("telescope.utils").path_tail(path) path = string.format("%s (%s)", tail, path) local highlights = { { { 0, -- highlight start position #path, -- highlight end position }, "Comment", -- highlight group name }, } return path, highlights end Default: {}]] ) append( "borderchars", { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, [[ Set the borderchars of telescope floating windows. It has to be a table of 8 string values. Default: { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }]] ) append( "get_status_text", function(self, opts) local multi_select_cnt = #(self:get_multi_selection()) local showing_cnt = (self.stats.processed or 0) - (self.stats.filtered or 0) local total_cnt = self.stats.processed or 0 local status_icon = "" local status_text if opts and not opts.completed then status_icon = "*" end if showing_cnt == 0 and total_cnt == 0 then status_text = status_icon elseif multi_select_cnt == 0 then status_text = string.format("%s %s / %s", status_icon, showing_cnt, total_cnt) else status_text = string.format("%s %s / %s / %s", status_icon, multi_select_cnt, showing_cnt, total_cnt) end -- quick workaround for extmark right_align side-scrolling limitation -- https://github.com/nvim-telescope/telescope.nvim/issues/2929 local prompt_width = vim.api.nvim_win_get_width(self.prompt_win) local cursor_col = vim.api.nvim_win_get_cursor(self.prompt_win)[2] local prefix_display_width = strings.strdisplaywidth(self.prompt_prefix) --[[@as integer]] local prefix_width = #self.prompt_prefix local prefix_shift = 0 if prefix_display_width ~= prefix_width then prefix_shift = prefix_display_width end local cursor_occluded = (prompt_width - cursor_col - #status_text + prefix_shift) < 0 if cursor_occluded then return "" else return status_text end end, [[ A function that determines what the virtual text looks like. Signature: function(picker) -> str Default: function that shows current count / all]] ) append( "hl_result_eol", true, [[ Changes if the highlight for the selected item in the results window is always the full width of the window Default: true]] ) append( "dynamic_preview_title", false, [[ Will change the title of the preview window dynamically, where it is supported. For example, the preview window's title could show up as the full filename. Default: false]] ) append( "results_title", "Results", [[ Defines the default title of the results window. A false value can be used to hide the title altogether. Default: "Results"]] ) append( "prompt_title", "Prompt", [[ Defines the default title of the prompt window. A false value can be used to hide the title altogether. Most of the times builtins define a prompt_title which will be preferred over this default. Default: "Prompt"]] ) append( "mappings", {}, [[ Your mappings to override telescope's default mappings. See: ~ |telescope.mappings| ]] ) append( "default_mappings", nil, [[ Not recommended to use except for advanced users. Will allow you to completely remove all of telescope's default maps and use your own. Default: nil ]] ) append( "history", { path = vim.fn.stdpath "data" .. os_sep .. "telescope_history", limit = 100, handler = function(...) return require("telescope.actions.history").get_simple_history(...) end, cycle_wrap = false, }, [[ This field handles the configuration for prompt history. By default it is a table, with default values (more below). To disable history, set it to false. Currently mappings still need to be added, Example: mappings = { i = { [""] = require('telescope.actions').cycle_history_next, [""] = require('telescope.actions').cycle_history_prev, }, }, Fields: - path: The path to the telescope history as string. Default: stdpath("data")/telescope_history - limit: The amount of entries that will be written in the history. Warning: If limit is set to nil it will grow unbound. Default: 100 - handler: A lua function that implements the history. This is meant as a developer setting for extensions to override the history handling, e.g., https://github.com/nvim-telescope/telescope-smart-history.nvim, which allows context sensitive (cwd + picker) history. Default: require('telescope.actions.history').get_simple_history - cycle_wrap: Indicates whether the cycle_history_next and cycle_history_prev functions should wrap around to the beginning or end of the history entries on reaching their respective ends Default: false]] ) append( "cache_picker", { num_pickers = 1, limit_entries = 1000, ignore_empty_prompt = false, }, [[ This field handles the configuration for picker caching. By default it is a table, with default values (more below). To disable caching, set it to false. Caching preserves all previous multi selections and results and therefore may result in slowdown or increased RAM occupation if too many pickers (`cache_picker.num_pickers`) or entries ('cache_picker.limit_entries`) are cached. Fields: - num_pickers: The number of pickers to be cached. Set to -1 to preserve all pickers of your session. If passed to a picker, the cached pickers with indices larger than `cache_picker.num_pickers` will be cleared. Default: 1 - limit_entries: The amount of entries that will be saved for each picker. Default: 1000 - ignore_empty_prompt: If true, the picker will not be cached if the prompt is empty (i.e., no text has been typed at the time of closing the prompt). Default: false ]] ) append( "preview", { check_mime_type = not has_win, filesize_limit = 25, highlight_limit = 1, timeout = 250, treesitter = true, msg_bg_fillchar = "╱", hide_on_startup = false, }, [[ This field handles the global configuration for previewers. By default it is a table, with default values (more below). To disable previewing, set it to false. If you have disabled previewers globally, but want to opt in to previewing for single pickers, you will have to pass `preview = true` or `preview = {...}` (your config) to the `opts` of your picker. Fields: - check_mime_type: Use `file` if available to try to infer whether the file to preview is a binary if filetype detection fails. Windows users get `file` from: https://github.com/julian-r/file-windows Set to false to attempt to preview any mime type. Default: true for all OS excl. Windows - filesize_limit: The maximum file size in MB attempted to be previewed. Set to false to attempt to preview any file size. Default: 25 - highlight_limit: The maximum file size in MB attempted to be highlighted. Set to false to attempt to highlight any file size. Default: 1 - timeout: Timeout the previewer if the preview did not complete within `timeout` milliseconds. Set to false to not timeout preview. Default: 250 - hook(s): Function(s) that takes `(filepath, bufnr, opts)`, where opts exposes winid and ft (filetype). Available hooks (in order of priority): {filetype, mime, filesize, timeout}_hook Important: the filetype_hook must return true or false to indicate whether to continue (true) previewing or not (false), respectively. Two examples: local putils = require("telescope.previewers.utils") ... -- preview is called in telescope.setup { ... } preview = { -- 1) Do not show previewer for certain files filetype_hook = function(filepath, bufnr, opts) -- you could analogously check opts.ft for filetypes local excluded = vim.tbl_filter(function(ending) return filepath:match(ending) end, { ".*%.csv", ".*%.toml", }) if not vim.tbl_isempty(excluded) then putils.set_preview_message( bufnr, opts.winid, string.format("I don't like %s files!", excluded[1]:sub(5, -1)) ) return false end return true end, -- 2) Truncate lines to preview window for too large files filesize_hook = function(filepath, bufnr, opts) local path = require("plenary.path"):new(filepath) -- opts exposes winid local height = vim.api.nvim_win_get_height(opts.winid) local lines = vim.split(path:head(height), "[\r]?\n") vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) end, } The configuration recipes for relevant examples. Note: we use vim.filetype filetype detection, so if you have troubles with files not highlighting correctly, please read |vim.filetype| Default: nil - treesitter: Determines whether the previewer performs treesitter highlighting, which falls back to regex-based highlighting. `true`: treesitter highlighting for all available filetypes `false`: regex-based highlighting for all filetypes `table`: may contain the following keys: - enable boolean|table: if boolean, enable ts highlighting for all supported filetypes. if table, ts highlighting is only enabled for given filetypes. - disable table: list of filetypes for which ts highlighting is not used if `enable = true`. Default: true - msg_bg_fillchar: Character to fill background of unpreviewable buffers with Default: "╱" - hide_on_startup: Hide previewer when picker starts. Previewer can be toggled with actions.layout.toggle_preview. Default: false - ls_short: Determines whether to use the `--short` flag for the `ls` command when previewing directories. Otherwise will result to using `--long`. Default: false ]] ) append( "vimgrep_arguments", { "rg", "--color=never", "--no-heading", "--with-filename", "--line-number", "--column", "--smart-case" }, [[ Defines the command that will be used for `live_grep` and `grep_string` pickers. Hint: Make sure that color is currently set to `never` because we do not yet interpret color codes Hint 2: Make sure that these options are in your changes arguments: "--no-heading", "--with-filename", "--line-number", "--column" because we need them so the ripgrep output is in the correct format. Default: { "rg", "--color=never", "--no-heading", "--with-filename", "--line-number", "--column", "--smart-case" }]] ) append( "use_less", true, [[ Boolean if less should be enabled in term_previewer (deprecated and currently no longer used in the builtin pickers). Default: true]] ) append( "set_env", nil, [[ Set an environment for term_previewer. A table of key values: Example: { COLORTERM = "truecolor", ... } Hint: Empty table is not allowed. Default: nil]] ) append( "color_devicons", true, [[ Boolean if devicons should be enabled or not. If set to false, the text highlight group is used. Hint: Coloring only works if |termguicolors| is enabled. Default: true]] ) append( "file_sorter", sorters.get_fzy_sorter, [[ A function pointer that specifies the file_sorter. This sorter will be used for find_files, git_files and similar. Hint: If you load a native sorter, you don't need to change this value, the native sorter will override it anyway. Default: require("telescope.sorters").get_fzy_sorter]] ) append( "generic_sorter", sorters.get_fzy_sorter, [[ A function pointer to the generic sorter. The sorter that should be used for everything that is not a file. Hint: If you load a native sorter, you don't need to change this value, the native sorter will override it anyway. Default: require("telescope.sorters").get_fzy_sorter]] ) --TODO(conni2461): Why is this even configurable??? append( "prefilter_sorter", sorters.prefilter, [[ This points to a wrapper sorter around the generic_sorter that is able to do prefiltering. It's usually used for lsp_*_symbols and lsp_*_diagnostics Default: require("telescope.sorters").prefilter]] ) append( "tiebreak", function(current_entry, existing_entry, _) return #current_entry.ordinal < #existing_entry.ordinal end, [[ A function that determines how to break a tie when two entries have the same score. Having a function that always returns false would keep the entries in the order they are found, so existing_entry before current_entry. Vice versa always returning true would place the current_entry before the existing_entry. Signature: function(current_entry, existing_entry, prompt) -> boolean Default: function that breaks the tie based on the length of the entry's ordinal]] ) append( "file_ignore_patterns", nil, [[ A table of lua regex that define the files that should be ignored. Example: { "^scratch/" } -- ignore all files in scratch directory Example: { "%.npz" } -- ignore all npz files See: https://www.lua.org/manual/5.1/manual.html#5.4.1 for more information about lua regex Note: `file_ignore_patterns` will be used in all pickers that have a file associated. This might lead to the problem that lsp_ pickers aren't displaying results because they might be ignored by `file_ignore_patterns`. For example, setting up node_modules as ignored will never show node_modules in any results, even if you are interested in lsp_ results. If you only want `file_ignore_patterns` for `find_files` and `grep_string`/`live_grep` it is suggested that you setup `gitignore` and have fd and or ripgrep installed because both tools will not show `gitignore`d files on default. Default: nil]] ) append( "get_selection_window", function() return 0 end, [[ Function that takes function(picker, entry) and returns a window id. The window ID will be used to decide what window the chosen file will be opened in and the cursor placed in upon leaving the picker. Default: `function() return 0 end` ]] ) append( "git_worktrees", nil, [[ A table of arrays of detached working trees with keys `gitdir` and `toplevel`. Used to pass `--git-dir` and `--work-tree` flags to git commands when telescope fails to infer the top-level directory of a given working tree based on cwd. Example: git_worktrees = { { toplevel = vim.env.HOME, gitdir = vim.env.HOME .. '/.cfg' } } Default: nil ]] ) append( "file_previewer", function(...) return require("telescope.previewers").vim_buffer_cat.new(...) end, [[ Function pointer to the default file_previewer. It is mostly used for find_files, git_files and similar. You can change this function pointer to either use your own previewer or use the command-line program bat as the previewer: require("telescope.previewers").cat.new Default: require("telescope.previewers").vim_buffer_cat.new]] ) append( "grep_previewer", function(...) return require("telescope.previewers").vim_buffer_vimgrep.new(...) end, [[ Function pointer to the default vim_grep previewer. It is mostly used for live_grep, grep_string and similar. You can change this function pointer to either use your own previewer or use the command-line program bat as the previewer: require("telescope.previewers").vimgrep.new Default: require("telescope.previewers").vim_buffer_vimgrep.new]] ) append( "qflist_previewer", function(...) return require("telescope.previewers").vim_buffer_qflist.new(...) end, [[ Function pointer to the default qflist previewer. It is mostly used for qflist, loclist and lsp. You can change this function pointer to either use your own previewer or use the command-line program bat as the previewer: require("telescope.previewers").qflist.new Default: require("telescope.previewers").vim_buffer_qflist.new]] ) append( "buffer_previewer_maker", function(...) return require("telescope.previewers").buffer_previewer_maker(...) end, [[ Developer option that defines the underlining functionality of the buffer previewer. For interesting configuration examples take a look at https://github.com/nvim-telescope/telescope.nvim/wiki/Configuration-Recipes Default: require("telescope.previewers").buffer_previewer_maker]] ) -- @param user_defaults table: a table where keys are the names of options, -- and values are the ones the user wants -- @param tele_defaults table: (optional) a table containing all of the defaults -- for telescope [defaults to `telescope_defaults`] function config.set_defaults(user_defaults, tele_defaults) user_defaults = vim.F.if_nil(user_defaults, {}) tele_defaults = vim.F.if_nil(tele_defaults, telescope_defaults) local function get(name, default_val) if name == "layout_config" then return smarter_depth_2_extend( vim.F.if_nil(user_defaults[name], {}), vim.tbl_deep_extend("keep", vim.F.if_nil(config.values[name], {}), vim.F.if_nil(default_val, {})) ) end if name == "history" or name == "cache_picker" or name == "preview" then if user_defaults[name] == false or config.values[name] == false then return false end if user_defaults[name] == true then return vim.F.if_nil(config.values[name], {}) end return smarter_depth_2_extend( vim.F.if_nil(user_defaults[name], {}), vim.tbl_deep_extend("keep", vim.F.if_nil(config.values[name], {}), vim.F.if_nil(default_val, {})) ) end return first_non_null(user_defaults[name], config.values[name], default_val) end local function set(name, default_val, description) assert(description, "Config values must always have a description") config.values[name] = get(name, default_val) config.descriptions[name] = strings.dedent(description) end for key, info in pairs(tele_defaults) do set(key, info[1], info[2]) end local M = {} M.get = get return M end function config.clear_defaults() for k, _ in pairs(config.values) do config.values[k] = nil end end config.set_defaults() return config ================================================ FILE: lua/telescope/debounce.lua ================================================ -- Credit: https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201 -- local M = {} ---Validates args for `throttle()` and `debounce()`. ---TODO(clason): remove shim when dropping support for Nvim 0.10 local td_validate = vim.fn.has "nvim-0.11" == 1 and function(fn, ms) vim.validate("fn", fn, "function") vim.validate("ms", ms, function(v) return type(v) == "number" and v > 0 end, "number > 0") end or function(fn, ms) vim.validate { fn = { fn, "f" }, ms = { ms, function(v) return type(v) == "number" and v > 0 end, "number > 0", }, } end --- Throttles a function on the leading edge. Automatically `schedule_wrap()`s. ---@param fn fun(...) Function to throttle ---@param ms number Timeout in ms ---@return fun(...) wrapped_fn Throttled function ---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory! function M.throttle_leading(fn, ms) td_validate(fn, ms) local timer = vim.uv.new_timer() local running = false local function wrapped_fn(...) if not running then timer:start(ms, 0, function() running = false end) running = true pcall(vim.schedule_wrap(fn), select(1, ...)) end end return wrapped_fn, timer end --- Throttles a function on the trailing edge. Automatically `schedule_wrap()`s. ---@param fn fun(...) Function to throttle ---@param ms number Timeout in ms ---@param last? boolean Whether to use the arguments of the last call to `fn` within the timeframe. --- Default: Use arguments of the first call. ---@return fun(...) wrapped_fn Throttled function ---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory! function M.throttle_trailing(fn, ms, last) td_validate(fn, ms) local timer = vim.uv.new_timer() local running = false local wrapped_fn if not last then function wrapped_fn(...) if not running then local argv = { ... } local argc = select("#", ...) timer:start(ms, 0, function() running = false pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) running = true end end else local argv, argc function wrapped_fn(...) argv = { ... } argc = select("#", ...) if not running then timer:start(ms, 0, function() running = false pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) running = true end end end return wrapped_fn, timer end --- Debounces a function on the leading edge. Automatically `schedule_wrap()`s. ---@param fn fun(...) Function to debounce ---@param ms number Timeout in ms ---@return fun(...) wrapped_fn Debounced function ---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory! function M.debounce_leading(fn, ms) td_validate(fn, ms) local timer = vim.uv.new_timer() local running = false local function wrapped_fn(...) timer:start(ms, 0, function() running = false end) if not running then running = true pcall(vim.schedule_wrap(fn), select(1, ...)) end end return wrapped_fn, timer end --- Debounces a function on the trailing edge. Automatically `schedule_wrap()`s. ---@param fn fun(...) Function to debounce ---@param ms number Timeout in ms ---@param first? boolean Whether to use the arguments of the first call to `fn` within the timeframe. --- Default: Use arguments of the last call. ---@return fun(...) wrapped_fn Debounced function ---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory! function M.debounce_trailing(fn, ms, first) td_validate(fn, ms) local timer = vim.uv.new_timer() local wrapped_fn if not first then function wrapped_fn(...) local argv = { ... } local argc = select("#", ...) timer:start(ms, 0, function() pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) end else local argv, argc function wrapped_fn(...) argv = argv or { ... } argc = argc or select("#", ...) timer:start(ms, 0, function() pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) end end return wrapped_fn, timer end --- Test deferment methods (`{throttle,debounce}_{leading,trailing}()`). ---@param bouncer string Bouncer function to test ---@param ms? number Timeout in ms, default 2000. ---@param firstlast? boolean Whether to use the 'other' fn call strategy. function M.test_defer(bouncer, ms, firstlast) local bouncers = { tl = M.throttle_leading, tt = M.throttle_trailing, dl = M.debounce_leading, dt = M.debounce_trailing, } local timeout = ms or 2000 local bounced = bouncers[bouncer](function(i) vim.cmd('echom "' .. bouncer .. ": " .. i .. '"') end, timeout, firstlast) for i, _ in ipairs { 1, 2, 3, 4, 5 } do bounced(i) vim.schedule(function() vim.cmd("echom " .. i) end) vim.fn.call("wait", { 1000, "v:false" }) end end return M ================================================ FILE: lua/telescope/entry_manager.lua ================================================ local log = require "telescope.log" local LinkedList = require "telescope.algos.linked_list" local EntryManager = {} EntryManager.__index = EntryManager function EntryManager:new(max_results, set_entry, info) log.trace "Creating entry_manager..." info = info or {} info.looped = 0 info.inserted = 0 info.find_loop = 0 -- state contains list of -- { entry, score } -- Stored directly in a table, accessed as [1], [2] set_entry = set_entry or function() end return setmetatable({ linked_states = LinkedList:new { track_at = max_results }, info = info, max_results = max_results, set_entry = set_entry, worst_acceptable_score = math.huge, }, self) end function EntryManager:num_results() return self.linked_states.size end function EntryManager:get_container(index) local count = 0 for val in self.linked_states:iter() do count = count + 1 if count == index then return val end end return {} end function EntryManager:get_entry(index) return self:get_container(index)[1] end function EntryManager:get_score(index) return self:get_container(index)[2] end function EntryManager:get_ordinal(index) return self:get_entry(index).ordinal end function EntryManager:find_entry(entry) local info = self.info local count = 0 for container in self.linked_states:iter() do count = count + 1 if container[1] == entry then info.find_loop = info.find_loop + count return count end end info.find_loop = info.find_loop + count return nil end function EntryManager:_update_score_from_tracked() local linked = self.linked_states if linked.tracked then self.worst_acceptable_score = math.min(self.worst_acceptable_score, linked.tracked[2]) end end function EntryManager:_insert_container_before(picker, index, linked_node, new_container) self.linked_states:place_before(index, linked_node, new_container) self.set_entry(picker, index, new_container[1], new_container[2], true) self:_update_score_from_tracked() end function EntryManager:_insert_container_after(picker, index, linked_node, new_container) self.linked_states:place_after(index, linked_node, new_container) self.set_entry(picker, index, new_container[1], new_container[2], true) self:_update_score_from_tracked() end function EntryManager:_append_container(picker, new_container, should_update) self.linked_states:append(new_container) self.worst_acceptable_score = math.min(self.worst_acceptable_score, new_container[2]) if should_update then self.set_entry(picker, self.linked_states.size, new_container[1], new_container[2]) end end function EntryManager:add_entry(picker, score, entry, prompt) score = score or 0 local max_res = self.max_results local worst_score = self.worst_acceptable_score local size = self.linked_states.size local info = self.info info.maxed = info.maxed or 0 local new_container = { entry, score } -- Short circuit for bad scores -- they never need to be displayed. -- Just save them and we'll deal with them later. if score >= worst_score then return self.linked_states:append(new_container) end -- Short circuit for first entry. if size == 0 then self.linked_states:prepend(new_container) self.set_entry(picker, 1, entry, score) return end for index, container, node in self.linked_states:ipairs() do info.looped = info.looped + 1 if container[2] > score then return self:_insert_container_before(picker, index, node, new_container) end if score < 1 and container[2] == score and picker.tiebreak(entry, container[1], prompt) then return self:_insert_container_before(picker, index, node, new_container) end -- Don't add results that are too bad. if index >= max_res then info.maxed = info.maxed + 1 return self:_append_container(picker, new_container, false) end end if self.linked_states.size >= max_res then self.worst_acceptable_score = math.min(self.worst_acceptable_score, score) end return self:_insert_container_after(picker, size + 1, self.linked_states.tail, new_container) end function EntryManager:iter() local iterator = self.linked_states:iter() return function() local val = iterator() if val then return val[1] end end end return EntryManager ================================================ FILE: lua/telescope/finders/async_job_finder.lua ================================================ local async_job = require "telescope._" local LinesPipe = require("telescope._").LinesPipe local make_entry = require "telescope.make_entry" local log = require "telescope.log" return function(opts) log.trace("Creating async_job:", opts) local entry_maker = opts.entry_maker or make_entry.gen_from_string(opts) local fn_command = function(prompt) local command_list = opts.command_generator(prompt) if command_list == nil then return nil end local command = table.remove(command_list, 1) local res = { command = command, args = command_list, } return res end local job local callable = function(_, prompt, process_result, process_complete) if job then job:close(true) end local job_opts = fn_command(prompt) if not job_opts then process_complete() return end local writer = nil -- if job_opts.writer and Job.is_job(job_opts.writer) then -- writer = job_opts.writer if opts.writer then error "async_job_finder.writer is not yet implemented" writer = async_job.writer(opts.writer) end local stdout = LinesPipe() job = async_job.spawn { command = job_opts.command, args = job_opts.args, cwd = job_opts.cwd or opts.cwd, env = job_opts.env or opts.env, writer = writer, stdout = stdout, } local line_num = 0 for line in stdout:iter(true) do line_num = line_num + 1 local entry = entry_maker(line) if entry then entry.index = line_num end if process_result(entry) then return end end process_complete() end return setmetatable({ close = function() if job then job:close(true) end end, }, { __call = callable, }) end ================================================ FILE: lua/telescope/finders/async_oneshot_finder.lua ================================================ local async = require "plenary.async" local async_job = require "telescope._" local LinesPipe = require("telescope._").LinesPipe local make_entry = require "telescope.make_entry" local await_count = 1000 return function(opts) opts = opts or {} local entry_maker = opts.entry_maker or make_entry.gen_from_string(opts) local cwd = opts.cwd local env = opts.env local fn_command = assert(opts.fn_command, "Must pass `fn_command`") local results = vim.F.if_nil(opts.results, {}) local num_results = #results local job_started = false local job_completed = false local stdout = nil local job return setmetatable({ close = function() if job then job:close() end end, results = results, entry_maker = entry_maker, }, { __call = function(_, prompt, process_result, process_complete) if not job_started then local job_opts = fn_command() -- TODO: Handle writers. -- local writer -- if job_opts.writer and Job.is_job(job_opts.writer) then -- writer = job_opts.writer -- elseif job_opts.writer then -- writer = Job:new(job_opts.writer) -- end stdout = LinesPipe() job = async_job.spawn { command = job_opts.command, args = job_opts.args, cwd = cwd, env = env, stdout = stdout, } job_started = true end if not job_completed then if not vim.tbl_isempty(results) then for _, v in ipairs(results) do process_result(v) end end for line in stdout:iter(false, { split_char = opts.split_char }) do num_results = num_results + 1 if num_results % await_count then async.util.scheduler() end local entry = entry_maker(line) if entry then entry.index = num_results end results[num_results] = entry process_result(entry) end process_complete() job_completed = true return end local current_count = num_results for index = 1, current_count do -- TODO: Figure out scheduling... if index % await_count then async.util.scheduler() end if process_result(results[index]) then break end end if job_completed then process_complete() end end, }) end ================================================ FILE: lua/telescope/finders/async_static_finder.lua ================================================ local scheduler = require("plenary.async").util.scheduler local make_entry = require "telescope.make_entry" return function(opts) local input_results if vim.islist(opts) then input_results = opts else input_results = opts.results end local entry_maker = opts.entry_maker or make_entry.gen_from_string(opts) local results = {} for k, v in ipairs(input_results) do local entry = entry_maker(v) if entry then entry.index = k table.insert(results, entry) end end return setmetatable({ results = results, entry_maker = entry_maker, close = function() end, }, { __call = function(_, _, process_result, process_complete) for i, v in ipairs(results) do if process_result(v) then break end if i % 1000 == 0 then scheduler() end end process_complete() end, }) end ================================================ FILE: lua/telescope/finders.lua ================================================ local Job = require "plenary.job" local make_entry = require "telescope.make_entry" local log = require "telescope.log" local async_static_finder = require "telescope.finders.async_static_finder" local async_oneshot_finder = require "telescope.finders.async_oneshot_finder" local async_job_finder = require "telescope.finders.async_job_finder" local finders = {} local _callable_obj = function() local obj = {} obj.__index = obj obj.__call = function(t, ...) return t:_find(...) end obj.close = function() end return obj end --[[ ============================================================= JobFinder Uses an external Job to get results. Processes results as they arrive. For more information about how Jobs are implemented, checkout 'plenary.job' -- ============================================================= ]] local JobFinder = _callable_obj() --- Create a new finder command --- ---@param opts table Keys: -- fn_command function The function to call function JobFinder:new(opts) opts = opts or {} assert(not opts.results, "`results` should be used with finder.new_table") assert(not opts.static, "`static` should be used with finder.new_oneshot_job") local obj = setmetatable({ entry_maker = opts.entry_maker or make_entry.gen_from_string(opts), fn_command = opts.fn_command, cwd = opts.cwd, writer = opts.writer, -- Maximum number of results to process. -- Particularly useful for live updating large queries. maximum_results = opts.maximum_results, }, self) return obj end function JobFinder:_find(prompt, process_result, process_complete) log.trace "Finding..." if self.job and not self.job.is_shutdown then log.debug "Shutting down old job" self.job:shutdown() end local line_num = 0 local on_output = function(_, line, _) line_num = line_num + 1 if not line or line == "" then return end local entry if self.entry_maker then entry = self.entry_maker(line) if entry then entry.index = line_num end else entry = line end process_result(entry) end local opts = self:fn_command(prompt) if not opts then process_complete() return end local writer = nil if opts.writer and Job.is_job(opts.writer) then writer = opts.writer elseif opts.writer then writer = Job:new(opts.writer) end self.job = Job:new { command = opts.command, args = opts.args, cwd = opts.cwd or self.cwd, maximum_results = self.maximum_results, writer = writer, enable_recording = false, on_stdout = on_output, -- on_stderr = on_output, on_exit = function() process_complete() end, } self.job:start() end local DynamicFinder = _callable_obj() function DynamicFinder:new(opts) opts = opts or {} assert(not opts.results, "`results` should be used with finder.new_table") assert(not opts.static, "`static` should be used with finder.new_oneshot_job") local obj = setmetatable({ curr_buf = opts.curr_buf, fn = opts.fn, entry_maker = opts.entry_maker or make_entry.gen_from_string(opts), }, self) return obj end function DynamicFinder:_find(prompt, process_result, process_complete) local results = self.fn(prompt) local result_num = 0 for _, result in ipairs(results) do result_num = result_num + 1 local entry = self.entry_maker(result) if entry then entry.index = result_num end if process_result(entry) then return end end process_complete() end --- Return a new Finder -- -- Use at your own risk. -- This opts dictionary is likely to change, but you are welcome to use it right now. -- I will try not to change it needlessly, but I will change it sometimes and I won't feel bad. finders._new = function(opts) assert(not opts.results, "finder.new is deprecated with `results`. You should use `finder.new_table`") return JobFinder:new(opts) end finders.new_async_job = function(opts) if opts.writer then return finders._new(opts) end return async_job_finder(opts) end finders.new_job = function(command_generator, entry_maker, _, cwd) return async_job_finder { command_generator = command_generator, entry_maker = entry_maker, cwd = cwd, } end --- One shot job ---@param command_list string[]: Command list to execute. ---@param opts table: stuff -- @key entry_maker function Optional: function(line: string) => table -- @key cwd string finders.new_oneshot_job = function(command_list, opts) opts = opts or {} assert(not opts.results, "`results` should be used with finder.new_table") command_list = vim.deepcopy(command_list) local command = table.remove(command_list, 1) return async_oneshot_finder { entry_maker = opts.entry_maker or make_entry.gen_from_string(opts), cwd = opts.cwd, maximum_results = opts.maximum_results, split_char = opts.split_char, fn_command = function() return { command = command, args = command_list, } end, } end --- Used to create a finder for a Lua table. -- If you only pass a table of results, then it will use that as the entries. -- -- If you pass a table, and then a function, it's used as: -- results table, the results to run on -- entry_maker function, the function to convert results to entries. finders.new_table = function(t) return async_static_finder(t) end --- Used to create a finder from a function. -- ---@param opts table: stuff -- @key fn function() => list[string] -- @key entry_maker function Optional: function(line: string) => table finders.new_dynamic = function(opts) return DynamicFinder:new(opts) end return finders ================================================ FILE: lua/telescope/from_entry.lua ================================================ --[[ ============================================================================= Get metadata from entries. This file is still WIP, so expect some changes if you're trying to consume these APIs. This will provide standard mechanism for accessing information from an entry. --============================================================================= ]] local utils = require "telescope.utils" local from_entry = {} function from_entry.path(entry, validate, escape) escape = vim.F.if_nil(escape, true) local path = entry.path if path == nil then path = entry.filename end if path == nil then path = entry.value end if path == nil then require("telescope.log").error(string.format("Invalid Entry: '%s'", vim.inspect(entry))) return end -- only 0 if neither filereadable nor directory if validate and path ~= "[No Name]" then -- We need to expand for filereadable and isdirectory -- TODO(conni2461): we are not going to return the expanded path because -- this would lead to cache misses in the perviewer. -- Requires overall refactoring in previewer interface local expanded = utils.path_expand(path) if (vim.fn.filereadable(expanded) + vim.fn.isdirectory(expanded)) == 0 then return end end if escape then return vim.fn.fnameescape(path) end return path end return from_entry ================================================ FILE: lua/telescope/health.lua ================================================ local health = vim.health local extension_module = require "telescope._extensions" local extension_info = require("telescope").extensions local is_win = vim.api.nvim_call_function("has", { "win32" }) == 1 local optional_dependencies = { { finder_name = "live-grep", package = { { name = "rg", url = "[BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep)", optional = false, }, }, }, { finder_name = "find-files", package = { { name = "fd", binaries = { "fdfind", "fd" }, url = "[sharkdp/fd](https://github.com/sharkdp/fd)", optional = true, }, }, }, } local required_plugins = { { lib = "plenary", optional = false }, } local check_binary_installed = function(package) local binaries = package.binaries or { package.name } for _, binary in ipairs(binaries) do local found = vim.fn.executable(binary) == 1 if not found and is_win then binary = binary .. ".exe" found = vim.fn.executable(binary) == 1 end if found then local handle = io.popen(binary .. " --version") local binary_version = handle:read "*a" handle:close() return true, binary_version end end end local function lualib_installed(lib_name) local res, _ = pcall(require, lib_name) return res end local M = {} M.check = function() -- Required lua libs health.start "Checking for required plugins" for _, plugin in ipairs(required_plugins) do if lualib_installed(plugin.lib) then health.ok(plugin.lib .. " installed.") else local lib_not_installed = plugin.lib .. " not found." if plugin.optional then health.warn(("%s %s"):format(lib_not_installed, plugin.info)) else health.error(lib_not_installed) end end end -- external dependencies -- TODO: only perform checks if user has enabled dependency in their config health.start "Checking external dependencies" for _, opt_dep in pairs(optional_dependencies) do for _, package in ipairs(opt_dep.package) do local installed, version = check_binary_installed(package) if not installed then local err_msg = ("%s: not found."):format(package.name) if package.optional then health.warn(("%s %s"):format(err_msg, ("Install %s for extended capabilities"):format(package.url))) else health.error( ("%s %s"):format( err_msg, ("`%s` finder will not function without %s installed."):format(opt_dep.finder_name, package.url) ) ) end else local eol = version:find "\n" local ver = eol and version:sub(0, eol - 1) or "(unknown version)" health.ok(("%s: found %s"):format(package.name, ver)) end end end -- Extensions health.start "===== Installed extensions =====" local installed = {} for extension_name, _ in pairs(extension_info) do installed[#installed + 1] = extension_name end table.sort(installed) for _, installed_ext in ipairs(installed) do local extension_healthcheck = extension_module._health[installed_ext] health.start(string.format("Telescope Extension: `%s`", installed_ext)) if extension_healthcheck then extension_healthcheck() else health.info "No healthcheck provided" end end end return M ================================================ FILE: lua/telescope/init.lua ================================================ local _extensions = require "telescope._extensions" local telescope = {} -- TODO(conni2461): also table of contents for tree-sitter-lua -- TODO: Add pre to the works -- ---@pre [[ -- ---@pre ]] ---@brief [[ --- Telescope.nvim is a plugin for fuzzy finding and neovim. It helps you search, --- filter, find and pick things in Lua. --- --- Getting started with telescope: --- 1. Run `:checkhealth telescope` to make sure everything is installed. --- 2. Evaluate it is working with --- `:Telescope find_files` or --- `:lua require("telescope.builtin").find_files()` --- 3. Put a `require("telescope").setup()` call somewhere in your neovim config. --- 4. Read |telescope.setup| to check what config keys are available and what you can put inside the setup call --- 5. Read |telescope.builtin| to check which builtin pickers are offered and what options these implement --- 6. Profit --- --- The below flow chart illustrates a simplified telescope architecture: ---
--- ┌───────────────────────────────────────────────────────────┐
--- │      ┌────────┐                                           │
--- │      │ Multi  │                                ┌───────+  │
--- │      │ Select │    ┌───────┐                   │ Entry │  │
--- │      └─────┬──*    │ Entry │    ┌────────+     │ Maker │  │
--- │            │   ┌───│Manager│────│ Sorter │┐    └───┬───*  │
--- │            ▼   ▼   └───────*    └────────┘│        │      │
--- │            1────────┐                 2───┴──┐     │      │
--- │      ┌─────│ Picker │                 │Finder│◀────┘      │
--- │      ▼     └───┬────┘                 └──────*            │
--- │ ┌────────┐     │       3────────+         ▲               │
--- │ │Selected│     └───────│ Prompt │─────────┘               │
--- │ │ Entry  │             └───┬────┘                         │
--- │ └────────*             ┌───┴────┐  ┌────────┐  ┌────────┐ │
--- │     │  ▲    4─────────┐│ Prompt │  │(Attach)│  │Actions │ │
--- │     ▼  └──▶ │ Results ││ Buffer │◀─┤Mappings│◀─┤User Fn │ │
--- │5─────────┐  └─────────┘└────────┘  └────────┘  └────────┘ │
--- ││Previewer│                                                │
--- │└─────────┘                   telescope.nvim architecture  │
--- └───────────────────────────────────────────────────────────┘
---
---   + The `Entry Maker` at least defines
---     - value: "raw" result of the finder
---     - ordinal: string to be sorted derived from value
---     - display: line representation of entry in results buffer
---
---   * The finder, entry manager, selected entry, and multi selections
---     comprises `entries` constructed by the `Entry Maker` from
---     raw results of the finder (`value`s)
---
---  Primary components:
---   1 Picker: central UI dedicated to varying use cases
---             (finding files, grepping, diagnostics, etc.)
---             see :h telescope.builtin
---   2 Finder: pipe or interactively generates results to pick over
---   3 Prompt: user input that triggers the finder which sorts results
---             in order into the entry manager
---   4 Results: listed entries scored by sorter from finder results
---   5 Previewer: preview of context of selected entry
---                see :h telescope.previewers
--- 
--- --- A practical introduction into telescope customization is our --- `developers.md` (top-level of repo) and `:h telescope.actions` that --- showcase how to access information about the state of the picker (current --- selection, etc.). ---
--- To find out more:
--- https://github.com/nvim-telescope/telescope.nvim
---
---   :h telescope.setup
---   :h telescope.command
---   :h telescope.builtin
---   :h telescope.themes
---   :h telescope.layout
---   :h telescope.resolve
---   :h telescope.actions
---   :h telescope.actions.state
---   :h telescope.actions.set
---   :h telescope.actions.utils
---   :h telescope.actions.generate
---   :h telescope.actions.history
---   :h telescope.previewers
--- 
---@brief ]] ---@tag telescope.nvim ---@config { ["name"] = "INTRODUCTION" } --- Setup function to be run by user. Configures the defaults, pickers and --- extensions of telescope. --- --- Usage: --- --- require('telescope').setup{ --- defaults = { --- -- Default configuration for telescope goes here: --- -- config_key = value, --- -- .. --- }, --- 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 --- } --- } --- ---@param opts table: Configuration opts. Keys: defaults, pickers, extensions ---@eval { ["description"] = require('telescope').__format_setup_keys() } function telescope.setup(opts) opts = opts or {} if opts.default then error "'default' is not a valid value for setup. See 'defaults'" end require("telescope.config").set_defaults(opts.defaults) require("telescope.config").set_pickers(opts.pickers) _extensions.set_config(opts.extensions) end --- Load an extension. --- - Notes: --- - Loading triggers ext setup via the config passed in |telescope.setup| ---@param name string: Name of the extension function telescope.load_extension(name) return _extensions.load(name) end --- Register an extension. To be used by plugin authors. ---@param mod table: Module function telescope.register_extension(mod) return _extensions.register(mod) end --- Use telescope.extensions to reference any extensions within your configuration.
--- While the docs currently generate this as a function, it's actually a table. Sorry. telescope.extensions = require("telescope._extensions").manager telescope.__format_setup_keys = function() local names = require("telescope.config").descriptions_order local descriptions = require("telescope.config").descriptions local result = { "
", "", "Valid keys for {opts.defaults}" }
  for _, name in ipairs(names) do
    local desc = descriptions[name]

    table.insert(result, "")
    table.insert(result, string.format("%s*telescope.defaults.%s*", string.rep(" ", 70 - 20 - #name), name))
    table.insert(result, string.format("%s: ~", name))
    for _, line in ipairs(vim.split(desc, "\n")) do
      table.insert(result, string.format("    %s", line))
    end
  end

  table.insert(result, "
") return result end return telescope ================================================ FILE: lua/telescope/log.lua ================================================ return require("plenary.log").new { plugin = "telescope", level = "info", } ================================================ FILE: lua/telescope/make_entry.lua ================================================ ---@tag telescope.make_entry ---@brief [[ --- --- Each picker has a finder made up of two parts, the results which are the --- data to be displayed, and the entry_maker. These entry_makers are functions --- returned from make_entry functions. These will be referred to as --- entry_makers in the following documentation. --- --- Every entry maker returns a function that accepts the data to be used for --- an entry. This function will return an entry table (or nil, meaning skip --- this entry) which contains the following important keys: --- - value any: value key can be anything but still required --- - valid bool (optional): is an optional key because it defaults to true but if the key --- is set to false it will not be displayed by the picker --- - ordinal string: is the text that is used for filtering --- - display string|function: is either a string of the text that is being --- displayed or a function receiving the entry at a later stage, when the entry --- is actually being displayed. A function can be useful here if a complex --- calculation has to be done. `make_entry` can also return a second value - --- a highlight array which will then apply to the line. Highlight entry in --- this array has the following signature `{ { start_col, end_col }, hl_group }` --- - filename string (optional): will be interpreted by the default `` action as --- open this file --- - bufnr number (optional): will be interpreted by the default `` action as open --- this buffer --- - lnum number (optional): lnum value which will be interpreted by the default `` --- action as a jump to this line --- - col number (optional): col value which will be interpreted by the default `` --- action as a jump to this column --- --- For more information on easier displaying, see |telescope.pickers.entry_display| --- --- TODO: Document something we call `entry_index` ---@brief ]] local api = vim.api local entry_display = require "telescope.pickers.entry_display" local utils = require "telescope.utils" local strings = require "plenary.strings" local Path = require "plenary.path" local treesitter_type_highlight = { ["associated"] = "TSConstant", ["constant"] = "TSConstant", ["field"] = "TSField", ["function"] = "TSFunction", ["method"] = "TSMethod", ["parameter"] = "TSParameter", ["property"] = "TSProperty", ["struct"] = "Struct", ["var"] = "TSVariableBuiltin", } local lsp_type_highlight = { ["Class"] = "TelescopeResultsClass", ["Constant"] = "TelescopeResultsConstant", ["Field"] = "TelescopeResultsField", ["Function"] = "TelescopeResultsFunction", ["Method"] = "TelescopeResultsMethod", ["Property"] = "TelescopeResultsOperator", ["Struct"] = "TelescopeResultsStruct", ["Variable"] = "TelescopeResultsVariable", } local get_filename_fn = function() local bufnr_name_cache = {} return function(bufnr) bufnr = vim.F.if_nil(bufnr, 0) local c = bufnr_name_cache[bufnr] if c then return c end local n = api.nvim_buf_get_name(bufnr) bufnr_name_cache[bufnr] = n return n end end local handle_entry_index = function(opts, t, k) local override = ((opts or {}).entry_index or {})[k] if not override then return end local val, save = override(t, opts) if save then rawset(t, k, val) end return val end local make_entry = {} make_entry.set_default_entry_mt = function(tbl, opts) return setmetatable({}, { __index = function(t, k) local override = handle_entry_index(opts, t, k) if override then return override end -- Only hit tbl once local val = tbl[k] if val then rawset(t, k, val) end return val end, }) end do local lookup_keys = { display = 1, ordinal = 1, value = 1, } function make_entry.gen_from_string(opts) local mt_string_entry = { __index = function(t, k) local override = handle_entry_index(opts, t, k) if override then return override end return rawget(t, rawget(lookup_keys, k)) end, } return function(line) return setmetatable({ line, }, mt_string_entry) end end end do local lookup_keys = { ordinal = 1, value = 1, filename = 1, cwd = 2, } function make_entry.gen_from_file(opts) opts = opts or {} local cwd = utils.path_expand(opts.cwd or vim.uv.cwd()) local disable_devicons = opts.disable_devicons local mt_file_entry = {} mt_file_entry.cwd = cwd mt_file_entry.display = function(entry) local hl_group, icon local display, path_style = utils.transform_path(opts, entry.value) display, hl_group, icon = utils.transform_devicons(entry.value, display, disable_devicons) if hl_group then local style = { { { 0, #icon + 1 }, hl_group } } style = utils.merge_styles(style, path_style, #icon + 1) return display, style else return display, path_style end end mt_file_entry.__index = function(t, k) local override = handle_entry_index(opts, t, k) if override then return override end local raw = rawget(mt_file_entry, k) if raw then return raw end if k == "path" then local retpath = Path:new({ t.cwd, t.value }):absolute() if not vim.uv.fs_access(retpath, "R") then retpath = t.value end return retpath end return rawget(t, rawget(lookup_keys, k)) end if opts.file_entry_encoding then return function(line) line = vim.iconv(line, opts.file_entry_encoding, "utf8") return setmetatable({ line }, mt_file_entry) end else return function(line) return setmetatable({ line }, mt_file_entry) end end end end do local lookup_keys = { value = 1, ordinal = 1, } -- Gets called only once to parse everything out for the vimgrep, after that looks up directly. local parse_with_col = function(t) local _, _, filename, lnum, col, text = string.find(t.value, [[(..-):(%d+):(%d+):(.*)]]) local ok ok, lnum = pcall(tonumber, lnum) if not ok then lnum = nil end ok, col = pcall(tonumber, col) if not ok then col = nil end t.filename = filename t.lnum = lnum t.col = col t.text = text return { filename, lnum, col, text } end local parse_without_col = function(t) local _, _, filename, lnum, text = string.find(t.value, [[(..-):(%d+):(.*)]]) local ok ok, lnum = pcall(tonumber, lnum) if not ok then lnum = nil end t.filename = filename t.lnum = lnum t.col = nil t.text = text return { filename, lnum, nil, text } end local parse_only_filename = function(t) t.filename = t.value t.lnum = nil t.col = nil t.text = "" return { t.filename, nil, nil, "" } end function make_entry.gen_from_vimgrep(opts) opts = opts or {} local mt_vimgrep_entry local parse = parse_with_col if opts.__matches == true then parse = parse_only_filename elseif opts.__inverted == true then parse = parse_without_col end local disable_devicons = opts.disable_devicons local disable_coordinates = opts.disable_coordinates local only_sort_text = opts.only_sort_text local execute_keys = { path = function(t) if Path:new(t.filename):is_absolute() then return t.filename, false else return Path:new({ t.cwd, t.filename }):absolute(), false end end, filename = function(t) return parse(t)[1], true end, lnum = function(t) return parse(t)[2], true end, col = function(t) return parse(t)[3], true end, text = function(t) return parse(t)[4], true end, } -- For text search only, the ordinal value is actually the text. if only_sort_text then execute_keys.ordinal = function(t) return t.text end end local display_string = "%s%s%s" mt_vimgrep_entry = { cwd = utils.path_expand(opts.cwd or vim.uv.cwd()), display = function(entry) local display_filename, path_style = utils.transform_path(opts, entry.filename) local coordinates = ":" if not disable_coordinates then if entry.lnum then if entry.col then coordinates = string.format(":%s:%s:", entry.lnum, entry.col) else coordinates = string.format(":%s:", entry.lnum) end end end local display, hl_group, icon = utils.transform_devicons( entry.filename, string.format(display_string, display_filename, coordinates, entry.text), disable_devicons ) if hl_group then local style = { { { 0, #icon }, hl_group } } style = utils.merge_styles(style, path_style, #icon + 1) return display, style else return display, path_style end end, __index = function(t, k) local override = handle_entry_index(opts, t, k) if override then return override end local raw = rawget(mt_vimgrep_entry, k) if raw then return raw end local executor = rawget(execute_keys, k) if executor then local val, save = executor(t) if save then rawset(t, k, val) end return val end return rawget(t, rawget(lookup_keys, k)) end, } return function(line) return setmetatable({ line }, mt_vimgrep_entry) end end end function make_entry.gen_from_git_stash(opts) local displayer = entry_display.create { separator = " ", items = { { width = 10 }, opts.show_branch and { width = 15 } or "", { remaining = true }, }, } local make_display = function(entry) return displayer { { entry.value, "TelescopeResultsLineNr" }, opts.show_branch and { entry.branch_name, "TelescopeResultsIdentifier" } or "", entry.commit_info, } end return function(entry) if entry == "" then return nil end local splitted = utils.max_split(entry, ": ", 2) local stash_idx = splitted[1] local _, branch_name = string.match(splitted[2], "^([WIP on|On]+) (.+)") local commit_info = splitted[3] return make_entry.set_default_entry_mt({ value = stash_idx, ordinal = commit_info, branch_name = branch_name, commit_info = commit_info, display = make_display, }, opts) end end function make_entry.gen_from_git_commits(opts) opts = opts or {} local displayer = entry_display.create { separator = " ", items = { { width = 8 }, { remaining = true }, }, } local make_display = function(entry) return displayer { { entry.value, "TelescopeResultsIdentifier" }, entry.msg, } end return function(entry) if entry == "" then return nil end local sha, msg = string.match(entry, "([^ ]+) (.+)") if not msg then sha = entry msg = "" end return make_entry.set_default_entry_mt({ value = sha, ordinal = sha .. " " .. msg, msg = msg, display = make_display, current_file = opts.current_file, }, opts) end end function make_entry.gen_from_quickfix(opts) opts = opts or {} local show_line = vim.F.if_nil(opts.show_line, true) local hidden = utils.is_path_hidden(opts) local make_display = function(entry) local display_filename, path_style = utils.transform_path(opts, entry.filename) local display_string = string.format("%s:%d:%d", display_filename, entry.lnum, entry.col) if hidden then display_string = string.format("%4d:%2d", entry.lnum, entry.col) end if show_line then local text = entry.text if opts.trim_text then text = vim.trim(text) end text = text:gsub(".* | ", "") display_string = display_string .. ":" .. text end return display_string, path_style end local get_filename = get_filename_fn() return function(entry) local filename = vim.F.if_nil(entry.filename, get_filename(entry.bufnr)) return make_entry.set_default_entry_mt({ value = entry, ordinal = (not hidden and filename or "") .. " " .. entry.text, display = make_display, bufnr = entry.bufnr, filename = filename, lnum = entry.lnum, col = entry.col, text = entry.text, start = entry.start, finish = entry.finish, }, opts) end end function make_entry.gen_from_lsp_symbols(opts) opts = opts or {} local bufnr = opts.bufnr or api.nvim_get_current_buf() -- Default we have two columns, symbol and type(unbound) -- If path is not hidden then its, filepath, symbol and type(still unbound) -- If show_line is also set, type is bound to len 8 local display_items = { { width = opts.symbol_width or 25 }, { remaining = true }, } local hidden = utils.is_path_hidden(opts) if not hidden then table.insert(display_items, 1, { width = vim.F.if_nil(opts.fname_width, 30) }) end if opts.show_line then -- bound type to len 8 or custom table.insert(display_items, #display_items, { width = opts.symbol_type_width or 8 }) end local displayer = entry_display.create { separator = " ", hl_chars = { ["["] = "TelescopeBorder", ["]"] = "TelescopeBorder" }, items = display_items, } local type_highlight = vim.F.if_nil(opts.symbol_highlights or lsp_type_highlight) local make_display = function(entry) local msg if opts.show_line then msg = vim.trim(vim.F.if_nil(api.nvim_buf_get_lines(bufnr, entry.lnum - 1, entry.lnum, false)[1], "")) end if hidden then return displayer { entry.symbol_name, { entry.symbol_type:lower(), type_highlight[entry.symbol_type] }, msg, } else local display_path, path_style = utils.transform_path(opts, entry.filename) return displayer { { display_path, function() return path_style end, }, entry.symbol_name, { entry.symbol_type:lower(), type_highlight[entry.symbol_type] }, msg, } end end local get_filename = get_filename_fn() return function(entry) local filename = vim.F.if_nil(entry.filename, get_filename(entry.bufnr)) local symbol_msg = entry.text local symbol_type, symbol_name = symbol_msg:match "%[(.+)%]%s+(.*)" local ordinal = "" if not hidden and filename then ordinal = filename .. " " end ordinal = ordinal .. symbol_name .. " " .. (symbol_type or "unknown") return make_entry.set_default_entry_mt({ value = entry, ordinal = ordinal, display = make_display, filename = filename, lnum = entry.lnum, col = entry.col, symbol_name = symbol_name, symbol_type = symbol_type, start = entry.start, finish = entry.finish, }, opts) end end function make_entry.gen_from_buffer(opts) opts = opts or {} local disable_devicons = opts.disable_devicons local icon_width = 0 if not disable_devicons then local icon, _ = utils.get_devicons("fname", disable_devicons) icon_width = strings.strdisplaywidth(icon) end local displayer = entry_display.create { separator = " ", items = { { width = opts.bufnr_width }, { width = 4 }, { width = icon_width }, { remaining = true }, }, } local cwd = utils.path_expand(opts.cwd or vim.uv.cwd()) local make_display = function(entry) -- bufnr_width + modes + icon + 3 spaces + : + lnum opts.__prefix = opts.bufnr_width + 4 + icon_width + 3 + 1 + #tostring(entry.lnum) local display_bufname, path_style = utils.transform_path(opts, entry.filename) local icon, hl_group = utils.get_devicons(entry.filename, disable_devicons) return displayer { { entry.bufnr, "TelescopeResultsNumber" }, { entry.indicator, "TelescopeResultsComment" }, { icon, hl_group }, { display_bufname .. ":" .. entry.lnum, function() return path_style end, }, } end return function(entry) local filename = entry.info.name ~= "" and entry.info.name or nil local bufname = filename and Path:new(filename):normalize(cwd) or "[No Name]" local hidden = entry.info.hidden == 1 and "h" or "a" local readonly = vim.bo[entry.bufnr].readonly and "=" or " " local changed = entry.info.changed == 1 and "+" or " " local indicator = entry.flag .. hidden .. readonly .. changed local lnum = 0 -- account for potentially stale lnum as getbufinfo might not be updated or from resuming buffers picker if entry.info.lnum ~= 0 then -- but make sure the buffer is loaded, otherwise line_count is 0 if api.nvim_buf_is_loaded(entry.bufnr) then local line_count = api.nvim_buf_line_count(entry.bufnr) lnum = math.max(math.min(entry.info.lnum, line_count), 1) else lnum = entry.info.lnum end end return make_entry.set_default_entry_mt({ value = bufname, ordinal = entry.bufnr .. " : " .. bufname, display = make_display, bufnr = entry.bufnr, path = filename, filename = bufname, lnum = lnum, indicator = indicator, }, opts) end end function make_entry.gen_from_treesitter(opts) opts = opts or {} local bufnr = opts.bufnr or api.nvim_get_current_buf() local display_items = { { width = opts.symbol_width or 25 }, { width = 10 }, { remaining = true }, } if opts.show_line then table.insert(display_items, 2, { width = 6 }) end local displayer = entry_display.create { separator = " ", items = display_items, } local type_highlight = opts.symbol_highlights or treesitter_type_highlight local make_display = function(entry) local msg = api.nvim_buf_get_lines(bufnr, entry.lnum, entry.lnum, false)[1] or "" msg = vim.trim(msg) local display_columns = { entry.text, { entry.kind, type_highlight[entry.kind], type_highlight[entry.kind] }, msg, } if opts.show_line then table.insert(display_columns, 2, { entry.lnum .. ":" .. entry.col, "TelescopeResultsLineNr" }) end return displayer(display_columns) end local get_filename = get_filename_fn() return function(entry) local start_row, start_col, end_row, _ = vim.treesitter.get_node_range(entry.node) local node_text = vim.treesitter.get_node_text(entry.node, bufnr) return make_entry.set_default_entry_mt({ value = entry.node, kind = entry.kind, ordinal = node_text .. " " .. (entry.kind or "unknown"), display = make_display, node_text = node_text, filename = get_filename(bufnr), -- need to add one since the previewer substacts one lnum = start_row + 1, col = start_col, text = node_text, start = start_row, finish = end_row, }, opts) end end function make_entry.gen_from_packages(opts) opts = opts or {} local make_display = function(module_name) local p_path = package.searchpath(module_name, package.path) or "" local display = string.format("%-" .. opts.column_len .. "s : %s", module_name, vim.fn.fnamemodify(p_path, ":~:.")) return display end return function(module_name) return make_entry.set_default_entry_mt({ valid = module_name ~= "", value = module_name, ordinal = module_name, display = make_display(module_name), }, opts) end end function make_entry.gen_from_apropos(opts) local sections = {} if #opts.sections == 1 and opts.sections[1] == "ALL" then setmetatable(sections, { __index = function() return true end, }) else for _, section in ipairs(opts.sections) do sections[section] = true end end local displayer = entry_display.create { separator = " ", items = { { width = 30 }, { remaining = true }, }, } local make_display = function(entry) return displayer { { entry.keyword, "TelescopeResultsFunction" }, entry.description, } end return function(line) local keyword, cmd, section, desc = line:match "^((.-)%s*%(([^)]+)%).-)%s+%-%s+(.*)$" -- apropos might return alternatives for the cmd which are split on `,` and breaks everything else -- for example on void linux it will return `alacritty, Alacritty` which will later result in -- `man 1 alacritty, Alacritty`. So we just take the first one. -- doing this outside of regex because of obvious reasons cmd = vim.split(cmd, ",")[1] return keyword and sections[section] and make_entry.set_default_entry_mt({ value = cmd, description = desc, ordinal = cmd, display = make_display, section = section, keyword = keyword, }, opts) or nil end end function make_entry.gen_from_marks(opts) return function(item) return make_entry.set_default_entry_mt({ value = item.line, ordinal = item.line, display = item.line, lnum = item.lnum, col = item.col, start = item.lnum, filename = item.filename, }, opts) end end function make_entry.gen_from_registers(opts) local displayer = entry_display.create { separator = " ", hl_chars = { ["["] = "TelescopeBorder", ["]"] = "TelescopeBorder" }, items = { { width = 3 }, { remaining = true }, }, } local make_display = function(entry) local content = entry.content return displayer { { "[" .. entry.value .. "]", "TelescopeResultsNumber" }, type(content) == "string" and content:gsub("\n", "\\n") or content, } end return function(entry) local contents = vim.fn.getreg(entry, 1) return make_entry.set_default_entry_mt({ value = entry, ordinal = string.format("%s %s", entry, contents), content = contents, display = make_display, }, opts) end end function make_entry.gen_from_keymaps(opts) local function get_desc(entry) if entry.callback and not entry.desc then return require("telescope.actions.utils")._get_anon_function_name(debug.getinfo(entry.callback)) end return vim.F.if_nil(entry.desc, entry.rhs):gsub("\n", "\\n") end local function get_lhs(entry) return utils.display_termcodes(entry.lhs) end local function get_attr(entry) local ret = "" if entry.value.noremap ~= 0 then ret = ret .. "*" end if entry.value.buffer ~= 0 then ret = ret .. "@" end return ret end local displayer = require("telescope.pickers.entry_display").create { separator = "▏", items = { { width = 3 }, { width = opts.width_lhs }, { width = 2 }, { remaining = true }, }, } local make_display = function(entry) return displayer { entry.mode, get_lhs(entry), get_attr(entry), get_desc(entry), } end return function(entry) local desc = get_desc(entry) local lhs = get_lhs(entry) return make_entry.set_default_entry_mt({ mode = entry.mode, lhs = lhs, desc = desc, valid = entry ~= "", value = entry, ordinal = entry.mode .. " " .. lhs .. " " .. desc, display = make_display, }, opts) end end function make_entry.gen_from_highlights(opts) local make_display = function(entry) local display = entry.value return display, { { { 0, #display }, display } } end return function(entry) return make_entry.set_default_entry_mt({ value = entry, display = make_display, ordinal = entry, }, opts) end end function make_entry.gen_from_picker(opts) local displayer = entry_display.create { separator = " │ ", items = { { width = 0.5 }, { remaining = true }, }, } local make_display = function(entry) return displayer { entry.value.prompt_title, entry.value.default_text, } end return function(entry) return make_entry.set_default_entry_mt({ value = entry, text = entry.prompt_title, ordinal = string.format("%s %s", entry.prompt_title, vim.F.if_nil(entry.default_text, "")), display = make_display, }, opts) end end function make_entry.gen_from_buffer_lines(opts) local displayer = entry_display.create { separator = " │ ", items = { { width = 5 }, { remaining = true }, }, } local make_display = function(entry) return displayer { { entry.lnum, opts.lnum_highlight_group or "TelescopeResultsSpecialComment" }, { entry.text, function() if not opts.line_highlights then return {} end local line_hl = opts.line_highlights[entry.lnum] or {} -- TODO: We could probably squash these together if the are the same... -- But I don't think that it's worth it at the moment. local result = {} for col, hl in pairs(line_hl) do table.insert(result, { { col, col + 1 }, hl }) end return result end, }, } end return function(entry) if opts.skip_empty_lines and string.match(entry.text, "^$") then return end return make_entry.set_default_entry_mt({ ordinal = entry.text, display = make_display, filename = entry.filename, lnum = entry.lnum, text = entry.text, }, opts) end end function make_entry.gen_from_vimoptions(opts) local displayer = entry_display.create { separator = "", hl_chars = { ["["] = "TelescopeBorder", ["]"] = "TelescopeBorder" }, items = { { width = 25 }, { width = 12 }, { width = 11 }, { remaining = true }, }, } local make_display = function(entry) return displayer { { entry.value.name, "Keyword" }, { "[" .. entry.value.type .. "]", "Type" }, { "[" .. entry.value.scope .. "]", "Identifier" }, utils.display_termcodes(tostring(entry.value.value)), } end return function(o) local entry = { display = make_display, value = { name = o.name, value = o.value, type = o.type, scope = o.scope, }, ordinal = string.format("%s %s %s %s", o.name, o.type, o.scope, utils.display_termcodes(tostring(o.value))), } return make_entry.set_default_entry_mt(entry, opts) end end function make_entry.gen_from_ctags(opts) opts = opts or {} local show_kind = vim.F.if_nil(opts.show_kind, true) local cwd = utils.path_expand(opts.cwd or vim.uv.cwd()) local current_file = Path:new(api.nvim_buf_get_name(opts.bufnr)):normalize(cwd) local display_items = { { width = 16 }, { remaining = true }, } local idx = 1 local hidden = utils.is_path_hidden(opts) if not hidden then table.insert(display_items, idx, { width = vim.F.if_nil(opts.fname_width, 30) }) idx = idx + 1 end if opts.show_line then table.insert(display_items, idx, { width = 30 }) end local displayer = entry_display.create { separator = " │ ", items = display_items, } local make_display = function(entry) local display_path, path_style = utils.transform_path(opts, entry.filename) local scode if opts.show_line then scode = entry.scode end if hidden then return displayer { entry.tag, scode, } else return displayer { { display_path, function() return path_style end, }, entry.tag, entry.kind, scode, } end end local mt = {} mt.__index = function(t, k) local override = handle_entry_index(opts, t, k) if override then return override end if k == "path" then local retpath = Path:new({ t.filename }):absolute() if not vim.uv.fs_access(retpath, "R") then retpath = t.filename end return retpath end end local current_file_cache = {} return function(line) if line == "" or line:sub(1, 1) == "!" then return nil end local tag, file, scode, lnum, extension_fields -- ctags gives us: 'tags\tfile\tsource;"extension_fields' tag, file, scode, extension_fields = string.match(line, '([^\t]+)\t([^\t]+)\t/^?\t?(.*)/;"\t+(.*)') if not tag then -- hasktags gives us: 'tags\tfile\tlnum' tag, file, lnum = string.match(line, "([^\t]+)\t([^\t]+)\t(%d+).*") end local kind = string.match(extension_fields or "", "kind:(%S+)") if Path.path.sep == "\\" then file = string.gsub(file, "/", "\\") end if opts.only_current_file then if current_file_cache[file] == nil then current_file_cache[file] = Path:new(file):normalize(cwd) == current_file end if current_file_cache[file] == false then return nil end end local tag_entry = {} if opts.only_sort_tags then tag_entry.ordinal = tag else tag_entry.ordinal = file .. ": " .. tag end tag_entry.display = make_display tag_entry.scode = scode tag_entry.tag = tag tag_entry.filename = file tag_entry.col = 1 tag_entry.lnum = lnum and tonumber(lnum) or 1 if show_kind then tag_entry.kind = kind end return setmetatable(tag_entry, mt) end end function make_entry.gen_from_diagnostics(opts) opts = opts or {} local diagnostic_config = vim.diagnostic.config() or {} local diagnostic_signs = {} if type(diagnostic_config.signs) == "table" then diagnostic_signs = diagnostic_config.signs.text or {} end local type_diagnostic = vim.diagnostic.severity local signs = (function() if opts.no_sign then return end local signs = {} for num, severity in ipairs(type_diagnostic) do local sign = diagnostic_signs[num] if not sign then local status status, sign = pcall(function() -- only the first char is upper all others are lowercase return vim.trim( vim.fn.sign_getdefined("DiagnosticSign" .. severity:lower():gsub("^%l", string.upper))[1].text ) end) if not status then sign = severity:sub(1, 1) end end signs[severity] = sign end return signs end)() local sign_width if opts.disable_coordinates then sign_width = signs ~= nil and 2 or 0 else sign_width = signs ~= nil and 10 or 8 end local display_items = { { width = sign_width }, { remaining = true }, } local line_width = vim.F.if_nil(opts.line_width, 0.5) local line_width_opts = { width = line_width } if type(line_width) == "string" and line_width == "full" then line_width_opts = {} end local hidden = utils.is_path_hidden(opts) if not hidden then table.insert(display_items, 2, line_width_opts) end local displayer = entry_display.create { separator = "▏", items = display_items, } local make_display = function(entry) local display_path, path_style = utils.transform_path(opts, entry.filename) -- add styling of entries local pos = string.format("%4d:%2d", entry.lnum, entry.col) local line_info_text = signs and signs[entry.type] .. " " or "" local line_info = { opts.disable_coordinates and line_info_text or line_info_text .. pos, "DiagnosticSign" .. entry.type, } return displayer { line_info, entry.text, { display_path, function() return path_style end, }, } end local errlist_type_map = { [type_diagnostic.ERROR] = "E", [type_diagnostic.WARN] = "W", [type_diagnostic.INFO] = "I", [type_diagnostic.HINT] = "N", } return function(entry) return make_entry.set_default_entry_mt({ value = entry, ordinal = ("%s %s"):format(not hidden and entry.filename or "", entry.text), display = make_display, filename = entry.filename, type = entry.type, lnum = entry.lnum, col = entry.col, text = entry.text, qf_type = errlist_type_map[type_diagnostic[entry.type]], }, opts) end end function make_entry.gen_from_autocommands(opts) local displayer = entry_display.create { separator = "▏", items = { { width = 14 }, { width = 18 }, { width = 16 }, { remaining = true }, }, } local make_display = function(entry) return displayer { { entry.value.event, "vimAutoEvent" }, { entry.value.group_name, "vimAugroup" }, { entry.value.pattern, "vimAutoCmdSfxList" }, entry.value.command, } end return function(entry) local group_name = vim.F.if_nil(entry.group_name, "") local command = entry.command if entry.desc and (entry.callback or vim.startswith(command, " --- { --- mode = { ..keys } --- } --- --- --- where {mode} is the one character letter for a mode ('i' for insert, 'n' for normal). --- --- For example: --- --- mappings = { --- i = { --- [""] = require('telescope.actions').close, --- }, --- } --- --- --- To disable a keymap, put `[map] = false`
--- For example: --- --- { --- ..., --- [""] = false, --- ..., --- } --- --- --- To override behavior of a key, simply set the value --- to be a function (either by requiring an action or by writing --- your own function) --- --- { --- ..., --- [""] = require('telescope.actions').select_default, --- ..., --- } --- --- --- If the function you want is part of `telescope.actions`, then you can --- simply supply the function name as a string. --- For example, the previous option is equivalent to: --- --- { --- ..., --- [""] = "select_default", --- ..., --- } --- --- --- You can also add other mappings using tables with `type = "command"`. --- For example: --- --- { --- ..., --- ["jj"] = { "", type = "command" }, --- ["kk"] = { "echo \"Hello, World!\"", type = "command" },) --- ..., --- } --- --- --- You can also add additional options for mappings of any type ("action" and "command"). --- For example: --- --- { --- ..., --- [""] = { --- actions.move_selection_next, type = "action", --- opts = { nowait = true, silent = true } --- }, --- ..., --- } --- --- --- There are three main places you can configure |telescope.mappings|. These are --- ordered from the lowest priority to the highest priority. --- --- 1. |telescope.defaults.mappings| --- 2. In the |telescope.setup()| table, inside a picker with a given name, use the `mappings` key --- --- require("telescope").setup { --- pickers = { --- find_files = { --- mappings = { --- n = { --- ["kj"] = "close", --- }, --- }, --- }, --- }, --- } --- --- 3. `attach_mappings` function for a particular picker. --- --- require("telescope.builtin").find_files { --- attach_mappings = function(_, map) --- map("i", "asdf", function(_prompt_bufnr) --- print "You typed asdf" --- end) --- --- map({"i", "n"}, "", function(_prompt_bufnr) --- print "You typed " --- end, { desc = "desc for which key"}) --- --- -- needs to return true if you want to map default_mappings and --- -- false if not --- return true --- end, --- } --- ---@brief ]] local api = vim.api local actions = require "telescope.actions" local config = require "telescope.config" local mappings = {} mappings.default_mappings = config.values.default_mappings or { i = { [""] = { actions.mouse_click, type = "action", opts = { expr = true }, }, ["<2-LeftMouse>"] = { actions.double_mouse_click, type = "action", opts = { expr = true }, }, [""] = actions.move_selection_next, [""] = actions.move_selection_previous, [""] = actions.close, [""] = actions.move_selection_next, [""] = actions.move_selection_previous, [""] = actions.select_default, [""] = actions.select_horizontal, [""] = actions.select_vertical, [""] = actions.select_tab, [""] = actions.preview_scrolling_up, [""] = actions.preview_scrolling_down, [""] = actions.preview_scrolling_left, [""] = actions.preview_scrolling_right, [""] = actions.results_scrolling_up, [""] = actions.results_scrolling_down, [""] = actions.results_scrolling_left, [""] = actions.results_scrolling_right, [""] = actions.toggle_selection + actions.move_selection_worse, [""] = actions.toggle_selection + actions.move_selection_better, [""] = actions.send_to_qflist + actions.open_qflist, [""] = actions.send_selected_to_qflist + actions.open_qflist, [""] = actions.complete_tag, [""] = actions.which_key, [""] = actions.which_key, -- keys from pressing [""] = { "", type = "command" }, [""] = actions.insert_original_cword, [""] = actions.insert_original_cWORD, [""] = actions.insert_original_cfile, [""] = actions.insert_original_cline, -- disable c-j because we dont want to allow new lines #2123 [""] = actions.nop, }, n = { [""] = { actions.mouse_click, type = "action", opts = { expr = true }, }, ["<2-LeftMouse>"] = { actions.double_mouse_click, type = "action", opts = { expr = true }, }, [""] = actions.close, [""] = actions.select_default, [""] = actions.select_horizontal, [""] = actions.select_vertical, [""] = actions.select_tab, [""] = actions.toggle_selection + actions.move_selection_worse, [""] = actions.toggle_selection + actions.move_selection_better, [""] = actions.send_to_qflist + actions.open_qflist, [""] = actions.send_selected_to_qflist + actions.open_qflist, -- TODO: This would be weird if we switch the ordering. ["j"] = actions.move_selection_next, ["k"] = actions.move_selection_previous, ["H"] = actions.move_to_top, ["M"] = actions.move_to_middle, ["L"] = actions.move_to_bottom, [""] = actions.move_selection_next, [""] = actions.move_selection_previous, ["gg"] = actions.move_to_top, ["G"] = actions.move_to_bottom, [""] = actions.preview_scrolling_up, [""] = actions.preview_scrolling_down, [""] = actions.preview_scrolling_left, [""] = actions.preview_scrolling_right, [""] = actions.results_scrolling_up, [""] = actions.results_scrolling_down, [""] = actions.results_scrolling_left, [""] = actions.results_scrolling_right, ["?"] = actions.which_key, }, } -- normal names are prefixed with telescope| -- encoded objects are prefixed with telescopej| ---@param key_func table|fun() ---@param opts table ---@return string? local get_desc_for_keyfunc = function(key_func, opts) if opts and opts.desc then return "telescope|" .. opts.desc end if type(key_func) == "table" then local name = "" for _, action in ipairs(key_func) do if type(action) == "string" then name = name == "" and action or name .. " + " .. action end end return "telescope|" .. name elseif type(key_func) == "function" then local info = debug.getinfo(key_func) return "telescopej|" .. vim.json.encode { source = info.source, linedefined = info.linedefined } end end local telescope_map = function(prompt_bufnr, mode, key_bind, key_func, opts) if not key_func then return end opts = opts or {} if opts.noremap == nil then opts.noremap = true end if opts.silent == nil then opts.silent = true end if type(key_func) == "string" then key_func = actions[key_func] elseif type(key_func) == "table" then if key_func.type == "command" then vim.keymap.set( mode, key_bind, key_func[1], vim.tbl_extend("force", opts or { silent = true, }, { buffer = prompt_bufnr }) ) return elseif key_func.type == "action_key" then key_func = actions[key_func[1]] elseif key_func.type == "action" then key_func = key_func[1] end end vim.keymap.set(mode, key_bind, function() local ret = key_func(prompt_bufnr) api.nvim_exec_autocmds("User", { pattern = "TelescopeKeymap" }) return ret end, vim.tbl_extend("force", opts, { buffer = prompt_bufnr, desc = get_desc_for_keyfunc(key_func, opts) })) end local extract_keymap_opts = function(key_func) if type(key_func) == "table" and key_func.opts ~= nil then -- we can't clear this because key_func could be a table from the config. -- If we clear it the table ref would lose opts after the first bind -- We need to copy it so noremap and silent won't be part of the table ref after the first bind return vim.deepcopy(key_func.opts) end return {} end mappings.apply_keymap = function(prompt_bufnr, attach_mappings, buffer_keymap) local applied_mappings = { n = {}, i = {} } local map = function(modes, key_bind, key_func, opts) if type(modes) == "string" then modes = { modes } end for _, mode in pairs(modes) do mode = string.lower(mode) local key_bind_internal = api.nvim_replace_termcodes(key_bind, true, true, true) applied_mappings[mode][key_bind_internal] = true telescope_map(prompt_bufnr, mode, key_bind, key_func, opts) end end if attach_mappings then local attach_results = attach_mappings(prompt_bufnr, map) if attach_results == nil then error( "Attach mappings must always return a value. `true` means use default mappings, " .. "`false` means only use attached mappings" ) end if not attach_results then return end end for mode, mode_map in pairs(buffer_keymap or {}) do mode = string.lower(mode) for key_bind, key_func in pairs(mode_map) do local key_bind_internal = api.nvim_replace_termcodes(key_bind, true, true, true) if not applied_mappings[mode][key_bind_internal] then applied_mappings[mode][key_bind_internal] = true telescope_map(prompt_bufnr, mode, key_bind, key_func, extract_keymap_opts(key_func)) end end end -- TODO: Probably should not overwrite any keymaps for mode, mode_map in pairs(mappings.default_mappings) do mode = string.lower(mode) for key_bind, key_func in pairs(mode_map) do local key_bind_internal = api.nvim_replace_termcodes(key_bind, true, true, true) if not applied_mappings[mode][key_bind_internal] then applied_mappings[mode][key_bind_internal] = true telescope_map(prompt_bufnr, mode, key_bind, key_func, extract_keymap_opts(key_func)) end end end end return mappings ================================================ FILE: lua/telescope/operators.lua ================================================ local operators = {} local last_operator = { callback = function(_) end, opts = {} } --- Execute the last saved operator callback and options operators.operator_callback = function() last_operator.callback(last_operator.opts) end --- Enters operator-pending mode, then executes callback. --- See `:h map-operator` --- ---@param callback function: the function to call after exiting operator-pending ---@param opts table: options to pass to the callback operators.run_operator = function(callback, opts) last_operator = { callback = callback, opts = opts } vim.o.operatorfunc = "v:lua.require'telescope.operators'.operator_callback" -- feed g@ to enter operator-pending mode -- 'i' required for which-key compatibility, etc. vim.api.nvim_feedkeys("g@", "mi", false) end return operators ================================================ FILE: lua/telescope/pickers/entry_display.lua ================================================ ---@tag telescope.pickers.entry_display ---@brief [[ --- Entry Display is used to format each entry shown in the result panel. --- --- Entry Display create() will give us a function based on the configuration --- of column widths we pass into it. We then can use this function n times to --- return a string based on structured input. --- --- Note that if you call `create()` inside `make_display` it will be called for --- every single entry. So it is suggested to do this outside of `make_display` --- for the best performance. --- --- The create function will use the column widths passed to it in --- configuration.items. Each item in that table is the number of characters in --- the column. It's also possible for the final column to not have a fixed --- width, this will be shown in the configuration as 'remaining = true'. --- --- An example of this configuration is shown for the buffers picker: --- --- local displayer = entry_display.create { --- separator = " ", --- items = { --- { width = opts.bufnr_width }, --- { width = 4 }, --- { width = icon_width }, --- { remaining = true }, --- }, --- } --- --- --- This shows 4 columns, the first is defined in the opts as the width we'll --- use when display_string is the number of the buffer. The second has a fixed --- width of 4 and the third column's width will be decided by the width of the --- icons we use. The fourth column will use the remaining space. Finally, we --- have also defined the separator between each column will be the space " ". --- --- An example of how the display reference will be used is shown, again for --- the buffers picker: --- --- return displayer { --- { entry.bufnr, "TelescopeResultsNumber" }, --- { entry.indicator, "TelescopeResultsComment" }, --- { icon, hl_group }, --- display_bufname .. ":" .. entry.lnum, --- } --- --- --- There are two types of values each column can have. Either a simple String --- or a table containing the String as well as the hl_group. --- --- The displayer can return values, string and an optional highlights. The string --- is all the text to be displayed for this entry as a single string. If parts of --- the string are to be highlighted they will be described in the highlights --- table. --- --- For a better understanding of how create() and displayer are used it's best to look --- at the code in make_entry.lua. ---@brief ]] local strings = require "plenary.strings" local state = require "telescope.state" local resolve = require "telescope.config.resolve" local entry_display = {} entry_display.truncate = strings.truncate entry_display.create = function(configuration) local generator = {} for _, v in ipairs(configuration.items) do if v.width then local justify = v.right_justify local width table.insert(generator, function(item) if width == nil then local status = state.get_status(vim.F.if_nil(configuration.prompt_bufnr, vim.api.nvim_get_current_buf())) local s = {} s[1] = vim.api.nvim_win_get_width(status.layout.results.winid) - #status.picker.selection_caret s[2] = vim.api.nvim_win_get_height(status.layout.results.winid) width = resolve.resolve_width(v.width)(nil, s[1], s[2]) end if type(item) == "table" then return strings.align_str(entry_display.truncate(item[1], width), width, justify), item[2] else return strings.align_str(entry_display.truncate(item, width), width, justify) end end) else table.insert(generator, function(item) if type(item) == "table" then return item[1], item[2] else return item end end) end end return function(self, picker) local results = {} local highlights = {} for i = 1, #generator do if self[i] ~= nil then local str, hl = generator[i](self[i], picker) if hl then local hl_start = 0 for j = 1, (i - 1) do hl_start = hl_start + #results[j] + (#configuration.separator or 1) end local hl_end = hl_start + #str:gsub("%s*$", "") if type(hl) == "function" then for _, hl_res in ipairs(hl()) do table.insert(highlights, { { hl_res[1][1] + hl_start, hl_res[1][2] + hl_start }, hl_res[2] }) end else table.insert(highlights, { { hl_start, hl_end }, hl }) end end table.insert(results, str) end end if configuration.separator_hl then local width = #configuration.separator or 1 local hl_start, hl_end for _, v in ipairs(results) do hl_start = (hl_end or 0) + #tostring(v) hl_end = hl_start + width table.insert(highlights, { { hl_start, hl_end }, configuration.separator_hl }) end end local final_str = table.concat(results, configuration.separator or "│") if configuration.hl_chars then for i = 1, #final_str do local c = final_str:sub(i, i) local hl = configuration.hl_chars[c] if hl then table.insert(highlights, { { i - 1, i }, hl }) end end end return final_str, highlights end end entry_display.resolve = function(self, entry) local display, display_highlights if type(entry.display) == "function" then self:_increment "display_fn" display, display_highlights = entry:display(self) if type(display) == "string" then return display, display_highlights end else display = entry.display end if type(display) == "string" then return display, display_highlights end end return entry_display ================================================ FILE: lua/telescope/pickers/highlights.lua ================================================ local api = vim.api local log = require "telescope.log" local conf = require("telescope.config").values local highlights = {} local ns_telescope_selection = api.nvim_create_namespace "telescope_selection" local ns_telescope_multiselection = api.nvim_create_namespace "telescope_multiselection" local ns_telescope_entry = api.nvim_create_namespace "telescope_entry" ---TODO(clason): remove when dropping support for Nvim 0.10 local hl = vim.hl or vim.highlight local Highlighter = {} Highlighter.__index = Highlighter function Highlighter:new(picker) return setmetatable({ picker = picker, }, self) end function Highlighter:hi_display(row, prefix, display_highlights) -- This is the bug that made my highlight fixes not work. -- We will leave the solution commented, so the test fails. if not display_highlights or vim.tbl_isempty(display_highlights) then return end local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr") api.nvim_buf_clear_namespace(results_bufnr, ns_telescope_entry, row, row + 1) local len_prefix = #prefix for _, hl_block in ipairs(display_highlights) do hl.range( results_bufnr, ns_telescope_entry, hl_block[2], { row, len_prefix + hl_block[1][1] }, { row, len_prefix + hl_block[1][2] } ) end end function Highlighter:clear_display() if not self or not self.picker or not self.picker.results_bufnr or not api.nvim_buf_is_valid(self.picker.results_bufnr) then return end api.nvim_buf_clear_namespace(self.picker.results_bufnr, ns_telescope_entry, 0, -1) end function Highlighter:hi_sorter(row, prompt, display) local picker = self.picker if not picker.sorter or not picker.sorter.highlighter then return end local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr") picker:highlight_one_row(results_bufnr, prompt, display, row) end function Highlighter:hi_selection(row, caret) caret = vim.F.if_nil(caret, "") local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr") api.nvim_buf_clear_namespace(results_bufnr, ns_telescope_selection, 0, -1) hl.range(results_bufnr, ns_telescope_selection, "TelescopeSelectionCaret", { row, 0 }, { row, #caret }) api.nvim_buf_set_extmark( results_bufnr, ns_telescope_selection, row, #caret, { end_line = row + 1, hl_eol = conf.hl_result_eol, hl_group = "TelescopeSelection" } ) end function Highlighter:hi_multiselect(row, is_selected) local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr") if is_selected then hl.range(results_bufnr, ns_telescope_multiselection, "TelescopeMultiSelection", { row, 0 }, { row, -1 }) if self.picker.multi_icon then local line = api.nvim_buf_get_lines(results_bufnr, row, row + 1, false)[1] local pos = line:find(self.picker.multi_icon) if pos and pos <= math.max(#self.picker.selection_caret, #self.picker.entry_prefix) then hl.range( results_bufnr, ns_telescope_multiselection, "TelescopeMultiIcon", { row, pos - 1 }, { row, pos - 1 + #self.picker.multi_icon } ) end end else local existing_marks = api.nvim_buf_get_extmarks( results_bufnr, ns_telescope_multiselection, { row, 0 }, { row, -1 }, {} ) -- This is still kind of weird to me, since it seems like I'm erasing stuff -- when I shouldn't... Perhaps it's about the gravity of the extmark? if #existing_marks > 0 then log.trace("Clearing highlight multi select row: ", row) api.nvim_buf_clear_namespace(results_bufnr, ns_telescope_multiselection, row, row + 1) end end end highlights.new = function(...) return Highlighter:new(...) end return highlights ================================================ FILE: lua/telescope/pickers/layout.lua ================================================ ---@tag telescope.pickers.layout ---@config { ["module"] = "telescope.pickers.layout" } ---@brief [[ --- The telescope pickers layout can be configured using the --- |telescope.defaults.create_layout| option. --- --- Parameters: ~ --- - picker : A Picker object. --- --- Return: ~ --- - layout : instance of `TelescopeLayout` class. --- --- Example: ~ --- --- local Layout = require "telescope.pickers.layout" --- --- require("telescope").setup { --- create_layout = function(picker) --- local function create_window(enter, width, height, row, col, title) --- local bufnr = vim.api.nvim_create_buf(false, true) --- local winid = vim.api.nvim_open_win(bufnr, enter, { --- style = "minimal", --- relative = "editor", --- width = width, --- height = height, --- row = row, --- col = col, --- border = "single", --- title = title, --- }) --- --- vim.wo[winid].winhighlight = "Normal:Normal" --- --- return Layout.Window { --- bufnr = bufnr, --- winid = winid, --- } --- end --- --- local function destory_window(window) --- if window then --- if vim.api.nvim_win_is_valid(window.winid) then --- vim.api.nvim_win_close(window.winid, true) --- end --- if vim.api.nvim_buf_is_valid(window.bufnr) then --- vim.api.nvim_buf_delete(window.bufnr, { force = true }) --- end --- end --- end --- --- local layout = Layout { --- picker = picker, --- mount = function(self) --- self.results = create_window(false, 40, 20, 0, 0, "Results") --- self.preview = create_window(false, 40, 23, 0, 42, "Preview") --- self.prompt = create_window(true, 40, 1, 22, 0, "Prompt") --- end, --- unmount = function(self) --- destory_window(self.results) --- destory_window(self.preview) --- destory_window(self.prompt) --- end, --- update = function(self) end, --- } --- --- return layout --- end, --- } --- ---@brief ]] local function wrap_instance(class, instance) local self = instance if not getmetatable(instance) then self = setmetatable(instance, { __index = class }) end return self end ---@class TelescopeWindowBorder.config ---@field bufnr integer ---@field winid integer|nil ---@field change_title nil|function: (self: TelescopeWindowBorder, title: string, pos?: "NW"|"N"|"NE"|"SW"|"S"|"SE"):nil ---@param class TelescopeWindowBorder ---@param config TelescopeWindowBorder.config ---@return TelescopeWindowBorder local function init_border(class, config) config = config or {} ---@type TelescopeWindowBorder local self = wrap_instance(class, config) if not self.change_title then self.change_title = class.change_title end return self end ---@class TelescopeWindowBorder ---@field bufnr integer|nil ---@field winid integer|nil local Border = setmetatable({}, { __call = init_border, __name = "TelescopeWindowBorder", }) ---@param title string ---@param pos "NW"|"N"|"NE"|"SW"|"S"|"SE"|nil function Border:change_title(title, pos) end ---@class TelescopeWindow.config ---@field bufnr integer ---@field winid integer|nil ---@field border TelescopeWindowBorder.config|nil ---@param class TelescopeWindow ---@param config TelescopeWindow.config ---@return TelescopeWindow local function init_window(class, config) config = config or {} ---@type TelescopeWindow local self = wrap_instance(class, config) self.border = Border(config.border) return self end ---@class TelescopeWindow ---@field border TelescopeWindowBorder ---@field bufnr integer ---@field winid integer local Window = setmetatable({}, { __call = init_window, __name = "TelescopeWindow", }) ---@class TelescopeLayout.config ---@field mount function: (self: TelescopeLayout):nil ---@field unmount function: (self: TelescopeLayout):nil ---@field update function: (self: TelescopeLayout):nil ---@field prompt TelescopeWindow|nil ---@field results TelescopeWindow|nil ---@field preview TelescopeWindow|nil ---@param class TelescopeLayout ---@param config TelescopeLayout.config ---@return TelescopeLayout local function init_layout(class, config) config = config or {} ---@type TelescopeLayout local self = wrap_instance(class, config) assert(config.mount, "missing layout:mount") assert(config.unmount, "missing layout:unmount") assert(config.update, "missing layout:update") return self end ---@class TelescopeLayout ---@field prompt TelescopeWindow ---@field results TelescopeWindow ---@field preview TelescopeWindow|nil local Layout = setmetatable({ Window = Window, }, { __call = init_layout, __name = "TelescopeLayout", }) --- Create the layout. --- This needs to ensure the required properties are populated. function Layout:mount() end --- Destroy the layout. --- This is responsible for performing clean-up, for example: --- - deleting buffers --- - closing windows --- - clearing autocmds function Layout:unmount() end --- Refresh the layout. --- This is called when, for example, vim is resized. function Layout:update() end ---@alias TelescopeWindow.constructor fun(config: TelescopeWindow.config): TelescopeWindow ---@alias TelescopeLayout.constructor fun(config: TelescopeLayout.config): TelescopeLayout return Layout --[[@as TelescopeLayout.constructor|{ Window: TelescopeWindow.constructor }]] ================================================ FILE: lua/telescope/pickers/layout_strategies.lua ================================================ ---@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: --- --- require('telescope').setup{ --- defaults = { --- layout_strategy = 'vertical', --- layout_config = { height = 0.95 }, --- }, --- } --- --- --- ──────────────────────────────────────────────────────────────────────────────── --- --- Layout strategies are different functions to position telescope. --- --- All layout strategies are functions with the following signature: --- --- --- function(picker, columns, lines, layout_config) --- -- Do some calculations here... --- return { --- preview = preview_configuration --- results = results_configuration, --- prompt = prompt_configuration, --- } --- end --- --- ---
---   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.
--- 
--- --- 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 api = vim.api 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 #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 error( 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()|" }, anchor_padding = { "Specifies an amount of additional padding around the anchor", "Values should be a positive integer", }, } -- 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 = { "
", "`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, "
") 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. --- ---
--- ┌──────────────────────────────────────────────────┐
--- │                                                  │
--- │    ┌───────────────────┐┌───────────────────┐    │
--- │    │                   ││                   │    │
--- │    │                   ││                   │    │
--- │    │                   ││                   │    │
--- │    │      Results      ││                   │    │
--- │    │                   ││      Preview      │    │
--- │    │                   ││                   │    │
--- │    │                   ││                   │    │
--- │    └───────────────────┘│                   │    │
--- │    ┌───────────────────┐│                   │    │
--- │    │      Prompt       ││                   │    │
--- │    └───────────────────┘└───────────────────┘    │
--- │                                                  │
--- └──────────────────────────────────────────────────┘
--- 
---@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 = layout_config.anchor or "" local anchor_padding = layout_config.anchor_padding or 1 local anchor_pos = resolve.resolve_anchor_pos(anchor, width, height, max_columns, max_lines, anchor_padding) 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. --- ---
--- ┌──────────────────────────────────────────────────┐
--- │    ┌────────────────────────────────────────┐    │
--- │    │                 Preview                │    │
--- │    │                 Preview                │    │
--- │    └────────────────────────────────────────┘    │
--- │    ┌────────────────────────────────────────┐    │
--- │    │                 Prompt                 │    │
--- │    ├────────────────────────────────────────┤    │
--- │    │                 Result                 │    │
--- │    │                 Result                 │    │
--- │    └────────────────────────────────────────┘    │
--- │                                                  │
--- │                                                  │
--- │                                                  │
--- │                                                  │
--- └──────────────────────────────────────────────────┘
--- 
---@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_padding = layout_config.anchor_padding or 1 local anchor_pos = resolve.resolve_anchor_pos(anchor, width, height, max_columns, max_lines, anchor_padding) 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. --- ---
--- ┌──────────────────────────────────────────────────┐
--- │                                                  │
--- │   █                                              │
--- │   ┌──────────────┐┌─────────────────────┐        │
--- │   │    Prompt    ││      Preview        │        │
--- │   ├──────────────┤│      Preview        │        │
--- │   │    Result    ││      Preview        │        │
--- │   │    Result    ││      Preview        │        │
--- │   └──────────────┘└─────────────────────┘        │
--- │                                         █        │
--- │                                                  │
--- │                                                  │
--- │                                                  │
--- │                                                  │
--- │                                                  │
--- └──────────────────────────────────────────────────┘
--- 
---@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 winid = self.original_win_id 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 = api.nvim_win_get_position(winid) local winbar = (function() if vim.fn.exists "&winbar" == 1 then return vim.wo[winid].winbar == "" and 0 or 1 end return 0 end)() local top_left = { line = api.nvim_win_call(winid, vim.fn.winline) + position[1] + bs + winbar, col = api.nvim_win_call(winid, 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. --- ---
--- ┌──────────────────────────────────────────────────┐
--- │                                                  │
--- │    ┌────────────────────────────────────────┐    │
--- │    │                 Preview                │    │
--- │    │                 Preview                │    │
--- │    │                 Preview                │    │
--- │    └────────────────────────────────────────┘    │
--- │    ┌────────────────────────────────────────┐    │
--- │    │                 Result                 │    │
--- │    │                 Result                 │    │
--- │    └────────────────────────────────────────┘    │
--- │    ┌────────────────────────────────────────┐    │
--- │    │                 Prompt                 │    │
--- │    └────────────────────────────────────────┘    │
--- │                                                  │
--- └──────────────────────────────────────────────────┘
--- 
---@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 = layout_config.anchor or "" local anchor_padding = layout_config.anchor_padding or 1 local anchor_pos = resolve.resolve_anchor_pos(anchor, width, height, max_columns, max_lines, anchor_padding) 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, layout_config.horizontal.preview_cutoff) local flip_lines = vim.F.if_nil(layout_config.flip_lines, layout_config.vertical.preview_cutoff) 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 = api.nvim_win_get_width(0) local window_height = 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 = 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: lua/telescope/pickers/multi.lua ================================================ local MultiSelect = {} MultiSelect.__index = MultiSelect function MultiSelect:new() return setmetatable({ _entries = {}, }, MultiSelect) end function MultiSelect:get() local marked_entries = {} for entry, count in pairs(self._entries) do table.insert(marked_entries, { count, entry }) end table.sort(marked_entries, function(left, right) return left[1] < right[1] end) local selections = {} for _, entry in ipairs(marked_entries) do table.insert(selections, entry[2]) end return selections end function MultiSelect:is_selected(entry) return self._entries[entry] end local multi_select_count = 0 function MultiSelect:add(entry) multi_select_count = multi_select_count + 1 self._entries[entry] = multi_select_count end function MultiSelect:drop(entry) self._entries[entry] = nil end function MultiSelect:toggle(entry) if self:is_selected(entry) then self:drop(entry) else self:add(entry) end end return MultiSelect ================================================ FILE: lua/telescope/pickers/scroller.lua ================================================ local scroller = {} local range_calculators = { ascending = function(max_results, num_results) return 0, math.min(max_results, num_results) end, descending = function(max_results, num_results) return math.max(max_results - num_results, 0), max_results end, } local scroll_calculators = { cycle = function(range_fn) return function(max_results, num_results, row) local start, finish = range_fn(max_results, num_results) if row >= finish then return start elseif row < start then return (finish - 1 < 0) and finish or finish - 1 end return row end end, limit = function(range_fn) return function(max_results, num_results, row) local start, finish = range_fn(max_results, num_results) if row >= finish and finish > 0 then return finish - 1 elseif row < start then return start end return row end end, } scroller.create = function(scroll_strategy, sorting_strategy) local range_fn = range_calculators[sorting_strategy] if not range_fn then error(debug.traceback("Unknown sorting strategy: " .. sorting_strategy)) end local scroll_fn = scroll_calculators[scroll_strategy] if not scroll_fn then error(debug.traceback("Unknown scroll strategy: " .. (scroll_strategy or ""))) end local calculator = scroll_fn(range_fn) return function(max_results, num_results, row) local result = calculator(max_results, num_results, row) if result < 0 then error( string.format( "Must never return a negative row: { result = %s, args = { %s %s %s } }", result, max_results, num_results, row ) ) end if result > max_results then error( string.format( "Must never exceed max results: { result = %s, args = { %s %s %s } }", result, max_results, num_results, row ) ) end return result end end scroller.top = function(sorting_strategy, max_results, num_results) if sorting_strategy == "ascending" then return 0 end return (num_results > max_results) and 0 or (max_results - num_results) end scroller.middle = function(sorting_strategy, max_results, num_results) local mid_pos if sorting_strategy == "ascending" then mid_pos = math.floor(num_results / 2) else mid_pos = math.floor(max_results - num_results / 2) end return (num_results < max_results) and mid_pos or math.floor(max_results / 2) end scroller.bottom = function(sorting_strategy, max_results, num_results) if sorting_strategy == "ascending" then return math.min(max_results, num_results) - 1 end return max_results - 1 end scroller.better = function(sorting_strategy) if sorting_strategy == "ascending" then return -1 else return 1 end end scroller.worse = function(sorting_strategy) return -(scroller.better(sorting_strategy)) end return scroller ================================================ FILE: lua/telescope/pickers/window.lua ================================================ local resolve = require "telescope.config.resolve" local p_window = {} function p_window.get_window_options(picker, max_columns, max_lines) local layout_strategy = picker.layout_strategy local getter = require("telescope.pickers.layout_strategies")[layout_strategy] if not getter then error(string.format("'%s' is not a valid layout strategy", layout_strategy)) end return getter(picker, max_columns, max_lines) end function p_window.get_initial_window_options(picker) local popup_border = resolve.win_option(picker.window.border) local popup_borderchars = resolve.win_option(picker.window.borderchars) local preview = { title = picker.preview_title, border = popup_border.preview, borderchars = popup_borderchars.preview, enter = false, highlight = false, } local results = { title = picker.results_title, border = popup_border.results, borderchars = popup_borderchars.results, enter = false, } local prompt = { title = picker.prompt_title, border = popup_border.prompt, borderchars = popup_borderchars.prompt, enter = true, } return { preview = preview, results = results, prompt = prompt, } end return p_window ================================================ FILE: lua/telescope/pickers.lua ================================================ require "telescope" local api = vim.api local async = require "plenary.async" local await_schedule = async.util.scheduler local channel = require("plenary.async.control").channel local popup = require "plenary.popup" local actions = require "telescope.actions" local config = require "telescope.config" local debounce = require "telescope.debounce" local log = require "telescope.log" local mappings = require "telescope.mappings" local state = require "telescope.state" local utils = require "telescope.utils" local entry_display = require "telescope.pickers.entry_display" local p_highlighter = require "telescope.pickers.highlights" local p_scroller = require "telescope.pickers.scroller" local p_window = require "telescope.pickers.window" local Layout = require "telescope.pickers.layout" local EntryManager = require "telescope.entry_manager" local MultiSelect = require "telescope.pickers.multi" local truncate = require("plenary.strings").truncate local strdisplaywidth = require("plenary.strings").strdisplaywidth local ns_telescope_matching = api.nvim_create_namespace "telescope_matching" local ns_telescope_prompt = api.nvim_create_namespace "telescope_prompt" local ns_telescope_prompt_prefix = api.nvim_create_namespace "telescope_prompt_prefix" ---@class telescope_popup_options ---@field border table<1|2|3|4, integer> ---@field borderchars table<1|2|3|4|5|6|7|8, string> ---@field borderhighlight string ---@field col integer ---@field enter boolean ---@field height integer ---@field highlight string ---@field line integer ---@field minheight integer ---@field title integer ---@field titlehighlight integer ---@field width integer -- Create three windows: -- 1. Prompt window -- 2. Options window -- 3. Preview window -- ---@param picker Picker local function default_create_layout(picker) local function make_border(border) if not border then return nil end border.winid = border.win_id return border end local layout = Layout { picker = picker, ---@param self TelescopeLayout mount = function(self) local line_count = vim.o.lines - vim.o.cmdheight if vim.o.laststatus ~= 0 then line_count = line_count - 1 end local popup_opts = picker:get_window_options(vim.o.columns, line_count) -- `popup.nvim` massaging so people don't have to remember minheight shenanigans popup_opts.results.focusable = true popup_opts.results.minheight = popup_opts.results.height popup_opts.results.highlight = "TelescopeResultsNormal" popup_opts.results.borderhighlight = "TelescopeResultsBorder" popup_opts.results.titlehighlight = "TelescopeResultsTitle" popup_opts.prompt.minheight = popup_opts.prompt.height popup_opts.prompt.highlight = "TelescopePromptNormal" popup_opts.prompt.borderhighlight = "TelescopePromptBorder" popup_opts.prompt.titlehighlight = "TelescopePromptTitle" if popup_opts.preview then popup_opts.preview.focusable = true popup_opts.preview.minheight = popup_opts.preview.height popup_opts.preview.highlight = "TelescopePreviewNormal" popup_opts.preview.borderhighlight = "TelescopePreviewBorder" popup_opts.preview.titlehighlight = "TelescopePreviewTitle" end local results_win, results_opts = picker:_create_window("", popup_opts.results) local results_bufnr = api.nvim_win_get_buf(results_win) self.results = Layout.Window { winid = results_win, bufnr = results_bufnr, border = make_border(results_opts.border), } if popup_opts.preview then local preview_win, preview_opts = picker:_create_window("", popup_opts.preview) local preview_bufnr = api.nvim_win_get_buf(preview_win) self.preview = Layout.Window { winid = preview_win, bufnr = preview_bufnr, border = make_border(preview_opts.border), } end local prompt_win, prompt_opts = picker:_create_window("", popup_opts.prompt) local prompt_bufnr = api.nvim_win_get_buf(prompt_win) self.prompt = Layout.Window { winid = prompt_win, bufnr = prompt_bufnr, border = make_border(prompt_opts.border), } end, ---@param self TelescopeLayout unmount = function(self) utils.win_delete("results_win", self.results.winid, true, true) if self.preview then utils.win_delete("preview_win", self.preview.winid, true, true) end utils.win_delete("prompt_border_win", self.prompt.border.winid, true, true) utils.win_delete("results_border_win", self.results.border.winid, true, true) if self.preview then utils.win_delete("preview_border_win", self.preview.border.winid, true, true) end -- we cant use win_delete. We first need to close and then delete the buffer if api.nvim_win_is_valid(self.prompt.winid) then api.nvim_win_close(self.prompt.winid, true) end vim.schedule(function() utils.buf_delete(self.prompt.bufnr) end) end, ---@param self TelescopeLayout update = function(self) local line_count = vim.o.lines - vim.o.cmdheight if vim.o.laststatus ~= 0 then line_count = line_count - 1 end local popup_opts = picker:get_window_options(vim.o.columns, line_count) -- `popup.nvim` massaging so people don't have to remember minheight shenanigans popup_opts.results.minheight = popup_opts.results.height popup_opts.prompt.minheight = popup_opts.prompt.height if popup_opts.preview then popup_opts.preview.minheight = popup_opts.preview.height end local prompt_win = self.prompt.winid local results_win = self.results.winid local preview_win = self.preview and self.preview.winid local preview_opts if popup_opts.preview then if preview_win ~= nil then -- Move all popups at the same time popup.move(prompt_win, popup_opts.prompt) popup.move(results_win, popup_opts.results) popup.move(preview_win, popup_opts.preview) else popup_opts.preview.focusable = true popup_opts.preview.highlight = "TelescopePreviewNormal" popup_opts.preview.borderhighlight = "TelescopePreviewBorder" popup_opts.preview.titlehighlight = "TelescopePreviewTitle" local preview_bufnr = (self.preview and self.preview.bufnr ~= nil) and api.nvim_buf_is_valid(self.preview.bufnr) and self.preview.bufnr or "" preview_win, preview_opts = picker:_create_window(preview_bufnr, popup_opts.preview) if preview_bufnr == "" then preview_bufnr = api.nvim_win_get_buf(preview_win) end self.preview = Layout.Window { winid = preview_win, bufnr = preview_bufnr, border = make_border(preview_opts.border), } if picker.previewer and picker.previewer.state and picker.previewer.state.winid then picker.previewer.state.winid = preview_win end -- Move prompt and results after preview created vim.defer_fn(function() popup.move(prompt_win, popup_opts.prompt) popup.move(results_win, popup_opts.results) end, 0) end elseif preview_win ~= nil then popup.move(prompt_win, popup_opts.prompt) popup.move(results_win, popup_opts.results) -- Remove preview after the prompt and results are moved vim.defer_fn(function() utils.win_delete("preview_win", preview_win, true) utils.win_delete("preview_win", self.preview.border.winid, true) self.preview = nil end, 0) else popup.move(prompt_win, popup_opts.prompt) popup.move(results_win, popup_opts.results) end end, } return layout end local pickers = {} -- TODO: Add overscroll option for results buffer ---@class Picker --- Picker is the main UI that shows up to interact w/ your results. -- Takes a filter & a previewer local Picker = {} Picker.__index = Picker --- Create new picker function Picker:new(opts) opts = opts or {} if opts.layout_strategy and opts.get_window_options then error "layout_strategy and get_window_options are not compatible keys" end if vim.fn.win_gettype() == "command" then error "Can't open telescope from command-line window. See E11" end -- We need to clear at the beginning not on close because after close we can still have select:post -- etc ... require("telescope.actions.mt").clear_all() -- TODO(conni2461): This seems like the better solution but it won't clear actions that were never mapped -- for _, v in ipairs(keymap_store[prompt_bufnr]) do -- pcall(v.clear) -- end local layout_strategy = vim.F.if_nil(opts.layout_strategy, config.values.layout_strategy) local winblend = vim.F.if_nil(opts.winblend, type(opts.window) == "table" and opts.window.winblend or config.values.winblend) if type(winblend) == "function" then winblend = winblend() end local obj = setmetatable({ prompt_title = vim.F.if_nil(opts.prompt_title, config.values.prompt_title), results_title = vim.F.if_nil(opts.results_title, config.values.results_title), -- either whats passed in by the user or whats defined by the previewer preview_title = opts.preview_title, prompt_prefix = vim.F.if_nil(opts.prompt_prefix, config.values.prompt_prefix), wrap_results = vim.F.if_nil(opts.wrap_results, config.values.wrap_results), selection_caret = vim.F.if_nil(opts.selection_caret, config.values.selection_caret), entry_prefix = vim.F.if_nil(opts.entry_prefix, config.values.entry_prefix), multi_icon = vim.F.if_nil(opts.multi_icon, config.values.multi_icon), initial_mode = vim.F.if_nil(opts.initial_mode, config.values.initial_mode), _original_mode = api.nvim_get_mode().mode, debounce = vim.F.if_nil(tonumber(opts.debounce), nil), _finder_attached = true, default_text = opts.default_text, get_status_text = vim.F.if_nil(opts.get_status_text, config.values.get_status_text), _on_input_filter_cb = opts.on_input_filter_cb or function() end, finder = assert(opts.finder, "Finder is required."), sorter = opts.sorter or require("telescope.sorters").empty(), all_previewers = opts.previewer, current_previewer_index = opts.current_previewer_index or 1, default_selection_index = opts.default_selection_index, get_selection_window = vim.F.if_nil(opts.get_selection_window, config.values.get_selection_window), cwd = opts.cwd, _find_id = 0, _completion_callbacks = type(opts._completion_callbacks) == "table" and opts._completion_callbacks or {}, manager = (type(opts.manager) == "table" and getmetatable(opts.manager) == EntryManager) and opts.manager, _multi = (type(opts._multi) == "table" and getmetatable(opts._multi) == getmetatable(MultiSelect:new())) and opts._multi or MultiSelect:new(), track = vim.F.if_nil(opts.track, false), stats = {}, attach_mappings = opts.attach_mappings, file_ignore_patterns = vim.F.if_nil(opts.file_ignore_patterns, config.values.file_ignore_patterns), scroll_strategy = vim.F.if_nil(opts.scroll_strategy, config.values.scroll_strategy), sorting_strategy = vim.F.if_nil(opts.sorting_strategy, config.values.sorting_strategy), tiebreak = vim.F.if_nil(opts.tiebreak, config.values.tiebreak), selection_strategy = vim.F.if_nil(opts.selection_strategy, config.values.selection_strategy), push_cursor_on_edit = vim.F.if_nil(opts.push_cursor_on_edit, false), push_tagstack_on_edit = vim.F.if_nil(opts.push_tagstack_on_edit, false), layout_strategy = layout_strategy, layout_config = config.smarter_depth_2_extend(opts.layout_config or {}, config.values.layout_config or {}), __cycle_layout_list = vim.F.if_nil(opts.cycle_layout_list, config.values.cycle_layout_list), window = { winblend = winblend, border = vim.F.if_nil(opts.border, type(opts.window) == "table" and opts.window.border or config.values.border), borderchars = vim.F.if_nil( opts.borderchars, type(opts.window) == "table" and opts.window.borderchars or config.values.borderchars ), }, cache_picker = config.resolve_table_opts(opts.cache_picker, vim.deepcopy(config.values.cache_picker)), __scrolling_limit = tonumber(vim.F.if_nil(opts.temp__scrolling_limit, 250)), __locations_input = vim.F.if_nil(opts.__locations_input, false), }, self) obj.create_layout = opts.create_layout or config.values.create_layout or default_create_layout obj.get_window_options = opts.get_window_options or p_window.get_window_options if obj.all_previewers ~= nil and obj.all_previewers ~= false then if obj.all_previewers[1] == nil then obj.all_previewers = { obj.all_previewers } end obj.previewer = obj.all_previewers[obj.current_previewer_index] if obj.preview_title == nil or #obj.all_previewers > 1 or opts.resumed_picker and opts.fix_preview_title ~= true then obj.preview_title = obj.previewer:title(nil, config.values.dynamic_preview_title) else obj.fix_preview_title = true end else obj.previewer = false end local __hide_previewer = opts.__hide_previewer if __hide_previewer then obj.hidden_previewer = obj.previewer obj.previewer = nil else obj.hidden_previewer = nil end -- TODO: It's annoying that this is create and everything else is "new" obj.scroller = p_scroller.create(obj.scroll_strategy, obj.sorting_strategy) obj.highlighter = p_highlighter.new(obj) if opts.on_complete then for _, on_complete_item in ipairs(opts.on_complete) do obj:register_completion_callback(on_complete_item) end end return obj end --- Take an index and get a row. ---@note: Rows are 0-indexed, and `index` is 1 indexed (table index) ---@param index number: the index in line_manager ---@return number: the row for the picker to display in function Picker:get_row(index) if self.sorting_strategy == "ascending" then return index - 1 else return self.max_results - index end end --- Take a row and get an index ---@note: Rows are 0-indexed, and `index` is 1 indexed (table index) ---@param row number: The row being displayed ---@return number: The index in line_manager function Picker:get_index(row) if self.sorting_strategy == "ascending" then return row + 1 else return self.max_results - row end end --- Get the row number of the "best" entry ---@return number: the number of the "reset" row function Picker:get_reset_row() if self.sorting_strategy == "ascending" then return 0 else return self.max_results - 1 end end --- Check if the picker is no longer in use ---@return boolean|nil: `true` if picker is closed, `nil` otherwise function Picker:is_done() if not self.manager then return true end end --- Clear rows that are after the final remaining entry ---@note: useful when number of remaining results is narrowed down ---@param results_bufnr number: the buffer number of the results buffer function Picker:clear_extra_rows(results_bufnr) if self:is_done() then log.trace "Not clearing due to being already complete" return end if not api.nvim_buf_is_valid(results_bufnr) then log.debug("Invalid results_bufnr for clearing:", results_bufnr) return end local worst_line, ok, msg if self.sorting_strategy == "ascending" then local num_results = self.manager:num_results() worst_line = math.min(num_results, self.max_results) ok, msg = pcall(api.nvim_buf_set_lines, results_bufnr, worst_line, -1, false, {}) else worst_line = self:get_row(self.manager:num_results()) if worst_line <= 0 then return end local empty_lines = utils.repeated_table(worst_line, "") ok, msg = pcall(api.nvim_buf_set_lines, results_bufnr, 0, worst_line, false, empty_lines) end if not ok then log.debug("Failed to set lines:", msg) end log.trace("Clearing:", worst_line) end --- Highlight the entry corresponding to the given row ---@param results_bufnr number: the buffer number of the results buffer ---@param prompt table: table with information about the prompt buffer ---@param display string: the text corresponding to the given row ---@param row number: the number of the chosen row function Picker:highlight_one_row(results_bufnr, prompt, display, row) if not self.sorter.highlighter then return end local highlights = self.sorter:highlighter(prompt, display) if highlights then for _, hl in ipairs(highlights) do local highlight, start, finish if type(hl) == "table" then highlight = hl.highlight or "TelescopeMatching" start = hl.start finish = hl.finish or hl.start elseif type(hl) == "number" then highlight = "TelescopeMatching" start = hl finish = hl else error "Invalid higlighter fn" end self:_increment "highlights" utils.hl_range(results_bufnr, ns_telescope_matching, highlight, { row, start - 1 }, { row, finish }) end end local entry = self.manager:get_entry(self:get_index(row)) self.highlighter:hi_multiselect(row, self:is_multi_selected(entry)) end --- Check if the given row number can be selected ---@param row number: the number of the chosen row in the results buffer ---@return boolean function Picker:can_select_row(row) if self.sorting_strategy == "ascending" then return row <= self.manager:num_results() and row < self.max_results else return row >= 0 and row <= self.max_results and row >= self.max_results - self.manager:num_results() end end --TODO: document what `find_id` is for function Picker:_next_find_id() local find_id = self._find_id + 1 self._find_id = find_id return find_id end --- A helper function for creating each of the windows in a picker ---@param bufnr number: the buffer number to be used in the window ---@param popup_opts table: options to pass to `popup.create` function Picker:_create_window(bufnr, popup_opts) local what = bufnr or "" local win, opts = popup.create(what, popup_opts) vim.wo[win].winblend = self.window.winblend local border_win = opts and opts.border and opts.border.win_id if border_win then vim.wo[border_win].winblend = self.window.winblend end return win, opts, border_win end --- Opens the given picker for the user to interact with ---@note: this is the main function for pickers, as it actually creates the interface for users function Picker:find() self:close_existing_pickers() self:reset_selection() self.__original_mousemoveevent = vim.o.mousemoveevent vim.o.mousemoveevent = true self.original_bufnr = api.nvim_get_current_buf() self.original_win_id = api.nvim_get_current_win() self.original_tabpage = api.nvim_get_current_tabpage() _, self.original_cword = pcall(vim.fn.expand, "") _, self.original_cWORD = pcall(vim.fn.expand, "") _, self.original_cfile = pcall(vim.fn.expand, "") _, self.original_cline = pcall(api.nvim_get_current_line) _, self.original_cline = pcall(vim.trim, self.original_cline) -- User autocmd run it before create Telescope window api.nvim_exec_autocmds("User", { pattern = "TelescopeFindPre" }) local layout = self:create_layout() layout:mount() self.layout = layout self.prompt_win, self.prompt_bufnr, self.prompt_border = layout.prompt.winid, layout.prompt.bufnr, layout.prompt.border self.results_win, self.results_bufnr, self.results_border = layout.results.winid, layout.results.bufnr, layout.results.border if layout.preview then self.preview_win, self.preview_bufnr, self.preview_border = layout.preview.winid, layout.preview.bufnr, layout.preview.border else self.preview_win, self.preview_bufnr, self.preview_border = nil, nil, nil end pcall(api.nvim_set_option_value, "tabstop", 1, { buf = self.results_bufnr }) -- #1834 pcall(api.nvim_set_option_value, "tabstop", 1, { buf = self.prompt_bufnr }) -- #1834 vim.bo[self.prompt_bufnr].buftype = "prompt" vim.wo[self.results_win].wrap = self.wrap_results vim.wo[self.prompt_win].wrap = true if self.preview_win then vim.wo[self.preview_win].wrap = true end -- Prompt prefix local prompt_prefix = self.prompt_prefix vim.fn.prompt_setprompt(self.prompt_bufnr, prompt_prefix) self:_reset_prefix_color() -- TODO: This could be configurable in the future, but I don't know why you would -- want to scroll through more than 10,000 items. -- -- This just lets us stop doing stuff after tons of things. self.max_results = self.__scrolling_limit api.nvim_buf_set_lines(self.results_bufnr, 0, self.max_results, false, utils.repeated_table(self.max_results, "")) local status_updater = self:get_status_updater(self.prompt_win, self.prompt_bufnr) local debounced_status = debounce.throttle_leading(status_updater, 50) local tx, rx = channel.mpsc() self._on_lines = tx.send local find_id = self:_next_find_id() if self.default_text then self:set_prompt(self.default_text) end if vim.tbl_contains({ "insert", "normal" }, self.initial_mode) then local mode = vim.fn.mode() local keys if self.initial_mode == "normal" then -- n: A makes sure cursor is at always at end of prompt w/o default_text keys = mode ~= "n" and "A" or "A" else -- always fully retrigger insert mode: required for going from one picker to next keys = mode ~= "n" and "A" or "A" end api.nvim_feedkeys(api.nvim_replace_termcodes(keys, true, false, true), "ni", true) else utils.notify("pickers.find", { msg = "`initial_mode` should be one of ['normal', 'insert'] but passed " .. self.initial_mode, level = "ERROR", }) end local main_loop = async.void(function() self.sorter:_init() -- Do filetype last, so that users can register at the last second. pcall(api.nvim_set_option_value, "filetype", "TelescopePrompt", { buf = self.prompt_bufnr }) pcall(api.nvim_set_option_value, "filetype", "TelescopeResults", { buf = self.results_bufnr }) await_schedule() while true do -- Wait for the next input rx.last() await_schedule() self:_reset_track() if not api.nvim_buf_is_valid(self.prompt_bufnr) then log.debug("ON_LINES: Invalid prompt_bufnr", self.prompt_bufnr) return end -- we kinda always wanna reset the color, because of `cc` and `dd` commands, -- which also delete the prefix and after prefix deletion we need to reapply highlighting self:_reset_prefix_color() local start_time = vim.uv.hrtime() local prompt = self:_get_next_filtered_prompt() state.set_global_key("current_line", prompt) if self.__locations_input == true then local filename, line_number, column_number = utils.__separate_file_path_location(prompt) if line_number or column_number then state.set_global_key("prompt_location", { row = line_number, col = column_number }) elseif state.get_global_key "prompt_location" then state.set_global_key("prompt_location", nil) end -- it is important to continue behaving as if there is no location in prompt prompt = filename elseif state.get_global_key "prompt_location" then -- in case new picker that does not support locations is opened clear the location state.set_global_key("prompt_location", nil) end -- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display if self.cache_picker == false or self.cache_picker.is_cached ~= true then self.sorter:_start(prompt) self.manager = EntryManager:new(self.max_results, self.entry_adder, self.stats) self:_reset_highlights() local process_result = self:get_result_processor(find_id, prompt, debounced_status) local process_complete = self:get_result_completor(self.results_bufnr, find_id, prompt, status_updater) local ok, msg = pcall(function() self.finder(prompt, process_result, process_complete) end) if not ok then log.warn("Finder failed with msg: ", msg) end local diff_time = (vim.uv.hrtime() - start_time) / 1e6 if self.debounce and diff_time < self.debounce then async.util.sleep(self.debounce - diff_time) end else -- TODO(scroll): This can only happen once, I don't like where it is. self:_resume_picker() end end end) -- Register attach api.nvim_buf_attach(self.prompt_bufnr, false, { on_lines = function(...) if self._finder_attached then find_id = self:_next_find_id() status_updater { completed = false } self._on_lines(...) end end, on_detach = function() self:_detach() end, }) api.nvim_create_augroup("PickerInsert", {}) -- TODO: Use WinLeave as well? api.nvim_create_autocmd("BufLeave", { buffer = self.prompt_bufnr, group = "PickerInsert", nested = true, once = true, callback = function() require("telescope.pickers").on_close_prompt(self.prompt_bufnr) end, }) api.nvim_create_autocmd("VimResized", { buffer = self.prompt_bufnr, group = "PickerInsert", nested = true, callback = function() require("telescope.pickers").on_resize_window(self.prompt_bufnr) end, }) state.set_status( self.prompt_bufnr, setmetatable({ layout = layout, picker = self, -- compatibility prompt_bufnr = self.prompt_bufnr, prompt_win = self.prompt_win, prompt_border_win = self.prompt_border.winid, results_bufnr = self.results_bufnr, results_win = self.results_win, results_border_win = self.results_border.winid, preview_bufnr = self.preview_bufnr, preview_win = self.preview_win, preview_border_win = self.preview_border and self.preview_border.winid, }, { __mode = "kv", }) ) mappings.apply_keymap(self.prompt_bufnr, self.attach_mappings, config.values.mappings) tx.send() main_loop() end --- A helper function to update picker windows when layout options are changed function Picker:recalculate_layout() local status = state.get_status(self.prompt_bufnr) status.layout:update() local layout = status.layout self.prompt_win, self.prompt_bufnr, self.prompt_border = layout.prompt.winid, layout.prompt.bufnr, layout.prompt.border self.results_win, self.results_bufnr, self.results_border = layout.results.winid, layout.results.bufnr, layout.results.border if layout.preview then self.preview_win, self.preview_bufnr, self.preview_border = layout.preview.winid, layout.preview.bufnr, layout.preview.border else self.preview_win, self.preview_bufnr, self.preview_border = nil, nil, nil end -- Temporarily disabled: Draw the screen ASAP. This makes things feel speedier. -- vim.cmd [[redraw]] -- self.max_results = popup_opts.results.height end local update_scroll = function(win, oldinfo, oldcursor, strategy, buf_maxline) if strategy == "ascending" then api.nvim_win_set_cursor(win, { buf_maxline, 0 }) api.nvim_win_set_cursor(win, { oldinfo.topline, 0 }) api.nvim_win_set_cursor(win, oldcursor) elseif strategy == "descending" then api.nvim_win_set_cursor(win, { 1, 0 }) api.nvim_win_set_cursor(win, { oldinfo.botline, 0 }) api.nvim_win_set_cursor(win, oldcursor) else error(debug.traceback("Unknown sorting strategy: " .. (strategy or ""))) end end --- A wrapper for `Picker:recalculate_layout()` that also handles maintaining cursor position function Picker:full_layout_update() local oldinfo = vim.fn.getwininfo(self.results_win)[1] local oldcursor = api.nvim_win_get_cursor(self.results_win) self:recalculate_layout() self:refresh_previewer() -- update scrolled position local buf_maxline = #api.nvim_buf_get_lines(self.results_bufnr, 0, -1, false) update_scroll(self.results_win, oldinfo, oldcursor, self.sorting_strategy, buf_maxline) end -- TODO: update multi-select with the correct tag name when available --- A simple interface to remove an entry from the results window without --- closing telescope. This either deletes the current selection or all the --- selections made using multi-select. It can be used to define actions --- such as deleting buffers or files. --- --- Example usage: --- --- actions.delete_something = function(prompt_bufnr) --- local current_picker = action_state.get_current_picker(prompt_bufnr) --- current_picker:delete_selection(function(selection) --- -- delete the selection outside of telescope --- end) --- end --- --- --- Example usage in telescope: --- - `actions.delete_buffer()` ---@param delete_cb function: called for each selection fn(s) -> bool|nil (true|nil removes the entry from the results) function Picker:delete_selection(delete_cb) utils.validate("delete_cb", delete_cb, "function") local original_selection_strategy = self.selection_strategy self.selection_strategy = "row" local delete_selections = self._multi:get() local used_multi_select = true if vim.tbl_isempty(delete_selections) then table.insert(delete_selections, self:get_selection()) used_multi_select = false end local selection_index = {} for result_index, result_entry in pairs(self.finder.results) do if vim.tbl_contains(delete_selections, result_entry) then table.insert(selection_index, result_index) end end -- Sort in reverse order as removing an entry from the table shifts down the -- other elements to close the hole. table.sort(selection_index, function(x, y) return x > y end) for _, index in ipairs(selection_index) do local delete_cb_return = delete_cb(self.finder.results[index]) if delete_cb_return == nil or delete_cb_return == true then table.remove(self.finder.results, index) end end if used_multi_select then self._multi = MultiSelect:new() end self:refresh() vim.defer_fn(function() self.selection_strategy = original_selection_strategy end, 50) end ---@param text string text to set as prompt ---@param reset boolean? whether to replace prompt with text entirely or just append function Picker:set_prompt(text, reset) reset = vim.F.if_nil(reset, true) if not reset then text = self:_get_prompt() .. text end self:reset_prompt(text) end --- Closes the windows for the prompt, results and preview ---@param status table: table containing information on the picker --- and associated windows. Generally obtained from `state.get_status` function Picker.close_windows(status) local prompt_bufnr = status.layout.prompt.bufnr status.layout:unmount() state.clear_status(prompt_bufnr) end --- Get the entry table of the current selection ---@return table function Picker:get_selection() return self._selection_entry end --- Get the row number of the current selection ---@return number function Picker:get_selection_row() if self._selection_row then -- If the current row is no longer selectable than reduce it to num_results - 1, so the next selectable row. -- This makes selection_strategy `row` work much better if the selected row is no longer part of the output. --TODO(conni2461): Maybe this can be moved to scroller. (currently in a hotfix so not viable) if self.selection_strategy == "row" then local num_results = self.manager:num_results() if self.sorting_strategy == "ascending" then if self._selection_row >= num_results then return num_results - 1 end else local max = self.max_results - num_results if self._selection_row < max then return self.max_results - num_results end end end return self._selection_row end return self:get_reset_row() end --- Move the current selection by `change` steps ---@param change number function Picker:move_selection(change) self:set_selection(self:get_selection_row() + change) end --- Add the entry of the given row to the multi-select object ---@param row number: the number of the chosen row function Picker:add_selection(row) local entry = self.manager:get_entry(self:get_index(row)) self._multi:add(entry) self:update_prefix(entry, row) self:get_status_updater(self.prompt_win, self.prompt_bufnr)() self.highlighter:hi_multiselect(row, true) end --- Remove the entry of the given row to the multi-select object ---@param row number: the number of the chosen row function Picker:remove_selection(row) local entry = self.manager:get_entry(self:get_index(row)) self._multi:drop(entry) self:update_prefix(entry, row) self:get_status_updater(self.prompt_win, self.prompt_bufnr)() self.highlighter:hi_multiselect(row, false) end --- Check if the given row is in the multi-select object ---@param entry table: table with information about the chosen entry ---@return number: the "count" associated to the entry in the multi-select --- object (if present), `nil` otherwise function Picker:is_multi_selected(entry) return self._multi:is_selected(entry) end --- Get a table containing all of the currently selected entries ---@return table: an integer indexed table of selected entries function Picker:get_multi_selection() return self._multi:get() end --- Toggle the given row in and out of the multi-select object. --- Also updates the highlighting for the given entry ---@param row number: the number of the chosen row function Picker:toggle_selection(row) local entry = self.manager and self.manager:get_entry(self:get_index(row)) if entry == nil then return end self._multi:toggle(entry) self:update_prefix(entry, row) self:get_status_updater(self.prompt_win, self.prompt_bufnr)() self.highlighter:hi_multiselect(row, self._multi:is_selected(entry)) end --- Set the current selection to `nil` ---@note: generally used when a picker is first activated with `find()` function Picker:reset_selection() self._selection_entry = nil self._selection_row = nil end function Picker:_reset_prefix_color(hl_group) self._current_prefix_hl_group = hl_group or nil if self.prompt_prefix ~= "" and api.nvim_buf_is_valid(self.prompt_bufnr) then utils.hl_range( self.prompt_bufnr, ns_telescope_prompt_prefix, self._current_prefix_hl_group or "TelescopePromptPrefix", { 0, 0 }, { 0, #self.prompt_prefix } ) end end -- TODO(conni2461): Maybe _ prefix these next two functions -- TODO(conni2461): Next two functions only work together otherwise color doesn't work -- Probably a issue with prompt buffers --- Change the prefix in the prompt to be `new_prefix` and apply `hl_group` ---@param new_prefix string: the string to be used as the new prefix ---@param hl_group string: the name of the chosen highlight function Picker:change_prompt_prefix(new_prefix, hl_group) if not new_prefix then return end if new_prefix ~= "" then vim.fn.prompt_setprompt(self.prompt_bufnr, new_prefix) else api.nvim_buf_set_text(self.prompt_bufnr, 0, 0, 0, #self.prompt_prefix, {}) vim.bo[self.prompt_bufnr].buftype = "" end self.prompt_prefix = new_prefix self:_reset_prefix_color(hl_group) end --- Reset the prompt to the provided `text` ---@param text string function Picker:reset_prompt(text) local prompt_text = self.prompt_prefix .. (text or "") api.nvim_buf_set_lines(self.prompt_bufnr, 0, -1, false, { prompt_text }) self:_reset_prefix_color(self._current_prefix_hl_group) if text then api.nvim_win_set_cursor(self.prompt_win, { 1, #prompt_text }) end end ---@param finder finder: telescope finder (see telescope/finders.lua) ---@param opts table: options to pass when refreshing the picker ---@field new_prefix string|table: either as string or { new_string, hl_group } ---@field reset_prompt bool: whether to reset the prompt ---@field multi MultiSelect: multi-selection to persist upon renewing finder (see telescope/pickers/multi.lua) function Picker:refresh(finder, opts) opts = opts or {} if opts.new_prefix then local handle = type(opts.new_prefix) == "table" and unpack or function(x) return x end self:change_prompt_prefix(handle(opts.new_prefix), opts.prefix_hl_group) end if finder then self.finder:close() self.finder = finder self._multi = vim.F.if_nil(opts.multi, MultiSelect:new()) end -- reset already triggers finder loop if opts.reset_prompt then self:reset_prompt() else self._on_lines(nil, nil, nil, 0, 1) end end ---Set the selection to the provided `row` ---@param row number function Picker:set_selection(row) if not self.manager then return end row = self.scroller(self.max_results, self.manager:num_results(), row) if not self:can_select_row(row) then -- If the current selected row exceeds number of currently displayed -- elements we have to reset it. Affects sorting_strategy = 'row'. if not self:can_select_row(self:get_selection_row()) then row = self:get_row(self.manager:num_results()) else log.trace("Cannot select row:", row, self.manager:num_results(), self.max_results) return end end local results_bufnr = self.results_bufnr if not api.nvim_buf_is_valid(results_bufnr) then return end if row > api.nvim_buf_line_count(results_bufnr) then log.debug( string.format("Should not be possible to get row this large %s %s", row, api.nvim_buf_line_count(results_bufnr)) ) return end local entry = self.manager:get_entry(self:get_index(row)) local prompt_location = state.get_global_key "prompt_location" if entry and prompt_location then entry.lnum = prompt_location.row or 0 if prompt_location.col and prompt_location.col > 0 then entry.col = prompt_location.col entry.colend = prompt_location.col + 1 else entry.col = 1 -- we do + 1 here because previewer does -1 entry.colend = 0 end end state.set_global_key("selected_entry", entry) if not entry then -- also refresh previewer when there is no entry selected, so the preview window is cleared self._selection_entry = entry self:refresh_previewer() return end local old_entry -- TODO: Probably should figure out what the rows are that made this happen... -- Probably something with setting a row that's too high for this? -- Not sure. local set_ok, set_errmsg = pcall(function() local prompt = self:_get_prompt() -- Check if previous selection is still visible if self._selection_entry and self.manager:find_entry(self._selection_entry) then -- Find old selection, and update prefix and highlights old_entry = self._selection_entry local old_row = self:get_row(self.manager:find_entry(old_entry)) self._selection_entry = entry if old_row >= 0 then self:update_prefix(old_entry, old_row) self.highlighter:hi_multiselect(old_row, self:is_multi_selected(old_entry)) end else self._selection_entry = entry end local caret = self:update_prefix(entry, row) local display, _ = entry_display.resolve(self, entry) display = caret .. display -- TODO: You should go back and redraw the highlights for this line from the sorter. -- That's the only smart thing to do. if not api.nvim_buf_is_valid(results_bufnr) then log.debug "Invalid buf somehow..." return end -- don't highlight any whitespace at the end of caret self.highlighter:hi_selection(row, caret:match "(.*%S)") self.highlighter:hi_sorter(row, prompt, display) self.highlighter:hi_multiselect(row, self:is_multi_selected(entry)) end) if not set_ok then log.debug(set_errmsg) return end self:refresh_previewer() if old_entry == entry and self._selection_row == row then return end -- TODO: Get row & text in the same obj self._selection_entry = entry self._selection_row = row api.nvim_win_set_cursor(self.results_win, { row + 1, 0 }) end --- Update prefix for entry on a given row function Picker:update_prefix(entry, row) local prefix = function(sel, multi) local t if sel then t = self.selection_caret else t = self.entry_prefix end if multi and type(self.multi_icon) == "string" then t = truncate(t, strdisplaywidth(t) - strdisplaywidth(self.multi_icon), "") .. self.multi_icon end return t end local line = api.nvim_buf_get_lines(self.results_bufnr, row, row + 1, false)[1] if not line then log.trace(string.format("no line found at row %d in buffer %d", row, self.results_bufnr)) return end local old_caret = string.sub(line, 0, #prefix(true)) == prefix(true) and prefix(true) or string.sub(line, 0, #prefix(true, true)) == prefix(true, true) and prefix(true, true) or string.sub(line, 0, #prefix(false)) == prefix(false) and prefix(false) or string.sub(line, 0, #prefix(false, true)) == prefix(false, true) and prefix(false, true) if old_caret == false then log.warn(string.format("can't identify old caret in line: %s", line)) return end local pre = prefix(entry == self._selection_entry, self:is_multi_selected(entry)) -- Only change the first couple characters, nvim_buf_set_text leaves the existing highlights api.nvim_buf_set_text(self.results_bufnr, row, 0, row, #old_caret, { pre }) return pre end --- Refresh the previewer based on the current `status` of the picker function Picker:refresh_previewer() local status = state.get_status(self.prompt_bufnr) if self.previewer and status.layout.preview and status.layout.preview.winid and api.nvim_win_is_valid(status.layout.preview.winid) then self:_increment "previewed" self.previewer:preview(self._selection_entry, status) if self.preview_border then if self.fix_preview_title then return end local new_title = self.previewer:title(self._selection_entry, config.values.dynamic_preview_title) if new_title ~= nil and new_title ~= self.preview_title then self.preview_title = new_title self.layout.preview.border:change_title(new_title) end end end end function Picker:cycle_previewers(next) local size = #self.all_previewers if size == 1 then return end self.current_previewer_index = self.current_previewer_index + next if self.current_previewer_index > size then self.current_previewer_index = 1 elseif self.current_previewer_index < 1 then self.current_previewer_index = size end if self.previewer then self.previewer = self.all_previewers[self.current_previewer_index] self:refresh_previewer() elseif self.hidden_previewer then self.hidden_previewer = self.all_previewers[self.current_previewer_index] end end --- Handler for when entries are added by `self.manager` ---@param index number: the index to add the entry at ---@param entry table: the entry that has been added to the manager ---@param insert boolean: whether the entry has been "inserted" or not function Picker:entry_adder(index, entry, _, insert) if not entry then return end local row = self:get_row(index) -- If it's less than 0, then we don't need to show it at all. if row < 0 then log.debug("ON_ENTRY: Weird row", row) return end local display, display_highlights = entry_display.resolve(self, entry) if not display then log.info("Weird entry", entry) return end -- This is the two spaces to manage the '> ' stuff. -- Maybe someday we can use extmarks or floaty text or something to draw this and not insert here. -- until then, insert two spaces local prefix = self.entry_prefix display = prefix .. display self:_increment "displayed" local offset = insert and 0 or 1 if not api.nvim_buf_is_valid(self.results_bufnr) then log.debug "ON_ENTRY: Invalid buffer" return end -- TODO: Does this every get called? -- local line_count = a.nvim_win_get_height(self.results_win) local line_count = api.nvim_buf_line_count(self.results_bufnr) if row > line_count then return end if insert then if self.sorting_strategy == "descending" then api.nvim_buf_set_lines(self.results_bufnr, 0, 1, false, {}) end end local set_ok, msg = pcall(api.nvim_buf_set_lines, self.results_bufnr, row, row + offset, false, { display }) if set_ok then if display_highlights then self.highlighter:hi_display(row, prefix, display_highlights) end self:update_prefix(entry, row) self:highlight_one_row(self.results_bufnr, self:_get_prompt(), display, row) end if not set_ok then log.debug("Failed to set lines...", msg) end -- This pretty much only fails when people leave newlines in their results. -- So we'll clean it up for them if it fails. if not set_ok and display:find "\n" then display = display:gsub("\n", " | ") api.nvim_buf_set_lines(self.results_bufnr, row, row + 1, false, { display }) end end --- Reset tracked information for this picker function Picker:_reset_track() self.stats.processed = 0 self.stats.displayed = 0 self.stats.display_fn = 0 self.stats.previewed = 0 self.stats.status = 0 self.stats.filtered = 0 self.stats.highlights = 0 end --- Increment the count of the tracked info at `self.stats[key]` ---@param key string function Picker:_increment(key) self.stats[key] = (self.stats[key] or 0) + 1 end --- Decrement the count of the tracked info at `self.stats[key]` ---@param key string function Picker:_decrement(key) self.stats[key] = (self.stats[key] or 0) - 1 end -- TODO: Decide how much we want to use this. -- Would allow for better debugging of items. function Picker:register_completion_callback(cb) table.insert(self._completion_callbacks, cb) end function Picker:clear_completion_callbacks() self._completion_callbacks = {} end function Picker:_on_complete() for _, v in ipairs(self._completion_callbacks) do pcall(v, self) end end --- Close all open Telescope pickers function Picker:close_existing_pickers() for _, prompt_bufnr in ipairs(state.get_existing_prompt_bufnrs()) do pcall(actions.close, prompt_bufnr) end end --- Returns a function that sets virtual text for the count indicator --- e.g. "10/50" as "filtered"/"processed" ---@param prompt_win number ---@param prompt_bufnr number ---@return function function Picker:get_status_updater(prompt_win, prompt_bufnr) return function(opts) if self.closed or not api.nvim_buf_is_valid(prompt_bufnr) then return end local current_prompt = self:_get_prompt() if not current_prompt then return end if not api.nvim_win_is_valid(prompt_win) then return end local text = self:get_status_text(opts) api.nvim_buf_clear_namespace(prompt_bufnr, ns_telescope_prompt, 0, -1) api.nvim_buf_set_extmark(prompt_bufnr, ns_telescope_prompt, 0, 0, { virt_text = { { text, "TelescopePromptCounter" } }, virt_text_pos = "right_align", }) self:_increment "status" end end --- Returns a function that will process an element. --- Returned function handles updating the "filtered" and "processed" counts --- as appropriate and runs the sorters score function ---@param find_id number ---@param prompt string ---@param status_updater function ---@return function function Picker:get_result_processor(find_id, prompt, status_updater) local count = 0 local cb_add = function(score, entry) -- may need the prompt for tiebreak self.manager:add_entry(self, score, entry, prompt) status_updater { completed = false } end local cb_filter = function(_) self:_increment "filtered" end return function(entry) if find_id ~= self._find_id then return true end if not entry or entry.valid == false then return end self:_increment "processed" count = count + 1 -- TODO: Probably should asyncify this / cache this / do something because this probably takes -- a ton of time on large results. log.trace("Processing result... ", entry) for _, v in ipairs(self.file_ignore_patterns or {}) do local file = vim.F.if_nil(entry.filename, type(entry.value) == "string" and entry.value) -- false if none is true if file then if string.find(file, v) then log.trace("SKIPPING", entry.value, "because", v) self:_decrement "processed" return end end end self.sorter:score(prompt, entry, cb_add, cb_filter) end end --- Handles updating the picker after all the entries are scored/processed. ---@param results_bufnr number ---@param _ number ---@param prompt string ---@param status_updater function function Picker:get_result_completor(results_bufnr, _, prompt, status_updater) return vim.schedule_wrap(function() if self.closed == true or self:is_done() then return end self:_do_selection(prompt) status_updater { completed = true } self:clear_extra_rows(results_bufnr) self.sorter:_finish(prompt) if self.sorting_strategy == "descending" then local visible_result_rows = api.nvim_win_get_height(self.results_win) api.nvim_win_set_cursor(self.results_win, { self.max_results - visible_result_rows, 1 }) api.nvim_win_set_cursor(self.results_win, { self.max_results, 1 }) else api.nvim_win_set_cursor(self.results_win, { 1, 0 }) end self:_on_complete() end) end function Picker:_do_selection(prompt) local selection_strategy = self.selection_strategy or "reset" -- TODO: Either: always leave one result or make sure we actually clean up the results when nothing matches if selection_strategy == "row" then if self._selection_row == nil and self.default_selection_index ~= nil then self:set_selection(self:get_row(self.default_selection_index)) else self:set_selection(self:get_selection_row()) end elseif selection_strategy == "follow" then if self._selection_row == nil and self.default_selection_index ~= nil then self:set_selection(self:get_row(self.default_selection_index)) else local index = self.manager:find_entry(self:get_selection()) if index then local follow_row = self:get_row(index) self:set_selection(follow_row) else self:set_selection(self:get_reset_row()) end end elseif selection_strategy == "reset" then if self.default_selection_index ~= nil then self:set_selection(self:get_row(self.default_selection_index)) else self:set_selection(self:get_reset_row()) end elseif selection_strategy == "closest" then if prompt == "" and self.default_selection_index ~= nil then self:set_selection(self:get_row(self.default_selection_index)) else self:set_selection(self:get_reset_row()) end elseif selection_strategy == "none" then if self._selection_entry then local old_entry, old_row = self._selection_entry, self._selection_row self:reset_selection() -- required to reset selection before updating prefix if old_row >= 0 then self:update_prefix(old_entry, old_row) self.highlighter:hi_multiselect(old_row, self:is_multi_selected(old_entry)) end end return else error("Unknown selection strategy: " .. selection_strategy) end end --- Wrapper function for `Picker:new` that incorporates user provided `opts` --- with the telescope `defaults` ---@param opts table ---@param defaults table ---@return Picker pickers.new = function(opts, defaults) opts = opts or {} defaults = defaults or {} local result = {} for k, v in pairs(opts) do assert(type(k) == "string" or type(k) == "number", "Should be string or number, found: " .. type(k)) result[k] = v end for k, v in pairs(defaults) do if result[k] == nil then assert(type(k) == "string", "Should be string, defaults") result[k] = v else -- For attach mappings, we want people to be able to pass in another function -- and apply their mappings after we've applied our defaults. if k == "attach_mappings" then local opt_value = result[k] result[k] = function(...) v(...) return opt_value(...) end end end end if result["previewer"] == false then result["previewer"] = defaults["previewer"] result["__hide_previewer"] = true elseif result["previewer"] == true then result["previewer"] = defaults["previewer"] elseif type(opts["preview"]) == "table" and opts["preview"]["hide_on_startup"] then result["__hide_previewer"] = true end return Picker:new(result) end --- Close the picker which has prompt with buffer number `prompt_bufnr` ---@param prompt_bufnr number function pickers.on_close_prompt(prompt_bufnr) local status = state.get_status(prompt_bufnr) local picker = status.picker require("telescope.actions.state").get_current_history():reset() if type(picker.cache_picker) == "table" then local cached_pickers = state.get_global_key "cached_pickers" or {} if type(picker.cache_picker.index) == "number" then if not vim.tbl_isempty(cached_pickers) then table.remove(cached_pickers, picker.cache_picker.index) end end -- if picker was disabled post-hoc (e.g. `cache_picker = false` conclude after deletion) if picker.cache_picker.disabled ~= true then if picker.cache_picker.limit_entries > 0 then -- edge case: starting in normal mode and not having run a search means having no manager instantiated if picker.manager then picker.manager.linked_states:truncate(picker.cache_picker.limit_entries) else picker.manager = EntryManager:new(picker.max_results, picker.entry_adder, picker.stats) end end local curr_prompt = picker:_get_prompt() picker.default_text = curr_prompt picker.cache_picker.selection_row = picker._selection_row -- Only cache if prompt is not empty or ignore_empty_prompt is false if not picker.cache_picker.ignore_empty_prompt or (curr_prompt and curr_prompt ~= "") then picker.cache_picker.cached_prompt = curr_prompt table.insert(cached_pickers, 1, picker) picker.cache_picker.is_cached = true end -- release pickers if picker.cache_picker.num_pickers > 0 then while #cached_pickers > picker.cache_picker.num_pickers do table.remove(cached_pickers, #cached_pickers) end end state.set_global_key("cached_pickers", cached_pickers) end end if picker.sorter then picker.sorter:_destroy() end if picker.all_previewers then for _, v in ipairs(picker.all_previewers) do v:teardown() end end if picker.finder then picker.finder:close() end -- so we dont call close_windows multiple times we clear that autocmd api.nvim_clear_autocmds { group = "PickerInsert", event = "BufLeave", buffer = prompt_bufnr, } picker.close_windows(status) vim.o.mousemoveevent = picker.__original_mousemoveevent end function pickers.on_resize_window(prompt_bufnr) local status = state.get_status(prompt_bufnr) local picker = status.picker picker:full_layout_update() end --- Get the prompt text without the prompt prefix. function Picker:_get_prompt() local cursor_line = api.nvim_win_get_cursor(self.prompt_win)[1] - 1 return api.nvim_buf_get_lines(self.prompt_bufnr, cursor_line, cursor_line + 1, false)[1]:sub(#self.prompt_prefix + 1) end function Picker:_reset_highlights() self.highlighter:clear_display() api.nvim_buf_clear_namespace(self.results_bufnr, ns_telescope_matching, 0, -1) end -- Toggles whether finder is attached to prompt buffer input function Picker:_toggle_finder_attach() self._finder_attached = not self._finder_attached end function Picker:_detach() self.finder:close() -- TODO: Can we add a "cleanup" / "teardown" function that completely removes these. -- self.finder = nil -- self.previewer = nil -- self.sorter = nil -- self.manager = nil self.closed = true end function Picker:_get_next_filtered_prompt() local prompt = self:_get_prompt() local on_input_result = self._on_input_filter_cb(prompt) or {} local new_prompt = on_input_result.prompt if new_prompt then prompt = new_prompt end local new_finder = on_input_result.updated_finder if new_finder then self.finder:close() self.finder = new_finder end return prompt end function Picker:_resume_picker() -- resume previous picker local index = 1 for entry in self.manager:iter() do self:entry_adder(index, entry, _, true) index = index + 1 end self.cache_picker.is_cached = false local on_resume_complete = function() if api.nvim_buf_is_valid(self.prompt_bufnr) then api.nvim_buf_call(self.prompt_bufnr, function() vim.cmd "do User TelescopeResumePost" end) end end -- if text changed, required to set anew to restart finder; otherwise hl and selection if self.cache_picker.cached_prompt ~= self.default_text then self:set_prompt(self.default_text) on_resume_complete() else -- scheduling required to apply highlighting and selection appropriately await_schedule(function() if self.cache_picker.selection_row ~= nil then self:set_selection(self.cache_picker.selection_row) end on_resume_complete() end) end end pickers._Picker = Picker return pickers ================================================ FILE: lua/telescope/previewers/buffer_previewer.lua ================================================ local api = vim.api local from_entry = require "telescope.from_entry" local Path = require "plenary.path" local utils = require "telescope.utils" local putils = require "telescope.previewers.utils" local Previewer = require "telescope.previewers.previewer" local conf = require("telescope.config").values local global_state = require "telescope.state" local buf_delete = utils.buf_delete local git_command = utils.__git_command local previewers = {} local ns_previewer = api.nvim_create_namespace "telescope.previewers" local has_file = 1 == vim.fn.executable "file" -- TODO(fdschmidt93) switch to Job once file_maker callbacks get cleaned up with plenary async -- avoids SIGABRT from utils.get_os_command_output due to vim.time in fs_stat cb local function capture(cmd, raw) local f = assert(io.popen(cmd, "r")) local s = assert(f:read "*a") f:close() if raw then return s end s = string.gsub(s, "^%s+", "") s = string.gsub(s, "%s+$", "") s = string.gsub(s, "[\n\r]+", " ") return s end local function defaulter(f, default_opts) default_opts = default_opts or {} return { new = function(opts) if opts.preview == false then return false end if conf.preview == false and not opts.preview then return false end opts.preview = type(opts.preview) ~= "table" and {} or opts.preview if type(conf.preview) == "table" then for k, v in pairs(conf.preview) do opts.preview[k] = vim.F.if_nil(opts.preview[k], v) end end return f(opts) end, __call = function() local ok, err = pcall(f(default_opts)) if not ok then error(debug.traceback(err)) end end, } end -- modified vim.split to incorporate a timer local function split(s, sep, plain, opts) opts = opts or {} local t = {} for c in vim.gsplit(s, sep, plain) do local line = opts.file_encoding and vim.iconv(c, opts.file_encoding, "utf8") or c table.insert(t, line) if opts.preview.timeout then local diff_time = (vim.uv.hrtime() - opts.start_time) / 1e6 if diff_time > opts.preview.timeout then return end end end return t end local bytes_to_megabytes = math.pow(1024, 2) local color_hash = { ["p"] = "TelescopePreviewPipe", ["c"] = "TelescopePreviewCharDev", ["d"] = "TelescopePreviewDirectory", ["b"] = "TelescopePreviewBlock", ["l"] = "TelescopePreviewLink", ["s"] = "TelescopePreviewSocket", ["."] = "TelescopePreviewNormal", ["r"] = "TelescopePreviewRead", ["w"] = "TelescopePreviewWrite", ["x"] = "TelescopePreviewExecute", ["-"] = "TelescopePreviewHyphen", ["T"] = "TelescopePreviewSticky", ["S"] = "TelescopePreviewSticky", [2] = "TelescopePreviewSize", [3] = "TelescopePreviewUser", [4] = "TelescopePreviewGroup", [5] = "TelescopePreviewDate", } color_hash[6] = function(line) return color_hash[line:sub(1, 1)] end local colorize_ls_long = function(bufnr, data, sections) local windows_add = Path.path.sep == "\\" and 2 or 0 for lnum, line in ipairs(data) do local section = sections[lnum] for i = 1, section[1].end_index - 1 do -- Highlight permissions local c = line:sub(i, i) utils.hl_range(bufnr, ns_previewer, color_hash[c], { lnum - 1, i - 1 }, { lnum - 1, i }) end for i = 2, #section do -- highlights size, (user, group), date and name local hl_group = color_hash[i + (i ~= 2 and windows_add or 0)] utils.hl_range( bufnr, ns_previewer, type(hl_group) == "function" and hl_group(line) or hl_group, { lnum - 1, section[i].start_index - 1 }, { lnum - 1, section[i].end_index - 1 } ) end end end local handle_directory_preview = function(filepath, bufnr, opts) opts.preview.ls_short = vim.F.if_nil(opts.preview.ls_short, false) local set_colorize_lines if opts.preview.ls_short then set_colorize_lines = function(data, sections) local PATH_SECTION = Path.path.sep == "\\" and 4 or 6 local paths = {} for i, line in ipairs(data) do local section = sections[i][PATH_SECTION] local path = line:sub(section.start_index, section.end_index) table.insert(paths, path) end api.nvim_buf_set_lines(bufnr, 0, -1, false, paths) for i, path in ipairs(paths) do local hl = color_hash[6](data[i]) utils.hl_range(bufnr, ns_previewer, hl, { i - 1, 0 }, { i - 1, #path }) end end else set_colorize_lines = function(data, sections) api.nvim_buf_set_lines(bufnr, 0, -1, false, data) colorize_ls_long(bufnr, data, sections) end end require("plenary.scandir").ls_async(filepath, { hidden = true, group_directories_first = true, on_exit = vim.schedule_wrap(function(data, sections) set_colorize_lines(data, sections) if opts.callback then opts.callback(bufnr) end end), }) end local handle_file_preview = function(filepath, bufnr, stat, opts) vim.schedule(function() opts.ft = opts.use_ft_detect and putils.filetype_detect(filepath) local possible_binary = false if type(opts.preview.filetype_hook) == "function" and opts.ft ~= nil and opts.ft ~= "" then if not opts.preview.filetype_hook(filepath, bufnr, opts) then return end end if opts.preview.check_mime_type == true and has_file and (opts.ft == nil or opts.ft == "") then -- avoid SIGABRT in buffer previewer happening with utils.get_os_command_output local mime_type = capture(string.format([[file --mime-type -b "%s"]], filepath)) if putils.binary_mime_type(mime_type) then if type(opts.preview.mime_hook) == "function" then opts.preview.mime_hook(filepath, bufnr, opts) return else possible_binary = true end end if mime_type[2] == "json" then opts.ft = "json" end end local mb_filesize = stat.size / bytes_to_megabytes if opts.preview.filesize_limit then if mb_filesize > opts.preview.filesize_limit then if type(opts.preview.filesize_hook) == "function" then opts.preview.filesize_hook(filepath, bufnr, opts) else putils.set_preview_message(bufnr, opts.winid, "File exceeds preview size limit", opts.preview.msg_bg_fillchar) end return end end opts.start_time = vim.uv.hrtime() Path:new(filepath):_read_async(vim.schedule_wrap(function(data) if not api.nvim_buf_is_valid(bufnr) then return end local processed_data = split(data, "[\r]?\n", nil, opts) if processed_data then local ok = pcall(api.nvim_buf_set_lines, bufnr, 0, -1, false, processed_data) if not ok then return end -- last resort, if ft is still empty at this point in time, -- we need to determine the filetype using the buffer contents if opts.ft == nil or opts.ft == "" then opts.ft = vim.filetype.match { filename = filepath, buf = bufnr } end -- we need to attempt to call filetype hook at this point "again" -- previously only if we had a valid filetype, now every time -- also if there will never be a filetype if type(opts.preview.filetype_hook) == "function" then if not opts.preview.filetype_hook(filepath, bufnr, opts) then return end end -- if we still dont have a ft we need to display the binary message if (opts.ft == nil or opts.ft == "") and possible_binary then putils.set_preview_message(bufnr, opts.winid, "Binary cannot be previewed", opts.preview.msg_bg_fillchar) return end if opts.callback then opts.callback(bufnr) end if not (opts.preview.highlight_limit and mb_filesize > opts.preview.highlight_limit) then putils.highlighter(bufnr, opts.ft, opts) end else if type(opts.preview.timeout_hook) == "function" then opts.preview.timeout_hook(filepath, bufnr, opts) else putils.set_preview_message(bufnr, opts.winid, "Previewer timed out", opts.preview.msg_bg_fillchar) end return end end)) end) end local PREVIEW_TIMEOUT_MS = 250 local PREVIEW_FILESIZE_MB = 25 local PREVIEW_HIGHLIGHT_MB = 1 previewers.file_maker = function(filepath, bufnr, opts) opts = vim.F.if_nil(opts, {}) opts.preview = vim.F.if_nil(opts.preview, {}) opts.preview.timeout = vim.F.if_nil(opts.preview.timeout, PREVIEW_TIMEOUT_MS) opts.preview.filesize_limit = vim.F.if_nil(opts.preview.filesize_limit, PREVIEW_FILESIZE_MB) opts.preview.highlight_limit = vim.F.if_nil(opts.preview.highlight_limit, PREVIEW_HIGHLIGHT_MB) opts.preview.msg_bg_fillchar = vim.F.if_nil(opts.preview.msg_bg_fillchar, "╱") opts.preview.treesitter = vim.F.if_nil(opts.preview.treesitter, true) if opts.use_ft_detect == nil then opts.use_ft_detect = true end if opts.bufname ~= filepath then if not vim.in_fast_event() then filepath = utils.path_expand(filepath) end vim.uv.fs_stat(filepath, function(_, stat) if not stat then return end if stat.type == "directory" then handle_directory_preview(filepath, bufnr, opts) else handle_file_preview(filepath, bufnr, stat, opts) end end) else if opts.callback then if vim.in_fast_event() then vim.schedule(function() opts.callback(bufnr) end) else opts.callback(bufnr) end end end end local search_cb_jump = function(self, bufnr, query) if not query then return end api.nvim_buf_call(bufnr, function() pcall(vim.fn.matchdelete, self.state.hl_id, self.state.winid) vim.cmd "keepjumps norm! gg" vim.fn.search(query, "W") vim.cmd "norm! zz" self.state.hl_id = vim.fn.matchadd("TelescopePreviewMatch", query) end) end local search_teardown = function(self) if self.state and self.state.hl_id then pcall(vim.fn.matchdelete, self.state.hl_id, self.state.hl_win) self.state.hl_id = nil end end local scroll_fn = function(self, direction) if not self.state then return end local input = direction > 0 and [[]] or [[]] local count = math.abs(direction) api.nvim_win_call(self.state.winid, function() vim.cmd([[normal! ]] .. count .. input) end) end local scroll_horizontal_fn = function(self, direction) if not self.state then return end local input = direction > 0 and [[zl]] or [[zh]] local count = math.abs(direction) api.nvim_win_call(self.state.winid, function() vim.wo[self.state.winid].virtualedit = "all" vim.cmd([[normal! ]] .. count .. input) end) end previewers.new_buffer_previewer = function(opts) opts = opts or {} assert(opts.define_preview, "define_preview 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 bufname_table = {} local preview_window_id local function get_bufnr(self) if not self.state then return nil end return self.state.bufnr end local function set_bufnr(self, value) if self.state then self.state.bufnr = value table.insert(old_bufs, value) end end local function get_bufnr_by_bufname(self, value) if not self.state then return nil end return bufname_table[value] end local function set_bufname(self, value) if self.state then self.state.bufname = value if value then bufname_table[value] = get_bufnr(self) end end end function opts.setup(self) local state = {} if opt_setup then state = 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 local last_nr if opts.keep_last_buf then last_nr = global_state.get_global_key "last_preview_bufnr" -- Push in another buffer so the last one will not be cleaned up if preview_window_id then local bufnr = api.nvim_create_buf(false, true) utils.win_set_buf_noautocmd(preview_window_id, bufnr) end end set_bufnr(self, nil) set_bufname(self, nil) for _, bufnr in ipairs(old_bufs) do if bufnr ~= last_nr then buf_delete(bufnr) end end -- enable resuming picker with existing previewer to avoid lookup of deleted bufs bufname_table = {} end function opts.preview_fn(self, entry, status) local preview_winid = status.layout.preview and status.layout.preview.winid if get_bufnr(self) == nil then set_bufnr(self, api.nvim_win_get_buf(preview_winid)) preview_window_id = preview_winid end if opts.get_buffer_by_name and get_bufnr_by_bufname(self, opts.get_buffer_by_name(self, entry)) then self.state.bufname = opts.get_buffer_by_name(self, entry) self.state.bufnr = get_bufnr_by_bufname(self, self.state.bufname) utils.win_set_buf_noautocmd(preview_winid, self.state.bufnr) else local bufnr = api.nvim_create_buf(false, true) set_bufnr(self, bufnr) vim.bo[bufnr].modifiable = true vim.schedule(function() if api.nvim_buf_is_valid(bufnr) then utils.win_set_buf_noautocmd(preview_winid, bufnr) end end) vim.wo[preview_winid].winhl = "Normal:TelescopePreviewNormal" vim.wo[preview_winid].signcolumn = "no" vim.wo[preview_winid].foldlevel = 100 vim.wo[preview_winid].wrap = false vim.wo[preview_winid].scrollbind = false self.state.winid = preview_winid self.state.bufname = nil end if opts.keep_last_buf then global_state.set_global_key("last_preview_bufnr", self.state.bufnr) end opts.define_preview(self, entry, status) vim.schedule(function() if not self or not self.state or not self.state.bufnr then return end if api.nvim_buf_is_valid(self.state.bufnr) then api.nvim_buf_call(self.state.bufnr, function() api.nvim_exec_autocmds("User", { pattern = "TelescopePreviewerLoaded", data = { title = entry.preview_title, bufname = self.state.bufname, filetype = putils.filetype_detect(self.state.bufname or ""), }, }) end) end end) if opts.get_buffer_by_name then set_bufname(self, opts.get_buffer_by_name(self, entry)) end end if not opts.scroll_fn then opts.scroll_fn = scroll_fn end if not opts.scroll_horizontal_fn then opts.scroll_horizontal_fn = scroll_horizontal_fn end return Previewer:new(opts) end previewers.cat = defaulter(function(opts) opts = opts or {} local cwd = opts.cwd or vim.uv.cwd() return previewers.new_buffer_previewer { title = "File Preview", dyn_title = function(_, entry) return Path:new(from_entry.path(entry, false, false)):normalize(cwd) end, get_buffer_by_name = function(_, entry) return from_entry.path(entry, false, false) end, define_preview = function(self, entry) local p = from_entry.path(entry, true, false) if p == nil or p == "" then return end conf.buffer_previewer_maker(p, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, file_encoding = opts.file_encoding, }) end, } end, {}) previewers.vimgrep = defaulter(function(opts) opts = opts or {} local cwd = opts.cwd or vim.uv.cwd() local jump_to_line = function(self, bufnr, entry) pcall(api.nvim_buf_clear_namespace, bufnr, ns_previewer, 0, -1) if entry.lnum and entry.lnum > 0 then local lnum, lnend = entry.lnum - 1, (entry.lnend or entry.lnum) - 1 local col, colend = 0, -1 -- Both col delimiters should be provided for them to take effect. -- This is to ensure that column range highlighting was opted in, as `col` -- is already used to determine the buffer jump position elsewhere. if entry.col and entry.colend then col, colend = entry.col - 1, entry.colend - 1 end for i = lnum, lnend do pcall( utils.hl_range, bufnr, ns_previewer, "TelescopePreviewLine", { i, i == lnum and col or 0 }, { i, i == lnend and colend or -1 } ) end local middle_ln = math.floor(lnum + (lnend - lnum) / 2) pcall(api.nvim_win_set_cursor, self.state.winid, { middle_ln + 1, 0 }) if bufnr ~= nil then api.nvim_buf_call(bufnr, function() vim.cmd "norm! zz" end) end end end return previewers.new_buffer_previewer { title = "Grep Preview", dyn_title = function(_, entry) return Path:new(from_entry.path(entry, false, false)):normalize(cwd) end, get_buffer_by_name = function(_, entry) return from_entry.path(entry, false, false) end, define_preview = function(self, entry) -- builtin.buffers: bypass path validation for terminal buffers that don't have appropriate path local has_buftype = entry.bufnr and api.nvim_buf_is_valid(entry.bufnr) and vim.bo[entry.bufnr].buftype ~= "" or false local p if not has_buftype then p = from_entry.path(entry, true, false) if p == nil or p == "" then return end end -- Workaround for unnamed buffer when using builtin.buffer if entry.bufnr and (p == "[No Name]" or has_buftype) then local lines = api.nvim_buf_get_lines(entry.bufnr, 0, -1, false) api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines) -- schedule so that the lines are actually there and can be jumped onto when we call jump_to_line vim.schedule(function() jump_to_line(self, self.state.bufnr, entry) end) else conf.buffer_previewer_maker(p, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, callback = function(bufnr) jump_to_line(self, bufnr, entry) end, file_encoding = opts.file_encoding, }) end end, } end, {}) previewers.qflist = previewers.vimgrep previewers.ctags = defaulter(function(opts) local determine_jump = function(entry) if entry.scode then return function(self) -- un-escape / then escape required -- special chars for vim.fn.search() -- ] ~ * local scode = entry.scode:gsub([[\/]], "/"):gsub("[%]~*]", function(x) return "\\" .. x end) pcall(vim.fn.matchdelete, self.state.hl_id, self.state.winid) vim.cmd "keepjumps norm! gg" vim.fn.search(scode, "W") vim.cmd "norm! zz" self.state.hl_id = vim.fn.matchadd("TelescopePreviewMatch", scode) end else return function(self, bufnr) if self.state.last_set_bufnr then pcall(api.nvim_buf_clear_namespace, self.state.last_set_bufnr, ns_previewer, 0, -1) end pcall( utils.hl_range, bufnr, ns_previewer, "TelescopePreviewMatch", { entry.lnum - 1, 0 }, { entry.lnum - 1, -1 } ) pcall(api.nvim_win_set_cursor, self.state.winid, { entry.lnum, 0 }) self.state.last_set_bufnr = bufnr end end end return previewers.new_buffer_previewer { title = "Tags Preview", teardown = function(self) if self.state and self.state.hl_id then pcall(vim.fn.matchdelete, self.state.hl_id, self.state.hl_win) self.state.hl_id = nil elseif self.state and self.state.last_set_bufnr and api.nvim_buf_is_valid(self.state.last_set_bufnr) then api.nvim_buf_clear_namespace(self.state.last_set_bufnr, ns_previewer, 0, -1) end end, get_buffer_by_name = function(_, entry) return entry.filename end, define_preview = function(self, entry) conf.buffer_previewer_maker(entry.filename, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, callback = function(bufnr) pcall(api.nvim_buf_call, bufnr, function() determine_jump(entry)(self, bufnr) end) end, file_encoding = opts.file_encoding, }) end, } end, {}) previewers.builtin = defaulter(function(opts) return previewers.new_buffer_previewer { title = "Grep Preview", teardown = search_teardown, get_buffer_by_name = function(_, entry) return entry.filename end, define_preview = function(self, entry) local module_name = vim.fn.fnamemodify(vim.fn.fnamemodify(entry.filename, ":h"), ":t") local text if entry.text:sub(1, #module_name) ~= module_name then text = module_name .. "." .. entry.text else text = entry.text:gsub("_", ".", 1) end conf.buffer_previewer_maker(entry.filename, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, callback = function(bufnr) search_cb_jump(self, bufnr, text) end, file_encoding = opts.file_encoding, }) end, } end, {}) previewers.help = defaulter(function(opts) return previewers.new_buffer_previewer { title = "Help Preview", teardown = search_teardown, get_buffer_by_name = function(_, entry) return entry.filename end, define_preview = function(self, entry) local query = entry.cmd query = query:sub(2) query = [[\V]] .. query conf.buffer_previewer_maker(entry.filename, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, callback = function(bufnr) putils.highlighter(bufnr, "help", opts) search_cb_jump(self, bufnr, query) end, file_encoding = opts.file_encoding, }) end, } end, {}) previewers.man = defaulter(function(opts) local pager = utils.get_lazy_default(opts.PAGER, function() return vim.fn.executable "col" == 1 and { "col", "-bx" } or { "cat" } end) return previewers.new_buffer_previewer { title = "Man Preview", get_buffer_by_name = function(_, entry) return entry.value .. "/" .. entry.section end, define_preview = function(self, entry) local win_width = api.nvim_win_get_width(self.state.winid) putils.job_maker(vim.deepcopy(pager), self.state.bufnr, { writer = { "man", entry.section, entry.value }, env = { ["MANWIDTH"] = win_width, PATH = vim.env.PATH, MANPATH = vim.env.MANPATH }, value = entry.value .. "/" .. entry.section, bufname = self.state.bufname, }) putils.highlighter(self.state.bufnr, "man", opts) end, } end) previewers.git_branch_log = defaulter(function(opts) local highlight_buffer = function(bufnr, content) for i = 1, #content do local line = content[i] local hstart, hend = line:find "[0-9a-fA-F]+" if hstart then if hend < #line then pcall( utils.hl_range, bufnr, ns_previewer, "TelescopeResultsIdentifier", { i - 1, hstart - 1 }, { i - 1, hend } ) end end local _, cstart = line:find "- %(" if cstart then local cend = string.find(line, "%) ") if cend then pcall(utils.hl_range, bufnr, ns_previewer, "TelescopeResultsConstant", { i - 1, cstart - 1 }, { i - 1, cend }) end end local dstart, _ = line:find " %(%d" if dstart then pcall(utils.hl_range, bufnr, ns_previewer, "TelescopeResultsSpecialComment", { i - 1, dstart }, { #line }) end end end return previewers.new_buffer_previewer { title = "Git Branch Preview", get_buffer_by_name = function(_, entry) return entry.value end, define_preview = function(self, entry) local cmd = git_command({ "--no-pager", "log", "--graph", "--max-count=1000", -- prevent fork bombing with large repos "--pretty=format:%h -%d %s (%cr)", "--abbrev-commit", "--date=relative", entry.value, }, opts) putils.job_maker(cmd, self.state.bufnr, { value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr, content) if not content then return end highlight_buffer(bufnr, content) end, }) end, } end, {}) previewers.git_stash_diff = defaulter(function(opts) return previewers.new_buffer_previewer { title = "Git Stash Preview", get_buffer_by_name = function(_, entry) return entry.value end, define_preview = function(self, entry, _) local cmd = git_command({ "--no-pager", "stash", "show", "-p", entry.value }, opts) putils.job_maker(cmd, self.state.bufnr, { value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) if api.nvim_buf_is_valid(bufnr) then putils.highlighter(bufnr, "diff", opts) end end, }) end, } end, {}) previewers.git_commit_diff_to_parent = defaulter(function(opts) return previewers.new_buffer_previewer { title = "Git Diff to Parent Preview", teardown = search_teardown, get_buffer_by_name = function(_, entry) return entry.value end, define_preview = function(self, entry) local cmd = git_command({ "--no-pager", "diff", entry.value .. "^!" }, opts) if opts.current_file then table.insert(cmd, "--") table.insert(cmd, opts.current_file) end putils.job_maker(cmd, self.state.bufnr, { value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) if api.nvim_buf_is_valid(bufnr) then search_cb_jump(self, bufnr, opts.current_line) putils.highlighter(bufnr, "diff", opts) end end, }) end, } end, {}) previewers.git_commit_diff_to_head = defaulter(function(opts) return previewers.new_buffer_previewer { title = "Git Diff to Head Preview", teardown = search_teardown, get_buffer_by_name = function(_, entry) return entry.value end, define_preview = function(self, entry) local cmd = git_command({ "--no-pager", "diff", "--cached", entry.value }, opts) if opts.current_file then table.insert(cmd, "--") table.insert(cmd, opts.current_file) end putils.job_maker(cmd, self.state.bufnr, { value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) if api.nvim_buf_is_valid(bufnr) then search_cb_jump(self, bufnr, opts.current_line) putils.highlighter(bufnr, "diff", opts) end end, }) end, } end, {}) previewers.git_commit_diff_as_was = defaulter(function(opts) return previewers.new_buffer_previewer { title = "Git Show Preview", teardown = search_teardown, get_buffer_by_name = function(_, entry) return entry.value end, define_preview = function(self, entry) local cmd = git_command({ "--no-pager", "show" }, opts) local cf = opts.current_file and Path:new(opts.current_file):make_relative(opts.cwd) local value = cf and (entry.value .. ":" .. cf) or entry.value local ft = cf and putils.filetype_detect(value) or "diff" table.insert(cmd, value) putils.job_maker(cmd, self.state.bufnr, { value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) if api.nvim_buf_is_valid(bufnr) then search_cb_jump(self, bufnr, opts.current_line) putils.highlighter(bufnr, ft, opts) end end, }) end, } end, {}) previewers.git_commit_message = defaulter(function(opts) local hl_map = { "TelescopeResultsIdentifier", "TelescopePreviewUser", "TelescopePreviewDate", } return previewers.new_buffer_previewer { title = "Git Message", get_buffer_by_name = function(_, entry) return entry.value end, define_preview = function(self, entry) local cmd = git_command({ "--no-pager", "log", "-n 1", entry.value }, opts) putils.job_maker(cmd, self.state.bufnr, { value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr, content) if not content then return end for k, v in ipairs(hl_map) do local _, s = content[k]:find "%s" if s then utils.hl_range(bufnr, ns_previewer, v, { k - 1, s }, { k - 1, #content[k] }) end end end, }) end, } end, {}) previewers.git_file_diff = defaulter(function(opts) return previewers.new_buffer_previewer { title = "Git File Diff Preview", get_buffer_by_name = function(_, entry) return entry.value end, define_preview = function(self, entry) if entry.status and (entry.status == "??" or entry.status == "A ") then local p = from_entry.path(entry, true, false) if p == nil or p == "" then return end conf.buffer_previewer_maker(p, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, file_encoding = opts.file_encoding, }) else local cmd = git_command({ "--no-pager", "diff", "HEAD", "--", entry.value }, opts) putils.job_maker(cmd, self.state.bufnr, { value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) if api.nvim_buf_is_valid(bufnr) then putils.highlighter(bufnr, "diff", opts) end end, }) end end, } end, {}) previewers.autocommands = defaulter(function(_) return previewers.new_buffer_previewer { title = "Autocommands Preview", teardown = function(self) if self.state and self.state.last_set_bufnr and api.nvim_buf_is_valid(self.state.last_set_bufnr) then pcall(api.nvim_buf_clear_namespace, self.state.last_set_bufnr, ns_previewer, 0, -1) end end, get_buffer_by_name = function(_, entry) return entry.value.group_name end, define_preview = function(self, entry, status) local results = vim.tbl_filter(function(x) return x.value.group_name == entry.value.group_name end, status.picker.finder.results) if self.state.last_set_bufnr then pcall(api.nvim_buf_clear_namespace, self.state.last_set_bufnr, ns_previewer, 0, -1) end local preview_winid = status.layout.preview and status.layout.preview.winid local selected_row = 0 if self.state.bufname ~= entry.value.group_name then local display = {} table.insert(display, string.format(" augroup: %s - [ %d entries ]", entry.value.group_name, #results)) -- TODO: calculate banner width/string in setup() -- TODO: get column characters to be the same HL group as border table.insert(display, string.rep("─", vim.fn.getwininfo(preview_winid)[1].width)) for idx, item in ipairs(results) do if item == entry then selected_row = idx end table.insert( display, string.format(" %-14s▏%-08s %s", item.value.event, item.value.pattern, item.value.command) ) end vim.bo[self.state.bufnr].filetype = "vim" api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, display) else for idx, item in ipairs(results) do if item == entry then selected_row = idx break end end end utils.hl_range( self.state.bufnr, ns_previewer, "TelescopePreviewLine", { selected_row + 1, 0 }, { selected_row + 1, -1 } ) -- set the cursor position after self.state.bufnr is connected to the -- preview window (which is scheduled in new_buffer_previewer) vim.schedule(function() pcall(api.nvim_win_set_cursor, preview_winid, { selected_row, 0 }) end) self.state.last_set_bufnr = self.state.bufnr end, } end, {}) previewers.highlights = defaulter(function(_) return previewers.new_buffer_previewer { title = "Highlights Preview", teardown = function(self) if self.state and self.state.last_set_bufnr and api.nvim_buf_is_valid(self.state.last_set_bufnr) then api.nvim_buf_clear_namespace(self.state.last_set_bufnr, ns_previewer, 0, -1) end end, get_buffer_by_name = function() return "highlights" end, define_preview = function(self, entry) if not self.state.bufname then local output = utils.split_lines(vim.fn.execute "highlight") local hl_groups = {} for _, v in ipairs(output) do if v ~= "" then if v:sub(1, 1) == " " then local part_of_old = v:match "%s+(.*)" hl_groups[#hl_groups] = hl_groups[#hl_groups] .. part_of_old else table.insert(hl_groups, v) end end end api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, hl_groups) for k, v in ipairs(hl_groups) do local startPos = string.find(v, "xxx", 1, true) - 1 local endPos = startPos + 3 local hlgroup = string.match(v, "([^ ]*)%s+.*") pcall(utils.hl_range, self.state.bufnr, 0, hlgroup, { k - 1, startPos }, { k - 1, endPos }) end end vim.schedule(function() api.nvim_buf_call(self.state.bufnr, function() vim.cmd "keepjumps norm! gg" vim.fn.search("^" .. entry.value .. " ") local lnum = api.nvim_win_get_cursor(self.state.winid)[1] -- That one is actually a match but its better to use it like that then matchadd pcall(api.nvim_buf_clear_namespace, self.state.bufnr, ns_previewer, 0, -1) utils.hl_range( self.state.bufnr, ns_previewer, "TelescopePreviewMatch", { lnum - 1, 0 }, { lnum - 1, #entry.value } ) -- we need to zz after the highlighting otherwise highlighting doesnt work vim.cmd "norm! zz" end) end) end, } end, {}) previewers.pickers = defaulter(function(_) local ns_telescope_multiselection = api.nvim_create_namespace "telescope_mulitselection" local get_row = function(picker, preview_height, index) if picker.sorting_strategy == "ascending" then return index - 1 else return preview_height - index end end return previewers.new_buffer_previewer { dyn_title = function(_, entry) if entry.value.default_text and entry.value.default_text ~= "" then return string.format("%s ─ %s", entry.value.prompt_title, entry.value.default_text) end return entry.value.prompt_title end, get_buffer_by_name = function(_, entry) return tostring(entry.value.prompt_bufnr) end, teardown = function(self) if self.state and self.state.last_set_bufnr and api.nvim_buf_is_valid(self.state.last_set_bufnr) then api.nvim_buf_clear_namespace(self.state.last_set_bufnr, ns_telescope_multiselection, 0, -1) end end, define_preview = function(self, entry) api.nvim_buf_call(self.state.bufnr, function() local ns_telescope_entry = api.nvim_create_namespace "telescope_entry" local preview_height = api.nvim_win_get_height(self.state.winid) if self.state.bufname then return end local picker = entry.value -- prefill buffer to be able to set lines individually local placeholder = utils.repeated_table(preview_height, "") api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, placeholder) for index = 1, math.min(preview_height, picker.manager:num_results()) do local row = get_row(picker, preview_height, index) local e = picker.manager:get_entry(index) local display, display_highlight -- if-clause as otherwise function return values improperly unpacked if type(e.display) == "function" then display, display_highlight = e:display() else display = e.display end api.nvim_buf_set_lines(self.state.bufnr, row, row + 1, false, { display }) if display_highlight ~= nil then for _, hl_block in ipairs(display_highlight) do utils.hl_range( self.state.bufnr, ns_telescope_entry, hl_block[2], { row, hl_block[1][1] }, { row, hl_block[1][2] } ) end end if picker._multi:is_selected(e) then utils.hl_range( self.state.bufnr, ns_telescope_multiselection, "TelescopeMultiSelection", { row, 0 }, { row, -1 } ) end end end) end, } end, {}) previewers.display_content = defaulter(function(_) return previewers.new_buffer_previewer { define_preview = function(self, entry) assert( type(entry.preview_command) == "function", "entry must provide a preview_command function which will put the content into the buffer" ) api.nvim_buf_call(self.state.bufnr, function() entry.preview_command(entry, self.state.bufnr) end) end, } end, {}) return previewers ================================================ FILE: lua/telescope/previewers/init.lua ================================================ ---@tag telescope.previewers ---@config { ["module"] = "telescope.previewers" } ---@brief [[ --- Provides a Previewer table that has to be implemented by each previewer. --- To achieve this, this module also provides two wrappers that abstract most --- of the work and make it really easy to create new previewers. --- - `previewers.new_termopen_previewer` --- - `previewers.new_buffer_previewer` --- --- Furthermore, there are a collection of previewers already defined which --- can be used for every picker, as long as the entries of the picker provide --- the necessary fields. The more important ones are --- - `previewers.cat` --- - `previewers.vimgrep` --- - `previewers.qflist` --- - `previewers.vim_buffer_cat` --- - `previewers.vim_buffer_vimgrep` --- - `previewers.vim_buffer_qflist` --- --- Previewers can be disabled for any builtin or custom picker by doing --- :Telescope find_files previewer=false ---@brief ]] local Previewer = require "telescope.previewers.previewer" local term_previewer = require "telescope.previewers.term_previewer" local buffer_previewer = require "telescope.previewers.buffer_previewer" local previewers = {} --- This is the base table all previewers have to implement. It's possible to --- write a wrapper for this because most previewers need to have the same --- keys set. --- Examples of wrappers are: --- - `new_buffer_previewer` --- - `new_termopen_previewer` --- --- To create a new table do following: --- - `local new_previewer = Previewer:new(opts)` --- --- What `:new` expects is listed below --- --- The interface provides the following set of functions. All of them, besides --- `new`, will be handled by telescope pickers. --- - `:new(opts)` --- - `:preview(entry, status)` --- - `:teardown()` --- - `:send_input(input)` --- - `:scroll_fn(direction)` --- - `:scroll_horizontal_fn(direction)` --- --- `Previewer:new()` expects a table as input with following keys: --- - `setup` function(self): Will be called the first time preview will be --- called. --- - `teardown` function(self): Will be called on clean up. --- - `preview_fn` function(self, entry, status): Will be called each time --- a new entry was selected. --- - `title` function(self): Will return the static title of the previewer. --- - `dynamic_title` function(self, entry): Will return the dynamic title of --- the previewer. Will only be called --- when config value dynamic_preview_title --- is true. --- - `send_input` function(self, input): This is meant for --- `termopen_previewer` and it can be --- used to send input to the terminal --- application, like less. --- - `scroll_fn` function(self, direction): Used to make scrolling work. --- - `scroll_horizontal_fn` function(self, direction): Used to make --- horizontal scrolling work. previewers.Previewer = Previewer --- A shorthand for creating a new Previewer. --- The provided table will be forwarded to `Previewer:new(...)` previewers.new = function(...) return Previewer:new(...) end --- Is a wrapper around Previewer and helps with creating a new --- `termopen_previewer`. --- --- It requires you to specify one table entry `get_command(entry, status)`. --- This `get_command` function has to return the terminal command that will be --- executed for each entry. Example: --- --- get_command = function(entry, status) --- return { 'bat', entry.path } --- end --- --- --- Additionally you can define: --- - `title` a static title for example "File Preview" --- - `dyn_title(self, entry)` a dynamic title function which gets called --- when config value `dynamic_preview_title = true` --- - `env` table: define environment variables to forward to the terminal --- process. Example: --- - `{ ['PAGER'] = '', ['MANWIDTH'] = 50 }` --- --- It's an easy way to get your first previewer going and it integrates well --- with `bat` and `less`. Providing out of the box scrolling if the command --- uses less. --- --- Furthermore, if `env` is not set, it will forward all `config.set_env` environment variables to --- that terminal process. previewers.new_termopen_previewer = term_previewer.new_termopen_previewer --- Provides a `termopen_previewer` which has the ability to display files. --- It will always show the top of the file and has support for --- `bat`(prioritized) and `cat`. Each entry has to provide either the field --- `path` or `filename` in order to make this previewer work. --- --- The preferred way of using this previewer is like this --- `require('telescope.config').values.cat_previewer` --- This will respect user configuration and will use `buffer_previewers` in --- case it's configured that way. previewers.cat = term_previewer.cat --- Provides a `termopen_previewer` which has the ability to display files at --- the provided line. It has support for `bat`(prioritized) and `cat`. --- Each entry has to provide either the field `path` or `filename` and --- a `lnum` field in order to make this previewer work. --- --- The preferred way of using this previewer is like this --- `require('telescope.config').values.grep_previewer` --- This will respect user configuration and will use `buffer_previewers` in --- case it's configured that way. previewers.vimgrep = term_previewer.vimgrep --- Provides a `termopen_previewer` which has the ability to display files at --- the provided line or range. It has support for `bat`(prioritized) and --- `cat`. Each entry has to provide either the field `path` or `filename`, --- `lnum` and a `start` and `finish` range in order to make this previewer --- work. --- --- The preferred way of using this previewer is like this --- `require('telescope.config').values.qflist_previewer` --- This will respect user configuration and will use buffer previewers in --- case it's configured that way. previewers.qflist = term_previewer.qflist --- An interface to instantiate a new `buffer_previewer`. --- That means that the content actually lives inside a vim buffer which --- enables us more control over the actual content. For example, we can --- use `vim.fn.search` to jump to a specific line or reuse buffers/already --- opened files more easily. --- This interface is more complex than `termopen_previewer` but offers more --- flexibility over your content. --- It was designed to display files but was extended to also display the --- output of terminal commands. --- --- In the following options, state table and general tips are mentioned to --- make your experience with this previewer more seamless. --- --- --- options: --- - `define_preview = function(self, entry, status)` (required) --- Is called for each selected entry, after each selection_move --- (up or down) and is meant to handle things like reading file, --- jump to line or attach a highlighter. --- - `setup = function(self)` (optional) --- Is called once at the beginning, before the preview for the first --- entry is displayed. You can return a table of vars that will be --- available in `self.state` in each `define_preview` call. --- - `teardown = function(self)` (optional) --- Will be called at the end, when the picker is being closed and is --- meant to clean up everything that was allocated by the previewer. --- The `buffer_previewer` will automatically clean up all created buffers. --- So you only need to handle things that were introduced by you. --- - `keep_last_buf = true` (optional) --- Will not delete the last selected buffer. This would allow you to --- reuse that buffer in the select action. For example, that buffer can --- be opened in a new split, rather than recreating that buffer in --- an action. To access the last buffer number: --- `require('telescope.state').get_global_key("last_preview_bufnr")` --- - `get_buffer_by_name = function(self, entry)` --- Allows you to set a unique name for each buffer. This is used for --- caching purposes. `self.state.bufname` will be nil if the entry was --- never loaded or the unique name when it was loaded once. For example, --- useful if you have one file but multiple entries. This happens for grep --- and lsp builtins. So to make the cache work only load content if --- `self.state.bufname ~= entry.your_unique_key` --- - `title` a static title for example "File Preview" --- - `dyn_title(self, entry)` a dynamic title function which gets called --- when config value `dynamic_preview_title = true` --- --- `self.state` table: --- - `self.state.bufnr` --- Is the current buffer number, in which you have to write the loaded --- content. --- Don't create a buffer yourself, otherwise it's not managed by the --- buffer_previewer interface and you will probably be better off --- writing your own interface. --- - self.state.winid --- Current window id. Useful if you want to set the cursor to a provided --- line number. --- - self.state.bufname --- Will return the current buffer name, if `get_buffer_by_name` is --- defined. nil will be returned if the entry was never loaded or when --- `get_buffer_by_name` is not set. --- --- Tips: --- - If you want to display content of a terminal job, use: --- `require('telescope.previewers.utils').job_maker(cmd, bufnr, opts)` --- - `cmd` table: for example { 'git', 'diff', entry.value } --- - `bufnr` number: in which the content will be written --- - `opts` table: with following keys --- - `bufname` string: used for cache --- - `value` string: used for cache --- - `mode` string: either "insert" or "append". "insert" is default --- - `env` table: define environment variables. Example: --- - `{ ['PAGER'] = '', ['MANWIDTH'] = 50 }` --- - `cwd` string: define current working directory for job --- - `callback` function(bufnr, content): will be called when job --- is done. Content will be nil if job is already loaded. --- So you can do highlighting only the first time the previewer --- is created for that entry. --- Use the returned `bufnr` and not `self.state.bufnr` in callback, --- because state can already be changed at this point in time. --- - If you want to attach a highlighter use: --- - `require('telescope.previewers.utils').highlighter(bufnr, ft)` --- - This will prioritize tree sitter highlighting if available for --- environment and language. --- - `require('telescope.previewers.utils').regex_highlighter(bufnr, ft)` --- - `require('telescope.previewers.utils').ts_highlighter(bufnr, ft)` --- - If you want to use `vim.fn.search` or similar you need to run it in --- that specific buffer context. Do --- --- vim.api.nvim_buf_call(bufnr, function() --- -- for example `search` and `matchadd` --- end) --- --- to achieve that. --- - If you want to read a file into the buffer it's best to use --- `buffer_previewer_maker`. But access this function with --- `require('telescope.config').values.buffer_previewer_maker` --- because it can be redefined by users. previewers.new_buffer_previewer = buffer_previewer.new_buffer_previewer --- A universal way of reading a file into a buffer previewer. --- It handles async reading, cache, highlighting, displaying directories --- and provides a callback which can be used, to jump to a line in the buffer. ---@param filepath string: String to the filepath, will be expanded ---@param bufnr number: Where the content will be written ---@param opts table: keys: `use_ft_detect`, `bufname` and `callback` previewers.buffer_previewer_maker = buffer_previewer.file_maker --- A previewer that is used to display a file. It uses the `buffer_previewer` --- interface and won't jump to the line. To integrate this one into your --- own picker make sure that the field `path` or `filename` is set for --- each entry. --- The preferred way of using this previewer is like this --- `require('telescope.config').values.file_previewer` --- This will respect user configuration and will use `termopen_previewer` in --- case it's configured that way. previewers.vim_buffer_cat = buffer_previewer.cat --- A previewer that is used to display a file and jump to the provided line. --- It uses the `buffer_previewer` interface. To integrate this one into your --- own picker make sure that the field `path` or `filename` and `lnum` is set --- in each entry. If the latter is not present, it will default to the first --- line. Additionally, `lnend`, `col` and `colend` can be set to highlight a --- text range instead of a single line. All line/column values are 1-indexed. --- The preferred way of using this previewer is like this --- `require('telescope.config').values.grep_previewer` --- This will respect user configuration and will use `termopen_previewer` in --- case it's configured that way. previewers.vim_buffer_vimgrep = buffer_previewer.vimgrep --- Is the same as `vim_buffer_vimgrep` and only exists for consistency with --- `term_previewers`. --- --- The preferred way of using this previewer is like this --- `require('telescope.config').values.qflist_previewer` --- This will respect user configuration and will use `termopen_previewer` in --- case it's configured that way. previewers.vim_buffer_qflist = buffer_previewer.qflist --- A previewer that shows a log of a branch as graph previewers.git_branch_log = buffer_previewer.git_branch_log --- A previewer that shows a diff of a stash previewers.git_stash_diff = buffer_previewer.git_stash_diff --- A previewer that shows a diff of a commit to a parent commit.
--- The run command is `git --no-pager diff SHA^! -- $CURRENT_FILE` --- --- The current file part is optional. So is only uses it with bcommits. previewers.git_commit_diff_to_parent = buffer_previewer.git_commit_diff_to_parent --- A previewer that shows a diff of a commit to head.
--- The run command is `git --no-pager diff --cached $SHA -- $CURRENT_FILE` --- --- The current file part is optional. So is only uses it with bcommits. previewers.git_commit_diff_to_head = buffer_previewer.git_commit_diff_to_head --- A previewer that shows a diff of a commit as it was.
--- The run command is `git --no-pager show $SHA:$CURRENT_FILE` or `git --no-pager show $SHA` previewers.git_commit_diff_as_was = buffer_previewer.git_commit_diff_as_was --- A previewer that shows the commit message of a diff.
--- The run command is `git --no-pager log -n 1 $SHA` previewers.git_commit_message = buffer_previewer.git_commit_message --- A previewer that shows the current diff of a file. Used in git_status.
--- The run command is `git --no-pager diff $FILE` previewers.git_file_diff = buffer_previewer.git_file_diff previewers.ctags = buffer_previewer.ctags previewers.builtin = buffer_previewer.builtin previewers.help = buffer_previewer.help previewers.man = buffer_previewer.man previewers.autocommands = buffer_previewer.autocommands previewers.highlights = buffer_previewer.highlights previewers.pickers = buffer_previewer.pickers --- A deprecated way of displaying content more easily. Was written at a time, --- where the buffer_previewer interface wasn't present. Nowadays it's easier --- to just use this. We will keep it around for backwards compatibility --- because some extensions use it. --- It doesn't use cache or some other clever tricks. previewers.display_content = buffer_previewer.display_content return previewers ================================================ FILE: lua/telescope/previewers/previewer.lua ================================================ local utils = require "telescope.utils" local Previewer = {} Previewer.__index = Previewer local force_function_wrap = function(value) if value ~= nil then if type(value) ~= "function" then return function() return tostring(value) end else return value end end end function Previewer:new(opts) opts = opts or {} return setmetatable({ state = nil, _title_fn = force_function_wrap(opts.title), _dyn_title_fn = force_function_wrap(opts.dyn_title), _setup_func = opts.setup, _teardown_func = opts.teardown, _send_input = opts.send_input, _scroll_fn = opts.scroll_fn, _scroll_horizontal_fn = opts.scroll_horizontal_fn, preview_fn = opts.preview_fn, _empty_bufnr = nil, }, Previewer) end function Previewer:preview(entry, status) if not entry then if not self._empty_bufnr then self._empty_bufnr = vim.api.nvim_create_buf(false, true) end if vim.api.nvim_buf_is_valid(self._empty_bufnr) then vim.api.nvim_win_set_buf(status.layout.preview.winid, self._empty_bufnr) end return end if not self.state then if self._setup_func then self.state = self:_setup_func(status) else self.state = {} end end return self:preview_fn(entry, status) end function Previewer:title(entry, dynamic) if dynamic == true and self._dyn_title_fn ~= nil then if entry == nil then if self._title_fn ~= nil then return self:_title_fn() else return "" end end return self:_dyn_title_fn(entry) end if self._title_fn ~= nil then return self:_title_fn() end end function Previewer:teardown() if self._empty_bufnr then utils.buf_delete(self._empty_bufnr) end if self._teardown_func then self:_teardown_func() end end function Previewer:send_input(input) if self._send_input then self:_send_input(input) else utils.notify("previewer.send_input", { msg = "not defined for this previewer", level = "ERROR" }) end end function Previewer:scroll_fn(direction) if self._scroll_fn then self:_scroll_fn(direction) else utils.notify("previewer.scroll_fn", { msg = "not defined for this previewer", level = "ERROR" }) end end function Previewer:scroll_horizontal_fn(direction) if self._scroll_horizontal_fn then self:_scroll_horizontal_fn(direction) else utils.notify("previewer.scroll_horizontal", { msg = "not defined for this previewer", level = "ERROR" }) end end return Previewer ================================================ FILE: lua/telescope/previewers/term_previewer.lua ================================================ local api = vim.api 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 putil = require "telescope.previewers.utils" 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.uv.fs_stat(utils.path_expand(filename)) or {} end local list_dir = (function() if vim.fn.has "win32" == 1 then return function(dirname) return { "cmd.exe", "/c", "dir", utils.path_expand(dirname) } end else return function(dirname) return { "ls", "-la", utils.path_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 vim.list_extend(command, { "--highlight-line", lnum }) end if has_less then if start then vim.list_extend(command, { "--pager", string.format("less -RS +%s", start) }) else vim.list_extend(command, { "--pager", "less -RS" }) end else if start and finish then vim.list_extend(command, { "-r", string.format("%s:%s", start, finish) }) end end return utils.flatten { command, bat_options, "--", utils.path_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 mime_type = utils.get_os_command_output({ "file", "--mime-type", "-b", filename })[1] if putil.binary_mime_type(mime_type) 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), utils.path_expand(filename) } else return { "less", "-RS", utils.path_expand(filename) } end else return { "cat", "--", utils.path_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 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 state = 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) local preview_winid = status.layout.preview and status.layout.preview.winid if get_bufnr(self) == nil then set_bufnr(self, api.nvim_win_get_buf(preview_winid)) end local prev_bufnr = get_bufnr_by_bufentry(self, entry) if prev_bufnr then set_bufnr(self, prev_bufnr) utils.win_set_buf_noautocmd(preview_winid, self.state.termopen_bufnr) self.state.termopen_id = term_ids[self.state.termopen_bufnr] else local bufnr = api.nvim_create_buf(false, true) set_bufnr(self, bufnr) utils.win_set_buf_noautocmd(preview_winid, bufnr) local term_opts = { cwd = opts.cwd or vim.uv.cwd(), env = opts.env or conf.set_env, } local cmd = opts.get_command(entry, status) if cmd then api.nvim_buf_call(bufnr, function() --TODO(clason): replace with jobstart when dropping support for Nvim 0.10 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 = api.nvim_replace_termcodes(input, true, false, true) local term_id = get_term_id(self) if term_id then if not utils.job_is_running(term_id) then return end 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.uv.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.uv.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.layout.preview and status.layout.preview.winid local height = 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.bo[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.uv.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.layout.preview and status.layout.preview.winid local height = 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 ================================================ FILE: lua/telescope/previewers/utils.lua ================================================ local api = vim.api local ts_utils = require "telescope.utils" local strings = require "plenary.strings" local conf = require("telescope.config").values local Job = require "plenary.job" local Path = require "plenary.path" local utils = {} local detect_from_shebang = function(p) local s = p:readbyterange(0, 256) if s then local lines = ts_utils.split_lines(s) return vim.filetype.match { contents = lines } end end local parse_modeline = function(tail) if tail:find "vim:" then return tail:match ".*:ft=([^: ]*):.*$" or "" end end local detect_from_modeline = function(p) local s = p:readbyterange(-256, 256) if s then local lines = ts_utils.split_lines(s) local idx = lines[#lines] ~= "" and #lines or #lines - 1 if idx >= 1 then return parse_modeline(lines[idx]) end end end utils.filetype_detect = function(filepath) if type(filepath) ~= string then filepath = tostring(filepath) end local match = vim.filetype.match { filename = filepath } if match and match ~= "" then return match end local p = Path:new(filepath) if p and p:is_file() then match = detect_from_shebang(p) if match and match ~= "" then return match end match = detect_from_modeline(p) if match and match ~= "" then return match end end end -- API helper functions for buffer previewer --- Job maker for buffer previewer utils.job_maker = function(cmd, bufnr, opts) opts = opts or {} opts.mode = opts.mode or "insert" -- bufname and value are optional -- if passed, they will be use as the cache key -- if any of them are missing, cache will be skipped if opts.bufname ~= opts.value or not opts.bufname or not opts.value then local command = table.remove(cmd, 1) local writer = (function() if opts.writer ~= nil then local wcommand = table.remove(opts.writer, 1) return Job:new { command = wcommand, args = opts.writer, env = opts.env, cwd = opts.cwd, } end end)() Job:new({ command = command, args = cmd, env = opts.env, cwd = opts.cwd, writer = writer, on_exit = vim.schedule_wrap(function(j) if not api.nvim_buf_is_valid(bufnr) then return end if opts.mode == "append" then api.nvim_buf_set_lines(bufnr, -1, -1, false, j:result()) elseif opts.mode == "insert" then api.nvim_buf_set_lines(bufnr, 0, -1, false, j:result()) end if opts.callback then opts.callback(bufnr, j:result()) end end), }):start() else if opts.callback then opts.callback(bufnr) end end end local function has_filetype(ft) return ft and ft ~= "" end --- Attach default highlighter which will choose between regex and ts utils.highlighter = function(bufnr, ft, opts) opts = vim.F.if_nil(opts, {}) opts.preview = vim.F.if_nil(opts.preview, {}) opts.preview.treesitter = (function() if type(opts.preview) == "table" and opts.preview.treesitter then return opts.preview.treesitter end if type(conf.preview) == "table" and conf.preview.treesitter then return conf.preview.treesitter end if type(conf.preview) == "boolean" then return conf.preview end -- We should never get here return false end)() if type(opts.preview.treesitter) == "boolean" then local temp = { enable = opts.preview.treesitter } opts.preview.treesitter = temp end local ts_highlighting = (function() if type(opts.preview.treesitter.enable) == "table" then if vim.tbl_contains(opts.preview.treesitter.enable, ft) then return true end return false end if vim.tbl_contains(vim.F.if_nil(opts.preview.treesitter.disable, {}), ft) then return false end return opts.preview.treesitter.enable == nil or opts.preview.treesitter.enable == true end)() local ts_success if ts_highlighting then ts_success = utils.ts_highlighter(bufnr, ft) end if not ts_highlighting or ts_success == false then utils.regex_highlighter(bufnr, ft) end end --- Attach regex highlighter utils.regex_highlighter = function(bufnr, ft) if has_filetype(ft) then return pcall(api.nvim_set_option_value, "syntax", ft, { buf = bufnr }) end return false end -- Attach ts highlighter utils.ts_highlighter = function(bufnr, ft) if has_filetype(ft) then local lang = vim.treesitter.language.get_lang(ft) or ft if lang and ts_utils.has_ts_parser(lang) then return vim.treesitter.start(bufnr, lang) end end return false end utils.set_preview_message = function(bufnr, winid, message, fillchar) fillchar = vim.F.if_nil(fillchar, "╱") local height = api.nvim_win_get_height(winid) local width = api.nvim_win_get_width(winid) api.nvim_buf_set_lines( bufnr, 0, -1, false, ts_utils.repeated_table(height, table.concat(ts_utils.repeated_table(width, fillchar), "")) ) local anon_ns = api.nvim_create_namespace "" local padding = table.concat(ts_utils.repeated_table(#message + 4, " "), "") local formatted_message = " " .. message .. " " -- Populate lines table based on height local lines = {} if height == 1 then lines[1] = formatted_message else for i = 1, math.min(height, 3), 1 do if i % 2 == 0 then lines[i] = formatted_message else lines[i] = padding end end end api.nvim_buf_set_extmark(bufnr, anon_ns, 0, 0, { end_line = height, hl_group = "TelescopePreviewMessageFillchar" }) local col = math.floor((width - strings.strdisplaywidth(formatted_message)) / 2) for i, line in ipairs(lines) do local line_pos = math.floor(height / 2) - 2 + i api.nvim_buf_set_extmark( bufnr, anon_ns, math.max(line_pos, 0), 0, { virt_text = { { line, "TelescopePreviewMessage" } }, virt_text_pos = "overlay", virt_text_win_col = col } ) end end --- Check if mime type is binary. --- NOT an exhaustive check, may get false negatives. Ideally should check --- filetype with `vim.filetype.match` or `filetype_detect` first for filetype --- info. ---@param mime_type string ---@return boolean utils.binary_mime_type = function(mime_type) local type_, subtype = unpack(vim.split(mime_type, "/")) if vim.tbl_contains({ "text", "inode" }, type_) then return false end if vim.tbl_contains({ "json", "javascript" }, subtype) then return false end return true end return utils ================================================ FILE: lua/telescope/sorters.lua ================================================ local log = require "telescope.log" local util = require "telescope.utils" local sorters = {} local ngram_highlighter = function(ngram_len, prompt, display) local highlights = {} display = display:lower() for disp_index = 1, #display do local char = display:sub(disp_index, disp_index + ngram_len - 1) if prompt:find(char, 1, true) then table.insert(highlights, { start = disp_index, finish = disp_index + ngram_len - 1, }) end end return highlights end local FILTERED = -1 local Sorter = {} Sorter.__index = Sorter ---@class Sorter --- Sorter sorts a list of results by return a single integer for a line, --- given a prompt --- --- Lower number is better (because it's like a closer match) --- But, any number below 0 means you want that line filtered out. ---@field scoring_function function: Function that has the interface: (sorter, prompt, line): number ---@field tags table: Unique tags collected at filtering for tag completion ---@field filter_function function: Function that can filter results ---@field highlighter function: Highlights results to display them pretty ---@field discard boolean: Whether this is a discardable style sorter or not. ---@field score function: Override the score function if desired. ---@field init function: Function to run when creating sorter ---@field start function: Function to run on every new prompt ---@field finish function: Function to run after every new prompt ---@field destroy function: Functo to run when destroying sorter function Sorter:new(opts) opts = opts or {} return setmetatable({ score = opts.score, state = {}, tags = opts.tags, -- State management init = opts.init, start = opts.start, finish = opts.finish, destroy = opts.destroy, _status = nil, filter_function = opts.filter_function, scoring_function = opts.scoring_function, highlighter = opts.highlighter, discard = opts.discard, _discard_state = { filtered = {}, prompt = "", }, }, Sorter) end function Sorter:_init() self._status = "init" if self.init then self:init() end end function Sorter:_destroy() self._status = "destroy" if self.destroy then self:destroy() end end -- TODO: We could make this a bit smarter and cache results "as we go" and where they got filtered. -- Then when we hit backspace, we don't have to re-caculate everything. -- Prime did a lot of the hard work already, but I don't want to copy as much memory around -- as he did in his example. -- Example can be found in ./scratch/prime_prompt_cache.lua function Sorter:_start(prompt) self._status = "start" if self.start then self:start(prompt) end if not self.discard then return end local previous = self._discard_state.prompt local len_previous = #previous if #prompt < len_previous then log.trace "Reset discard because shorter prompt" self._discard_state.filtered = {} elseif string.sub(prompt, 1, len_previous) ~= previous then log.trace "Reset discard no match" self._discard_state.filtered = {} end self._discard_state.prompt = prompt end function Sorter:_finish(prompt) self._status = "finish" if self.finish then self:finish(prompt) end end -- TODO: Consider doing something that makes it so we can skip the filter checks -- if we're not discarding. Also, that means we don't have to check otherwise as well :) function Sorter:score(prompt, entry, cb_add, cb_filter) if not entry or not entry.ordinal then return end if self._status and self._status ~= "start" then return end local ordinal = entry.ordinal if self:_was_discarded(prompt, ordinal) then return cb_filter(entry) end local filter_score if self.filter_function ~= nil then if self.tags then self.tags:insert(entry) end filter_score, prompt = self:filter_function(prompt, entry, cb_add, cb_filter) end if filter_score == FILTERED then return cb_filter(entry) end local score = self:scoring_function(prompt or "", ordinal, entry, cb_add, cb_filter) if score == FILTERED then self:_mark_discarded(prompt, ordinal) return cb_filter(entry) end if cb_add then return cb_add(score, entry) else return score end end function Sorter:_was_discarded(prompt, ordinal) return self.discard and self._discard_state.filtered[ordinal] end function Sorter:_mark_discarded(prompt, ordinal) if not self.discard then return end self._discard_state.filtered[ordinal] = true end function sorters.new(...) return Sorter:new(...) end sorters.Sorter = Sorter local make_cached_tail = function() local os_sep = util.get_separator() local match_string = "[^" .. os_sep .. "]*$" return setmetatable({}, { __index = function(t, k) local tail = string.match(k, match_string) rawset(t, k, tail) return tail end, }) end local make_cached_uppers = function() return setmetatable({}, { __index = function(t, k) local obj = {} for i = 1, #k do local s_byte = k:byte(i, i) if s_byte <= 90 and s_byte >= 65 then obj[s_byte] = true end end rawset(t, k, obj) return obj end, }) end -- TODO: Match on upper case words -- TODO: Match on last match sorters.get_fuzzy_file = function(opts) opts = opts or {} local ngram_len = opts.ngram_len or 2 local cached_ngrams = {} local function overlapping_ngrams(s, n) if cached_ngrams[s] and cached_ngrams[s][n] then return cached_ngrams[s][n] end local R = {} for i = 1, s:len() - n + 1 do R[#R + 1] = s:sub(i, i + n - 1) end if not cached_ngrams[s] then cached_ngrams[s] = {} end cached_ngrams[s][n] = R return R end local cached_tails = make_cached_tail() local cached_uppers = make_cached_uppers() return Sorter:new { scoring_function = function(_, prompt, line) local N = #prompt if N == 0 or N < ngram_len then -- TODO: If the character is in the line, -- then it should get a point or somethin. return 1 end local prompt_lower = prompt:lower() local line_lower = line:lower() local prompt_lower_ngrams = overlapping_ngrams(prompt_lower, ngram_len) -- Contains the original string local contains_string = line_lower:find(prompt_lower, 1, true) local prompt_uppers = cached_uppers[prompt] local line_uppers = cached_uppers[line] local uppers_matching = 0 for k, _ in pairs(prompt_uppers) do if line_uppers[k] then uppers_matching = uppers_matching + 1 end end -- TODO: Consider case senstivity local tail = cached_tails[line_lower] local contains_tail = tail:find(prompt, 1, true) local consecutive_matches = 0 local previous_match_index = 0 local match_count = 0 for i = 1, #prompt_lower_ngrams do local match_start = line_lower:find(prompt_lower_ngrams[i], 1, true) if match_start then match_count = match_count + 1 if match_start > previous_match_index then consecutive_matches = consecutive_matches + 1 end previous_match_index = match_start end end local tail_modifier = 1 if contains_tail then tail_modifier = 2 end local denominator = ( (10 * match_count / #prompt_lower_ngrams) -- biases for shorter strings + 3 * match_count * ngram_len / #line + consecutive_matches + N / (contains_string or (2 * #line)) -- + 30/(c1 or 2*N) -- TODO: It might be possible that this too strongly correlates, -- but it's unlikely for people to type capital letters without actually -- wanting to do something with a capital letter in it. + uppers_matching ) * tail_modifier if denominator == 0 or denominator ~= denominator then return -1 end if #prompt > 2 and denominator < 0.5 then return -1 end return 1 / denominator end, highlighter = opts.highlighter or function(_, prompt, display) return ngram_highlighter(ngram_len, prompt, display) end, } end sorters.get_generic_fuzzy_sorter = function(opts) opts = opts or {} local ngram_len = opts.ngram_len or 2 local cached_ngrams = {} local function overlapping_ngrams(s, n) if cached_ngrams[s] and cached_ngrams[s][n] then return cached_ngrams[s][n] end local R = {} for i = 1, s:len() - n + 1 do R[#R + 1] = s:sub(i, i + n - 1) end if not cached_ngrams[s] then cached_ngrams[s] = {} end cached_ngrams[s][n] = R return R end return Sorter:new { -- self -- prompt (which is the text on the line) -- line (entry.ordinal) -- entry (the whole entry) scoring_function = function(_, prompt, line, _) if prompt == 0 or #prompt < ngram_len then return 1 end local prompt_lower = prompt:lower() local line_lower = line:lower() local prompt_ngrams = overlapping_ngrams(prompt_lower, ngram_len) local N = #prompt local contains_string = line_lower:find(prompt_lower, 1, true) local consecutive_matches = 0 local previous_match_index = 0 local match_count = 0 for i = 1, #prompt_ngrams do local match_start = line_lower:find(prompt_ngrams[i], 1, true) if match_start then match_count = match_count + 1 if match_start > previous_match_index then consecutive_matches = consecutive_matches + 1 end previous_match_index = match_start end end -- TODO: Copied from ashkan. local denominator = ( (10 * match_count / #prompt_ngrams) -- biases for shorter strings -- TODO(ashkan): this can bias towards repeated finds of the same -- subpattern with overlapping_ngrams + 3 * match_count * ngram_len / #line + consecutive_matches + N / (contains_string or (2 * #line)) -- + 30/(c1 or 2*N) ) if denominator == 0 or denominator ~= denominator then return -1 end if #prompt > 2 and denominator < 0.5 then return -1 end return 1 / denominator end, highlighter = opts.highlighter or function(_, prompt, display) return ngram_highlighter(ngram_len, prompt, display) end, } end sorters.fuzzy_with_index_bias = function(opts) opts = opts or {} opts.ngram_len = 2 -- TODO: Probably could use a better sorter here. local fuzzy_sorter = sorters.get_generic_fuzzy_sorter(opts) return Sorter:new { scoring_function = function(_, prompt, line, entry, cb_add, cb_filter) local base_score = fuzzy_sorter:scoring_function(prompt, line, cb_add, cb_filter) if base_score == FILTERED then return FILTERED end if not base_score or base_score == 0 then return entry.index else return math.min(math.pow(entry.index, 0.25), 2) * base_score end end, highlighter = fuzzy_sorter.highlighter, } end -- Sorter using the fzy algorithm sorters.get_fzy_sorter = function(opts) opts = opts or {} local fzy = opts.fzy_mod or require "telescope.algos.fzy" local OFFSET = -fzy.get_score_floor() return sorters.Sorter:new { discard = true, scoring_function = function(_, prompt, line) -- Check for actual matches before running the scoring alogrithm. if not fzy.has_match(prompt, line) then return -1 end local fzy_score = fzy.score(prompt, line) -- The fzy score is -inf for empty queries and overlong strings. Since -- this function converts all scores into the range (0, 1), we can -- convert these to 1 as a suitable "worst score" value. if fzy_score == fzy.get_score_min() then return 1 end -- Poor non-empty matches can also have negative values. Offset the score -- so that all values are positive, then invert to match the -- telescope.Sorter "smaller is better" convention. Note that for exact -- matches, fzy returns +inf, which when inverted becomes 0. return 1 / (fzy_score + OFFSET) end, -- The fzy.positions function, which returns an array of string indices, is -- compatible with telescope's conventions. It's moderately wasteful to -- call call fzy.score(x,y) followed by fzy.positions(x,y): both call the -- fzy.compute function, which does all the work. But, this doesn't affect -- perceived performance. highlighter = function(_, prompt, display) return fzy.positions(prompt, display) end, } end -- TODO: Could probably do something nice where we check their conf -- and choose their default for this. -- But I think `fzy` is good default for now. sorters.highlighter_only = function(opts) opts = opts or {} local fzy = opts.fzy_mod or require "telescope.algos.fzy" return Sorter:new { scoring_function = function() return 1 end, highlighter = function(_, prompt, display) return fzy.positions(prompt, display) end, } end sorters.empty = function() return Sorter:new { scoring_function = function() return 1 end, } end -- Bad & Dumb Sorter sorters.get_levenshtein_sorter = function() return Sorter:new { scoring_function = function(_, prompt, line) return require "telescope.algos.string_distance"(prompt, line) end, } end local substr_highlighter = function(make_display) return function(_, prompt, display) local highlights = {} display = make_display(prompt, display) local search_terms = util.max_split(prompt, "%s") local hl_start, hl_end for _, word in pairs(search_terms) do hl_start, hl_end = display:find(word, 1, true) if hl_start then table.insert(highlights, { start = hl_start, finish = hl_end }) end end return highlights end end sorters.get_substr_matcher = function() local make_display = vim.o.smartcase and function(prompt, display) local has_upper_case = not not prompt:match "%u" return has_upper_case and display or display:lower() end or function(_, display) return display:lower() end return Sorter:new { highlighter = substr_highlighter(make_display), scoring_function = function(_, prompt, _, entry) if #prompt == 0 then return 1 end local display = make_display(prompt, entry.ordinal) local search_terms = util.max_split(prompt, "%s") for _, word in pairs(search_terms) do if not display:find(word, 1, true) then return -1 end end return entry.index end, } end local substr_matcher = function(_, prompt, line, _) local display = line:lower() local search_terms = util.max_split(prompt:lower(), "%s") local matched = 0 local total_search_terms = 0 for _, word in pairs(search_terms) do total_search_terms = total_search_terms + 1 if display:find(word, 1, true) then matched = matched + 1 end end return matched == total_search_terms and 0 or FILTERED end local filter_function = function(opts) local scoring_function = vim.F.if_nil(opts.filter_function, substr_matcher) local tag = vim.F.if_nil(opts.tag, "ordinal") return function(_, prompt, entry) local filter = "^(" .. opts.delimiter .. "(%S+)" .. "[" .. opts.delimiter .. "%s]" .. ")" local matched = prompt:match(filter) if matched == nil then return 0, prompt end -- clear prompt of tag prompt = prompt:sub(#matched + 1, -1) local query = vim.trim(matched:gsub(opts.delimiter, "")) return scoring_function(_, query, entry[tag], _), prompt end end local function create_tag_set(tag) tag = vim.F.if_nil(tag, "ordinal") local set = {} return setmetatable(set, { __index = { insert = function(set_, entry) local value = entry[tag] if not set_[value] then set_[value] = true end end, }, }) end sorters.prefilter = function(opts) local sorter = opts.sorter opts.delimiter = vim.F.if_nil(opts.delimiter, ":") sorter._delimiter = opts.delimiter sorter.tags = create_tag_set(opts.tag) sorter.filter_function = filter_function(opts) sorter._was_discarded = function() return false end return sorter end return sorters ================================================ FILE: lua/telescope/state.lua ================================================ 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_prompt_bufnrs() local prompt_bufnrs = {} for key, _ in pairs(TelescopeGlobalState) do if type(key) == "number" then table.insert(prompt_bufnrs, key) end end return prompt_bufnrs end return state ================================================ FILE: lua/telescope/testharness/helpers.lua ================================================ local test_helpers = {} test_helpers.get_picker = function() local state = require "telescope.state" return state.get_status(vim.api.nvim_get_current_buf()).picker end test_helpers.get_results_bufnr = function() local state = require "telescope.state" return state.get_status(vim.api.nvim_get_current_buf()).layout.results.bufnr end test_helpers.get_file = function() return vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":t") end test_helpers.get_prompt = function() return vim.api.nvim_buf_get_lines(0, 0, -1, false)[1] end test_helpers.get_results = function() return vim.api.nvim_buf_get_lines(test_helpers.get_results_bufnr(), 0, -1, false) end test_helpers.get_best_result = function() local results = test_helpers.get_results() local picker = test_helpers.get_picker() if picker.sorting_strategy == "ascending" then return results[1] else return results[#results] end end test_helpers.get_selection = function() local state = require "telescope.state" return state.get_global_key "selected_entry" end test_helpers.get_selection_value = function() return test_helpers.get_selection().value end test_helpers.make_globals = function() GetFile = test_helpers.get_file -- luacheck: globals GetFile GetPrompt = test_helpers.get_prompt -- luacheck: globals GetPrompt GetResults = test_helpers.get_results -- luacheck: globals GetResults GetBestResult = test_helpers.get_best_result -- luacheck: globals GetBestResult GetSelection = test_helpers.get_selection -- luacheck: globals GetSelection GetSelectionValue = test_helpers.get_selection_value -- luacheck: globals GetSelectionValue end return test_helpers ================================================ FILE: lua/telescope/testharness/init.lua ================================================ local assert = require "luassert" local Path = require "plenary.path" local tester = {} tester.debug = false local get_results_from_contents = function(content) local nvim = vim.fn.jobstart( { "nvim", "--noplugin", "-u", "scripts/minimal_init.vim", "--headless", "--embed" }, { rpc = true } ) local result = vim.fn.rpcrequest(nvim, "nvim_exec_lua", content, {}) assert.are.same(true, result[1], vim.inspect(result)) local count = 0 while vim.fn.rpcrequest(nvim, "nvim_exec_lua", "return require('telescope.testharness.runner').state.done", {}) ~= true do count = count + 1 vim.wait(100) -- TODO: Could maybe wait longer, but it's annoying to wait if the test is going to timeout. if count > 100 then break end end local state = vim.fn.rpcrequest(nvim, "nvim_exec_lua", "return require('telescope.testharness.runner').state", {}) vim.fn.jobstop(nvim) assert.are.same(true, state.done, vim.inspect(state)) local result_table = {} for _, v in ipairs(state.results) do table.insert(result_table, v) end return result_table, state end local check_results = function(results, state) assert(state, "Must pass state") for _, v in ipairs(results) do local assertion if not v._type or v._type == "are" or v._type == "_default" then assertion = assert.are.same else assertion = assert.are_not.same end -- TODO: I think it would be nice to be able to see the state, -- but it clutters up the test output so much here. -- -- So we would have to consider how to do that I think. assertion(v.expected, v.actual, string.format("Test Case: %s // %s", v.location, v.case)) end end tester.run_string = function(contents) contents = [[ return (function() local tester = require('telescope.testharness') local runner = require('telescope.testharness.runner') local helper = require('telescope.testharness.helpers') helper.make_globals() local ok, msg = pcall(function() runner.log("Loading Test") ]] .. contents .. [[ end) return {ok, msg or runner.state} end)() ]] check_results(get_results_from_contents(contents)) end tester.run_file = function(filename) local file = "./lua/tests/pickers/" .. filename .. ".lua" local path = Path:new(file) if not path:exists() then assert.are.same("", file) end local contents = string.format( [[ return (function() local runner = require('telescope.testharness.runner') local helper = require('telescope.testharness.helpers') helper.make_globals() local ok, msg = pcall(function() runner.log("Loading Test") return loadfile("%s")() end) return {ok, msg or runner.state} end)() ]], path:absolute() ) check_results(get_results_from_contents(contents)) end tester.not_ = function(val) val._type = "are_not" return val end return tester ================================================ FILE: lua/telescope/testharness/runner.lua ================================================ local builtin = require "telescope.builtin" local DELAY = vim.g.telescope_test_delay or 50 local runner = {} -- State is test variable runner.state = { done = false, results = {}, msgs = {}, } local writer = function(val) table.insert(runner.state.results, val) end local invalid_test_case = function(k) error { case = k, expected = "", actual = k } end local _VALID_KEYS = { post_typed = true, post_close = true, } local replace_terms = function(input) return vim.api.nvim_replace_termcodes(input, true, false, true) end runner.nvim_feed = function(text, feed_opts) feed_opts = feed_opts or "m" vim.api.nvim_feedkeys(text, feed_opts, true) end local end_test_cases = function() runner.state.done = true end local execute_test_case = function(location, key, spec) local ok, actual = pcall(spec[2]) if not ok then writer { location = "Error: " .. location, case = key, expected = "To succeed and return: " .. tostring(spec[1]), actual = actual, _type = spec._type, } end_test_cases() else writer { location = location, case = key, expected = spec[1], actual = actual, _type = spec._type, } end return ok end runner.log = function(msg) table.insert(runner.state.msgs, msg) end runner.picker = function(picker_name, input, test_cases, opts) opts = opts or {} for k, _ in pairs(test_cases) do if not _VALID_KEYS[k] then return invalid_test_case(k) end end opts.on_complete = { runner.create_on_complete(input, test_cases), } opts._on_error = function(self, msg) runner.state.done = true writer { location = "Error while running on complete", expected = "To Work", actual = msg, } end runner.log "Starting picker" builtin[picker_name](opts) runner.log "Called picker" end runner.create_on_complete = function(input, test_cases) input = replace_terms(input) local actions = {} for i = 1, #input do local char = input:sub(i, i) table.insert(actions, { cb = function() runner.log("Inserting char: " .. char) runner.nvim_feed(char, "") end, char = char, }) end return function() local action repeat action = table.remove(actions, 1) if action then action.cb() end until not action or string.match(action.char, "%g") if #actions > 0 then return end vim.defer_fn(function() if test_cases.post_typed then for k, v in ipairs(test_cases.post_typed) do if not execute_test_case("post_typed", k, v) then return end end end vim.defer_fn(function() runner.nvim_feed(replace_terms "", "") vim.defer_fn(function() if test_cases.post_close then for k, v in ipairs(test_cases.post_close) do if not execute_test_case("post_close", k, v) then return end end end vim.defer_fn(end_test_cases, DELAY) end, DELAY) end, DELAY) end, DELAY) end end return runner ================================================ FILE: lua/telescope/themes.lua ================================================ -- Prototype Theme System (WIP) -- Currently certain designs need a number of parameters. -- -- local opts = themes.get_dropdown { winblend = 3 } ---@tag telescope.themes ---@config { ["module"] = "telescope.themes" } ---@brief [[ --- Themes are ways to combine several elements of styling together. --- --- They are helpful for managing the several different UI aspects for telescope and provide --- a simple interface for users to get a particular "style" of picker. ---@brief ]] local themes = {} --- Dropdown style theme. --- --- Usage: --- --- local opts = {...} -- picker options --- local builtin = require('telescope.builtin') --- local themes = require('telescope.themes') --- builtin.find_files(themes.get_dropdown(opts)) --- function themes.get_dropdown(opts) opts = opts or {} local theme_opts = { theme = "dropdown", results_title = false, sorting_strategy = "ascending", layout_strategy = "center", layout_config = { preview_cutoff = 1, -- Preview should always show (unless previewer = false) width = function(_, max_columns, _) return math.min(max_columns, 80) end, height = function(_, _, max_lines) return math.min(max_lines, 15) end, }, border = true, borderchars = { prompt = { "─", "│", " ", "│", "╭", "╮", "│", "│" }, results = { "─", "│", "─", "│", "├", "┤", "╯", "╰" }, preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, }, } if opts.layout_config and opts.layout_config.prompt_position == "bottom" then theme_opts.borderchars = { prompt = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, results = { "─", "│", "─", "│", "╭", "╮", "┤", "├" }, preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, } end return vim.tbl_deep_extend("force", theme_opts, opts) end --- Cursor style theme. --- --- Usage: --- --- local opts = {...} -- picker options --- local builtin = require('telescope.builtin') --- local themes = require('telescope.themes') --- builtin.find_files(themes.get_cursor(opts)) --- function themes.get_cursor(opts) opts = opts or {} local theme_opts = { theme = "cursor", sorting_strategy = "ascending", results_title = false, layout_strategy = "cursor", layout_config = { width = 80, height = 9, }, borderchars = { prompt = { "─", "│", " ", "│", "╭", "╮", "│", "│" }, results = { "─", "│", "─", "│", "├", "┤", "╯", "╰" }, preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, }, } return vim.tbl_deep_extend("force", theme_opts, opts) end --- Ivy style theme. --- --- Usage: --- --- local opts = {...} -- picker options --- local builtin = require('telescope.builtin') --- local themes = require('telescope.themes') --- builtin.find_files(themes.get_ivy(opts)) --- function themes.get_ivy(opts) opts = opts or {} local theme_opts = { theme = "ivy", sorting_strategy = "ascending", layout_strategy = "bottom_pane", layout_config = { height = 25, }, border = true, borderchars = { prompt = { "─", " ", " ", " ", "─", "─", " ", " " }, results = { " " }, preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, }, } if opts.layout_config and opts.layout_config.prompt_position == "bottom" then theme_opts.borderchars = { prompt = { " ", " ", "─", " ", " ", " ", "─", "─" }, results = { "─", " ", " ", " ", "─", "─", " ", " " }, preview = { "─", " ", "─", "│", "┬", "─", "─", "╰" }, } end return vim.tbl_deep_extend("force", theme_opts, opts) end return themes ================================================ FILE: lua/telescope/utils.lua ================================================ ---@tag telescope.utils ---@config { ["module"] = "telescope.utils" } ---@brief [[ --- Utilities for writing telescope pickers ---@brief ]] local api = vim.api 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.iswin = vim.uv.os_uname().sysname == "Windows_NT" utils.nvim011 = vim.fn.has "nvim-0.11" == 1 ---TODO(clason): remove when dropping support for Nvim 0.10 utils.hl_range = utils.nvim011 and vim.hl.range or vim.highlight.range ---TODO(clason): remove when dropping support for Nvim 0.10 utils.str_byteindex = utils.nvim011 and vim.str_byteindex or vim.lsp.util._str_byteindex_enc ---TODO(clason): remove when dropping support for Nvim 0.10 ---@param k string ---@param v any ---@param ty type utils.validate = utils.nvim011 and vim.validate or function(k, v, ty) vim.validate { [k] = { v, ty } } end ---@param t table ---@return table utils.flatten = function(t) return vim.iter(t):flatten():totable() end --- Hybrid of `vim.fn.expand()` and custom `vim.fs.normalize()` --- --- Paths starting with '%', '#' or '<' are expanded with `vim.fn.expand()`. --- Otherwise avoids using `vim.fn.expand()` due to its overly aggressive --- expansion behavior which can sometimes lead to errors or the creation of --- non-existent paths when dealing with valid absolute paths. --- --- Other paths will have '~' and environment variables expanded. --- Unlike `vim.fs.normalize()`, backslashes are preserved. This has better --- compatibility with `plenary.path` and also avoids mangling valid Unix paths --- with literal backslashes. --- --- Trailing slashes are trimmed. With the exception of root paths. --- eg. `/` on Unix or `C:\` on Windows --- ---@param path string ---@return string utils.path_expand = function(path) utils.validate("path", path, "string") if utils.is_uri(path) then return path end if path:match "^[%%#<]" then path = vim.fn.expand(path) end if path:sub(1, 1) == "~" then local home = vim.uv.os_homedir() or "~" if home:sub(-1) == "\\" or home:sub(-1) == "/" then home = home:sub(1, -2) end path = home .. path:sub(2) end path = path:gsub("%$([%w_]+)", vim.uv.os_getenv) path = path:gsub("/+", "/") if utils.iswin then path = path:gsub("\\+", "\\") if path:match "^%w:\\$" then return path else return (path:gsub("(.)\\$", "%1")) end end return (path:gsub("(.)/$", "%1")) end 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, post_filter) 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 if type(post_filter) == "function" then filtered_symbols = post_filter(filtered_symbols) end if not vim.tbl_isempty(filtered_symbols) then 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 return {} end local path_filename_first = function(path, reverse_directories) local dirs = vim.split(path, utils.get_separator()) local filename if reverse_directories then dirs = utils.reverse_table(dirs) filename = table.remove(dirs, 1) else filename = table.remove(dirs, #dirs) end local tail = table.concat(dirs, utils.get_separator()) -- Trim prevents a top-level filename to have a trailing white space local transformed_path = vim.trim(filename .. " " .. tail) local path_style = { { { #filename, #transformed_path }, "TelescopeResultsComment" } } return transformed_path, path_style end local calc_result_length = function(truncate_len) local status = get_status(api.nvim_get_current_buf()) local len = api.nvim_win_get_width(status.layout.results.winid) - status.picker.selection_caret:len() - 2 return type(truncate_len) == "number" and len - truncate_len or len end local path_truncate = function(path, truncate_len, opts) if opts.__length == nil then opts.__length = calc_result_length(truncate_len) end if opts.__prefix == nil then opts.__prefix = 0 end return truncate(path, opts.__length - opts.__prefix, nil, -1) end local path_shorten = function(path, length, exclude) if exclude ~= nil then return Path:new(path):shorten(length, exclude) else return Path:new(path):shorten(length) end end local path_abs = function(path, opts) local cwd if opts.cwd then cwd = opts.cwd if not vim.in_fast_event() then cwd = utils.path_expand(opts.cwd) end else cwd = vim.uv.cwd() end return Path:new(path):make_relative(cwd) end -- IMPORTANT: This function should have been a local function as it's only used -- in this file, but the code was already exported a long time ago. By making it -- local we would potential break consumers of this method. utils.path_smart = (function() local paths = {} local os_sep = utils.get_separator() return function(filepath) local final = filepath if #paths ~= 0 then local dirs = vim.split(filepath, os_sep) local max = 1 for _, p in pairs(paths) do if #p > 0 and p ~= filepath then local _dirs = vim.split(p, os_sep) 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 os_sep 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 ".." .. os_sep .. final else return filepath end end end)() utils.path_tail = (function() local os_sep = utils.get_separator() if os_sep == "/" then 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 else return function(path) for i = #path, 1, -1 do local c = path:sub(i, i) if c == os_sep or c == "/" then return path:sub(i + 1, -1) end end return path end 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 utils.is_uri = function(filename) local char = string.byte(filename, 1) or 0 -- is alpha? if char < 65 or (char > 90 and char < 97) or char > 122 then return false end for i = 2, #filename do char = string.byte(filename, i) if char == 58 then -- `:` return i < #filename and string.byte(filename, i + 1) ~= 92 -- `\` elseif not ( (char >= 48 and char <= 57) -- 0-9 or (char >= 65 and char <= 90) -- A-Z or (char >= 97 and char <= 122) -- a-z or char == 43 -- `+` or char == 46 -- `.` or char == 45 -- `-` ) then return false end end return false 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|nil: The path that should be formatted ---@return string: path to be displayed ---@return table: The transformed path ready to be displayed with the styling utils.transform_path = function(opts, path) if path == nil then return "", {} end if utils.is_uri(path) then return path, {} end ---@type fun(opts:table, path: string): string, table? local path_display = vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display) local transformed_path = path local path_style = {} if type(path_display) == "function" then local custom_transformed_path, custom_path_style = path_display(opts, transformed_path) return custom_transformed_path, custom_path_style or path_style elseif utils.is_path_hidden(nil, path_display) then return "", path_style elseif type(path_display) == "table" then if vim.tbl_contains(path_display, "tail") or path_display.tail then return utils.path_tail(transformed_path), path_style end if not vim.tbl_contains(path_display, "absolute") and not path_display.absolute then transformed_path = path_abs(transformed_path, opts) end if vim.tbl_contains(path_display, "smart") or path_display.smart then transformed_path = utils.path_smart(transformed_path) end if vim.tbl_contains(path_display, "shorten") or path_display["shorten"] ~= nil then local length local exclude = nil if type(path_display["shorten"]) == "table" then local shorten = path_display["shorten"] length = shorten.len exclude = shorten.exclude else length = type(path_display["shorten"]) == "number" and path_display["shorten"] end transformed_path = path_shorten(transformed_path, length, exclude) end if vim.tbl_contains(path_display, "truncate") or path_display.truncate then transformed_path = path_truncate(transformed_path, path_display.truncate, opts) end if vim.tbl_contains(path_display, "filename_first") or path_display["filename_first"] ~= nil then local reverse_directories = false if type(path_display["filename_first"]) == "table" then local filename_first_opts = path_display["filename_first"] if filename_first_opts.reverse_directories == nil or filename_first_opts.reverse_directories == false then reverse_directories = false else reverse_directories = filename_first_opts.reverse_directories end end transformed_path, path_style = path_filename_first(transformed_path, reverse_directories) end return transformed_path, path_style else log.warn("`path_display` must be either a function or a table.", "See `:help telescope.defaults.path_display.") return transformed_path, path_style 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 api.nvim_buf_is_valid(bufnr) and api.nvim_buf_is_loaded(bufnr) then 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 api.nvim_win_is_valid(win_id) then return end local bufnr = api.nvim_win_get_buf(win_id) if bdelete then utils.buf_delete(bufnr) end if not api.nvim_win_is_valid(win_id) then return end if not pcall(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 -- IMPORTANT: This function should have been a local function as it's only used -- in this file, but the code was already exported a long time ago. By making it -- local we would potential break consumers of this method. 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), ""):gsub("", ""):gsub(" ", "") 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" 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 -- IMPORTANT: This function should have been a local function as it's only used -- in this file, but the code was already exported a long time ago. By making it -- local we would potential break consumers of this method. 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) --- TODO(clason): remove when dropping support for Nvim 0.10 --- Checks if treesitter parser for language is installed utils.has_ts_parser = utils.nvim011 and vim.treesitter.language.add or function(lang) return pcall(vim.treesitter.language.add, lang) 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 --- Generate git command optionally with git env variables ---@param args string[] ---@param opts? table ---@return string[] utils.__git_command = function(args, opts) opts = opts or {} local _args = { "git" } if opts.gitdir then vim.list_extend(_args, { "--git-dir", opts.gitdir }) end if opts.toplevel then vim.list_extend(_args, { "--work-tree", opts.toplevel }) end return vim.list_extend(_args, args) end utils.list_find = function(func, list) for i, v in ipairs(list) do if func(v, i, list) then return i, v end end end --- Takes the path and parses optional cursor location `$file:$line:$column` --- If line or column not present `0` returned. ---@param path string ---@return string path ---@return integer? lnum ---@return integer? col utils.__separate_file_path_location = function(path) local location_numbers = {} for i = #path, 1, -1 do if path:sub(i, i) == ":" then if i == #path then path = path:sub(1, i - 1) else local location_value = tonumber(path:sub(i + 1)) if location_value then table.insert(location_numbers, location_value) path = path:sub(1, i - 1) if #location_numbers == 2 then -- There couldn't be more than 2 : separated number break end end end end end if #location_numbers == 2 then -- because of the reverse the line number will be second return path, location_numbers[2], location_numbers[1] end if #location_numbers == 1 then return path, location_numbers[1], 0 end return path, nil, nil end local function add_offset(offset, obj) return { obj[1] + offset, obj[2] + offset } end utils.merge_styles = function(style1, style2, offset) for _, item in ipairs(style2) do item[1] = add_offset(offset, item[1]) table.insert(style1, item) end return style1 end -- IMPORTANT: This function should have been a local function as it's only used -- in this file, but the code was already exported a long time ago. By making it -- local we would potential break consumers of this method. utils.reverse_table = function(input_table) local temp_table = {} for index = 0, #input_table do temp_table[#input_table - index] = input_table[index + 1] -- Reverses the order end return temp_table end utils.split_lines = (function() if utils.iswin then return function(s, opts) return vim.split(s, "\r?\n", opts) end else return function(s, opts) return vim.split(s, "\n", opts) end end end)() return utils ================================================ FILE: lua/tests/automated/action_spec.lua ================================================ local actions = require "telescope.actions" local action_set = require "telescope.actions.set" local transform_mod = require("telescope.actions.mt").transform_mod local eq = assert.are.same describe("actions", function() it("should allow creating custom actions", function() local a = transform_mod { x = function() return 5 end, } eq(5, a.x()) end) it("allows adding actions", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local x_plus_y = a.x + a.y eq({ "x", "y" }, { x_plus_y() }) end) it("ignores nils from added actions", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, nil_maker = function() return nil end, } local x_plus_y = a.x + a.nil_maker + a.y eq({ "x", "y" }, { x_plus_y() }) end) it("allows overriding an action", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } -- actions.file_goto_selection_edit:replace(...) a.x:replace(function() return "foo" end) eq("foo", a.x()) a._clear() eq("x", a.x()) end) it("allows overriding an action only in specific cases with if", function() local a = transform_mod { x = function(e) return e * 10 end, y = function() return "y" end, } -- actions.file_goto_selection_edit:replace(...) a.x:replace_if(function(e) return e > 0 end, function(e) return (e / 10) end) eq(-100, a.x(-10)) eq(10, a.x(100)) eq(1, a.x(10)) a._clear() eq(100, a.x(10)) end) it("allows overriding an action only in specific cases with mod", function() local a = transform_mod { x = function(e) return e * 10 end, y = function() return "y" end, } -- actions.file_goto_selection_edit:replace(...) a.x: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, } eq(-100, a.x(-10)) eq(10, a.x(100)) eq(1, a.x(10)) eq(10, a.x(0)) a._clear() eq(100, a.x(10)) end) it("continuous replacement", function() local a = transform_mod { x = function() return "cleared" end, y = function() return "y" end, } -- Replace original, which becomes new fallback a.x:replace(function() return "negative" end) -- actions.file_goto_selection_edit:replace(...) a.x:replace_map { [function(e) return e > 0 end] = function(e) return "positive" end, [function(e) return e == 0 end] = function(e) return "zero" end, } eq("positive", a.x(10)) eq("zero", a.x(0)) eq("negative", a.x(-10)) a._clear() eq("cleared", a.x(10)) end) it("enhance.pre", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local called_pre = false a.y:enhance { pre = function() called_pre = true end, } eq("y", a.y()) eq(true, called_pre) end) it("enhance.post", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local called_post = false a.y:enhance { post = function() called_post = true end, } eq("y", a.y()) eq(true, called_post) end) it("static_pre static_post", function() local called_pre = false local called_post = false local static_post = 0 local a = transform_mod { x = { pre = function() called_pre = true end, action = function() return "x" end, post = function() called_post = true end, }, } eq("x", a.x()) eq(true, called_pre) eq(true, called_post) end) it("can call both", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local called_count = 0 local count_inc = function() called_count = called_count + 1 end a.y:enhance { pre = count_inc, post = count_inc, } eq("y", a.y()) eq(2, called_count) end) it("can call both even when combined", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local called_count = 0 local count_inc = function() called_count = called_count + 1 end a.y:enhance { pre = count_inc, post = count_inc, } a.x:enhance { post = count_inc, } local x_plus_y = a.x + a.y x_plus_y() eq(3, called_count) end) it( "can call replace fn even when combined before replace registered the fn (because that happens with mappings)", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local called_count = 0 local count_inc = function() called_count = called_count + 1 end local x_plus_y = a.x + a.y a.x:replace(function() count_inc() end) a.y:replace(function() count_inc() end) x_plus_y() eq(2, called_count) end ) it( "can call enhance fn even when combined before enhance registed fns (because that happens with mappings)", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local called_count = 0 local count_inc = function() called_count = called_count + 1 end local x_plus_y = a.x + a.y a.y:enhance { pre = count_inc, post = count_inc, } a.x:enhance { post = count_inc, } x_plus_y() eq(3, called_count) end ) it("clears enhance", function() local a = transform_mod { x = function() return "x" end, y = function() return "y" end, } local called_post = false a.y:enhance { post = function() called_post = true end, } a._clear() eq("y", a.y()) eq(false, called_post) end) it("handles passing arguments", function() local a = transform_mod { x = function(bufnr) return string.format "bufnr: %s" end, } a.x:replace(function(bufnr) return string.format("modified: %s", bufnr) end) eq("modified: 5", a.x(5)) end) it("handles add with two different tables", function() local count_a = 0 local count_b = 0 local a = transform_mod { x = function() count_a = count_a + 1 end, } local b = transform_mod { y = function() count_b = count_b + 1 end, } local called_count = 0 local count_inc = function() called_count = called_count + 1 end a.x:enhance { post = count_inc, } b.y:enhance { post = count_inc, } local x_plus_y = a.x + b.y x_plus_y() eq(2, called_count) eq(1, count_a) eq(1, count_b) end) it("handles tripple concat with static pre post", function() local count_a = 0 local count_b = 0 local count_c = 0 local static_pre = 0 local static_post = 0 local a = transform_mod { x = { pre = function() static_pre = static_pre + 1 end, action = function() count_a = count_a + 1 end, post = function() static_post = static_post + 1 end, }, } local b = transform_mod { y = { pre = function() static_pre = static_pre + 1 end, action = function() count_b = count_b + 1 end, post = function() static_post = static_post + 1 end, }, } local c = transform_mod { z = { pre = function() static_pre = static_pre + 1 end, action = function() count_c = count_c + 1 end, post = function() static_post = static_post + 1 end, }, } local replace_count = 0 a.x:replace(function() replace_count = replace_count + 1 end) local x_plus_y_plus_z = a.x + b.y + c.z x_plus_y_plus_z() eq(0, count_a) eq(1, count_b) eq(1, count_c) eq(1, replace_count) eq(3, static_pre) eq(3, static_post) end) describe("action_set", function() it("can replace `action_set.edit`", function() action_set.edit:replace(function(_, arg) return "replaced:" .. arg end) eq("replaced:edit", actions.file_edit()) eq("replaced:vnew", actions.file_vsplit()) end) pending("handles backwards compat with select and edit files", function() -- Reproduce steps: -- In config, we have { [""] = actions.select, ... } -- In caller, we have actions._goto:replace(...) -- Person calls `select`, does not see update action_set.edit:replace(function(_, arg) return "default_to_edit:" .. arg end) eq("default_to_edit:edit", actions.select_default()) action_set.select:replace(function(_, arg) return "override_with_select:" .. arg end) eq("override_with_select:default", actions.select_default()) -- Sometimes you might want to change the default selection... -- but you don't want to prohibit the ability to edit the code... end) end) end) ================================================ FILE: lua/tests/automated/command_spec.lua ================================================ local command = require "telescope.command" local eq = assert.are.same describe("command_parser", function() local test_parse = function(should, input, output) it(should, function() command.convert_user_opts(input) eq(output, input) end) end -- Strings test_parse("should handle cwd", { cwd = "string" }, { cwd = "string" }) -- Find commands test_parse( "should handle find_command 1", { find_command = "rg,--ignore,--hidden,files" }, { find_command = { "rg", "--ignore", "--hidden", "files" } } ) test_parse( "should handle find_command 2", { find_command = "fd,-t,f,-H" }, { find_command = { "fd", "-t", "f", "-H" } } ) test_parse( "should handle find_command 3", { find_command = "fdfind,--type,f,--no-ignore" }, { find_command = { "fdfind", "--type", "f", "--no-ignore" } } ) -- Dictionaries/tables test_parse( "should handle layout_config viml 1", { layout_config = "{'prompt_position':'top'}" }, { layout_config = { prompt_position = "top" } } ) test_parse( "should handle layout_config viml 2", { layout_config = "#{prompt_position:'bottom'}" }, { layout_config = { prompt_position = "bottom" } } ) test_parse( "should handle layout_config viml 3", { layout_config = "{'mirror':v:true}" }, { layout_config = { mirror = true } } ) test_parse( "should handle layout_config viml 4", { layout_config = "#{mirror:v:true}" }, { layout_config = { mirror = true } } ) test_parse( "should handle layout_config lua 1", { layout_config = "{prompt_position='bottom'}" }, { layout_config = { prompt_position = "bottom" } } ) test_parse( "should handle layout_config lua 2", { layout_config = "{mirror=true}" }, { layout_config = { mirror = true } } ) -- Lists/tables test_parse( "should handle symbols commas list", { symbols = "alpha,beta,gamma" }, { symbols = { "alpha", "beta", "gamma" } } ) test_parse( "should handle symbols viml list", { symbols = "['alpha','beta','gamma']" }, { symbols = { "alpha", "beta", "gamma" } } ) test_parse( "should handle symbols lua list", { symbols = "{'alpha','beta','gamma'}" }, { symbols = { "alpha", "beta", "gamma" } } ) -- Booleans test_parse("should handle booleans 1", { hidden = "true" }, { hidden = true }) test_parse("should handle booleans 2", { no_ignore = "false" }, { no_ignore = false }) -- Numbers test_parse("should handle numbers 1", { depth = "2" }, { depth = 2 }) test_parse("should handle numbers 2", { bufnr_width = "4" }, { bufnr_width = 4 }) test_parse("should handle numbers 3", { severity = "27" }, { severity = 27 }) -- Multiple options test_parse( "should handle multiple options 1", { layout_config = '{prompt_position="top"}', cwd = "/foobar", severity = "27" }, { layout_config = { prompt_position = "top" }, cwd = "/foobar", severity = 27 } ) test_parse( "should handle multiple options 2", { symbols = "['alef','bet','gimel']", depth = "2", find_command = "rg,--ignore,files" }, { symbols = { "alef", "bet", "gimel" }, depth = 2, find_command = { "rg", "--ignore", "files" } } ) end) ================================================ FILE: lua/tests/automated/entry_display_spec.lua ================================================ local entry_display = require "telescope.pickers.entry_display" describe("truncate", function() for _, ambiwidth in ipairs { "single", "double" } do for _, case in ipairs { { args = { "abcde", 6 }, expected = { single = "abcde", double = "abcde" } }, { args = { "abcde", 5 }, expected = { single = "abcde", double = "abcde" } }, { args = { "abcde", 4 }, expected = { single = "abc…", double = "ab…" } }, { args = { "アイウエオ", 11 }, expected = { single = "アイウエオ", double = "アイウエオ" } }, { args = { "アイウエオ", 10 }, expected = { single = "アイウエオ", double = "アイウエオ" } }, { args = { "アイウエオ", 9 }, expected = { single = "アイウエ…", double = "アイウ…" } }, { args = { "アイウエオ", 8 }, expected = { single = "アイウ…", double = "アイウ…" } }, { args = { "├─┤", 7 }, expected = { single = "├─┤", double = "├─┤" } }, { args = { "├─┤", 6 }, expected = { single = "├─┤", double = "├─┤" } }, { args = { "├─┤", 5 }, expected = { single = "├─┤", double = "├…" } }, { args = { "├─┤", 4 }, expected = { single = "├─┤", double = "├…" } }, { args = { "├─┤", 3 }, expected = { single = "├─┤", double = "…" } }, { args = { "├─┤", 2 }, expected = { single = "├…", double = "…" } }, } do local msg = ("can truncate: ambiwidth = %s, [%s, %d] -> %s"):format( ambiwidth, case.args[1], case.args[2], case.expected[ambiwidth] ) it(msg, function() local original = vim.o.ambiwidth vim.o.ambiwidth = ambiwidth assert.are.same(case.expected[ambiwidth], entry_display.truncate(case.args[1], case.args[2])) vim.o.ambiwidth = original end) end end end) ================================================ FILE: lua/tests/automated/entry_manager_spec.lua ================================================ local EntryManager = require "telescope.entry_manager" local eq = assert.are.same describe("process_result", function() it("works with one entry", function() local manager = EntryManager:new(5, nil) manager:add_entry(nil, 1, "hello", "") eq(1, manager:get_score(1)) end) it("works with two entries", function() local manager = EntryManager:new(5, nil) manager:add_entry(nil, 1, "hello", "") manager:add_entry(nil, 2, "later", "") eq(2, manager.linked_states.size) eq("hello", manager:get_entry(1)) eq("later", manager:get_entry(2)) end) it("calls functions when inserting", function() local called_count = 0 local manager = EntryManager:new(5, function() called_count = called_count + 1 end) assert(called_count == 0) manager:add_entry(nil, 1, "hello", "") assert(called_count == 1) end) it("calls functions when inserting twice", function() local called_count = 0 local manager = EntryManager:new(5, function() called_count = called_count + 1 end) assert(called_count == 0) manager:add_entry(nil, 1, "hello", "") manager:add_entry(nil, 2, "world", "") assert(called_count == 2) end) it("correctly sorts lower scores", function() local called_count = 0 local manager = EntryManager:new(5, function() called_count = called_count + 1 end) manager:add_entry(nil, 5, "worse result", "") manager:add_entry(nil, 2, "better result", "") eq("better result", manager:get_entry(1)) eq("worse result", manager:get_entry(2)) eq(2, called_count) end) it("respects max results", function() local called_count = 0 local manager = EntryManager:new(1, function() called_count = called_count + 1 end) manager:add_entry(nil, 2, "better result", "") manager:add_entry(nil, 5, "worse result", "") eq("better result", manager:get_entry(1)) eq(1, called_count) end) it("should allow simple entries", function() local manager = EntryManager:new(5) local counts_executed = 0 manager:add_entry( nil, 1, setmetatable({}, { __index = function(t, k) local val = nil if k == "ordinal" then counts_executed = counts_executed + 1 -- This could be expensive, only call later val = "wow" end rawset(t, k, val) return val end, }), "" ) eq("wow", manager:get_ordinal(1)) eq("wow", manager:get_ordinal(1)) eq("wow", manager:get_ordinal(1)) eq(1, counts_executed) end) it("should not loop a bunch", function() local info = {} local manager = EntryManager:new(5, nil, info) manager:add_entry(nil, 4, "better result", "") manager:add_entry(nil, 3, "better result", "") manager:add_entry(nil, 2, "better result", "") -- Loops once to find 3 < 4 -- Loops again to find 2 < 3 eq(2, info.looped) end) it("should not loop a bunch, part 2", function() local info = {} local manager = EntryManager:new(5, nil, info) manager:add_entry(nil, 4, "better result", "") manager:add_entry(nil, 2, "better result", "") manager:add_entry(nil, 3, "better result", "") -- Loops again to find 2 < 4 -- Loops once to find 3 > 2 -- but less than 4 eq(3, info.looped) end) it("should update worst score in all append case", function() local manager = EntryManager:new(2, nil) manager:add_entry(nil, 2, "result 2", "") manager:add_entry(nil, 3, "result 3", "") manager:add_entry(nil, 4, "result 4", "") eq(3, manager.worst_acceptable_score) end) it("should update worst score in all prepend case", function() local called_count = 0 local manager = EntryManager:new(2, function() called_count = called_count + 1 end) manager:add_entry(nil, 5, "worse result", "") manager:add_entry(nil, 4, "less worse result", "") manager:add_entry(nil, 2, "better result", "") -- Once for insert 5 -- Once for prepend 4 -- Once for prepend 2 eq(3, called_count) eq("better result", manager:get_entry(1)) eq(4, manager.worst_acceptable_score) end) it("should call tiebreaker if score is the same, sort length", function() local manager = EntryManager:new(5, nil) local picker = { tiebreak = function(curr, prev, prompt) eq("asdf", prompt) return #curr < #prev end, } manager:add_entry(picker, 0.5, "same same", "asdf") manager:add_entry(picker, 0.5, "same", "asdf") eq("same", manager:get_entry(1)) eq("same same", manager:get_entry(2)) end) it("should call tiebreaker if score is the same, keep initial", function() local manager = EntryManager:new(5, nil) local picker = { tiebreak = function(_, _, prompt) eq("asdf", prompt) return false end, } manager:add_entry(picker, 0.5, "same same", "asdf") manager:add_entry(picker, 0.5, "same", "asdf") eq("same", manager:get_entry(2)) eq("same same", manager:get_entry(1)) end) end) ================================================ FILE: lua/tests/automated/layout_strategies_spec.lua ================================================ local config = require "telescope.config" local resolve = require "telescope.config.resolve" local layout_strats = require "telescope.pickers.layout_strategies" local validate_layout_config = layout_strats._validate_layout_config local eq = assert.are.same describe("layout_strategies", function() it("should have validator", function() assert(validate_layout_config, "Has validator") end) local test_height = function(should, output, input, opts) opts = opts or {} local max_columns, max_lines = opts.max_columns or 100, opts.max_lines or 100 it(should, function() local layout_config = validate_layout_config("horizontal", { height = true }, { height = input }) eq(output, resolve.resolve_height(layout_config.height)({}, max_columns, max_lines)) end) end test_height("should handle numbers", 10, 10) test_height("should handle percentage: 100", 10, 0.1, { max_lines = 100 }) test_height("should handle percentage: 110", 11, 0.1, { max_lines = 110 }) test_height("should call functions: simple", 5, function() return 5 end) test_height("should call functions: percentage", 15, function(_, _, lines) return 0.1 * lines end, { max_lines = 150, }) local test_defaults_key = function(should, key, strat, output, ours, theirs, override) ours = ours or {} theirs = theirs or {} override = override or {} it(should, function() config.clear_defaults() config.set_defaults({ layout_config = theirs }, { layout_config = { ours, "description" } }) local layout_config = validate_layout_config(strat, layout_strats._configurations[strat], override) eq(output, layout_config[key]) end) end test_defaults_key( "should use ours if theirs and override don't give the key", "height", "horizontal", 50, { height = 50 }, { width = 100 }, { width = 120 } ) test_defaults_key( "should use ours if theirs and override don't give the key for this strategy", "height", "horizontal", 50, { height = 50 }, { vertical = { height = 100 } }, { vertical = { height = 120 } } ) test_defaults_key( "should use theirs if override doesn't give the key", "height", "horizontal", 100, { height = 50 }, { height = 100 }, { width = 120 } ) test_defaults_key( "should use override if key given", "height", "horizontal", 120, { height = 50 }, { height = 100 }, { height = 120 } ) test_defaults_key( "should use override if key given for this strategy", "height", "horizontal", 120, { height = 50 }, { height = 100 }, { horizontal = { height = 120 } } ) test_defaults_key( "should use theirs if override doesn't give key (even if ours has strategy specific)", "height", "horizontal", 100, { horizontal = { height = 50 } }, { height = 100 }, { width = 120 } ) test_defaults_key( "should use override (even if ours has strategy specific)", "height", "horizontal", 120, { horizontal = { height = 50 } }, { height = 100 }, { height = 120 } ) test_defaults_key( "should use override (even if theirs has strategy specific)", "height", "horizontal", 120, { height = 50 }, { horizontal = { height = 100 } }, { height = 120 } ) test_defaults_key( "should use override (even if ours and theirs have strategy specific)", "height", "horizontal", 120, { horizontal = { height = 50 } }, { horizontal = { height = 100 } }, { height = 120 } ) test_defaults_key( "should handle user config overriding a table with a number", "height", "horizontal", 120, { height = { padding = 5 } }, { height = 120 }, {} ) test_defaults_key( "should handle user oneshot overriding a table with a number", "height", "horizontal", 120, {}, { height = { padding = 5 } }, { height = 120 } ) end) ================================================ FILE: lua/tests/automated/linked_list_spec.lua ================================================ local LinkedList = require "telescope.algos.linked_list" describe("LinkedList", function() it("can create a list", function() local l = LinkedList:new() assert.are.same(0, l.size) end) it("can add a single entry to the list", function() local l = LinkedList:new() l:append "hello" assert.are.same(1, l.size) end) it("can iterate over one item", function() local l = LinkedList:new() l:append "hello" for val in l:iter() do assert.are.same("hello", val) end end) it("iterates in order", function() local l = LinkedList:new() l:append "hello" l:append "world" local x = {} for val in l:iter() do table.insert(x, val) end assert.are.same({ "hello", "world" }, x) end) it("iterates in order, for prepend", function() local l = LinkedList:new() l:prepend "world" l:prepend "hello" local x = {} for val in l:iter() do table.insert(x, val) end assert.are.same({ "hello", "world" }, x) end) it("iterates in order, for combo", function() local l = LinkedList:new() l:prepend "world" l:prepend "hello" l:append "last" l:prepend "first" local x = {} for val in l:iter() do table.insert(x, val) end assert.are.same({ "first", "hello", "world", "last" }, x) assert.are.same(#x, l.size) end) it("has ipairs", function() local l = LinkedList:new() l:prepend "world" l:prepend "hello" l:append "last" l:prepend "first" local x = {} for v in l:iter() do table.insert(x, v) end assert.are.same({ "first", "hello", "world", "last" }, x) local expected = {} for i, v in ipairs(x) do table.insert(expected, { i, v }) end local actual = {} for i, v in l:ipairs() do table.insert(actual, { i, v }) end assert.are.same(expected, actual) end) describe("track_at", function() it("should update tracked when only appending", function() local l = LinkedList:new { track_at = 2 } l:append "first" l:append "second" l:append "third" assert.are.same("second", l.tracked) end) it("should update tracked when first some prepend and then append", function() local l = LinkedList:new { track_at = 2 } l:prepend "first" l:append "second" l:append "third" assert.are.same("second", l.tracked) end) it("should update when only prepending", function() local l = LinkedList:new { track_at = 2 } l:prepend "third" l:prepend "second" l:prepend "first" assert.are.same("second", l.tracked) end) it("should update when lots of prepend and append", function() local l = LinkedList:new { track_at = 2 } l:prepend "third" l:prepend "second" l:prepend "first" l:append "fourth" l:prepend "zeroth" assert.are.same("first", l.tracked) end) end) end) ================================================ FILE: lua/tests/automated/pickers/find_files_spec.lua ================================================ -- Just skip on mac, it has flaky CI for some reason if vim.fn.has "mac" == 1 or require("telescope.utils").iswin then return end local tester = require "telescope.testharness" local disp = function(val) return vim.inspect(val, { newline = " ", indent = "" }) end describe("builtin.find_files", function() it("should find the readme", function() tester.run_file "find_files__readme" end) it("should handle cycling for full list", function() tester.run_file "find_files__scrolling_descending_cycle" end) for _, configuration in ipairs { { sorting_strategy = "descending" }, { sorting_strategy = "ascending" }, } do it("should not display devicons when disabled: " .. disp(configuration), function() tester.run_string(string.format( [[ local max_results = 5 runner.picker('find_files', 'README.md', { post_typed = { { "> README.md", GetPrompt }, { "> README.md", GetBestResult }, }, post_close = { { 'README.md', GetFile }, { 'README.md', GetFile }, } }, vim.tbl_extend("force", { disable_devicons = true, sorter = require('telescope.sorters').get_fzy_sorter(), layout_strategy = 'center', layout_config = { height = max_results + 1, width = 0.9, }, }, vim.json.decode([==[%s]==]))) ]], vim.json.encode(configuration) )) end) pending("use devicons, if it has it when enabled", function() if not pcall(require, "nvim-web-devicons") then return end local md = require("nvim-web-devicons").get_icon "md" tester.run_string(string.format( [[ runner.picker('find_files', 'README.md', { post_typed = { { "> README.md", GetPrompt }, { "> %s README.md", GetBestResult } }, post_close = { { 'README.md', GetFile }, { 'README.md', GetFile }, } }, vim.tbl_extend("force", { disable_devicons = false, sorter = require('telescope.sorters').get_fzy_sorter(), }, vim.json.decode([==[%s]==]))) ]], md, vim.json.encode(configuration) )) end) end it("should find the readme, using lowercase", function() tester.run_string [[ runner.picker('find_files', 'readme.md', { post_close = { { 'README.md', GetFile }, } }) ]] end) it("should find the pickers.lua, using lowercase", function() tester.run_string [[ runner.picker('find_files', 'pickers.lua', { post_close = { { 'pickers.lua', GetFile }, } }) ]] end) it("should find the pickers.lua", function() tester.run_string [[ runner.picker('find_files', 'pickers.lua', { post_close = { { 'pickers.lua', GetFile }, { 'pickers.lua', GetFile }, } }) ]] end) it("should be able to c-n the items", function() tester.run_string [[ runner.picker('find_files', 'fixtures/find_files/file', { post_typed = { { { " lua/tests/fixtures/find_files/file_a.txt", "> lua/tests/fixtures/find_files/file_abc.txt", }, GetResults }, }, post_close = { { 'file_abc.txt', GetFile }, }, }, { sorter = require('telescope.sorters').get_fzy_sorter(), sorting_strategy = "ascending", disable_devicons = true, }) ]] end) it("should be able to get the current selection", function() tester.run_string [[ runner.picker('find_files', 'fixtures/find_files/file_abc', { post_typed = { { 'lua/tests/fixtures/find_files/file_abc.txt', GetSelectionValue }, } }) ]] end) end) ================================================ FILE: lua/tests/automated/pickers/live_grep_spec.lua ================================================ if vim.fn.has "mac" == 1 or require("telescope.utils").iswin then return end local tester = require "telescope.testharness" local disp = function(val) return vim.inspect(val, { newline = " ", indent = "" }) end describe("builtin.live_grep", function() for _, configuration in ipairs { { sorting_strategy = "descending" }, { sorting_strategy = "ascending" }, } do it("clears results correctly when " .. disp(configuration), function() tester.run_string(string.format( [[ runner.picker( "live_grep", "abcdG", { post_typed = { { 5, function() return #vim.tbl_filter(function(line) return line ~= "" end, GetResults()) end, }, }, }, vim.tbl_extend("force", { sorter = require("telescope.sorters").get_fzy_sorter(), layout_strategy = "center", cwd = "./lua/tests/fixtures/live_grep", temp__scrolling_limit = 5, }, vim.json.decode [==[%s]==]) ) ]], vim.json.encode(configuration) )) end) end end) ================================================ FILE: lua/tests/automated/resolver_spec.lua ================================================ local eq = function(a, b) assert.are.same(a, b) end local resolve = require "telescope.config.resolve" describe("telescope.config.resolve", function() describe("win_option", function() it("should resolve for percentages", function() local height_config = 0.8 local opt = resolve.win_option(height_config) eq(height_config, opt.preview) eq(height_config, opt.prompt) eq(height_config, opt.results) end) it("should resolve for percentages with default", function() local height_config = 0.8 local opt = resolve.win_option(nil, height_config) eq(height_config, opt.preview) eq(height_config, opt.prompt) eq(height_config, opt.results) end) it("should resolve table values", function() local table_val = { "a" } local opt = resolve.win_option(nil, table_val) eq(table_val, opt.preview) eq(table_val, opt.prompt) eq(table_val, opt.results) end) it("should allow overrides for different wins", function() local prompt_override = { "a", prompt = "b" } local opt = resolve.win_option(prompt_override) eq("a", opt.preview) eq("a", opt.results) eq("b", opt.prompt) end) it("should allow overrides for all wins", function() local all_specified = { preview = "a", prompt = "b", results = "c" } local opt = resolve.win_option(all_specified) eq("a", opt.preview) eq("b", opt.prompt) eq("c", opt.results) end) it("should allow some specified with a simple default", function() local some_specified = { prompt = "b", results = "c" } local opt = resolve.win_option(some_specified, "a") eq("a", opt.preview) eq("b", opt.prompt) eq("c", opt.results) end) end) describe("resolve_height/width", function() local test_sizes = { { 24, 100 }, { 35, 125 }, { 60, 59 }, { 100, 40 }, } it("should handle percentages", function() local percentages = { 0.1, 0.33333, 0.5, 0.99 } for _, s in ipairs(test_sizes) do for _, p in ipairs(percentages) do eq(math.floor(s[1] * p), resolve.resolve_width(p)(nil, unpack(s))) eq(math.floor(s[2] * p), resolve.resolve_height(p)(nil, unpack(s))) end end end) it("should handle percentages with min/max boundary", function() eq(20, resolve.resolve_width { 0.1, min = 20 }(nil, 40, 120)) eq(30, resolve.resolve_height { 0.1, min = 20 }(nil, 40, 300)) eq(24, resolve.resolve_width { 0.4, max = 80 }(nil, 60, 60)) eq(80, resolve.resolve_height { 0.4, max = 80 }(nil, 60, 300)) end) it("should handle fixed size", function() local fixed = { 5, 8, 13, 21, 34 } for _, s in ipairs(test_sizes) do for _, f in ipairs(fixed) do eq(math.min(f, s[1]), resolve.resolve_width(f)(nil, unpack(s))) eq(math.min(f, s[2]), resolve.resolve_height(f)(nil, unpack(s))) end end end) it("should handle functions", function() local func = function(_, max_columns, max_lines) if max_columns < 45 then return math.min(max_columns, max_lines) elseif max_columns < max_lines then return max_columns * 0.8 else return math.min(max_columns, max_lines) * 0.5 end end for _, s in ipairs(test_sizes) do eq(func(nil, unpack(s)), resolve.resolve_height(func)(nil, unpack(s))) end end) it("should handle padding", function() local func = function(_, max_columns, max_lines) return math.floor(math.min(max_columns * 0.6, max_lines * 0.8)) end local pads = { 0.1, 5, func } for _, s in ipairs(test_sizes) do for _, p in ipairs(pads) do eq(s[1] - 2 * resolve.resolve_width(p)(nil, unpack(s)), resolve.resolve_width { padding = p }(nil, unpack(s))) eq( s[2] - 2 * resolve.resolve_height(p)(nil, unpack(s)), resolve.resolve_height { padding = p }(nil, unpack(s)) ) end end end) end) describe("resolve_anchor_pos", function() local test_sizes = { { 6, 7, 8, 9, 1 }, { 10, 20, 30, 40, 1 }, { 15, 15, 16, 16, 1 }, { 17, 19, 23, 31, 1 }, { 21, 18, 26, 24, 1 }, { 50, 100, 150, 200, 1 }, } it([[should not adjust when "CENTER" or "" is the anchor]], function() for _, s in ipairs(test_sizes) do eq({ 0, 0 }, resolve.resolve_anchor_pos("", unpack(s))) eq({ 0, 0 }, resolve.resolve_anchor_pos("center", unpack(s))) eq({ 0, 0 }, resolve.resolve_anchor_pos("CENTER", unpack(s))) end end) it([[should end up at top when "N" in the anchor]], function() local top_test = function(anchor, p_width, p_height, max_columns, max_lines) local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines, 1) eq(1, pos[2] + math.floor((max_lines - p_height) / 2)) end for _, s in ipairs(test_sizes) do top_test("NW", unpack(s)) top_test("N", unpack(s)) top_test("NE", unpack(s)) end end) it([[should end up at left when "W" in the anchor]], function() local left_test = function(anchor, p_width, p_height, max_columns, max_lines) local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines, 1) eq(1, pos[1] + math.floor((max_columns - p_width) / 2)) end for _, s in ipairs(test_sizes) do left_test("NW", unpack(s)) left_test("W", unpack(s)) left_test("SW", unpack(s)) end end) it([[should end up at bottom when "S" in the anchor]], function() local bot_test = function(anchor, p_width, p_height, max_columns, max_lines) local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines, 1) eq(max_lines - 1, pos[2] + p_height + math.floor((max_lines - p_height) / 2)) end for _, s in ipairs(test_sizes) do bot_test("SW", unpack(s)) bot_test("S", unpack(s)) bot_test("SE", unpack(s)) end end) it([[should end up at right when "E" in the anchor]], function() local right_test = function(anchor, p_width, p_height, max_columns, max_lines) local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines, 1) eq(max_columns - 1, pos[1] + p_width + math.floor((max_columns - p_width) / 2)) end for _, s in ipairs(test_sizes) do right_test("NE", unpack(s)) right_test("E", unpack(s)) right_test("SE", unpack(s)) end end) it([[should ignore casing of the anchor]], function() local case_test = function(a1, a2, p_width, p_height, max_columns, max_lines) local pos1 = resolve.resolve_anchor_pos(a1, p_width, p_height, max_columns, max_lines, 1) local pos2 = resolve.resolve_anchor_pos(a2, p_width, p_height, max_columns, max_lines, 1) eq(pos1, pos2) end for _, s in ipairs(test_sizes) do case_test("ne", "NE", unpack(s)) case_test("w", "W", unpack(s)) case_test("sW", "sw", unpack(s)) case_test("cEnTeR", "CeNtEr", unpack(s)) end end) end) end) ================================================ FILE: lua/tests/automated/scroller_spec.lua ================================================ local p_scroller = require "telescope.pickers.scroller" local log = require "telescope.log" log.use_console = false local eq = assert.are.same describe("scroller", function() local max_results = 10 describe("ascending cycle", function() local cycle_scroller = p_scroller.create("cycle", "ascending") it("should return values within the max results", function() eq(5, cycle_scroller(max_results, max_results, 5)) end) it("should return 0 at 0", function() eq(0, cycle_scroller(max_results, max_results, 0)) end) it("should cycle you to the top when you go below 0", function() eq(max_results - 1, cycle_scroller(max_results, max_results, -1)) end) it("should cycle you to 0 when you go past the results", function() eq(0, cycle_scroller(max_results, max_results, max_results + 1)) end) it("should cycle when current results is less than max_results", function() eq(0, cycle_scroller(max_results, 5, 7)) end) end) describe("ascending limit", function() local limit_scroller = p_scroller.create("limit", "ascending") it("should return values within the max results", function() eq(5, limit_scroller(max_results, max_results, 5)) end) it("should return 0 at 0", function() eq(0, limit_scroller(max_results, max_results, 0)) end) it("should not cycle", function() eq(0, limit_scroller(max_results, max_results, -1)) end) it("should not cycle you to 0 when you go past the results", function() eq(max_results - 1, limit_scroller(max_results, max_results, max_results + 1)) end) it("should stay at current results when current results is less than max_results", function() local current = 5 eq(current - 1, limit_scroller(max_results, current, 7)) end) end) describe("descending cycle", function() local cycle_scroller = p_scroller.create("cycle", "descending") it("should return values within the max results", function() eq(5, cycle_scroller(max_results, max_results, 5)) end) it("should return max_results - 1 at 0", function() eq(0, cycle_scroller(max_results, max_results, 0)) end) it("should cycle you to the bot when you go below 0", function() eq(max_results - 1, cycle_scroller(max_results, max_results, -1)) end) it("should cycle you to 0 when you go past the results", function() eq(0, cycle_scroller(max_results, max_results, max_results + 1)) end) it("should cycle when current results is less than max_results", function() eq(9, cycle_scroller(max_results, 5, 4)) end) end) describe("descending limit", function() local limit_scroller = p_scroller.create("limit", "descending") it("should return values within the max results", function() eq(5, limit_scroller(max_results, max_results, 5)) end) it("should return 0 at 0", function() eq(0, limit_scroller(max_results, max_results, 0)) end) it("should not cycle", function() eq(0, limit_scroller(max_results, max_results, -1)) end) it("should not cycle you to 0 when you go past the results", function() eq(max_results - 1, limit_scroller(max_results, max_results, max_results + 1)) end) it("should stay at current results when current results is less than max_results", function() local current = 5 eq(max_results - current, limit_scroller(max_results, current, 4)) end) end) describe("https://github.com/nvim-telescope/telescope.nvim/pull/293#issuecomment-751463224", function() it("should handle having many more results than necessary", function() local scroller = p_scroller.create("cycle", "descending") -- 23 112 23 eq(0, scroller(23, 112, 23)) end) end) describe("should give top, middle and bottom index", function() it("should handle ascending", function() eq(0, p_scroller.top("ascending", 20, 1000)) eq(19, p_scroller.bottom("ascending", 20, 1000)) eq(0, p_scroller.top("ascending", 20, 10)) eq(9, p_scroller.bottom("ascending", 20, 10)) eq(5, p_scroller.middle("ascending", 11, 100)) eq(10, p_scroller.middle("ascending", 20, 100)) eq(12, p_scroller.middle("ascending", 25, 100)) end) it("should handle descending", function() eq(0, p_scroller.top("descending", 20, 1000)) eq(19, p_scroller.bottom("descending", 20, 1000)) eq(10, p_scroller.top("descending", 20, 10)) eq(19, p_scroller.bottom("descending", 20, 10)) eq(25, p_scroller.middle("descending", 30, 10)) eq(50, p_scroller.middle("descending", 60, 20)) eq(105, p_scroller.middle("descending", 120, 30)) end) end) end) ================================================ FILE: lua/tests/automated/sorters_spec.lua ================================================ local sorters = require "telescope.sorters" describe("get_substr_matcher", function() local function with_smartcase(smartcase, case) local original = vim.o.smartcase vim.o.smartcase = smartcase describe("scoring_function", function() it(case.msg, function() local matcher = sorters.get_substr_matcher() assert.are.same(case.expected_score, matcher.scoring_function(_, case.prompt, _, case.entry)) end) end) describe("highlighter", function() it("returns valid highlights", function() local matcher = sorters.get_substr_matcher() local highlights = matcher.highlighter(_, case.prompt, case.entry.ordinal) table.sort(highlights, function(a, b) return a.start < b.start end) assert.are.same(case.expected_highlights, highlights) end) end) vim.o.smartcase = original end describe("when smartcase=OFF", function() for _, case in ipairs { { msg = "doesn't match", prompt = "abc def", entry = { index = 3, ordinal = "abc d" }, expected_score = -1, expected_highlights = { { start = 1, finish = 3 } }, }, { msg = "matches with lower case letters only", prompt = "abc def", entry = { index = 3, ordinal = "abc def ghi" }, expected_score = 3, expected_highlights = { { start = 1, finish = 3 }, { start = 5, finish = 7 } }, }, { msg = "doesn't match with upper case letters", prompt = "ABC def", entry = { index = 3, ordinal = "ABC def ghi" }, expected_score = -1, expected_highlights = { { start = 5, finish = 7 } }, }, } do with_smartcase(false, case) end end) describe("when smartcase=OFF", function() for _, case in ipairs { { msg = "doesn't match", prompt = "abc def", entry = { index = 3, ordinal = "abc d" }, expected_score = -1, expected_highlights = { { start = 1, finish = 3 } }, }, { msg = "matches with lower case letters only", prompt = "abc def", entry = { index = 3, ordinal = "abc def ghi" }, expected_score = 3, expected_highlights = { { start = 1, finish = 3 }, { start = 5, finish = 7 } }, }, { msg = "matches with upper case letters", prompt = "ABC def", entry = { index = 3, ordinal = "ABC def ghi" }, expected_score = 3, expected_highlights = { { start = 1, finish = 3 }, { start = 5, finish = 7 } }, }, } do with_smartcase(true, case) end end) end) ================================================ FILE: lua/tests/automated/telescope_spec.lua ================================================ local picker = require "telescope.pickers" local Path = require "plenary.path" local eq = assert.are.same local function new_path(unix_path) return Path:new(unpack(vim.split(unix_path, "/"))).filename end describe("telescope", function() describe("Picker", function() describe("window_dimensions", function() it("", function() assert(true) end) end) describe("attach_mappings", function() local new_picker = function(a, b) a.finder = true return picker.new(a, b) end it("should allow for passing in a function", function() local p = new_picker({}, { attach_mappings = function() return 1 end, }) eq(1, p.attach_mappings()) end) it("should override an attach mappings passed in by opts", function() local called_order = {} local p = new_picker({ attach_mappings = function() table.insert(called_order, "opts") end, }, { attach_mappings = function() table.insert(called_order, "default") end, }) p.attach_mappings() eq({ "default", "opts" }, called_order) end) end) end) describe("Sorters", function() describe("generic_fuzzy_sorter", function() it("sort matches well", function() local sorter = require("telescope.sorters").get_generic_fuzzy_sorter() local exact_match = sorter:score("hello", { ordinal = "hello" }) local no_match = sorter:score("abcdef", { ordinal = "ghijkl" }) local ok_match = sorter:score("abcdef", { ordinal = "ab" }) assert(exact_match < no_match, "exact match better than no match") assert(exact_match < ok_match, "exact match better than ok match") assert(ok_match < no_match, "ok match better than no match") end) it("sorts multiple finds better", function() local sorter = require("telescope.sorters").get_generic_fuzzy_sorter() local multi_match = sorter:score("generics", "exercises/generics/generics2.rs") local one_match = sorter:score("abcdef", "exercises/generics/README.md") -- assert(multi_match < one_match) end) end) describe("fuzzy_file", function() it("sort matches well", function() local sorter = require("telescope.sorters").get_fuzzy_file() local exact_match = sorter:score("abcdef", { ordinal = "abcdef" }) local no_match = sorter:score("abcdef", { ordinal = "ghijkl" }) local ok_match = sorter:score("abcdef", { ordinal = "ab" }) assert(exact_match < no_match, string.format("Exact match better than no match: %s %s", exact_match, no_match)) assert(exact_match < ok_match, string.format("Exact match better than OK match: %s %s", exact_match, ok_match)) assert(ok_match < no_match, "OK match better than no match") end) it("sorts matches after last os sep better", function() local sorter = require("telescope.sorters").get_fuzzy_file() local better_match = sorter:score("aaa", { ordinal = new_path "bbb/aaa" }) local worse_match = sorter:score("aaa", { ordinal = new_path "aaa/bbb" }) assert(better_match < worse_match, "Final match should be stronger") end) pending("sorts multiple finds better", function() local sorter = require("telescope.sorters").get_fuzzy_file() local multi_match = sorter:score("generics", { ordinal = "exercises/generics/generics2.rs" }) local one_match = sorter:score("abcdef", { ordinal = "exercises/generics/README.md" }) assert(multi_match < one_match) end) end) describe("fzy", function() local sorter = require("telescope.sorters").get_fzy_sorter() local function score(prompt, line) line = new_path(line) return sorter:score(prompt, { ordinal = line }, function(val) return val end, function() return -1 end) end describe("matches", function() it("exact matches", function() assert.True(score("a", "a") >= 0) assert.True(score("a.bb", "a.bb") >= 0) end) it("ignore case", function() assert.True(score("AbB", "abb") >= 0) assert.True(score("abb", "ABB") >= 0) end) it("partial matches", function() assert.True(score("a", "ab") >= 0) assert.True(score("a", "ba") >= 0) assert.True(score("aba", "baabbaab") >= 0) end) it("with delimiters between", function() assert.True(score("abc", "a|b|c") >= 0) end) it("with empty query", function() assert.True(score("", "") >= 0) assert.True(score("", "a") >= 0) end) it("rejects non-matches", function() assert.True(score("a", "") < 0) assert.True(score("a", "b") < 0) assert.True(score("aa", "a") < 0) assert.True(score("ba", "a") < 0) assert.True(score("ab", "a") < 0) end) end) describe("scoring", function() it("prefers beginnings of words", function() assert.True(score("amor", "app/models/order") < score("amor", "app/models/zrder")) end) it("prefers consecutive letters", function() assert.True(score("amo", "app/models/foo") < score("amo", "app/m/foo")) assert.True(score("erf", "perfect") < score("erf", "terrific")) end) it("prefers contiguous over letter following period", function() assert.True(score("gemfil", "Gemfile") < score("gemfil", "Gemfile.lock")) end) it("prefers shorter matches", function() assert.True(score("abce", "abcdef") < score("abce", "abc de")) assert.True(score("abc", " a b c ") < score("abc", " a b c ")) assert.True(score("abc", " a b c ") < score("abc", " a b c ")) end) it("prefers shorter candidates", function() assert.True(score("test", "tests") < score("test", "testing")) end) it("prefers matches at the beginning", function() assert.True(score("ab", "abbb") < score("ab", "babb")) assert.True(score("test", "testing") < score("test", "/testing")) end) it("prefers matches at some locations", function() assert.True(score("a", "/a") < score("a", "ba")) assert.True(score("a", "bA") < score("a", "ba")) assert.True(score("a", ".a") < score("a", "ba")) end) end) local function positions(prompt, line) return sorter:highlighter(prompt, new_path(line)) end describe("positioning", function() it("favors consecutive positions", function() assert.same({ 1, 5, 6 }, positions("amo", "app/models/foo")) end) it("favors word beginnings", function() assert.same({ 1, 5, 12, 13 }, positions("amor", "app/models/order")) end) it("works when there are no bonuses", function() assert.same({ 2, 4 }, positions("as", "tags")) assert.same({ 3, 8 }, positions("as", "examples.txt")) end) it("favors smaller groupings of positions", function() assert.same({ 3, 5, 7 }, positions("abc", "a/a/b/c/c")) assert.same({ 3, 5 }, positions("ab", "caacbbc")) end) it("handles exact matches", function() assert.same({ 1, 2, 3 }, positions("foo", "foo")) end) it("ignores empty requests", function() assert.same({}, positions("", "")) assert.same({}, positions("", "foo")) assert.same({}, positions("foo", "")) end) end) end) describe("layout_strategies", function() describe("center", function() it("should handle large terminals", function() -- TODO: This could call layout_strategies.center w/ some weird edge case. -- and then assert stuff about the dimensions. end) end) end) end) end) ================================================ FILE: lua/tests/automated/utils_spec.lua ================================================ local Path = require "plenary.path" local utils = require "telescope.utils" local eq = assert.are.equal describe("path_expand()", function() it("removes trailing os_sep", function() if utils.iswin then eq([[C:\Users\a\b]], utils.path_expand [[C:\Users\a\b\]]) else eq("/home/user", utils.path_expand "/home/user/") end end) it("works with root dir", function() if utils.iswin then eq([[C:\]], utils.path_expand [[C:\]]) else eq("/", utils.path_expand "/") end end) it("works with ~", function() eq(vim.uv.os_homedir() .. "/src/foo", utils.path_expand "~/src/foo") end) it("handles duplicate os_sep", function() if utils.iswin then eq([[C:\Users\a]], utils.path_expand [[C:\\\Users\\a]]) else eq("/home/user", utils.path_expand "/home///user") end end) it("preserves fake whitespace characters and whitespace", function() local path_space = "/home/user/hello world" eq(path_space, utils.path_expand(path_space)) local path_newline = [[/home/user/hello\nworld]] eq(path_newline, utils.path_expand(path_newline)) end) describe("early return for uri", function() local uris = { [[https://www.example.com/index.html]], [[ftp://ftp.example.com/files/document.pdf]], [[mailto:user@example.com]], [[tel:+1234567890]], [[file:///home/user/documents/report.docx]], [[news:comp.lang.python]], [[ldap://ldap.example.com:389/dc=example,dc=com]], [[git://github.com/user/repo.git]], [[steam://run/123456]], [[magnet:?xt=urn:btih:6B4C3343E1C63A1BC36AEB8A3D1F52C4EDEEB096]], } for _, uri in ipairs(uris) do it(uri, function() eq(uri, utils.path_expand(uri)) end) end end) end) describe("is_uri", function() describe("detects valid uris", function() local uris = { [[https://www.example.com/index.html]], [[ftp://ftp.example.com/files/document.pdf]], [[mailto:user@example.com]], [[tel:+1234567890]], [[file:///home/user/documents/report.docx]], [[news:comp.lang.python]], [[ldap://ldap.example.com:389/dc=example,dc=com]], [[git://github.com/user/repo.git]], [[steam://run/123456]], [[magnet:?xt=urn:btih:6B4C3343E1C63A1BC36AEB8A3D1F52C4EDEEB096]], } for _, uri in ipairs(uris) do it(uri, function() assert.True(utils.is_uri(uri)) end) end end) describe("detects invalid uris/paths", function() local inputs = { "hello", "hello:", "123", "", } for _, input in ipairs(inputs) do it(input, function() assert.False(utils.is_uri(input)) end) end end) describe("handles windows paths", function() local paths = { [[C:\Users\Usuario\Documents\archivo.txt]], [[D:\Projects\project_folder\source_code.py]], [[E:\Music\song.mp3]], } for _, uri in ipairs(paths) do it(uri, function() assert.False(utils.is_uri(uri)) end) end end) describe("handles linux paths", function() local paths = { [[/home/usuario/documents/archivo.txt]], [[/var/www/html/index.html]], [[/mnt/backup/backup_file.tar.gz]], } for _, path in ipairs(paths) do it(path, function() assert.False(utils.is_uri(path)) end) end end) describe("handles macos paths", function() local paths = { [[/Users/Usuario/Documents/archivo.txt]], [[/Applications/App.app/Contents/MacOS/app_executable]], [[/Volumes/ExternalDrive/Data/file.xlsx]], } for _, path in ipairs(paths) do it(path, function() assert.False(utils.is_uri(path)) end) end end) end) describe("__separates_file_path_location", function() local suites = { { input = "file.txt:12:4", file = "file.txt", row = 12, col = 4, }, { input = "file.txt:12", file = "file.txt", row = 12, col = 0, }, { input = "file:12:4", file = "file", row = 12, col = 4, }, { input = "file:12:", file = "file", row = 12, col = 0, }, { input = "file:", file = "file", }, } for _, suite in ipairs(suites) do it("separtates file path for " .. suite.input, function() local file, row, col = utils.__separate_file_path_location(suite.input) eq(file, suite.file) eq(row, suite.row) eq(col, suite.col) end) end end) describe("transform_path", function() local cwd = (function() if utils.iswin then return [[C:\Users\user\projects\telescope.nvim]] else return "/home/user/projects/telescope.nvim" end end)() local function new_relpath(unix_path) return Path:new(unpack(vim.split(unix_path, "/"))).filename end local function assert_path(path_display, path, expect) local opts = { cwd = cwd, __length = 15 } if type(path_display) == "string" then opts.path_display = { path_display } eq(expect, utils.transform_path(opts, path)) opts.path_display = { [path_display] = true } eq(expect, utils.transform_path(opts, path)) elseif type(path_display) == "table" then opts.path_display = path_display eq(expect, utils.transform_path(opts, path)) elseif type(path_display) == "function" then opts.path_display = path_display eq(expect, utils.transform_path(opts, path)) elseif path_display == nil then eq(expect, utils.transform_path(opts, path)) end end it("handles nil path", function() assert_path(nil, nil, "") end) it("returns back uri", function() local uri = [[https://www.example.com/index.html]] assert_path(nil, uri, uri) end) it("handles 'hidden' path_display", function() eq("", utils.transform_path({ cwd = cwd, path_display = "hidden" }, "foobar")) assert_path("hidden", "foobar", "") end) it("returns relative path for default opts", function() local relative = Path:new { "lua", "telescope", "init.lua" } local absolute = Path:new { cwd, relative } assert_path(nil, absolute.filename, relative.filename) assert_path(nil, relative.filename, relative.filename) end) it("handles 'tail' path_display", function() local path = new_relpath "lua/telescope/init.lua" assert_path("tail", path, "init.lua") end) it("handles 'smart' path_display", function() local path1 = new_relpath "lua/telescope/init.lua" local path2 = new_relpath "lua/telescope/finders.lua" local path3 = new_relpath "lua/telescope/finders/async_job_finder.lua" local path4 = new_relpath "plugin/telescope.lua" assert_path("smart", path1, path1) assert_path("smart", path2, new_relpath "../telescope/finders.lua") assert_path("smart", path3, new_relpath "../telescope/finders/async_job_finder.lua") assert_path("smart", path4, path4) end) it("handles 'absolute' path_display", function() local relative = Path:new { "lua", "telescope", "init.lua" } local absolute = Path:new { cwd, relative } -- TODO: feels like 'absolute' should turn relative paths to absolute -- assert_path("absolute", relative.filename, absolute.filename) assert_path("absolute", absolute.filename, absolute.filename) end) it("handles default 'shorten' path_display", function() assert_path("shorten", new_relpath "lua/telescope/init.lua", new_relpath "l/t/init.lua") end) it("handles 'shorten' with number", function() assert_path({ shorten = 2 }, new_relpath "lua/telescope/init.lua", new_relpath "lu/te/init.lua") end) it("handles 'shorten' with option table", function() assert_path({ shorten = { len = 2 } }, new_relpath "lua/telescope/init.lua", new_relpath "lu/te/init.lua") assert_path( { shorten = { len = 2, exclude = { 1, 3, -1 } } }, new_relpath "lua/telescope/builtin/init.lua", new_relpath "lua/te/builtin/init.lua" ) end) it("handles default 'truncate' path_display", function() assert_path({ "truncate" }, new_relpath "lua/telescope/init.lua", new_relpath "…scope/init.lua") end) it("handles 'filename_first' path_display", function() assert_path("filename_first", new_relpath "init.lua", new_relpath "init.lua") assert_path("filename_first", new_relpath "lua/telescope/init.lua", new_relpath "init.lua lua/telescope") end) it("handles 'filename_first' path_display with the option to reverse directories", function() assert_path({ filename_first = { reverse_directories = true } }, new_relpath "init.lua", new_relpath "init.lua") assert_path( { filename_first = { reverse_directories = true } }, new_relpath "lua/telescope/init.lua", new_relpath "init.lua telescope/lua" ) assert_path({ filename_first = { reverse_directories = false } }, new_relpath "init.lua", new_relpath "init.lua") assert_path( { filename_first = { reverse_directories = false } }, new_relpath "lua/telescope/init.lua", new_relpath "init.lua lua/telescope" ) end) it("handles function passed to path_display", function() assert_path(function(_, path) return string.gsub(path, "^doc", "d") end, new_relpath "doc/mydoc.md", new_relpath "d/mydoc.md") end) end) describe("path_tail", function() local function assert_tails(paths) for _, path in ipairs(paths) do it("gets the tail of " .. path, function() local tail = vim.fn.fnamemodify(path, ":p:t") eq(tail, utils.path_tail(path)) end) end end if jit and jit.os:lower() == "windows" then describe("handles windows paths", function() local paths = { [[C:\Users\username\AppData\Local\nvim-data\log]], [[D:\Projects\project_folder\source_code.py]], [[E:\Music\song.mp3]], [[/home/usuario/documents/archivo.txt]], [[/var/www/html/index.html]], [[/mnt/backup/backup_file.tar.gz]], } assert_tails(paths) end) elseif jit and jit.os:lower() == "linux" then describe("handles linux paths", function() local paths = { [[/home/usuario/documents/archivo.txt]], [[/var/www/html/index.html]], [[/mnt/backup/backup_file.tar.gz]], } assert_tails(paths) end) elseif jit and jit.os:lower() == "osx" then describe("handles macos paths", function() local paths = { [[/Users/Usuario/Documents/archivo.txt]], [[/Applications/App.app/Contents/MacOS/app_executable]], [[/Volumes/ExternalDrive/Data/file.xlsx]], } assert_tails(paths) end) end end) describe("split_lines", function() local expect = { "", "", "line3 of the file", "", "line5 of the file", "", "", "line8 of the file, last line of file", "", } local function get_fake_file(line_ending) return table.concat(expect, line_ending) end local newline_file = get_fake_file "\n" local carriage_newline_file = get_fake_file "\r\n" if utils.iswin then describe("handles files on Windows", function() it("reads file with newline only", function() assert.are.same(expect, utils.split_lines(newline_file)) end) it("reads file with carriage return and newline", function() assert.are.same(expect, utils.split_lines(carriage_newline_file)) end) end) else describe("handles files on non Windows environment", function() it("reads file with newline only", function() assert.are.same(expect, utils.split_lines(newline_file)) end) end) end end) ================================================ FILE: lua/tests/fixtures/find_files/file_a.txt ================================================ ================================================ FILE: lua/tests/fixtures/find_files/file_abc.txt ================================================ ================================================ FILE: lua/tests/fixtures/live_grep/a.txt ================================================ abc abc abc abc abc abcd abcd abcd abcd abcd abcde ================================================ FILE: lua/tests/helpers.lua ================================================ local finders = require "telescope.finders" local make_entry = require "telescope.make_entry" local previewers = require "telescope.previewers" local pickers = require "telescope.pickers" local sorters = require "telescope.sorters" local utils = require "telescope.utils" local helpers = {} -- TODO: We should do something with builtins to get those easily. helpers.auto_find_files = function(opts) opts = opts or {} opts.prompt_prefix = "" local find_command = opts.find_command if not find_command then if 1 == vim.fn.executable "fd" then find_command = { "fd", "--type", "f" } elseif 1 == vim.fn.executable "fdfind" then find_command = { "fdfind", "--type", "f" } elseif 1 == vim.fn.executable "rg" then find_command = { "rg", "--files" } end end if opts.cwd then opts.cwd = utils.path_expand(opts.cwd) end opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts) local p = pickers.new(opts, { prompt = "Find Files", finder = finders.new_oneshot_job(find_command, opts), previewer = previewers.cat.new(opts), sorter = sorters.get_fuzzy_file(), track = true, }) local count = 0 p:register_completion_callback(function(s) print( count, vim.inspect(s.stats, { process = function(item) if type(item) == "string" and item:sub(1, 1) == "_" then return nil end return item end, }) ) count = count + 1 end) local feed = function(text, feed_opts) feed_opts = feed_opts or "n" vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(text, true, false, true), feed_opts, true) end p:register_completion_callback(coroutine.wrap(function() local input = opts.input for i = 1, #input do feed(input:sub(i, i)) coroutine.yield() end vim.wait(300, function() end) feed("", "") vim.defer_fn(function() PASSED = opts.condition() COMPLETED = true end, 500) coroutine.yield() end)) p:find() end return helpers ================================================ FILE: lua/tests/pickers/find_files__readme.lua ================================================ local helper = require "telescope.testharness.helpers" local runner = require "telescope.testharness.runner" runner.picker("find_files", "README.md", { post_close = { { "README.md", helper.get_file }, }, }) ================================================ FILE: lua/tests/pickers/find_files__scrolling_descending_cycle.lua ================================================ local tester = require "telescope.testharness" local helper = require "telescope.testharness.helpers" local runner = require "telescope.testharness.runner" runner.picker("find_files", "telescope", { post_close = { tester.not_ { "plugin/telescope.vim", helper.get_file }, }, }, { sorting_strategy = "descending", scroll_strategy = "cycle", }) ================================================ FILE: plugin/telescope.lua ================================================ if 1 ~= vim.fn.has "nvim-0.10.4" then error "Telescope.nvim requires at least nvim-0.10.4." return end if vim.g.loaded_telescope == 1 then return end vim.g.loaded_telescope = 1 local highlights = { -- Sets the highlight for selected items within the picker. TelescopeSelection = { default = true, link = "Visual" }, TelescopeSelectionCaret = { default = true, link = "TelescopeSelection" }, TelescopeMultiSelection = { default = true, link = "Type" }, TelescopeMultiIcon = { default = true, link = "Identifier" }, -- "Normal" in the floating windows created by telescope. TelescopeNormal = { default = true, link = "Normal" }, TelescopePreviewNormal = { default = true, link = "TelescopeNormal" }, TelescopePromptNormal = { default = true, link = "TelescopeNormal" }, TelescopeResultsNormal = { default = true, link = "TelescopeNormal" }, -- Border highlight groups. -- Use TelescopeBorder to override the default. -- Otherwise set them specifically TelescopeBorder = { default = true, link = "TelescopeNormal" }, TelescopePromptBorder = { default = true, link = "TelescopeBorder" }, TelescopeResultsBorder = { default = true, link = "TelescopeBorder" }, TelescopePreviewBorder = { default = true, link = "TelescopeBorder" }, -- Title highlight groups. -- Use TelescopeTitle to override the default. -- Otherwise set them specifically TelescopeTitle = { default = true, link = "TelescopeBorder" }, TelescopePromptTitle = { default = true, link = "TelescopeTitle" }, TelescopeResultsTitle = { default = true, link = "TelescopeTitle" }, TelescopePreviewTitle = { default = true, link = "TelescopeTitle" }, TelescopePromptCounter = { default = true, link = "NonText" }, -- Used for highlighting characters that you match. TelescopeMatching = { default = true, link = "Special" }, -- Used for the prompt prefix TelescopePromptPrefix = { default = true, link = "Identifier" }, -- Used for highlighting the matched line inside Previewer. Works only for (vim_buffer_ previewer) TelescopePreviewLine = { default = true, link = "Visual" }, TelescopePreviewMatch = { default = true, link = "Search" }, TelescopePreviewPipe = { default = true, link = "Constant" }, TelescopePreviewCharDev = { default = true, link = "Constant" }, TelescopePreviewDirectory = { default = true, link = "Directory" }, TelescopePreviewBlock = { default = true, link = "Constant" }, TelescopePreviewLink = { default = true, link = "Special" }, TelescopePreviewSocket = { default = true, link = "Statement" }, TelescopePreviewRead = { default = true, link = "Constant" }, TelescopePreviewWrite = { default = true, link = "Statement" }, TelescopePreviewExecute = { default = true, link = "String" }, TelescopePreviewHyphen = { default = true, link = "NonText" }, TelescopePreviewSticky = { default = true, link = "Keyword" }, TelescopePreviewSize = { default = true, link = "String" }, TelescopePreviewUser = { default = true, link = "Constant" }, TelescopePreviewGroup = { default = true, link = "Constant" }, TelescopePreviewDate = { default = true, link = "Directory" }, TelescopePreviewMessage = { default = true, link = "TelescopePreviewNormal" }, TelescopePreviewMessageFillchar = { default = true, link = "TelescopePreviewMessage" }, -- Used for Picker specific Results highlighting TelescopeResultsClass = { default = true, link = "Function" }, TelescopeResultsConstant = { default = true, link = "Constant" }, TelescopeResultsField = { default = true, link = "Function" }, TelescopeResultsFunction = { default = true, link = "Function" }, TelescopeResultsMethod = { default = true, link = "Method" }, TelescopeResultsOperator = { default = true, link = "Operator" }, TelescopeResultsStruct = { default = true, link = "Struct" }, TelescopeResultsVariable = { default = true, link = "SpecialChar" }, TelescopeResultsLineNr = { default = true, link = "LineNr" }, TelescopeResultsIdentifier = { default = true, link = "Identifier" }, TelescopeResultsNumber = { default = true, link = "Number" }, TelescopeResultsComment = { default = true, link = "Comment" }, TelescopeResultsSpecialComment = { default = true, link = "SpecialComment" }, -- Used for git status Results highlighting TelescopeResultsDiffChange = { default = true, link = "DiffChange" }, TelescopeResultsDiffAdd = { default = true, link = "DiffAdd" }, TelescopeResultsDiffDelete = { default = true, link = "DiffDelete" }, TelescopeResultsDiffUntracked = { default = true, link = "NonText" }, } for k, v in pairs(highlights) do vim.api.nvim_set_hl(0, k, v) end -- This is like "" in your terminal. -- To use it, do `cmap (TelescopeFuzzyCommandSearch) vim.keymap.set( "c", "(TelescopeFuzzyCommandSearch)", "e \"lua require('telescope.builtin').command_history " .. '{ default_text = [=[" . escape(getcmdline(), \'"\') . "]=] }"', { silent = true, noremap = true } ) vim.api.nvim_create_user_command("Telescope", function(opts) require("telescope.command").load_command(unpack(opts.fargs)) end, { nargs = "*", complete = function(_, line) local builtin_list = vim.tbl_keys(require "telescope.builtin") local extensions_list = vim.tbl_keys(require("telescope._extensions").manager) local l = vim.split(line, "%s+") local n = #l - 2 if n == 0 then local commands = { builtin_list, extensions_list } commands = vim.iter(commands):flatten():totable() table.sort(commands) return vim.tbl_filter(function(val) return vim.startswith(val, l[2]) end, commands) end if n == 1 then local is_extension = vim.tbl_filter(function(val) return val == l[2] end, extensions_list) if #is_extension > 0 then local extensions_subcommand_dict = require("telescope.command").get_extensions_subcommand() local commands = extensions_subcommand_dict[l[2]] table.sort(commands) return vim.tbl_filter(function(val) return vim.startswith(val, l[3]) end, commands) end end local options_list = vim.tbl_keys(require("telescope.config").values) table.sort(options_list) return vim.tbl_filter(function(val) return vim.startswith(val, l[#l]) end, options_list) end, }) ================================================ FILE: scripts/gendocs.lua ================================================ -- Setup telescope with defaults if RELOAD then RELOAD "telescope" end require("telescope").setup() local docgen = require "docgen" local docs = {} docs.test = function() -- TODO: Fix the other files so that we can add them here. local input_files = { "./lua/telescope/init.lua", "./lua/telescope/command.lua", "./lua/telescope/builtin/init.lua", "./lua/telescope/themes.lua", "./lua/telescope/mappings.lua", "./lua/telescope/pickers/layout.lua", "./lua/telescope/pickers/layout_strategies.lua", "./lua/telescope/config/resolve.lua", "./lua/telescope/make_entry.lua", "./lua/telescope/pickers/entry_display.lua", "./lua/telescope/utils.lua", "./lua/telescope/actions/init.lua", "./lua/telescope/actions/state.lua", "./lua/telescope/actions/set.lua", "./lua/telescope/actions/layout.lua", "./lua/telescope/actions/utils.lua", "./lua/telescope/actions/generate.lua", "./lua/telescope/previewers/init.lua", "./lua/telescope/actions/history.lua", } local output_file = "./doc/telescope.txt" local output_file_handle = io.open(output_file, "w") for _, input_file in ipairs(input_files) do docgen.write(input_file, output_file_handle) end output_file_handle:write " vim:tw=78:ts=8:ft=help:norl:\n" output_file_handle:close() vim.cmd [[checktime]] end docs.test() return docs ================================================ FILE: scripts/minimal_init.vim ================================================ set rtp+=. set rtp+=../plenary.nvim/ set rtp+=../tree-sitter-lua/ runtime! plugin/plenary.vim runtime! plugin/telescope.lua let g:telescope_test_delay = 100 ================================================ FILE: telescope.nvim-scm-1.rockspec ================================================ local MODREV, SPECREV = 'scm', '-1' rockspec_format = '3.0' package = 'telescope.nvim' version = MODREV .. SPECREV description = { summary = 'Find, Filter, Preview, Pick. All lua, all the time.', detailed = [[ 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. ]], labels = { 'neovim', 'plugin', }, homepage = 'https://github.com/nvim-telescope/telescope.nvim', license = 'MIT', } dependencies = { 'lua == 5.1', 'plenary.nvim', } source = { url = 'git://github.com/nvim-telescope/telescope.nvim', } build = { type = 'builtin', copy_directories = { 'doc', 'ftplugin', 'plugin', 'scripts', 'data', } }