Repository: folke/trouble.nvim Branch: main Commit: bd67efe408d4 Files: 66 Total size: 327.7 KB Directory structure: gitextract_atuxkb0r/ ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ ├── labeler.yml │ ├── pr.yml │ ├── stale.yml │ └── update.yml ├── .gitignore ├── .lua-format ├── .markdownlint-cli2.yaml ├── .neoconf.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── doc/ │ ├── trouble.nvim-examples.txt │ ├── trouble.nvim-filter.txt │ └── trouble.nvim.txt ├── docs/ │ ├── examples.md │ └── filter.md ├── lua/ │ └── trouble/ │ ├── api.lua │ ├── async.lua │ ├── cache.lua │ ├── command.lua │ ├── config/ │ │ ├── actions.lua │ │ ├── highlights.lua │ │ ├── init.lua │ │ └── parser.lua │ ├── docs.lua │ ├── filter.lua │ ├── format.lua │ ├── init.lua │ ├── item.lua │ ├── promise.lua │ ├── providers/ │ │ └── telescope.lua │ ├── sort.lua │ ├── sources/ │ │ ├── diagnostics.lua │ │ ├── fzf.lua │ │ ├── init.lua │ │ ├── lsp.lua │ │ ├── qf.lua │ │ ├── snacks.lua │ │ └── telescope.lua │ ├── spec.lua │ ├── tree.lua │ ├── util.lua │ └── view/ │ ├── indent.lua │ ├── init.lua │ ├── main.lua │ ├── preview.lua │ ├── render.lua │ ├── section.lua │ ├── text.lua │ ├── treesitter.lua │ └── window.lua ├── scripts/ │ ├── docs │ └── test ├── selene.toml ├── stylua.toml ├── tests/ │ ├── minit.lua │ ├── parser_spec.lua │ └── spec_spec.lua └── vim.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] insert_final_newline = true indent_style = space indent_size = 2 charset = utf-8 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug Report description: File a bug/issue title: "bug: " labels: [bug] body: - type: markdown attributes: value: | **Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/trouble.nvim) and search [existing issues](https://github.com/folke/trouble.nvim/issues). Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/trouble.nvim/discussions) and will be closed. - type: checkboxes attributes: label: Did you check docs and existing issues? description: Make sure you checked all of the below before submitting an issue options: - label: I have read all the trouble.nvim docs required: true - label: I have updated the plugin to the latest version before submitting this issue required: true - label: I have searched the existing issues of trouble.nvim required: true - label: I have searched the existing issues of plugins related to this issue required: true - type: input attributes: label: "Neovim version (nvim -v)" placeholder: "0.8.0 commit db1b0ee3b30f" validations: required: true - type: input attributes: label: "Operating system/version" placeholder: "MacOS 11.5" validations: required: true - type: textarea attributes: label: Describe the bug description: A clear and concise description of what the bug is. Please include any related errors you see in Neovim. validations: required: true - type: textarea attributes: label: Steps To Reproduce description: Steps to reproduce the behavior. placeholder: | 1. 2. 3. validations: required: true - type: textarea attributes: label: Expected Behavior description: A concise description of what you expected to happen. validations: required: true - type: textarea attributes: label: Repro description: Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua` value: | vim.env.LAZY_STDPATH = ".repro" load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))() require("lazy.minit").repro({ spec = { { "folke/trouble.nvim", opts = {} }, -- add any other plugins here }, }) render: lua validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Ask a question url: https://github.com/folke/trouble.nvim/discussions about: Use Github discussions instead ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature Request description: Suggest a new feature title: "feature: " labels: [enhancement] body: - type: checkboxes attributes: label: Did you check the docs? description: Make sure you read all the docs before submitting a feature request options: - label: I have read all the trouble.nvim docs required: true - type: textarea validations: required: true attributes: label: Is your feature request related to a problem? Please describe. description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - type: textarea validations: required: true attributes: label: Describe the solution you'd like description: A clear and concise description of what you want to happen. - type: textarea validations: required: true attributes: label: Describe alternatives you've considered description: A clear and concise description of any alternative solutions or features you've considered. - type: textarea validations: required: false attributes: label: Additional context description: Add any other context or screenshots about the feature request here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Description ## Related Issue(s) ## Screenshots ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: [main, master] pull_request: jobs: ci: uses: folke/github/.github/workflows/ci.yml@main secrets: inherit with: plugin: trouble.nvim repo: folke/trouble.nvim ================================================ FILE: .github/workflows/labeler.yml ================================================ name: "PR Labeler" on: - pull_request_target jobs: labeler: uses: folke/github/.github/workflows/labeler.yml@main secrets: inherit ================================================ FILE: .github/workflows/pr.yml ================================================ name: PR Title on: pull_request_target: types: - opened - edited - synchronize - reopened - ready_for_review permissions: pull-requests: read jobs: pr-title: uses: folke/github/.github/workflows/pr.yml@main secrets: inherit ================================================ FILE: .github/workflows/stale.yml ================================================ name: Stale Issues & PRs on: schedule: - cron: "30 1 * * *" jobs: stale: if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner) uses: folke/github/.github/workflows/stale.yml@main secrets: inherit ================================================ FILE: .github/workflows/update.yml ================================================ name: Update Repo on: workflow_dispatch: schedule: # Run every hour - cron: "0 * * * *" jobs: update: if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner) uses: folke/github/.github/workflows/update.yml@main secrets: inherit ================================================ FILE: .gitignore ================================================ *.log /.repro /.tests /build /debug /doc/tags foo.* node_modules tt.* ================================================ FILE: .lua-format ================================================ # https://github.com/Koihik/LuaFormatter/blob/master/docs/Style-Config.md #column_limit: 100 #indent_width: 4 #continuation_indent_width: 4 #use_tab: false #chop_down_parameter: true #chop_down_table: true #chop_down_kv_table: true #single_quote_to_double_quote: true #spaces_inside_table_braces: true #align_parameter: true #keep_simple_control_block_one_line: true #extra_sep_at_table_end: true ================================================ FILE: .markdownlint-cli2.yaml ================================================ config: MD013: false MD033: false ================================================ FILE: .neoconf.json ================================================ { "neodev": { "library": { "plugins": [ "plenary.nvim", "nvim-web-devicons", "telescope.nvim", "lazy.nvim" ] } }, "lspconfig": { "lua_ls": { "Lua.runtime.version": "LuaJIT", "Lua.workspace.checkThirdParty": false } } } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [3.7.1](https://github.com/folke/trouble.nvim/compare/v3.7.0...v3.7.1) (2025-01-28) ### Bug Fixes * **lsp:** make_position_params with offset encoding. Closes [#606](https://github.com/folke/trouble.nvim/issues/606) ([6f380b8](https://github.com/folke/trouble.nvim/commit/6f380b8826fb819c752c8fd7daaee9ef96d4c689)) ## [3.7.0](https://github.com/folke/trouble.nvim/compare/v3.6.0...v3.7.0) (2025-01-15) ### Features * **config:** add `close` counterparts to jump split actions ([#584](https://github.com/folke/trouble.nvim/issues/584)) ([928e6d0](https://github.com/folke/trouble.nvim/commit/928e6d01c83b87137a7baf7221fdd070aed3b313)) * **preview:** allow sources to decorate the preview buffer/window ([affd249](https://github.com/folke/trouble.nvim/commit/affd249ab579c1380da8513b9f850463c6408e9b)) * **sources:** added snacks picker source ([fa32f71](https://github.com/folke/trouble.nvim/commit/fa32f71be4d6c7c2cd4db5bf89cd836248c7bd67)) ### Bug Fixes * **format:** for "attempt to index local 'signs' (a boolean value)" errors in nvim-0.10.1+ ([#579](https://github.com/folke/trouble.nvim/issues/579)) ([2e7cb80](https://github.com/folke/trouble.nvim/commit/2e7cb80e2a4f64373228b78cb2080c423d771ef8)) * **lsp:** always use actual symbol kind names. See [#568](https://github.com/folke/trouble.nvim/issues/568) ([11bcbc0](https://github.com/folke/trouble.nvim/commit/11bcbc0361420875b8bd803267cd532a350c398b)) * **lsp:** handle invalid line positions ([1a2efaf](https://github.com/folke/trouble.nvim/commit/1a2efaf06d2966ffe3a1ef4a90d0bd8b9d870643)) * **lsp:** use new vim.str_byteindex if available to calculate start positions of LSP ranges ([86746d2](https://github.com/folke/trouble.nvim/commit/86746d2b5890139a0270c6693ece219912fd73c0)) * **lsp:** use old-style args for vim.str_byteindex. Fixes [#604](https://github.com/folke/trouble.nvim/issues/604) ([c633e85](https://github.com/folke/trouble.nvim/commit/c633e8559adf529b85167a4cb489d7358e9efb1a)) * **snacks:** use filtered items instead of all ([2423cd2](https://github.com/folke/trouble.nvim/commit/2423cd20ae2faadec9edd7013617f7b80a3ae628)) ### Performance Improvements * **debug:** don't create obj dumps ([1fe80c7](https://github.com/folke/trouble.nvim/commit/1fe80c7cdf86d6a92ab83c0d1dac1cf8aff68b0d)) * **treesitter:** set regions early ([#587](https://github.com/folke/trouble.nvim/issues/587)) ([20aa858](https://github.com/folke/trouble.nvim/commit/20aa858a86a09458c3851464eab0c5560b5249c0)) ## [3.6.0](https://github.com/folke/trouble.nvim/compare/v3.5.2...v3.6.0) (2024-07-21) ### Features * allow disabling a key ([891e76d](https://github.com/folke/trouble.nvim/commit/891e76df4628d5bb3ad41edb4269592c19b35537)) ### Bug Fixes * **text:** skip treesitter when buf is no longer valid. Fixes [#556](https://github.com/folke/trouble.nvim/issues/556) ([05694b4](https://github.com/folke/trouble.nvim/commit/05694b4e7d67fe1c46503e92a7b812fa58d92702)) ## [3.5.2](https://github.com/folke/trouble.nvim/compare/v3.5.1...v3.5.2) (2024-07-19) ### Bug Fixes * **util:** concealcursor ([e01c99e](https://github.com/folke/trouble.nvim/commit/e01c99eb36c93c77e8985ce9615a75bb73c8c7cf)) ### Performance Improvements * **treesitter:** incremental parsing for highlighter ([85154ce](https://github.com/folke/trouble.nvim/commit/85154cedf9b5bf64e56046d493cad7afc3416621)) ## [3.5.1](https://github.com/folke/trouble.nvim/compare/v3.5.0...v3.5.1) (2024-07-04) ### Bug Fixes * **command:** weird issue with number keys. no idea when this happens. oh well... Fixes [#528](https://github.com/folke/trouble.nvim/issues/528) ([95568c6](https://github.com/folke/trouble.nvim/commit/95568c61416ff3dea2b6177deeb8a51130d6fd7a)) * **diagnostics:** ruff generates a `vim.NIL` diag code. Closes [#527](https://github.com/folke/trouble.nvim/issues/527) ([edd9684](https://github.com/folke/trouble.nvim/commit/edd9684089b19684d5dad90bd5fcfceb48719212)) * **lsp:** use caller text for call locations in incoming lsp calls. Fixes [#529](https://github.com/folke/trouble.nvim/issues/529) ([12dc19a](https://github.com/folke/trouble.nvim/commit/12dc19a8aba6f964fc6c4060649782a2e57de0cf)) ## [3.5.0](https://github.com/folke/trouble.nvim/compare/v3.4.3...v3.5.0) (2024-07-04) ### Features * added explicit support for mini.icons ([42dcb58](https://github.com/folke/trouble.nvim/commit/42dcb58e95723f833135d5cf406c38bd54304389)) ### Bug Fixes * **telescope:** item path. Fixes [#521](https://github.com/folke/trouble.nvim/issues/521) ([6e19371](https://github.com/folke/trouble.nvim/commit/6e1937138b2c292ac0d3e8d9bfc36a29a515a380)) * **telescope:** use (lnum, 0) for telescope item without col ([#524](https://github.com/folke/trouble.nvim/issues/524)) ([25204b7](https://github.com/folke/trouble.nvim/commit/25204b7e134005dfcb694a0b6d227c98ce3ad164)) ## [3.4.3](https://github.com/folke/trouble.nvim/compare/v3.4.2...v3.4.3) (2024-06-23) ### Bug Fixes * **item:** empty filenames ([77f17d1](https://github.com/folke/trouble.nvim/commit/77f17d1bb29b32e06f75afa5c4fe0eba6f5ab397)) * **promise:** vim.loop. Fixes [#513](https://github.com/folke/trouble.nvim/issues/513) ([1acfb6c](https://github.com/folke/trouble.nvim/commit/1acfb6c45c38f07f4d0a5e4cbdd60c9bb6880908)) * **util:** crlf. Fixes [#518](https://github.com/folke/trouble.nvim/issues/518) ([032fa2c](https://github.com/folke/trouble.nvim/commit/032fa2c36a7c8738eb1e1d2f52a433be085f603a)) * **utils:** use `vim.loop or vim.ev` declared in the beginning of a file ([#519](https://github.com/folke/trouble.nvim/issues/519)) ([235dc61](https://github.com/folke/trouble.nvim/commit/235dc61cf49b61e7970897c3eed51b1b30121b9e)) ## [3.4.2](https://github.com/folke/trouble.nvim/compare/v3.4.1...v3.4.2) (2024-06-14) ### Bug Fixes * correct invalid float positions. Fixes [#502](https://github.com/folke/trouble.nvim/issues/502) ([88a40f1](https://github.com/folke/trouble.nvim/commit/88a40f1cc3af846b520ae167f0177b5faa148c86)) * **diagnostics:** custom format for code. Fixes [#508](https://github.com/folke/trouble.nvim/issues/508) ([ada78fa](https://github.com/folke/trouble.nvim/commit/ada78fae41fc05d52883f19fb5e22d5a61e0ef08)) * fixup ([60b0ac3](https://github.com/folke/trouble.nvim/commit/60b0ac3772e991bc194207afc28368a5f15d913a)) * **highlights:** link TroubleBasename to TroubleFilename. Fixes [#507](https://github.com/folke/trouble.nvim/issues/507) ([276e7b7](https://github.com/folke/trouble.nvim/commit/276e7b7a8764cd59de5c8a588771a54a979ab3c3)) * **main:** handle windows with changed buffers ([286c044](https://github.com/folke/trouble.nvim/commit/286c04474cbb24894d233e6b0c00f1e6c8d2ae54)) * **view:** dont go to main when not in the trouble window when closing ([8d5e05c](https://github.com/folke/trouble.nvim/commit/8d5e05c0d0ce7a2c630ce92cb3cc923044848063)) ## [3.4.1](https://github.com/folke/trouble.nvim/compare/v3.4.0...v3.4.1) (2024-06-12) ### Bug Fixes * **fzf:** added descriptions ([5e45bb7](https://github.com/folke/trouble.nvim/commit/5e45bb78f8da3444d35616934c180fce3742c439)) ## [3.4.0](https://github.com/folke/trouble.nvim/compare/v3.3.0...v3.4.0) (2024-06-11) ### Features * added fzf-lua integration ([d14323f](https://github.com/folke/trouble.nvim/commit/d14323fe3461b89e91fb569148b44731655ae196)) * **fzf-lua:** added smart open/add that will use selection or all when nothing selected. ([bed3c5b](https://github.com/folke/trouble.nvim/commit/bed3c5b79298d94d4981d86ed699c70f58ceccff)) ### Bug Fixes * **fzf-lua:** smart-open on windows ([4d0f045](https://github.com/folke/trouble.nvim/commit/4d0f0454ae2a246ec3e0ff541a347164dac23b7b)) * initialize `auto_open`. Fixes [#489](https://github.com/folke/trouble.nvim/issues/489) ([0793267](https://github.com/folke/trouble.nvim/commit/0793267d3d4b782e46161931b7cbaaf062a892d7)) * **spec:** properly process actions. Fixes [#494](https://github.com/folke/trouble.nvim/issues/494) ([3082f4b](https://github.com/folke/trouble.nvim/commit/3082f4b10fe9f0a8aa922065b998bc37115c4bef)) * **telescope:** autmatically select telescope_files mode if list are files without locations. Fixes [#466](https://github.com/folke/trouble.nvim/issues/466) ([1ad6b14](https://github.com/folke/trouble.nvim/commit/1ad6b141316f90a658c6d654516092d43e3e596c)) * **telescope:** set end_pos to end of word ([4deb811](https://github.com/folke/trouble.nvim/commit/4deb8111e7ffa48a4a27bad1ecdfb7779f4efb7d)) * **views:** pending should be considered open. Fixes [#492](https://github.com/folke/trouble.nvim/issues/492) ([57b50a6](https://github.com/folke/trouble.nvim/commit/57b50a6dc129f3a82c3bdd9f81b9f2d4e770ac09)) ## [3.3.0](https://github.com/folke/trouble.nvim/compare/v3.2.0...v3.3.0) (2024-06-07) ### Features * **lsp:** most lsp sources now support `params.include_current`. Fixes [#482](https://github.com/folke/trouble.nvim/issues/482) ([29d19d4](https://github.com/folke/trouble.nvim/commit/29d19d4f2102306176578f1fe537fbd9740b19e1)) * **window:** more options for mapping keys ([fdcfc5a](https://github.com/folke/trouble.nvim/commit/fdcfc5a200491e9509e56e04c6b3cdee8ada3153)) * you can now use `dd` and `d` to delete items in the trouble list. Fixes [#149](https://github.com/folke/trouble.nvim/issues/149). Fixes [#347](https://github.com/folke/trouble.nvim/issues/347) ([e879302](https://github.com/folke/trouble.nvim/commit/e879302d003bf5bda746a36365431d4a72cf3226)) ### Bug Fixes * **api:** only refresh on open if there's no action. Fixes [#488](https://github.com/folke/trouble.nvim/issues/488) ([2661f46](https://github.com/folke/trouble.nvim/commit/2661f4612209cbbc1106fb9537666ea0133e4859)) * **preview:** fixed mouse clicks in the preview main window. Fixes [#484](https://github.com/folke/trouble.nvim/issues/484) ([98d9ed7](https://github.com/folke/trouble.nvim/commit/98d9ed74aec4e82171de3ae0541cdd078558e546)) * **telescope:** show error when use tries to add when telescope picker does not exist ([c11dc27](https://github.com/folke/trouble.nvim/commit/c11dc2777d52da2c8da25836817e43608ec951a5)) * use vim.loop for nvim 0.9 in view/init.lua ([#487](https://github.com/folke/trouble.nvim/issues/487)) ([791278e](https://github.com/folke/trouble.nvim/commit/791278e498e1147520e4214982767f77ca4a99df)) * **view:** when calling open when the view is already open, do a refresh. See [#485](https://github.com/folke/trouble.nvim/issues/485) ([39595e8](https://github.com/folke/trouble.nvim/commit/39595e883e2f91456413ca4df287575d31665940)) ## [3.2.0](https://github.com/folke/trouble.nvim/compare/v3.1.0...v3.2.0) (2024-06-06) ### Features * **lsp:** add incoming/outgoing calls to lsp mode ([8adafc1](https://github.com/folke/trouble.nvim/commit/8adafc14d8fe2a4471a0311ff72927250390d7bd)) * **lsp:** added support for showing locations from lsp execute commands ([b1d16ac](https://github.com/folke/trouble.nvim/commit/b1d16ac02d787e40165130e0cd09474ce639b175)) * promise class ([84f0c6d](https://github.com/folke/trouble.nvim/commit/84f0c6d047dbf182622f3d89bc47ec4a70c900b2)) ### Bug Fixes * **api:** show error when an invalid mode was used. Fixes [#465](https://github.com/folke/trouble.nvim/issues/465) ([4b1914c](https://github.com/folke/trouble.nvim/commit/4b1914c5cdbf7be18fee797c410df2faa2be13f2)) * **format:** pos format. See [#472](https://github.com/folke/trouble.nvim/issues/472) ([abdfa1d](https://github.com/folke/trouble.nvim/commit/abdfa1daeb9713470a9b61676a82f24f32e31900)) * **lsp:** check for nil on faulty lsp results ([d7f69ff](https://github.com/folke/trouble.nvim/commit/d7f69ff5638cf1864cabac54ade1b1694adfe085)) * **lsp:** dont process nil results ([06a4892](https://github.com/folke/trouble.nvim/commit/06a48922e83b114a78c63ec770819b4afacd2166)) * **lsp:** send request only to needed clients ([c147a75](https://github.com/folke/trouble.nvim/commit/c147a75c421b2df6986d82f61657ccec2f302091)) * **lsp:** use document uri of document symbols don't have an uri set. Fixes [#480](https://github.com/folke/trouble.nvim/issues/480) ([358f0ee](https://github.com/folke/trouble.nvim/commit/358f0ee6ce4c379a3b0c37bb04ab6587c86e285a)) * **preview:** hide winbar when previewing in main. Fixes [#464](https://github.com/folke/trouble.nvim/issues/464) ([250ea79](https://github.com/folke/trouble.nvim/commit/250ea79c810a3e5fff846c788792441f1c795c92)) * **preview:** respect fold settings. Fixes [#459](https://github.com/folke/trouble.nvim/issues/459) ([29d1bb8](https://github.com/folke/trouble.nvim/commit/29d1bb81adc847e89ddbbf5b11ff0079daf7cc0a)) * **preview:** set correct extmark priorities in preview highlight. Fixes [#476](https://github.com/folke/trouble.nvim/issues/476) ([13ad959](https://github.com/folke/trouble.nvim/commit/13ad95902cf479b0fa091a77368af0e03b486fe3)) * **view:** correctly set folding options to wo. See [#477](https://github.com/folke/trouble.nvim/issues/477) ([9151797](https://github.com/folke/trouble.nvim/commit/915179759c9459b69faae90a38da6fc1ca6b90d7)) * **view:** ensure fold settings are correct for the trouble views. See [#477](https://github.com/folke/trouble.nvim/issues/477) ([b5181b6](https://github.com/folke/trouble.nvim/commit/b5181b65912c704d5378f8fe6889924f0182c357)) * **view:** execute actions on first render ([97bfb74](https://github.com/folke/trouble.nvim/commit/97bfb74826476b26634b5321c5d8dfbc46e41497)) * **window:** account for winbar for preview in main. Fixes [#468](https://github.com/folke/trouble.nvim/issues/468) ([23ded52](https://github.com/folke/trouble.nvim/commit/23ded52593d017fd7d6042215460419801e35481)) * **window:** set default winblend=0. See [#468](https://github.com/folke/trouble.nvim/issues/468) ([e296940](https://github.com/folke/trouble.nvim/commit/e2969409cf3f38f69913cc8fd9aa13137aabe760)) ### Performance Improvements * use promises for fetching sections ([e49a490](https://github.com/folke/trouble.nvim/commit/e49a49044cca072c4aca1cb3a5013aa92ac3b4f9)) ## [3.1.0](https://github.com/folke/trouble.nvim/compare/v3.0.0...v3.1.0) (2024-05-31) ### Features * added severity filter keymap and improved filtering actions ([7842dbb](https://github.com/folke/trouble.nvim/commit/7842dbb70f088cbaae969004bd2fbae09b2a2d26)) * only open trouble when results (optionally). Fixes [#450](https://github.com/folke/trouble.nvim/issues/450) ([8fbd2ab](https://github.com/folke/trouble.nvim/commit/8fbd2abb3ff42ebb134e389f405bfa9140db1fe3)) * **telescope:** allow passing additional trouble options to telescope open/add. Fixes [#457](https://github.com/folke/trouble.nvim/issues/457) ([4eaaf9c](https://github.com/folke/trouble.nvim/commit/4eaaf9cf8b967010998ccfc4af525b3e6d70b8b5)) ### Bug Fixes * close section session when needed ([2caf73d](https://github.com/folke/trouble.nvim/commit/2caf73d2d136625d77c0d25cc3b5d5e1e0bef3d0)) * **fold:** start folding with closest non leaf node. Fixes [#420](https://github.com/folke/trouble.nvim/issues/420) ([f248c69](https://github.com/folke/trouble.nvim/commit/f248c6941ba5a48be531cbb25aac32e1042c65ad)) * **follow:** improve the way follow works ([cf81aac](https://github.com/folke/trouble.nvim/commit/cf81aaca820017388fc630c534774c95b58233f2)) * **format:** compat old signs ([0e843ed](https://github.com/folke/trouble.nvim/commit/0e843edbdc1b25ca6a5468d636b22e7035a4ad69)) * **format:** fallback to sign_defined. Fixes [#448](https://github.com/folke/trouble.nvim/issues/448) ([36545cb](https://github.com/folke/trouble.nvim/commit/36545cb88fa999f211bfc341998f501803bf5434)) * **lsp:** batch get offset position for lsp results. See [#452](https://github.com/folke/trouble.nvim/issues/452) ([96c30dc](https://github.com/folke/trouble.nvim/commit/96c30dc6ae10e42ab47c1f68d7f715bf01100c48)) * **lsp:** correctly clear location cache ([7ea94a6](https://github.com/folke/trouble.nvim/commit/7ea94a6366141878758938010e4a0818a56721ad)) * **lsp:** exclude locations that match the current line ([8c03e13](https://github.com/folke/trouble.nvim/commit/8c03e133bc88fb7c242e9915d06f0a8978511c29)) * make sure line is always a string passed to get_line_col ([5a12185](https://github.com/folke/trouble.nvim/commit/5a12185787896da209738bd41cbe4133d82ce9bb)) * **preview:** correctly load non-scratch buffers ([965f56f](https://github.com/folke/trouble.nvim/commit/965f56f3e17baee4213cf50637f92de4be32d8e9)) * **preview:** correctly pass options to create scratch buffers. Fixes [#451](https://github.com/folke/trouble.nvim/issues/451) ([c50c7e3](https://github.com/folke/trouble.nvim/commit/c50c7e35d4f504d6336875994109c546ff0634b5)) * **preview:** don't error on invalid positions ([6112c3c](https://github.com/folke/trouble.nvim/commit/6112c3c5c903a05178276a083edc756ba3cb65a0)) * **qf:** only listen for TextChanged in the main buffer. See [#201](https://github.com/folke/trouble.nvim/issues/201) ([f75992f](https://github.com/folke/trouble.nvim/commit/f75992f9a1b93cc4490dca28f93acc921c25419e)) * **qf:** update qflist on TextChanged to update pos. Fixes [#201](https://github.com/folke/trouble.nvim/issues/201) ([c1d9294](https://github.com/folke/trouble.nvim/commit/c1d9294eb73479fd4007237613eb7e945cd84e20)) * stop ([bda8de4](https://github.com/folke/trouble.nvim/commit/bda8de4205f06c3939b8b59e4da1f3713d04ea05)) * **telescope:** remove filter on `buf = 0`. See [#399](https://github.com/folke/trouble.nvim/issues/399) ([f776ab0](https://github.com/folke/trouble.nvim/commit/f776ab0ff1658f052b7345d4bbd5961b443ea8a0)) * **view:** restore loc on first render and dont delete last loc if trouble window was never visisted. See [#367](https://github.com/folke/trouble.nvim/issues/367) ([51bf510](https://github.com/folke/trouble.nvim/commit/51bf51068d929173157ebcfb863115760c837355)) ### Performance Improvements * **lsp:** cache location requests ([6053627](https://github.com/folke/trouble.nvim/commit/6053627943020d9774c75ec637eb06847a79c7a1)) * **lsp:** optimize batch fetching lsp item locations. Fixes [#452](https://github.com/folke/trouble.nvim/issues/452) ([a6f1af5](https://github.com/folke/trouble.nvim/commit/a6f1af567fc987306f0f328e78651bab1bfe874e)) * much faster treesitter highlighter ([d4de08d](https://github.com/folke/trouble.nvim/commit/d4de08d9314a9ddf7278ee16efb58d0efe332bc8)) * prevent autocmd leaks ([9e3391c](https://github.com/folke/trouble.nvim/commit/9e3391ce735f4f6fa98fe70ba9a3e444f2fd539a)) * **preview:** re-use existing preview when preview is for the same file ([a415b64](https://github.com/folke/trouble.nvim/commit/a415b64b8a702ab6388e3aaaf16306750fc53f79)) ## [3.0.0](https://github.com/folke/trouble.nvim/compare/v2.10.0...v3.0.0) (2024-05-30) ### ⚠ BREAKING CHANGES * Trouble v3 is now merged in main. You may need to update your configs. ### Features * `Trouble` now shows vim.ui.select to chose a mode ([0189184](https://github.com/folke/trouble.nvim/commit/01891844a9adb3b5b2de508724024d516a2b891a)) * added basename/dirname ([bb3740a](https://github.com/folke/trouble.nvim/commit/bb3740a1c41e83bcd59c3fe04714a85b445c4742)) * added help ([68ac238](https://github.com/folke/trouble.nvim/commit/68ac238aeef333a37dc95d875bed46a2698793d5)) * added kind symbol highlights ([de08657](https://github.com/folke/trouble.nvim/commit/de086574208b19b762055487334ce50ca95cc008)) * added lpeg parser for parsing `:Trouble` args into lua tables ([b25ef53](https://github.com/folke/trouble.nvim/commit/b25ef53117b0bdc5733d26e42a55c7f32daadbe5)) * added missing fold keymaps. folding is now feature complete ([9fb1be0](https://github.com/folke/trouble.nvim/commit/9fb1be0915202989bd17e0c9768be23ae7b15010)) * added multiline option back ([d80e978](https://github.com/folke/trouble.nvim/commit/d80e978f70cc3c026ad028dafbde9e3ad45ba54c)) * added proper api ([a327003](https://github.com/folke/trouble.nvim/commit/a3270035999dc965176ccd140dcc9afe57f0934a)) * added support for formatting fields with a treesitter language ([21cfee9](https://github.com/folke/trouble.nvim/commit/21cfee9e4e026482c1c9719156aae3152b2c590a)) * allow items without buf ([c3b01ce](https://github.com/folke/trouble.nvim/commit/c3b01ce7662dda3a542c52aa1521f8467300c84a)) * allow top-level filter ([12447df](https://github.com/folke/trouble.nvim/commit/12447df2a81205b8bda12dd1c9271c1c0059184f)) * **config:** added `auto_jump` to jump to the item when there's only one. Fixes [#409](https://github.com/folke/trouble.nvim/issues/409) ([94a84ab](https://github.com/folke/trouble.nvim/commit/94a84ab884757b1a9f697807e7bdace8b8919afb)) * **config:** added keymap to inspect an item. Useful for dev ([9da1a47](https://github.com/folke/trouble.nvim/commit/9da1a4783bc0d87427f5cbf6964321774e0bb1bc)) * **config:** set `focus=false` by default ([c7e5398](https://github.com/folke/trouble.nvim/commit/c7e539819e6d21428a747f46715a23b3d1a204b6)) * **diagnostics:** added support for diagnostics signs on Neovim >= 0.10.0. Fixes [#369](https://github.com/folke/trouble.nvim/issues/369), fixes [#389](https://github.com/folke/trouble.nvim/issues/389) ([6303740](https://github.com/folke/trouble.nvim/commit/6303740eb1a0730b5654d554ba38bd9614c87e28)) * **filter:** added filetype filter ([e541444](https://github.com/folke/trouble.nvim/commit/e5414444bdbd5fb70a954ad24abeaa0866179f62)) * **filter:** easier filtering of any values ([1b528d8](https://github.com/folke/trouble.nvim/commit/1b528d8f3b91fe07ab4f27cbe8eee65b0532192b)) * **filter:** range filter ([34a06d6](https://github.com/folke/trouble.nvim/commit/34a06d6f4bd32b37f685a36a5037c1556ce6b88f)) * filters, formatters and sorters are now configurable ([c16679d](https://github.com/folke/trouble.nvim/commit/c16679ddf67f28b5df0735f488497a4c1e7881ee)) * **format:** formats now support `{one|two}`. First field that returns a value will be used ([26ad82e](https://github.com/folke/trouble.nvim/commit/26ad82eb3c81a434ade3d7bebd97e02565ac717e)) * global view filters and easy toggling of just items of the current buffer ([11e7c39](https://github.com/folke/trouble.nvim/commit/11e7c39803ff33c68346019a46172fe9be5f3f6d)) * improved commandline parser and completion ([f7eccfb](https://github.com/folke/trouble.nvim/commit/f7eccfbddef64f3379cf6997617cb41e3005f355)) * initial commit of rewrite ([d9542ca](https://github.com/folke/trouble.nvim/commit/d9542ca97e37a43844d9088bf453bbb257de423c)) * item hierarchies and directory grouping ([d2ed413](https://github.com/folke/trouble.nvim/commit/d2ed41320e548149d024634d8a6aa1b5c40396a1)) * Item.get_lang and Item.get_ft ([498da6b](https://github.com/folke/trouble.nvim/commit/498da6bcff8170f620506f66dd71465b3565baaa)) * **item:** util method to add missing text to items ([de9e7e6](https://github.com/folke/trouble.nvim/commit/de9e7e68ebb7aa36d78a68207d42943d49a31a85)) * **lsp:** added `lsp_incoming_calls` and `lsp_outgoing_calls`. Closes [#222](https://github.com/folke/trouble.nvim/issues/222) ([b855469](https://github.com/folke/trouble.nvim/commit/b855469429f10c74a3314432ba2735a32115cbb2)) * **lsp:** document symbols caching and compat with Neovim 0.9.5 ([2f49b92](https://github.com/folke/trouble.nvim/commit/2f49b920b0822a3d06c25d24d840830016168a82)) * main window tracking ([23a0631](https://github.com/folke/trouble.nvim/commit/23a06316607fe2d2ab311fdb5bb45157c2d8ec91)) * make the preview action a toggle ([86da179](https://github.com/folke/trouble.nvim/commit/86da1794855f71ec592efec2a6a65911f08892a2)) * preview can now be shown in a split/float ([e2919eb](https://github.com/folke/trouble.nvim/commit/e2919eb565ccc66d4adad729b4e323a718d41953)) * preview is now fully configurable ([b99110a](https://github.com/folke/trouble.nvim/commit/b99110adc3815f1b7b8fe3dd40f4c9da315f7cca)) * **preview:** option to force loading real buffers in preview. Fixes [#435](https://github.com/folke/trouble.nvim/issues/435) ([ccacba2](https://github.com/folke/trouble.nvim/commit/ccacba22b2c1946cfe1b9f767f7880bcd031ad7c)) * **preview:** use a float to show preview in the main window instead of messing with the main window itself ([9e0311d](https://github.com/folke/trouble.nvim/commit/9e0311d177af7cd6750d88280d589ceca4f7685a)) * **preview:** window var to know a window is a preview win ([dcecbb9](https://github.com/folke/trouble.nvim/commit/dcecbb9b9d67770c8df4c1c49b91631fbdae8ae5)) * **qf:** add treesitter highlighting to quickfix/loclist. Fixes [#441](https://github.com/folke/trouble.nvim/issues/441) ([325d681](https://github.com/folke/trouble.nvim/commit/325d681953611336cdfdf08a3d71e5125c5f89a5)) * **render:** `{field:ts}` will now use the treesitter lang of the item buffer for highlighting ([21af85c](https://github.com/folke/trouble.nvim/commit/21af85cc97860e3bcf157891c2598af517f9b421)) * **render:** added support for rendering multiple sections ([332b25b](https://github.com/folke/trouble.nvim/commit/332b25b09c7159a82322ec97f7aa1717133ffa6f)) * **source:** added lsp source ([3969907](https://github.com/folke/trouble.nvim/commit/39699074cd18cdeb5e9a29e80ffd2d239c58ac7e)) * **source:** added quickfix source ([3507b7b](https://github.com/folke/trouble.nvim/commit/3507b7b694ddee5921c7004c6bed0b71ab8e0920)) * **sources:** added support for loading external sources ([89ac6f1](https://github.com/folke/trouble.nvim/commit/89ac6f1a7f238ca65d964569786b205a496ad213)) * **sources:** added telescope source ([39069e2](https://github.com/folke/trouble.nvim/commit/39069e2f4139c7ae28cf7e16fe610b8462fb3939)) * **source:** sources can now have multiple child sources ([c433301](https://github.com/folke/trouble.nvim/commit/c4333014a770eca5a66c7685f4063d8de13517db)) * **source:** sources now always execute in the context of the main window even when a preview is active ([4eab561](https://github.com/folke/trouble.nvim/commit/4eab56122bb335a5627405791e4691af5043493e)) * **statusline:** added statusline component ([5c0b163](https://github.com/folke/trouble.nvim/commit/5c0b1639c83266427489bb515bffd8b8bc055809)) * **statusline:** allow 'fixing' the statusline bg color based on a hl_group. Fixes [#411](https://github.com/folke/trouble.nvim/issues/411) ([986b44d](https://github.com/folke/trouble.nvim/commit/986b44d4471ee8b8a708a0172fb0829a9c858543)) * **statusline:** statusline api ([c219a1a](https://github.com/folke/trouble.nvim/commit/c219a1a9f56a70ac55e89325c333fcfd0616ea97)) * **telescope:** added option to add telescope results to trouble, without clearing the existing results. Fixes [#370](https://github.com/folke/trouble.nvim/issues/370) ([a7119ab](https://github.com/folke/trouble.nvim/commit/a7119abb0cd1b1ef058fc99c036230bbe153504f)) * **tree:** added `flatten()` to get all items from the tree ([35c0236](https://github.com/folke/trouble.nvim/commit/35c0236ceb78fc37a94f5882f736416ebb15c306)) * Trouble v3 is now merged in main. You may need to update your configs. ([1b362b8](https://github.com/folke/trouble.nvim/commit/1b362b861eacb9b2367ce92129fad86352707311)) * **util:** better notify functions ([c68c915](https://github.com/folke/trouble.nvim/commit/c68c915353dc5162e5d7494b5ca0919f0e336318)) * **util:** fast get_lines for a buffer ([6940cd8](https://github.com/folke/trouble.nvim/commit/6940cd8c6913e834254e7221ede0ca6a38c81fc0)) * **util:** fast plain text split ([6a30aec](https://github.com/folke/trouble.nvim/commit/6a30aec15ca90e8b396a381f8f1b7c452f6ce68a)) * **util:** make throttles configurable ([8c297c1](https://github.com/folke/trouble.nvim/commit/8c297c171547e8c81fc918a7f31b3c2a8ce58512)) * **view:** added support for pinned views. Main window of the view will stay the same as long as its a valid window ([17131e2](https://github.com/folke/trouble.nvim/commit/17131e2b9a0b7d17046cb9b7ed9b7eeb36b6423a)) * **view:** expose some params in the trouble window var. Fixes [#357](https://github.com/folke/trouble.nvim/issues/357) ([a4b9849](https://github.com/folke/trouble.nvim/commit/a4b9849ce7ec14213069034403f5fc96d174046b)) * **view:** follow now also scrolls to the file in the list when not on an item ([30b939e](https://github.com/folke/trouble.nvim/commit/30b939efebd8559e9e84c95e3658c447f702b1c2)) * **view:** follow the current item in the list ([e76e280](https://github.com/folke/trouble.nvim/commit/e76e280701e643e8a3074edcb86e819298e7df12)) * **view:** when toggling a trouble list, restore to the last location. Fixes [#367](https://github.com/folke/trouble.nvim/issues/367) ([1d951f5](https://github.com/folke/trouble.nvim/commit/1d951f5c13fd56933e9170b84bfcbbf8ca1a582b)) * **window:** added possibility to override TroubleNormalNC. Fixes [#216](https://github.com/folke/trouble.nvim/issues/216) ([daa5157](https://github.com/folke/trouble.nvim/commit/daa5157e3f0f6cf80ca473d7e43bd73734d6594d)) * **window:** added support for showing a floating window over the main window ([3525169](https://github.com/folke/trouble.nvim/commit/35251698e7836ecb3ee981efe2efe7bcb64ca5f3)) * **window:** allow setting width/height a size for splits. either will be used based on position ([e8ee9b0](https://github.com/folke/trouble.nvim/commit/e8ee9b01ee46f9cd2e2a8fe8b883320f3212608d)) * **window:** expose some window vars for integration with other plugins (edgy.nvim) ([aae1da8](https://github.com/folke/trouble.nvim/commit/aae1da81cba50ca207ce3681e5ef927bd369f074)) ### Bug Fixes * add vim to parser globals ([6077342](https://github.com/folke/trouble.nvim/commit/6077342efe9e7f77756dd064cdb2129ffea5674f)) * **api:** make sure new=true works when opening with a mode string ([398ac76](https://github.com/folke/trouble.nvim/commit/398ac76019a74bbbe0c2b77c0a0a7a1e4ce71c3d)) * better defaults for lsp/diagnostics ([569416d](https://github.com/folke/trouble.nvim/commit/569416d52f1953c35ba8494d7cab2be1b01fe409)) * better way of creating preview buffers ([667b010](https://github.com/folke/trouble.nvim/commit/667b010ba81d7ce49fa2cca6cb80c0b85b328543)) * **command:** improved command completion ([9f59aac](https://github.com/folke/trouble.nvim/commit/9f59aac5ccdb3b5c2c1425176e0112efd7b84a16)) * **commmand:** show mode descriptions ([ce488b9](https://github.com/folke/trouble.nvim/commit/ce488b9b4ddc0f9a61d48d0ae8974c22e5472e0c)) * **config:** fixed some highlights to use the latest treesitter hl groups ([63313cd](https://github.com/folke/trouble.nvim/commit/63313cd5a1e55d1ec870f9716f2e6cc9b6471cfc)) * deprecated tbl_islist ([#436](https://github.com/folke/trouble.nvim/issues/436)) ([5aa7993](https://github.com/folke/trouble.nvim/commit/5aa79935f1de301ed3592981ad7031c166cc5c84)) * diagnostics sections ([27efb63](https://github.com/folke/trouble.nvim/commit/27efb6326d8bb9019d2b69e737c1c3741a3af568)) * **diagnostics:** use main buffer for buffer-local diags ([259770d](https://github.com/folke/trouble.nvim/commit/259770dd860fd08007d13c116a5a7ee985e5f5bf)) * **filter:** fix range filter to include col ([6cae8af](https://github.com/folke/trouble.nvim/commit/6cae8af72ccf1be72718d3f735a467bff23c9beb)) * **filter:** range should also check that the buffer is the same ([fdd27d8](https://github.com/folke/trouble.nvim/commit/fdd27d8ac5276147b91cee2c2578b178a3dd7be2)) * **format:** always pass a valid buffer to ftdetect even just the current onw ([b01b11e](https://github.com/folke/trouble.nvim/commit/b01b11efc901dc3bc5f89d164de5eb71d56cc257)) * **help:** sort keymaps case incensitive ([5d81927](https://github.com/folke/trouble.nvim/commit/5d81927bc7eb9de085d9ea6cf8b977616694f771)) * **highlights:** reset statusline hl groups when colorscheme changes ([a665272](https://github.com/folke/trouble.nvim/commit/a665272b1e1d4b06b1b6824cc3f853874b06c0a1)) * **item:** clamp pos ([6c0204c](https://github.com/folke/trouble.nvim/commit/6c0204cb7d758d70edc5b88919f19a76de853aa7)) * **jump:** save current main cursor to jump list before jumping. Fixes [#385](https://github.com/folke/trouble.nvim/issues/385) ([bd8bfc8](https://github.com/folke/trouble.nvim/commit/bd8bfc8abedbf992a6d8c0db941780e1a79d3b41)) * **lsp:** check if buf is still valid after receiving document symbols ([33ec71c](https://github.com/folke/trouble.nvim/commit/33ec71cf377c518d8b8c022e2b46511f85c3a47d)) * **lsp:** handle invalid positions. Fixes [#434](https://github.com/folke/trouble.nvim/issues/434) ([371cf26](https://github.com/folke/trouble.nvim/commit/371cf26bcbddb39e2e91d69dae90a480f29c3fc0)) * **lsp:** refresh on LspAttach ([17afc44](https://github.com/folke/trouble.nvim/commit/17afc44fc317449128f1804ea6f316ce457295cc)) * **main:** always return a main window, even when no main. Fixes [#426](https://github.com/folke/trouble.nvim/issues/426) ([bda72a5](https://github.com/folke/trouble.nvim/commit/bda72a548e4eb9cab3bcf567ca965b21a136263d)) * make focus the default ([ba1ae49](https://github.com/folke/trouble.nvim/commit/ba1ae497e1899af0e57adb815ab30e37b73d0b76)) * **parser:** handle empty args ([e667da7](https://github.com/folke/trouble.nvim/commit/e667da705c64509347829326d19668e3276355e9)) * **preview:** better preview for multiline items ([60c9fdc](https://github.com/folke/trouble.nvim/commit/60c9fdcad7003fe6ed6f4a225bf709acd19068df)) * **preview:** center preview location and open folds. See [#408](https://github.com/folke/trouble.nvim/issues/408) ([a87fa2a](https://github.com/folke/trouble.nvim/commit/a87fa2ae521d058ad67dc7610efdd234b792d6ef)) * **preview:** clear highlights of preview buffer ([d590491](https://github.com/folke/trouble.nvim/commit/d590491de9515caf5ec3a3a0bd0fdb3047b1fda3)) * **preview:** dont show preview for directories. Fixes [#410](https://github.com/folke/trouble.nvim/issues/410) ([769ee0f](https://github.com/folke/trouble.nvim/commit/769ee0f632ea3b6ffc5716590a711db340e80caf)) * **preview:** fixup for directory check ([eed25b2](https://github.com/folke/trouble.nvim/commit/eed25b2bcea6e59e5f5c92c184ac08be4590b6de)) * **preview:** pass valid buffer so that ftdetect works for ts files. See [#435](https://github.com/folke/trouble.nvim/issues/435) ([65f2430](https://github.com/folke/trouble.nvim/commit/65f2430f6d6276832ec9500b5eafabb01e17d01a)) * **preview:** set correct winhighlight for preview window in main. See [#408](https://github.com/folke/trouble.nvim/issues/408) ([8c3c1db](https://github.com/folke/trouble.nvim/commit/8c3c1db742e74f0e48134cd4e84c976c2706e6b6)) * **preview:** unload preview buffer again when closing and when it wasnt loaded before ([8cc680a](https://github.com/folke/trouble.nvim/commit/8cc680a25f30e63c9e92005155c7916b9a000ac9)) * proper deprecated fix ffs... Fixes [#438](https://github.com/folke/trouble.nvim/issues/438) ([a8264a6](https://github.com/folke/trouble.nvim/commit/a8264a65a0b894832ea642844f5b7c30112c458f)) * properly deal with multiline treesitter segments ([31681a9](https://github.com/folke/trouble.nvim/commit/31681a92e2e7fa0537284ad1516561b46cdb4a24)) * **qf:** col/row offsets ([8ad817f](https://github.com/folke/trouble.nvim/commit/8ad817f12b4c9c4d6bd239c963cda3ac518d272a)) * remove `buf = 0` sorting since it acts weirdly with next/prev ([2b589e9](https://github.com/folke/trouble.nvim/commit/2b589e938c4b5245d7f74b7f23293645e566cee3)) * remove space from `zz zv` command ([ca2cd56](https://github.com/folke/trouble.nvim/commit/ca2cd56d14df8fc619ba44bebd5334d78b57d74c)) * require Neovim >= 0.9.2 ([2448521](https://github.com/folke/trouble.nvim/commit/24485219198a1ab10731e45229f2736ec3242231)) * **section:** dont trigger on invalid buffers ([29ee890](https://github.com/folke/trouble.nvim/commit/29ee890b28280b0a8a504571596a0508244122e1)) * **setup:** add check for NEovim 0.10.0 or markdown parsers. Fixes [#413](https://github.com/folke/trouble.nvim/issues/413) ([6267ef1](https://github.com/folke/trouble.nvim/commit/6267ef15e98bd8df29be7a8d6f47b0e724ceaaaf)) * **sources:** always load sources when not registered yet. Fixes [#393](https://github.com/folke/trouble.nvim/issues/393) ([1470302](https://github.com/folke/trouble.nvim/commit/1470302bd7eef110aef3710dfc8808bc3c3a2179)) * specs and tests ([315f624](https://github.com/folke/trouble.nvim/commit/315f624492c54f9893631459fc79e6c0b33b7cad)) * **statusline:** double escape `#`. Fixes [#424](https://github.com/folke/trouble.nvim/issues/424) ([9ddfd47](https://github.com/folke/trouble.nvim/commit/9ddfd47eec3a1bd43f5e7dd34eb1084f6793eaba)) * **statusline:** make sure max_items is honored ([da8ba7d](https://github.com/folke/trouble.nvim/commit/da8ba7dfba2341c5ea679e6cff85c01943777380)) * **statusline:** schedule statusline refresh ([f000daa](https://github.com/folke/trouble.nvim/commit/f000daadd6d49b30eebacb2a6dd7c4d9758f2de6)) * **telescope:** close telescope after sending results to trouble ([2753932](https://github.com/folke/trouble.nvim/commit/2753932bed13cff73be80e2565043dccce899983)) * **telescope:** deprecation warning for old telescope provider ([0d7cdeb](https://github.com/folke/trouble.nvim/commit/0d7cdeba2d139f26314d53e2e06507b6a7b72e3b)) * **throttle:** fixed throttling so that it now only gets scheduled when there are pending args ([0db2084](https://github.com/folke/trouble.nvim/commit/0db20847636e6715cb2d1c542fee34d349f47ee3)) * **tree:** fixed tree item count. Fixes [#419](https://github.com/folke/trouble.nvim/issues/419) ([cb59440](https://github.com/folke/trouble.nvim/commit/cb594402cf4407bbf54111c20ef39d42d9b017f6)) * **tree:** make sure qf items always have a unique id. Fixes [#367](https://github.com/folke/trouble.nvim/issues/367) ([c0755d5](https://github.com/folke/trouble.nvim/commit/c0755d59731869994187b04adcb89ebce75c27eb)) * **treesitter:** show warning for missing treesitter parsers ([4253652](https://github.com/folke/trouble.nvim/commit/425365272136c731eea9da4c337f7b1dfe4d44d6)) * **tree:** use format as node id for group without fields ([a29c293](https://github.com/folke/trouble.nvim/commit/a29c29382da0dcb8554da25e40a4d2495dff771f)) * **ui:** better deal with invalid items positions and extmarks. Fixes [#404](https://github.com/folke/trouble.nvim/issues/404) ([bc0a194](https://github.com/folke/trouble.nvim/commit/bc0a19482ee8f68eb427939df2e28cca800b9cbb)) * **util:** deprecation warnings for tbl_islist ([7577f3a](https://github.com/folke/trouble.nvim/commit/7577f3a82ff60ef7425451b1f40dd83ffee12307)) * **util:** typo ([37f6266](https://github.com/folke/trouble.nvim/commit/37f62665dfc8db002f7fe62ae6b467c566329ec3)) * **util:** use xpcall in throttle for better stack traces ([6d9a0ba](https://github.com/folke/trouble.nvim/commit/6d9a0baeb226548b967329d7c045c38ff16a19e8)) * **view:** check if trouble win is still valid in OptionSet. Fixes [#400](https://github.com/folke/trouble.nvim/issues/400) ([e9fae8c](https://github.com/folke/trouble.nvim/commit/e9fae8c453eac69aa33dc4899c46b76417a04e3e)) * **view:** check that window is open before checking active item ([5b5446d](https://github.com/folke/trouble.nvim/commit/5b5446ddf2d6c50b555d28459ce7632ca39d6ac0)) * **view:** do `norm! zz zv` after jump. See [#408](https://github.com/folke/trouble.nvim/issues/408) ([74e31e7](https://github.com/folke/trouble.nvim/commit/74e31e732fa6888a72f6b73b3115b8ea84ef47f5)) * **view:** dont refresh items when calling `open` and already open ([7485aa7](https://github.com/folke/trouble.nvim/commit/7485aa70e0341c55bd25e2287b29e991c61297d1)) * **view:** dont trigger follow when moving. Fixes [#3359](https://github.com/folke/trouble.nvim/issues/3359) ([7cc4df2](https://github.com/folke/trouble.nvim/commit/7cc4df259b208a1f19a67a171da7d5f7e917e7c4)) * **view:** store restore locations when moving the cursor. See [#367](https://github.com/folke/trouble.nvim/issues/367) ([d8265a6](https://github.com/folke/trouble.nvim/commit/d8265a6f0eea8fff249decf3a1304991f850715b)) * **window:** main window float should have regular winhighlight ([6ccd579](https://github.com/folke/trouble.nvim/commit/6ccd579a177dc81d81706194131e23b24e93e5fa)) * **window:** properly deal with alien buffers opening in trouble windows ([92832c4](https://github.com/folke/trouble.nvim/commit/92832c4676e079c4fe824fd5c9a5ba3259d2e506)) * **windows:** Corrected regex matching path with backslash. ([#396](https://github.com/folke/trouble.nvim/issues/396)) ([a784506](https://github.com/folke/trouble.nvim/commit/a784506b4e5f649e568ec6763aa90d7a7ec4c0b3)) * **window:** set cursorlineopt for the trouble window. Fixes [#356](https://github.com/folke/trouble.nvim/issues/356) ([4c07228](https://github.com/folke/trouble.nvim/commit/4c07228dec2663d9dda9b12f68785a9f9ec9fd72)) ### Performance Improvements * better throttle ([c10d53d](https://github.com/folke/trouble.nvim/commit/c10d53d3d7a48dc8090e44f375d2a12ca7ef0fb6)) * only trigger refresh when event happens in main for some sources ([6eac568](https://github.com/folke/trouble.nvim/commit/6eac5689fe59fbb8038eebb150c66dbb2a3960a0)) * use weak references to prevent memory leaks ([4d31d77](https://github.com/folke/trouble.nvim/commit/4d31d77561860cbe582239ebb970db1c75872018)) * **util:** get_lines can now use a buf or filename or both ([e987642](https://github.com/folke/trouble.nvim/commit/e9876428329f2a91e5dd8b29bd854d9b9ff7813a)) ## [2.10.0](https://github.com/folke/trouble.nvim/compare/v2.9.1...v2.10.0) (2023-10-18) ### Features * `open({focus=false})` now works as intended ([600fe24](https://github.com/folke/trouble.nvim/commit/600fe24ad04f130030fa54f0c70949ff084810a3)) ### Bug Fixes * **auto_open:** dont steal focus on auto open. Fixes [#344](https://github.com/folke/trouble.nvim/issues/344) ([1f00b6f](https://github.com/folke/trouble.nvim/commit/1f00b6f730c5ef6bcfeb829a5659ed3780778087)) ## [2.9.1](https://github.com/folke/trouble.nvim/compare/v2.9.0...v2.9.1) (2023-10-09) ### Bug Fixes * **preview:** skip non-existing. Fixes [#87](https://github.com/folke/trouble.nvim/issues/87). Fixes [#188](https://github.com/folke/trouble.nvim/issues/188). Fixes [#336](https://github.com/folke/trouble.nvim/issues/336). ([#338](https://github.com/folke/trouble.nvim/issues/338)) ([5e78824](https://github.com/folke/trouble.nvim/commit/5e7882429ee2e235148ab759a6159950afd8021a)) ## [2.9.0](https://github.com/folke/trouble.nvim/compare/v2.8.0...v2.9.0) (2023-10-07) ### Features * Make floating window configuration customizable ([#310](https://github.com/folke/trouble.nvim/issues/310)) ([ef0336a](https://github.com/folke/trouble.nvim/commit/ef0336a818e562439e25638b866cb4638a0fdc26)) ### Bug Fixes * check that view is valid before render and focus ([#319](https://github.com/folke/trouble.nvim/issues/319)) ([81e1643](https://github.com/folke/trouble.nvim/commit/81e1643a7c6b426535cf23ebdb28baec4ab7428e)) * only filter msg if sev is hardcoded ([#328](https://github.com/folke/trouble.nvim/issues/328)) ([0ccc43d](https://github.com/folke/trouble.nvim/commit/0ccc43d61e0f9278056a8eeefbe022ce71707a85)) * **qf:** properly deal with invalid qf entries. Fixes [#87](https://github.com/folke/trouble.nvim/issues/87). Fixes [#188](https://github.com/folke/trouble.nvim/issues/188). Fixes [#336](https://github.com/folke/trouble.nvim/issues/336) ([46b60e9](https://github.com/folke/trouble.nvim/commit/46b60e9fb942d60740c647f61fd779f05e7b9392)) ## [2.8.0](https://github.com/folke/trouble.nvim/compare/v2.7.0...v2.8.0) (2023-07-25) ### Features * Create Configuration for IncludeDeclaration ([#312](https://github.com/folke/trouble.nvim/issues/312)) ([7691d93](https://github.com/folke/trouble.nvim/commit/7691d93131be9c4ef7788892a9c52374642beb89)) ## [2.7.0](https://github.com/folke/trouble.nvim/compare/v2.6.0...v2.7.0) (2023-07-25) ### Features * Expose help action ([#311](https://github.com/folke/trouble.nvim/issues/311)) ([467dc20](https://github.com/folke/trouble.nvim/commit/467dc204af863a9f11bc3444b8f89af286fbf6b2)) * Use code descriptions for opening URIs with extra information ([#309](https://github.com/folke/trouble.nvim/issues/309)) ([d2b0f1d](https://github.com/folke/trouble.nvim/commit/d2b0f1de1fe6f013d38234f7557c7935a9f97655)) ## [2.6.0](https://github.com/folke/trouble.nvim/compare/v2.5.0...v2.6.0) (2023-07-22) ### Features * make multiline the default ([1f2eb71](https://github.com/folke/trouble.nvim/commit/1f2eb71948b8d08cd8fe0947f9dae95c441baf6d)) ## [2.5.0](https://github.com/folke/trouble.nvim/compare/v2.4.0...v2.5.0) (2023-07-22) ### Features * add multiline diagnostic support ([#305](https://github.com/folke/trouble.nvim/issues/305)) ([7a6abd7](https://github.com/folke/trouble.nvim/commit/7a6abd7ed811def9494316d4217d1dcc80b05048)) * Map double click to jump action ([#158](https://github.com/folke/trouble.nvim/issues/158)) ([ef53b9a](https://github.com/folke/trouble.nvim/commit/ef53b9a1401919a9a3ae5b2949068c456ce23085)) * use markdown to render hover ([835b87d](https://github.com/folke/trouble.nvim/commit/835b87d93537a3cc403b961c084ca8c2998758cd)) * **util:** trigger TroubleJump on jump. Closes [#248](https://github.com/folke/trouble.nvim/issues/248) ([d91f3b3](https://github.com/folke/trouble.nvim/commit/d91f3b3d588b0259060780c73dd4c93a8f158f38)) ## [2.4.0](https://github.com/folke/trouble.nvim/compare/v2.3.0...v2.4.0) (2023-07-16) ### Features * add option to control cycling of result list ([#302](https://github.com/folke/trouble.nvim/issues/302)) ([e7805dc](https://github.com/folke/trouble.nvim/commit/e7805dc3448f28599e022dc7a0e58060dfdeeb9a)) * rendering messages from provider ([#304](https://github.com/folke/trouble.nvim/issues/304)) ([a66a78b](https://github.com/folke/trouble.nvim/commit/a66a78b8878780e3b3154e9812ff040ec9b0f1d6)) ### Bug Fixes * Check parent window is valid before setting active ([#291](https://github.com/folke/trouble.nvim/issues/291)) ([c14786d](https://github.com/folke/trouble.nvim/commit/c14786d5e88f3e66360c70bab56694abd0e60af6)) * move end of doc pos to last line. Fixes [#151](https://github.com/folke/trouble.nvim/issues/151) ([cb4da04](https://github.com/folke/trouble.nvim/commit/cb4da0401abe7ae6f368bf79d2ed6c2571b1e7ba)) ## [2.3.0](https://github.com/folke/trouble.nvim/compare/v2.2.3...v2.3.0) (2023-05-25) ### Features * filter diagnostics by severity level ([#285](https://github.com/folke/trouble.nvim/issues/285)) ([b1f607f](https://github.com/folke/trouble.nvim/commit/b1f607ff0f2c107faf8b0c26d09877028b549d63)) ## [2.2.3](https://github.com/folke/trouble.nvim/compare/v2.2.2...v2.2.3) (2023-05-22) ### Bug Fixes * set window options locally ([#282](https://github.com/folke/trouble.nvim/issues/282)) ([a5649c9](https://github.com/folke/trouble.nvim/commit/a5649c9a60d7c5aa2fed1781057af3f29b10f167)) ## [2.2.2](https://github.com/folke/trouble.nvim/compare/v2.2.1...v2.2.2) (2023-04-17) ### Bug Fixes * **util:** auto_jump when trouble is open. Fixes [#144](https://github.com/folke/trouble.nvim/issues/144) ([e4f1623](https://github.com/folke/trouble.nvim/commit/e4f1623b51e18eb4e2835446e50886062c339f80)) * **util:** save position in jump list before jump. Fixes [#143](https://github.com/folke/trouble.nvim/issues/143) Fixes [#235](https://github.com/folke/trouble.nvim/issues/235) ([f0477b0](https://github.com/folke/trouble.nvim/commit/f0477b0e78d9a16ff326e356235876ff3f87882d)) ## [2.2.1](https://github.com/folke/trouble.nvim/compare/v2.2.0...v2.2.1) (2023-03-26) ### Bug Fixes * **icons:** fixed deprecated icons with nerdfix ([39db399](https://github.com/folke/trouble.nvim/commit/39db3994c8de87b0b5ca7a4d3d415926f201f1fc)) ## [2.2.0](https://github.com/folke/trouble.nvim/compare/v2.1.1...v2.2.0) (2023-02-28) ### Features * enable looping during next/prev ([#232](https://github.com/folke/trouble.nvim/issues/232)) ([fc4c0f8](https://github.com/folke/trouble.nvim/commit/fc4c0f82c9181f3c27a4cbdd5db97c110fd78ee9)) * expose renderer.signs. Fixes [#252](https://github.com/folke/trouble.nvim/issues/252) ([5581e73](https://github.com/folke/trouble.nvim/commit/5581e736c8afc8b227ad958ded1929c8a39f049e)) ## [2.1.1](https://github.com/folke/trouble.nvim/compare/v2.1.0...v2.1.1) (2023-02-19) ### Bug Fixes * ensure that the diagnostic parameters are complete ([#179](https://github.com/folke/trouble.nvim/issues/179)) ([210969f](https://github.com/folke/trouble.nvim/commit/210969fce79e7d11554c61bca263d7e1ac77bde0)) * icorrect row/line in diagnostics. Fixes [#264](https://github.com/folke/trouble.nvim/issues/264) ([32fa4ed](https://github.com/folke/trouble.nvim/commit/32fa4ed742fc91f3075c98edd3c131b716b9d782)) ## [2.1.0](https://github.com/folke/trouble.nvim/compare/v2.0.1...v2.1.0) (2023-02-18) ### Features * expose `require("trouble").is_open()` ([2eb27b3](https://github.com/folke/trouble.nvim/commit/2eb27b34442894e903fdc6e01edea6d7c476be63)) ## [2.0.1](https://github.com/folke/trouble.nvim/compare/v2.0.0...v2.0.1) (2023-02-16) ### Bug Fixes * **init:** version check ([73eea32](https://github.com/folke/trouble.nvim/commit/73eea32efec2056cdce7593787390fc9aadf9c0c)) ## [2.0.0](https://github.com/folke/trouble.nvim/compare/v1.0.2...v2.0.0) (2023-02-16) ### ⚠ BREAKING CHANGES * Trouble now requires Neovim >= 0.7.2 ### Features * Trouble now requires Neovim >= 0.7.2 ([ef93259](https://github.com/folke/trouble.nvim/commit/ef9325970b341d436f43c50ce876aa0a665d3cf0)) ### Bug Fixes * Focus parent before closing ([#259](https://github.com/folke/trouble.nvim/issues/259)) ([66b057b](https://github.com/folke/trouble.nvim/commit/66b057b2b07881bceb969624f4c3b5727703c2c8)) * **preview:** properly load buffer when showing preview ([949199a](https://github.com/folke/trouble.nvim/commit/949199a9ac60ce784a417f90388b8f173ef53819)) * **util:** properly load a buffer when jumping to it ([bf0eeea](https://github.com/folke/trouble.nvim/commit/bf0eeead88d59d51003f4da1b649b4977ed90e2b)) ### Performance Improvements * dont load buffers when processing items. Get line with luv instead ([82c9a9a](https://github.com/folke/trouble.nvim/commit/82c9a9a9cd2cd2cdb05e05a3e6538529e2473e14)) ## [1.0.2](https://github.com/folke/trouble.nvim/compare/v1.0.1...v1.0.2) (2023-02-10) ### Bug Fixes * **telescope:** properly fix issue with relative filenames in telescope. See [#250](https://github.com/folke/trouble.nvim/issues/250) ([7da0821](https://github.com/folke/trouble.nvim/commit/7da0821d20342751a7eedecd28cf16040146cbf7)) ## [1.0.1](https://github.com/folke/trouble.nvim/compare/v1.0.0...v1.0.1) (2023-01-23) ### Bug Fixes * ensure first line is selected when padding is false ([#233](https://github.com/folke/trouble.nvim/issues/233)) ([b2d6ac8](https://github.com/folke/trouble.nvim/commit/b2d6ac8607e1ab612a85c1ec563aaff3a60f0603)) * **telescope:** correctly use cwd for files. Fixes [#250](https://github.com/folke/trouble.nvim/issues/250) ([3174767](https://github.com/folke/trouble.nvim/commit/3174767c61b3786e65d78f539c60c6f70d26cdbe)) ## 1.0.0 (2023-01-04) ### ⚠ BREAKING CHANGES * renamed use_lsp_diagnostic_signs to use_diagnostic_signs * removed deprecated commands ### Features * added "hover" action that defaults to "K" to show the full multiline text [#11](https://github.com/folke/trouble.nvim/issues/11) ([9111a5e](https://github.com/folke/trouble.nvim/commit/9111a5eb7881a84cd66107077118614e218fba61)) * added actions for opening in new tab, split and vsplit. Fixes [#36](https://github.com/folke/trouble.nvim/issues/36) ([c94cc59](https://github.com/folke/trouble.nvim/commit/c94cc599badb7086878559653ec705ed68579682)) * added mapping for jump & close (defaults to "o") [#15](https://github.com/folke/trouble.nvim/issues/15) ([09de784](https://github.com/folke/trouble.nvim/commit/09de78495bad194b2d0d85498a1c1a7996182a71)) * added support for vim.diagnostics and Neovim 0.7 ([735dcd5](https://github.com/folke/trouble.nvim/commit/735dcd599871179a835d1e0ebd777d4db24c2c72)) * allow proper passing of plugin options ([79513ed](https://github.com/folke/trouble.nvim/commit/79513ed42a273a1bc80d82c7e1117d3a2e0f2c79)) * Api to go to first and last items ([#157](https://github.com/folke/trouble.nvim/issues/157)) ([0649811](https://github.com/folke/trouble.nvim/commit/0649811e69a11dea4708a19deee9ab0b1e90313e)) * better preview and mode ([160fa6c](https://github.com/folke/trouble.nvim/commit/160fa6cb213db6c7a421450b67adc495ae69cef0)) * command complete ([9923b01](https://github.com/folke/trouble.nvim/commit/9923b01692a238535420d58e440b139a89c3de46)) * comments to open/toggle workspace or ducument mode directly ([f7db1c2](https://github.com/folke/trouble.nvim/commit/f7db1c29d7eb76cb3310e0aa56a4d546420e7814)) * config for auto_preview ([0ad97fb](https://github.com/folke/trouble.nvim/commit/0ad97fb67b21579729090214cbb3bce78fd153b7)) * define multiple keybindings for the same action (better for defaults) ([bf8e8ee](https://github.com/folke/trouble.nvim/commit/bf8e8ee63c38103fb42de0b889810b584e378962)) * expose items ([#41](https://github.com/folke/trouble.nvim/issues/41)) ([4f84ca4](https://github.com/folke/trouble.nvim/commit/4f84ca4530829b9448c6f13530c26df6d7020fd0)) * indent lines ([f9e6930](https://github.com/folke/trouble.nvim/commit/f9e6930b5188593b9e6408d8937093d04198e90a)) * inital version ([980fb07](https://github.com/folke/trouble.nvim/commit/980fb07fd33ea0f72b274e1ad3c8626bf8a14ac9)) * Lsp implementation ([#50](https://github.com/folke/trouble.nvim/issues/50)) ([069cdae](https://github.com/folke/trouble.nvim/commit/069cdae61d58d2477b150af91692ace636000d47)) * lsp references, loclist and quickfix lists! ([0b852c8](https://github.com/folke/trouble.nvim/commit/0b852c8418d65191983b2c9b8f90ad6d7f45ff51)) * made it easier to integrate with trouble ([1dd72c2](https://github.com/folke/trouble.nvim/commit/1dd72c22403519c160b0c694762091971bcf191e)) * make file grouping and padding configurable ([#66](https://github.com/folke/trouble.nvim/issues/66)) ([ff40475](https://github.com/folke/trouble.nvim/commit/ff40475143ecd40c86f13054935f3afc5653c469)) * make position of the trouble list configurable (top, bottom, left or right) [#27](https://github.com/folke/trouble.nvim/issues/27) ([0c9ca5e](https://github.com/folke/trouble.nvim/commit/0c9ca5e10c2e5dd8d8479e864e12383b1d614273)) * make signs configurable ([ff9fd51](https://github.com/folke/trouble.nvim/commit/ff9fd51ab05398c83c2a0b384999d49269d95572)) * make sorting keys configurable ([#190](https://github.com/folke/trouble.nvim/issues/190)) ([68d3dc5](https://github.com/folke/trouble.nvim/commit/68d3dc52fe49375fe556af69d1e91e0a88b67935)) * next/previous API. Implements [#44](https://github.com/folke/trouble.nvim/issues/44) ([a2a7dbf](https://github.com/folke/trouble.nvim/commit/a2a7dbfefc5ebdf1a9c1d37e9df1d26a3b13c1cd)) * option to automatically jump when there is only one result (fixes [#57](https://github.com/folke/trouble.nvim/issues/57)) ([#79](https://github.com/folke/trouble.nvim/issues/79)) ([09fafb2](https://github.com/folke/trouble.nvim/commit/09fafb2e01fbaa4fe6ecede10a7e7a738464deba)) * **providers.lsp:** Add definitions support ([#20](https://github.com/folke/trouble.nvim/issues/20)) ([a951198](https://github.com/folke/trouble.nvim/commit/a95119893c8dfd4b4bed42da97d601c25c7a495f)) * sort files by current directory and prefer non-hidden ([ea9a5e3](https://github.com/folke/trouble.nvim/commit/ea9a5e331b70cf4011081c951015033f0079a0cc)) * sort items by severity / filename / lnum / col ([4a45782](https://github.com/folke/trouble.nvim/commit/4a45782db943f95500b61ffce187bf4cada954ae)) * sort results by row and column isntead of just row ([#118](https://github.com/folke/trouble.nvim/issues/118)) ([5897b09](https://github.com/folke/trouble.nvim/commit/5897b09933731298382e86a5cf4d1a4861630873)) * **telescope provider:** (Smart) multiselect ([#39](https://github.com/folke/trouble.nvim/issues/39)) ([45ff198](https://github.com/folke/trouble.nvim/commit/45ff198f4d436d256f02b14db9c817024c7fc85c)) * Telescope support ([9c81e16](https://github.com/folke/trouble.nvim/commit/9c81e16adec697ffd0b694eb86e14cfee453917d)) * use vim.notify for logging ([293118e](https://github.com/folke/trouble.nvim/commit/293118e195639c373a6a744621b9341e5e18f6e4)) ### Bug Fixes * Add nowait option to keymap ([#30](https://github.com/folke/trouble.nvim/issues/30)) ([4375f1f](https://github.com/folke/trouble.nvim/commit/4375f1f0b2457fcbb91d32de457e6e3b3bb7eba7)) * added additional space between message and code ([aae12e7](https://github.com/folke/trouble.nvim/commit/aae12e7b23b3a2b8337ec5b1d6b7b4317aa3929b)) * added compatibility to retrieve signs from vim.diagnostic ([dab82ef](https://github.com/folke/trouble.nvim/commit/dab82ef0f39893f50908881fdc5e96bfb1578ba1)) * added suport for vim.diagnostic hl groups ([d25a8e6](https://github.com/folke/trouble.nvim/commit/d25a8e6779462127fb227397fa92b07bced8a6fe)) * added support for new handler signatures (backward compatible with 0.5) ([87cae94](https://github.com/folke/trouble.nvim/commit/87cae946aee4798bee621ea6108224c08c218d69)) * auto_open was broken. Fixed now [#29](https://github.com/folke/trouble.nvim/issues/29) ([a2f2b92](https://github.com/folke/trouble.nvim/commit/a2f2b9248bed41522d8caa3a7e9932981c4087ec)) * better detection of the parent window ([4c5fd8a](https://github.com/folke/trouble.nvim/commit/4c5fd8abaf6058312ebe52f662ca002bf0aa9f77)) * default to current window in jump_to_item ([#175](https://github.com/folke/trouble.nvim/issues/175)) ([ec24219](https://github.com/folke/trouble.nvim/commit/ec242197b1f72cabe17dfd61119c896f58bda672)) * don't "edit" en existing buffer. Use "buffer" instead. ([#5](https://github.com/folke/trouble.nvim/issues/5), [#6](https://github.com/folke/trouble.nvim/issues/6)) ([abef115](https://github.com/folke/trouble.nvim/commit/abef1158c0ff236333f67f9f091e5d9ae67d6a89)) * don't steal focus on auto_open. Fixes [#48](https://github.com/folke/trouble.nvim/issues/48) ([36b6813](https://github.com/folke/trouble.nvim/commit/36b6813a2103d85b469a61721b030903ddd8b3b3)) * don't try to fetch sign for "other" ([5b50990](https://github.com/folke/trouble.nvim/commit/5b509904f8865bea7d09b7a686e139077a2484c6)) * don't use file sorter for items without a valid filename ([20469be](https://github.com/folke/trouble.nvim/commit/20469be985143d024c460d95326ebeff9971d714)) * dont advance two items at a time. Fixes https://github.com/folke/todo-comments.nvim/issues/39 ([7de8bc4](https://github.com/folke/trouble.nvim/commit/7de8bc46164ec1f787dee34b6843b61251b1ea91)) * files without col/row should be set to col=1 and row=1 [#22](https://github.com/folke/trouble.nvim/issues/22) ([fcd5f1f](https://github.com/folke/trouble.nvim/commit/fcd5f1fc035ee3d9832c63a307247c09f25c9cd1)) * filetype set too early ([#230](https://github.com/folke/trouble.nvim/issues/230)) ([c4da921](https://github.com/folke/trouble.nvim/commit/c4da921ba613aa6d6659dc18edc204c37e4b8833)) * fixed auto_open swicth_to_parent. Fixes [#7](https://github.com/folke/trouble.nvim/issues/7) ([7cf1aa1](https://github.com/folke/trouble.nvim/commit/7cf1aa1195245d3098097bc3a2510dc358c87363)) * give focus back to correct window when closing ([#72](https://github.com/folke/trouble.nvim/issues/72)) ([a736b8d](https://github.com/folke/trouble.nvim/commit/a736b8db9f49b8b49ac96fbab7f8e396032cfa37)) * handle normal api calls to trouble as it should [#42](https://github.com/folke/trouble.nvim/issues/42) ([52b875d](https://github.com/folke/trouble.nvim/commit/52b875d1aaf88f32e9f070a0119190c3e65b51a5)) * if grouping is off, decrease indent ([#140](https://github.com/folke/trouble.nvim/issues/140)) ([ed65f84](https://github.com/folke/trouble.nvim/commit/ed65f84abc4a1e5d8f368d7e02601fc0357ea15e)) * lazy include telescope when needed ([7e3d4f9](https://github.com/folke/trouble.nvim/commit/7e3d4f9efc157bbfeb3e37837f8ded9289c48f25)) * lsp diag creates ugly buffers for unopened files in the workspace. Fixed now ([91d1139](https://github.com/folke/trouble.nvim/commit/91d1139d85407b99bd4d2f6850200a793631679b)) * lsp diagnostics codes ([dbbd523](https://github.com/folke/trouble.nvim/commit/dbbd523d91fe51e8421909147bf069b1ec780720)) * lsp handler error log ([#95](https://github.com/folke/trouble.nvim/issues/95)) ([063aefd](https://github.com/folke/trouble.nvim/commit/063aefd69a8146e27cde860c9ddd807891e5a119)) * **lsp:** avoid overwriting uri of result ([#60](https://github.com/folke/trouble.nvim/issues/60)) ([655391c](https://github.com/folke/trouble.nvim/commit/655391c2f592ef61943b6325030333dfacc54757)) * only use old hl groups when they exist (Fixes [#49](https://github.com/folke/trouble.nvim/issues/49)) ([d4ce76f](https://github.com/folke/trouble.nvim/commit/d4ce76fa82cdbd12dcf9dbfa682dae89b2a143ac)) * possible vim.NIL on diagnostics code ([1faa347](https://github.com/folke/trouble.nvim/commit/1faa347a93748531b5e418d84276c93da21b86a7)) * prevent segfault on closing ([756f09d](https://github.com/folke/trouble.nvim/commit/756f09de113a775ab16ba6d26c090616b40a999d)) * properly close trouble window on close ([d10ee4b](https://github.com/folke/trouble.nvim/commit/d10ee4bc99b8e2bb842c2274316db400b197cca9)) * properly exit when trouble is the last window. Fixes [#24](https://github.com/folke/trouble.nvim/issues/24) ([2b27b96](https://github.com/folke/trouble.nvim/commit/2b27b96c7893ac534ba0cbfc95d52c6c609a0b20)) * remove useless "no results" notification ([#164](https://github.com/folke/trouble.nvim/issues/164)) ([da61737](https://github.com/folke/trouble.nvim/commit/da61737d860ddc12f78e638152834487eabf0ee5)), closes [#154](https://github.com/folke/trouble.nvim/issues/154) * removed space betweend rendering of source + code ([b676029](https://github.com/folke/trouble.nvim/commit/b6760291874d078668f4ff04d78acc0670536ca9)) * removed unused plenary require. Fixes [#1](https://github.com/folke/trouble.nvim/issues/1) ([1ff45e2](https://github.com/folke/trouble.nvim/commit/1ff45e274de32e816b891b1ca12f73f73b58a604)) * replace possible newlines in rendered text ([08d068f](https://github.com/folke/trouble.nvim/commit/08d068fb1668b7f898af721cbc8a1ae72ddf6565)) * restore item indentation ([7c93271](https://github.com/folke/trouble.nvim/commit/7c93271e7a6a147b8f4342f5b377fa863419846f)) * set buftype before filetype ([#67](https://github.com/folke/trouble.nvim/issues/67)) ([169b2ec](https://github.com/folke/trouble.nvim/commit/169b2ec3a4d0cac01f22cc8f7332f1d0a11f1fa4)) * set EndOfBuffer to LspTroubleNormal and hide ~ [#23](https://github.com/folke/trouble.nvim/issues/23) ([7d67f34](https://github.com/folke/trouble.nvim/commit/7d67f34d92b3b52ca63c84f929751d98b3f56b63)) * set nowrap for the trouble window. Fixes [#69](https://github.com/folke/trouble.nvim/issues/69) ([51dd917](https://github.com/folke/trouble.nvim/commit/51dd9175eb506b026189c70f81823dfa77defe86)) * set the filetype lastly so autocmd's can override options from it ([#126](https://github.com/folke/trouble.nvim/issues/126)) ([b5353dd](https://github.com/folke/trouble.nvim/commit/b5353ddcd09bd7e93d6f934149d25792d455a8fb)) * show warning when icons=true but devicons is not installed ([7aabea5](https://github.com/folke/trouble.nvim/commit/7aabea5cca2d51ba5432c988fe84ff9d3644637a)) * support LocationLink ([#94](https://github.com/folke/trouble.nvim/issues/94)) ([7f3761b](https://github.com/folke/trouble.nvim/commit/7f3761b6dbadd682a20bd1ff4cb588985c14c9a0)) * typos ([#55](https://github.com/folke/trouble.nvim/issues/55)) ([059ea2b](https://github.com/folke/trouble.nvim/commit/059ea2b999171f50019291ee776dd496799fdf3a)) * use deprecated vim.lsp.diagnostics for now ([afb300f](https://github.com/folke/trouble.nvim/commit/afb300f18c09f7b474783aa12eb680ea59785b46)) * use new DiagnosticChanged event ([#127](https://github.com/folke/trouble.nvim/issues/127)) ([4d0a711](https://github.com/folke/trouble.nvim/commit/4d0a711e7432eed022611ce385f3a7714e81f63b)), closes [#122](https://github.com/folke/trouble.nvim/issues/122) * use vim.diagnostic instead of vim.lsp.diagnostic when available ([a2e2e7b](https://github.com/folke/trouble.nvim/commit/a2e2e7b53f389f84477a1a11c086c9a379af702e)) * workspace and document diagnostics were switched around ([1fa8469](https://github.com/folke/trouble.nvim/commit/1fa84691236d16a2d1c12707c1fbc54060c910f7)) ### Performance Improvements * debounce auto refresh when diagnostics get updated ([068476d](https://github.com/folke/trouble.nvim/commit/068476db8576e5b32acf20df040e7fca032cd11d)) * much faster async preview ([2c9b319](https://github.com/folke/trouble.nvim/commit/2c9b3195a7fa8cfc19a368666c9f83fd7a20a482)) * only fetch line when needed. Fixes [#26](https://github.com/folke/trouble.nvim/issues/26) ([52f18fd](https://github.com/folke/trouble.nvim/commit/52f18fd6bea57af54265247a3ec39f19a31adce3)) * only update diagnostics once when window changes ([d965d22](https://github.com/folke/trouble.nvim/commit/d965d22ee37e50be0ab32f6a5987a8cd88206f10)) * prevent nested loading of preview [#2](https://github.com/folke/trouble.nvim/issues/2) ([b20a784](https://github.com/folke/trouble.nvim/commit/b20a7844a035cf6795270db575ad8c4db2a774c9)) * use vim.lsp.util.get_line to get line instad of bufload ([607b1d5](https://github.com/folke/trouble.nvim/commit/607b1d5bbfdbd19242659415746b5e62f5ddfb94)) ### Code Refactoring * removed deprecated commands ([dd89ad9](https://github.com/folke/trouble.nvim/commit/dd89ad9ebb63e131098ff04857f8598eb88d8d79)) * renamed use_lsp_diagnostic_signs to use_diagnostic_signs ([9db77e1](https://github.com/folke/trouble.nvim/commit/9db77e194d848744139673aa246efa00fbcba982)) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # 🚦 Trouble A pretty list for showing diagnostics, references, telescope results, quickfix and location lists to help you solve all the trouble your code is causing. ![image](https://github.com/folke/trouble.nvim/assets/292349/481bc1f7-cb93-432d-8ab6-f54044334b96) ## ✨ Features - Diagnostics - LSP references - LSP implementations - LSP definitions - LSP type definitions - LSP Document Symbols - LSP Incoming/Outgoing calls - quickfix list - location list - [Telescope](https://github.com/nvim-telescope/telescope.nvim) search results - [fzf-lua](https://github.com/ibhagwan/fzf-lua) results ## 📰 What's new? This is a full rewrite of the original **trouble.nvim**. The new version is much more flexible and powerful, with a lot of new features and improvements: - multiple trouble windows at the same time - LSP document symbols - LSP incoming/outgoing calls - lots of options to configure trouble windows (floats or splits) - `focus` option to focus the trouble window when opened (or not) - `follow` option to follow the item under the cursor - `pinned` option to pin the buffer as the source for the opened trouble window - full tree views of anything - highly configurable views with custom formatters, filters, and sorters - show multiple sections in the same view - multi-line messages - prettier and configurable indent guides - tree view that follows the natural hierarchy of the items (like document symbols, or file structure) - expansive API and `Trouble` command - trouble `modes` to define custom views - statusline component (useful with document symbols) ## ⚡️ Requirements - Neovim >= 0.9.2 - Neovim >= 0.10.0 **OR** the `markdown` and `markdown_inline` [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) parsers - Properly configured Neovim LSP client - [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) is optional to enable file icons - a theme with properly configured highlight groups for Neovim Diagnostics - a [patched font](https://www.nerdfonts.com/) for the default severity and fold icons ## 📦 Installation Install the plugin with your preferred package manager: ### [lazy.nvim](https://github.com/folke/lazy.nvim) ```lua { "folke/trouble.nvim", opts = {}, -- for default options, refer to the configuration section for custom setup. cmd = "Trouble", keys = { { "xx", "Trouble diagnostics toggle", desc = "Diagnostics (Trouble)", }, { "xX", "Trouble diagnostics toggle filter.buf=0", desc = "Buffer Diagnostics (Trouble)", }, { "cs", "Trouble symbols toggle focus=false", desc = "Symbols (Trouble)", }, { "cl", "Trouble lsp toggle focus=false win.position=right", desc = "LSP Definitions / references / ... (Trouble)", }, { "xL", "Trouble loclist toggle", desc = "Location List (Trouble)", }, { "xQ", "Trouble qflist toggle", desc = "Quickfix List (Trouble)", }, }, } ``` ## ⚙️ Configuration ### Setup **Trouble** is highly configurable. Please refer to the default settings below.
Default Settings ```lua ---@class trouble.Mode: trouble.Config,trouble.Section.spec ---@field desc? string ---@field sections? string[] ---@class trouble.Config ---@field mode? string ---@field config? fun(opts:trouble.Config) ---@field formatters? table custom formatters ---@field filters? table custom filters ---@field sorters? table custom sorters local defaults = { auto_close = false, -- auto close when there are no items auto_open = false, -- auto open when there are items auto_preview = true, -- automatically open preview when on an item auto_refresh = true, -- auto refresh when open auto_jump = false, -- auto jump to the item when there's only one focus = false, -- Focus the window when opened restore = true, -- restores the last location in the list when opening follow = true, -- Follow the current item indent_guides = true, -- show indent guides max_items = 200, -- limit number of items that can be displayed per section multiline = true, -- render multi-line messages pinned = false, -- When pinned, the opened trouble window will be bound to the current buffer warn_no_results = true, -- show a warning when there are no results open_no_results = false, -- open the trouble window when there are no results ---@type trouble.Window.opts win = {}, -- window options for the results window. Can be a split or a floating window. -- Window options for the preview window. Can be a split, floating window, -- or `main` to show the preview in the main editor window. ---@type trouble.Window.opts preview = { type = "main", -- when a buffer is not yet loaded, the preview window will be created -- in a scratch buffer with only syntax highlighting enabled. -- Set to false, if you want the preview to always be a real loaded buffer. scratch = true, }, -- Throttle/Debounce settings. Should usually not be changed. ---@type table throttle = { refresh = 20, -- fetches new data when needed update = 10, -- updates the window render = 10, -- renders the window follow = 100, -- follows the current item preview = { ms = 100, debounce = true }, -- shows the preview for the current item }, -- Key mappings can be set to the name of a builtin action, -- or you can define your own custom action. ---@type table keys = { ["?"] = "help", r = "refresh", R = "toggle_refresh", q = "close", o = "jump_close", [""] = "cancel", [""] = "jump", ["<2-leftmouse>"] = "jump", [""] = "jump_split", [""] = "jump_vsplit", -- go down to next item (accepts count) -- j = "next", ["}"] = "next", ["]]"] = "next", -- go up to prev item (accepts count) -- k = "prev", ["{"] = "prev", ["[["] = "prev", dd = "delete", d = { action = "delete", mode = "v" }, i = "inspect", p = "preview", P = "toggle_preview", zo = "fold_open", zO = "fold_open_recursive", zc = "fold_close", zC = "fold_close_recursive", za = "fold_toggle", zA = "fold_toggle_recursive", zm = "fold_more", zM = "fold_close_all", zr = "fold_reduce", zR = "fold_open_all", zx = "fold_update", zX = "fold_update_all", zn = "fold_disable", zN = "fold_enable", zi = "fold_toggle_enable", gb = { -- example of a custom action that toggles the active view filter action = function(view) view:filter({ buf = 0 }, { toggle = true }) end, desc = "Toggle Current Buffer Filter", }, s = { -- example of a custom action that toggles the severity action = function(view) local f = view:get_filter("severity") local severity = ((f and f.filter.severity or 0) + 1) % 5 view:filter({ severity = severity }, { id = "severity", template = "{hl:Title}Filter:{hl} {severity}", del = severity == 0, }) end, desc = "Toggle Severity Filter", }, }, ---@type table modes = { -- sources define their own modes, which you can use directly, -- or override like in the example below lsp_references = { -- some modes are configurable, see the source code for more details params = { include_declaration = true, }, }, -- The LSP base mode for: -- * lsp_definitions, lsp_references, lsp_implementations -- * lsp_type_definitions, lsp_declarations, lsp_command lsp_base = { params = { -- don't include the current location in the results include_current = false, }, }, -- more advanced example that extends the lsp_document_symbols symbols = { desc = "document symbols", mode = "lsp_document_symbols", focus = false, win = { position = "right" }, filter = { -- remove Package since luals uses it for control flow structures ["not"] = { ft = "lua", kind = "Package" }, any = { -- all symbol kinds for help / markdown files ft = { "help", "markdown" }, -- default set of symbol kinds kind = { "Class", "Constructor", "Enum", "Field", "Function", "Interface", "Method", "Module", "Namespace", "Package", "Property", "Struct", "Trait", }, }, }, }, }, icons = { ---@type trouble.Indent.symbols indent = { top = "│ ", middle = "├╴", last = "└╴", -- last = "-╴", -- last = "╰╴", -- rounded fold_open = " ", fold_closed = " ", ws = " ", }, folder_closed = " ", folder_open = " ", kinds = { Array = " ", Boolean = "󰨙 ", Class = " ", Constant = "󰏿 ", Constructor = " ", Enum = " ", EnumMember = " ", Event = " ", Field = " ", File = " ", Function = "󰊕 ", Interface = " ", Key = " ", Method = "󰊕 ", Module = " ", Namespace = "󰦮 ", Null = " ", Number = "󰎠 ", Object = " ", Operator = " ", Package = " ", Property = " ", String = " ", Struct = "󰆼 ", TypeParameter = " ", Variable = "󰀫 ", }, }, } ```
Make sure to check the [Examples](/docs/examples.md)! ## 🚀 Usage ### Commands The **Trouble** command is a wrapper around the **Trouble** API. It can do anything the regular API can do. - `Trouble [mode] [action] [options]` Some examples: - Toggle diagnostics for the current buffer and stay in the current window: - `Trouble diagnostics toggle focus=false filter.buf=0` - Show document symbols on the right of the current window. Keep the document symbols in sync with the buffer you started the command in. - `Trouble symbols toggle pinned=true win.relative=win win.position=right` - You can use **lua** code in the options for the `Trouble` command. The examples below all do the same thing. - `Trouble diagnostics filter.severity=vim.diagnostic.severity.ERROR` - `Trouble diagnostics filter.severity = vim.diagnostic.severity.ERROR` - `Trouble diagnostics filter = { severity=vim.diagnostic.severity.ERROR }` - Merging of nested options, with or without quoting strings: - `Trouble diagnostics win.type = split win.position=right` - `Trouble diagnostics win = { type = split, position=right}` - `Trouble diagnostics win = { type = "split", position='right'}` Please refer to the API section for more information on the available actions and options. Modes: - **diagnostics**: diagnostics - **fzf**: FzfLua results previously opened with `require('trouble.sources.fzf').open()`. - **fzf_files**: FzfLua results previously opened with `require('trouble.sources.fzf').open()`. - **loclist**: Location List - **lsp**: LSP definitions, references, implementations, type definitions, and declarations - **lsp_command**: command - **lsp_declarations**: declarations - **lsp_definitions**: definitions - **lsp_document_symbols**: document symbols - **lsp_implementations**: implementations - **lsp_incoming_calls**: Incoming Calls - **lsp_outgoing_calls**: Outgoing Calls - **lsp_references**: references - **lsp_type_definitions**: type definitions - **qflist**: Quickfix List - **quickfix**: Quickfix List - **snacks**: Snacks results previously opened with `require('trouble.sources.snacks').open()`. - **snacks_files**: Snacks results previously opened with `require('trouble.sources.snacks').open()`. - **symbols**: document symbols - **telescope**: Telescope results previously opened with `require('trouble.sources.telescope').open()`. - **telescope_files**: Telescope results previously opened with `require('trouble.sources.telescope').open()`. ### Filters Please refer to the [filter docs](docs/filter.md) for more information examples on filters. ### API You can use the following functions in your keybindings:
API ```lua -- Opens trouble with the given mode. -- If a view is already open with the same mode, -- it will be focused unless `opts.focus = false`. -- When a view is already open and `opts.new = true`, -- a new view will be created. ---@param opts? trouble.Mode | { new?: boolean, refresh?: boolean } | string ---@return trouble.View? require("trouble").open(opts) -- Closes the last open view matching the filter. ---@param opts? trouble.Mode|string ---@return trouble.View? require("trouble").close(opts) -- Toggle the view with the given mode. ---@param opts? trouble.Mode|string ---@return trouble.View? require("trouble").toggle(opts) -- Returns true if there is an open view matching the mode. ---@param opts? trouble.Mode|string require("trouble").is_open(opts) -- Refresh all open views. Normally this is done automatically, -- unless you disabled auto refresh. ---@param opts? trouble.Mode|string require("trouble").refresh(opts) -- Get all items from the active view for a given mode. ---@param opts? trouble.Mode|string require("trouble").get_items(opts) -- Renders a trouble list as a statusline component. -- Check the docs for examples. ---@param opts? trouble.Mode|string|{hl_group?:string} ---@return {get: (fun():string), has: (fun():boolean)} require("trouble").statusline(opts) -- Closes the preview and goes to the main window. -- The Trouble window is not closed. ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").cancel(opts) -- Open the preview ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").delete(opts) -- filter ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").filter(opts) -- Go to the first item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").first(opts) -- Focus the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").focus(opts) -- Fold close ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_close(opts) -- fold close all ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_close_all(opts) -- Fold close recursive ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_close_recursive(opts) -- fold disable ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_disable(opts) -- fold enable ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_enable(opts) -- fold more ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_more(opts) -- Fold open ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_open(opts) -- fold open all ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_open_all(opts) -- Fold open recursive ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_open_recursive(opts) -- fold reduce ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_reduce(opts) -- Fold toggle ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_toggle(opts) -- fold toggle enable ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_toggle_enable(opts) -- Fold toggle recursive ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_toggle_recursive(opts) -- fold update ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_update(opts) -- fold update all ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_update_all(opts) -- Show the help ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").help(opts) -- Dump the item to the console ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").inspect(opts) -- Jump to the item if on an item, otherwise fold the node ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump(opts) -- Jump to the item and close the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_close(opts) -- Jump to the item if on an item, otherwise do nothing ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_only(opts) -- Open the item in a split ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_split(opts) -- Open the item in a split and close the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_split_close(opts) -- Open the item in a vsplit ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_vsplit(opts) -- Open the item in a vsplit and close the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_vsplit_close(opts) -- Go to the last item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").last(opts) -- Go to the next item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").next(opts) -- Go to the previous item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").prev(opts) -- Open the preview ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").preview(opts) -- Refresh the trouble source ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").refresh(opts) -- Toggle the preview ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").toggle_preview(opts) -- Toggle the auto refresh ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").toggle_refresh(opts) ```
### Telescope You can easily open any search results in **Trouble**, by defining a custom action: ```lua local actions = require("telescope.actions") local open_with_trouble = require("trouble.sources.telescope").open -- Use this to add more results without clearing the trouble list local add_to_trouble = require("trouble.sources.telescope").add local telescope = require("telescope") telescope.setup({ defaults = { mappings = { i = { [""] = open_with_trouble }, n = { [""] = open_with_trouble }, }, }, }) ``` When you open telescope, you can now hit `` to open the results in **Trouble** ### fzf-lua You can easily open any search results in **Trouble**, by defining a custom action: ```lua local config = require("fzf-lua.config") local actions = require("trouble.sources.fzf").actions config.defaults.actions.files["ctrl-t"] = actions.open ``` When you open fzf-lua, you can now hit `` to open the results in **Trouble** ### Statusline Component Example for [lualine.nvim](https://github.com/nvim-lualine/lualine.nvim): ```lua { "nvim-lualine/lualine.nvim", opts = function(_, opts) local trouble = require("trouble") local symbols = trouble.statusline({ mode = "lsp_document_symbols", groups = {}, title = false, filter = { range = true }, format = "{kind_icon}{symbol.name:Normal}", -- The following line is needed to fix the background color -- Set it to the lualine section you want to use hl_group = "lualine_c_normal", }) table.insert(opts.sections.lualine_c, { symbols.get, cond = symbols.has, }) end, } ``` ## 🎨 Colors The table below shows all the highlight groups defined for Trouble.
Highlight Groups | Highlight Group | Default Group | Description | | --- | --- | --- | | **TroubleBasename** | ***TroubleFilename*** | | | **TroubleCode** | ***Special*** | | | **TroubleCount** | ***TabLineSel*** | | | **TroubleDirectory** | ***Directory*** | | | **TroubleFilename** | ***Directory*** | | | **TroubleIconArray** | ***@punctuation.bracket*** | | | **TroubleIconBoolean** | ***@boolean*** | | | **TroubleIconClass** | ***@type*** | | | **TroubleIconConstant** | ***@constant*** | | | **TroubleIconConstructor** | ***@constructor*** | | | **TroubleIconDirectory** | ***Special*** | | | **TroubleIconEnum** | ***@lsp.type.enum*** | | | **TroubleIconEnumMember** | ***@lsp.type.enumMember*** | | | **TroubleIconEvent** | ***Special*** | | | **TroubleIconField** | ***@variable.member*** | | | **TroubleIconFile** | ***Normal*** | | | **TroubleIconFunction** | ***@function*** | | | **TroubleIconInterface** | ***@lsp.type.interface*** | | | **TroubleIconKey** | ***@lsp.type.keyword*** | | | **TroubleIconMethod** | ***@function.method*** | | | **TroubleIconModule** | ***@module*** | | | **TroubleIconNamespace** | ***@module*** | | | **TroubleIconNull** | ***@constant.builtin*** | | | **TroubleIconNumber** | ***@number*** | | | **TroubleIconObject** | ***@constant*** | | | **TroubleIconOperator** | ***@operator*** | | | **TroubleIconPackage** | ***@module*** | | | **TroubleIconProperty** | ***@property*** | | | **TroubleIconString** | ***@string*** | | | **TroubleIconStruct** | ***@lsp.type.struct*** | | | **TroubleIconTypeParameter** | ***@lsp.type.typeParameter*** | | | **TroubleIconVariable** | ***@variable*** | | | **TroubleIndent** | ***LineNr*** | | | **TroubleIndentFoldClosed** | ***CursorLineNr*** | | | **TroubleIndentFoldOpen** | ***TroubleIndent*** | | | **TroubleIndentLast** | ***TroubleIndent*** | | | **TroubleIndentMiddle** | ***TroubleIndent*** | | | **TroubleIndentTop** | ***TroubleIndent*** | | | **TroubleIndentWs** | ***TroubleIndent*** | | | **TroubleNormal** | ***NormalFloat*** | | | **TroubleNormalNC** | ***NormalFloat*** | | | **TroublePos** | ***LineNr*** | | | **TroublePreview** | ***Visual*** | | | **TroubleSource** | ***Comment*** | | | **TroubleText** | ***Normal*** | |
================================================ FILE: doc/trouble.nvim-examples.txt ================================================ *trouble.nvim-examples.txt* trouble.nvim examples docs ============================================================================== Table of Contents *trouble.nvim-examples-table-of-contents* 1. Window Options |trouble.nvim-examples-window-options| - Preview in small float top right|trouble.nvim-examples-window-options-preview-in-small-float-top-right| - Preview in a split to the right of the trouble list|trouble.nvim-examples-window-options-preview-in-a-split-to-the-right-of-the-trouble-list| - Make Trouble look like v2|trouble.nvim-examples-window-options-make-trouble-look-like-v2| 2. Filtering |trouble.nvim-examples-filtering| - Diagnostics for the current buffer only|trouble.nvim-examples-filtering-diagnostics-for-the-current-buffer-only| - Diagnostics for the current buffer and errors from the current project|trouble.nvim-examples-filtering-diagnostics-for-the-current-buffer-and-errors-from-the-current-project| - Diagnostics Cascade |trouble.nvim-examples-filtering-diagnostics-cascade| 3. Other |trouble.nvim-examples-other| - Automatically Open Trouble Quickfix|trouble.nvim-examples-other-automatically-open-trouble-quickfix| - Open Trouble Quickfix when the qf list opens|trouble.nvim-examples-other-open-trouble-quickfix-when-the-qf-list-opens| 4. Links |trouble.nvim-examples-links| ============================================================================== 1. Window Options *trouble.nvim-examples-window-options* PREVIEW IN SMALL FLOAT TOP RIGHT*trouble.nvim-examples-window-options-preview-in-small-float-top-right* >lua { modes = { preview_float = { mode = "diagnostics", preview = { type = "float", relative = "editor", border = "rounded", title = "Preview", title_pos = "center", position = { 0, -2 }, size = { width = 0.3, height = 0.3 }, zindex = 200, }, }, }, } < PREVIEW IN A SPLIT TO THE RIGHT OF THE TROUBLE LIST*trouble.nvim-examples-window-options-preview-in-a-split-to-the-right-of-the-trouble-list* >lua { modes = { test = { mode = "diagnostics", preview = { type = "split", relative = "win", position = "right", size = 0.3, }, }, }, } < MAKE TROUBLE LOOK LIKE V2*trouble.nvim-examples-window-options-make-trouble-look-like-v2* >lua { icons = { indent = { middle = " ", last = " ", top = " ", ws = "│ ", }, }, modes = { diagnostics = { groups = { { "filename", format = "{file_icon} {basename:Title} {count}" }, }, }, }, } < ============================================================================== 2. Filtering *trouble.nvim-examples-filtering* DIAGNOSTICS FOR THE CURRENT BUFFER ONLY*trouble.nvim-examples-filtering-diagnostics-for-the-current-buffer-only* >lua { modes = { diagnostics_buffer = { mode = "diagnostics", -- inherit from diagnostics mode filter = { buf = 0 }, -- filter diagnostics to the current buffer }, } } < DIAGNOSTICS FOR THE CURRENT BUFFER AND ERRORS FROM THE CURRENT PROJECT*trouble.nvim-examples-filtering-diagnostics-for-the-current-buffer-and-errors-from-the-current-project* >lua { modes = { mydiags = { mode = "diagnostics", -- inherit from diagnostics mode filter = { any = { buf = 0, -- current buffer { severity = vim.diagnostic.severity.ERROR, -- errors only -- limit to files in the current project function(item) return item.filename:find((vim.loop or vim.uv).cwd(), 1, true) end, }, }, }, } } } < DIAGNOSTICS CASCADE *trouble.nvim-examples-filtering-diagnostics-cascade* The following example shows how to create a new mode that shows only the most severe diagnostics. Once those are resolved, less severe diagnostics will be shown. >lua { modes = { cascade = { mode = "diagnostics", -- inherit from diagnostics mode filter = function(items) local severity = vim.diagnostic.severity.HINT for _, item in ipairs(items) do severity = math.min(severity, item.severity) end return vim.tbl_filter(function(item) return item.severity == severity end, items) end, }, }, } < ============================================================================== 3. Other *trouble.nvim-examples-other* AUTOMATICALLY OPEN TROUBLE QUICKFIX*trouble.nvim-examples-other-automatically-open-trouble-quickfix* >lua vim.api.nvim_create_autocmd("QuickFixCmdPost", { callback = function() vim.cmd([[Trouble qflist open]]) end, }) < Test with something like `:silent grep vim %` OPEN TROUBLE QUICKFIX WHEN THE QF LIST OPENS*trouble.nvim-examples-other-open-trouble-quickfix-when-the-qf-list-opens* This is **NOT** recommended, since you won’t be able to use the quickfix list for other things. >lua vim.api.nvim_create_autocmd("BufRead", { callback = function(ev) if vim.bo[ev.buf].buftype == "quickfix" then vim.schedule(function() vim.cmd([[cclose]]) vim.cmd([[Trouble qflist open]]) end) end end, }) < ============================================================================== 4. Links *trouble.nvim-examples-links* 1. *image*: https://github.com/folke/trouble.nvim/assets/292349/f422b8fd-579e-427b-87d3-62daab85d2e0 2. *image*: https://github.com/folke/trouble.nvim/assets/292349/adfa02df-b3dd-4c90-af3c-41683c0b5356 Generated by panvimdoc vim:tw=78:ts=8:noet:ft=help:norl: ================================================ FILE: doc/trouble.nvim-filter.txt ================================================ *trouble.nvim-filter.txt* trouble.nvim filter docs ============================================================================== Table of Contents *trouble.nvim-filter-table-of-contents* 1. Examples |trouble.nvim-filter-examples| 2. Advanced examples |trouble.nvim-filter-advanced-examples| - The not filter |trouble.nvim-filter-advanced-examples-the-not-filter| - The any filter |trouble.nvim-filter-advanced-examples-the-any-filter| 3. Item attributes |trouble.nvim-filter-item-attributes| ============================================================================== 1. Examples *trouble.nvim-filter-examples* A simple filter is a table whose keys are item attributes. The following filter keeps items with attribute `buf = 0` **and** `ft = 'lua'`, i.e., diagnostics with severity error to the current buffer when its filetype is `lua`. >lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = { buf = 0, ft = 'lua' }, }, }, } < A filter may be a function that takes `items` as parameter. The following filter keeps items with severity `HINT` >lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = function(items) return vim.tbl_filter(function(item) return item.severity == vim.diagnostic.severity.HINT end, items) end, }, }, } < ============================================================================== 2. Advanced examples *trouble.nvim-filter-advanced-examples* THE NOT FILTER *trouble.nvim-filter-advanced-examples-the-not-filter* The `not` negates filter results. The following filter **removes** diagnostics with severity `INFO` >lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = { ['not'] = { severity = vim.diagnostic.severity.INFO }, }, }, }, } < THE ANY FILTER *trouble.nvim-filter-advanced-examples-the-any-filter* The `any` filter provides logical disjunction. The following filter **keeps** diagnostics for the current buffer **or** diagnostics with severity `ERROR` for the current project. >lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = { any = { buf = 0, { severity = vim.diagnostic.severity.ERROR, function(item) return item.filename:find((vim.loop or vim.uv).cwd(), 1, true) end, }, }, }, }, }, } < ============================================================================== 3. Item attributes *trouble.nvim-filter-item-attributes* Item attributes are documented in `lua/trouble/item.lua` ------------------------------------------------------------------------------ Name Type Description ---------- -------------------------- ---------------------------------------- any Logical or Filter result disjunction basename string File name. buf number Buffer id. dirname string Directory path. filename string Full file path. ft string or string[] File types. kind string Symbol kind. See :h symbol. not Logical not Filter result negation. pos {[1]:number, [2]:number} Item position. severity number Diagnostic severity. See :h diagnostic-severity. source string Item source. ------------------------------------------------------------------------------ Generated by panvimdoc vim:tw=78:ts=8:noet:ft=help:norl: ================================================ FILE: doc/trouble.nvim.txt ================================================ *trouble.nvim.txt* trouble.nvim docs ============================================================================== Table of Contents *trouble.nvim-table-of-contents* 1. Trouble |trouble.nvim-trouble| - Features |trouble.nvim-trouble-features| - What’s new? |trouble.nvim-trouble-what’s-new?| - Requirements |trouble.nvim-trouble-requirements| - Installation |trouble.nvim-trouble-installation| - Configuration |trouble.nvim-trouble-configuration| - Usage |trouble.nvim-trouble-usage| - Colors |trouble.nvim-trouble-colors| 2. Links |trouble.nvim-links| ============================================================================== 1. Trouble *trouble.nvim-trouble* A pretty list for showing diagnostics, references, telescope results, quickfix and location lists to help you solve all the trouble your code is causing. FEATURES *trouble.nvim-trouble-features* - Diagnostics - LSP references - LSP implementations - LSP definitions - LSP type definitions - LSP Document Symbols - LSP Incoming/Outgoing calls - quickfix list - location list - Telescope search results - fzf-lua results WHAT’S NEW? *trouble.nvim-trouble-what’s-new?* This is a full rewrite of the original **trouble.nvim**. The new version is much more flexible and powerful, with a lot of new features and improvements: - multiple trouble windows at the same time - LSP document symbols - LSP incoming/outgoing calls - lots of options to configure trouble windows (floats or splits) - `focus` option to focus the trouble window when opened (or not) - `follow` option to follow the item under the cursor - `pinned` option to pin the buffer as the source for the opened trouble window - full tree views of anything - highly configurable views with custom formatters, filters, and sorters - show multiple sections in the same view - multi-line messages - prettier and configurable indent guides - tree view that follows the natural hierarchy of the items (like document symbols, or file structure) - expansive API and `Trouble` command - trouble `modes` to define custom views - statusline component (useful with document symbols) REQUIREMENTS *trouble.nvim-trouble-requirements* - Neovim >= 0.9.2 - Neovim >= 0.10.0 **OR** the `markdown` and `markdown_inline` nvim-treesitter parsers - Properly configured Neovim LSP client - nvim-web-devicons is optional to enable file icons - a theme with properly configured highlight groups for Neovim Diagnostics - a patched font for the default severity and fold icons INSTALLATION *trouble.nvim-trouble-installation* Install the plugin with your preferred package manager: LAZY.NVIM ~ >lua { "folke/trouble.nvim", opts = {}, -- for default options, refer to the configuration section for custom setup. cmd = "Trouble", keys = { { "xx", "Trouble diagnostics toggle", desc = "Diagnostics (Trouble)", }, { "xX", "Trouble diagnostics toggle filter.buf=0", desc = "Buffer Diagnostics (Trouble)", }, { "cs", "Trouble symbols toggle focus=false", desc = "Symbols (Trouble)", }, { "cl", "Trouble lsp toggle focus=false win.position=right", desc = "LSP Definitions / references / ... (Trouble)", }, { "xL", "Trouble loclist toggle", desc = "Location List (Trouble)", }, { "xQ", "Trouble qflist toggle", desc = "Quickfix List (Trouble)", }, }, } < CONFIGURATION *trouble.nvim-trouble-configuration* SETUP ~ **Trouble** is highly configurable. Please refer to the default settings below. Default Settings ~ >lua ---@class trouble.Mode: trouble.Config,trouble.Section.spec ---@field desc? string ---@field sections? string[] ---@class trouble.Config ---@field mode? string ---@field config? fun(opts:trouble.Config) ---@field formatters? table custom formatters ---@field filters? table custom filters ---@field sorters? table custom sorters local defaults = { auto_close = false, -- auto close when there are no items auto_open = false, -- auto open when there are items auto_preview = true, -- automatically open preview when on an item auto_refresh = true, -- auto refresh when open auto_jump = false, -- auto jump to the item when there's only one focus = false, -- Focus the window when opened restore = true, -- restores the last location in the list when opening follow = true, -- Follow the current item indent_guides = true, -- show indent guides max_items = 200, -- limit number of items that can be displayed per section multiline = true, -- render multi-line messages pinned = false, -- When pinned, the opened trouble window will be bound to the current buffer warn_no_results = true, -- show a warning when there are no results open_no_results = false, -- open the trouble window when there are no results ---@type trouble.Window.opts win = {}, -- window options for the results window. Can be a split or a floating window. -- Window options for the preview window. Can be a split, floating window, -- or `main` to show the preview in the main editor window. ---@type trouble.Window.opts preview = { type = "main", -- when a buffer is not yet loaded, the preview window will be created -- in a scratch buffer with only syntax highlighting enabled. -- Set to false, if you want the preview to always be a real loaded buffer. scratch = true, }, -- Throttle/Debounce settings. Should usually not be changed. ---@type table throttle = { refresh = 20, -- fetches new data when needed update = 10, -- updates the window render = 10, -- renders the window follow = 100, -- follows the current item preview = { ms = 100, debounce = true }, -- shows the preview for the current item }, -- Key mappings can be set to the name of a builtin action, -- or you can define your own custom action. ---@type table keys = { ["?"] = "help", r = "refresh", R = "toggle_refresh", q = "close", o = "jump_close", [""] = "cancel", [""] = "jump", ["<2-leftmouse>"] = "jump", [""] = "jump_split", [""] = "jump_vsplit", -- go down to next item (accepts count) -- j = "next", ["}"] = "next", ["]]"] = "next", -- go up to prev item (accepts count) -- k = "prev", ["{"] = "prev", ["[["] = "prev", dd = "delete", d = { action = "delete", mode = "v" }, i = "inspect", p = "preview", P = "toggle_preview", zo = "fold_open", zO = "fold_open_recursive", zc = "fold_close", zC = "fold_close_recursive", za = "fold_toggle", zA = "fold_toggle_recursive", zm = "fold_more", zM = "fold_close_all", zr = "fold_reduce", zR = "fold_open_all", zx = "fold_update", zX = "fold_update_all", zn = "fold_disable", zN = "fold_enable", zi = "fold_toggle_enable", gb = { -- example of a custom action that toggles the active view filter action = function(view) view:filter({ buf = 0 }, { toggle = true }) end, desc = "Toggle Current Buffer Filter", }, s = { -- example of a custom action that toggles the severity action = function(view) local f = view:get_filter("severity") local severity = ((f and f.filter.severity or 0) + 1) % 5 view:filter({ severity = severity }, { id = "severity", template = "{hl:Title}Filter:{hl} {severity}", del = severity == 0, }) end, desc = "Toggle Severity Filter", }, }, ---@type table modes = { -- sources define their own modes, which you can use directly, -- or override like in the example below lsp_references = { -- some modes are configurable, see the source code for more details params = { include_declaration = true, }, }, -- The LSP base mode for: -- * lsp_definitions, lsp_references, lsp_implementations -- * lsp_type_definitions, lsp_declarations, lsp_command lsp_base = { params = { -- don't include the current location in the results include_current = false, }, }, -- more advanced example that extends the lsp_document_symbols symbols = { desc = "document symbols", mode = "lsp_document_symbols", focus = false, win = { position = "right" }, filter = { -- remove Package since luals uses it for control flow structures ["not"] = { ft = "lua", kind = "Package" }, any = { -- all symbol kinds for help / markdown files ft = { "help", "markdown" }, -- default set of symbol kinds kind = { "Class", "Constructor", "Enum", "Field", "Function", "Interface", "Method", "Module", "Namespace", "Package", "Property", "Struct", "Trait", }, }, }, }, }, icons = { ---@type trouble.Indent.symbols indent = { top = "│ ", middle = "├╴", last = "└╴", -- last = "-╴", -- last = "╰╴", -- rounded fold_open = " ", fold_closed = " ", ws = " ", }, folder_closed = " ", folder_open = " ", kinds = { Array = " ", Boolean = "󰨙 ", Class = " ", Constant = "󰏿 ", Constructor = " ", Enum = " ", EnumMember = " ", Event = " ", Field = " ", File = " ", Function = "󰊕 ", Interface = " ", Key = " ", Method = "󰊕 ", Module = " ", Namespace = "󰦮 ", Null = " ", Number = "󰎠 ", Object = " ", Operator = " ", Package = " ", Property = " ", String = " ", Struct = "󰆼 ", TypeParameter = " ", Variable = "󰀫 ", }, }, } < Make sure to check the Examples ! USAGE *trouble.nvim-trouble-usage* COMMANDS ~ The **Trouble** command is a wrapper around the **Trouble** API. It can do anything the regular API can do. - `Trouble [mode] [action] [options]` Some examples: - Toggle diagnostics for the current buffer and stay in the current window: - `Trouble diagnostics toggle focus=false filter.buf=0` - Show document symbols on the right of the current window. Keep the document symbols in sync with the buffer you started the command in. - `Trouble symbols toggle pinned=true win.relative=win win.position=right` - You can use **lua** code in the options for the `Trouble` command. The examples below all do the same thing. - `Trouble diagnostics filter.severity=vim.diagnostic.severity.ERROR` - `Trouble diagnostics filter.severity = vim.diagnostic.severity.ERROR` - `Trouble diagnostics filter = { severity=vim.diagnostic.severity.ERROR }` - Merging of nested options, with or without quoting strings: - `Trouble diagnostics win.type = split win.position=right` - `Trouble diagnostics win = { type = split, position=right}` - `Trouble diagnostics win = { type = "split", position='right'}` Please refer to the API section for more information on the available actions and options. Modes: - **diagnostics**: diagnostics - **fzf**: FzfLua results previously opened with `require('trouble.sources.fzf').open()`. - **fzf_files**: FzfLua results previously opened with `require('trouble.sources.fzf').open()`. - **loclist**: Location List - **lsp**: LSP definitions, references, implementations, type definitions, and declarations - **lsp_command**: command - **lsp_declarations**: declarations - **lsp_definitions**: definitions - **lsp_document_symbols**: document symbols - **lsp_implementations**: implementations - **lsp_incoming_calls**: Incoming Calls - **lsp_outgoing_calls**: Outgoing Calls - **lsp_references**: references - **lsp_type_definitions**: type definitions - **qflist**: Quickfix List - **quickfix**: Quickfix List - **snacks**: Snacks results previously opened with `require('trouble.sources.snacks').open()`. - **snacks_files**: Snacks results previously opened with `require('trouble.sources.snacks').open()`. - **symbols**: document symbols - **telescope**: Telescope results previously opened with `require('trouble.sources.telescope').open()`. - **telescope_files**: Telescope results previously opened with `require('trouble.sources.telescope').open()`. FILTERS ~ Please refer to the filter docs for more information examples on filters. API ~ You can use the following functions in your keybindings: API ~ >lua -- Opens trouble with the given mode. -- If a view is already open with the same mode, -- it will be focused unless `opts.focus = false`. -- When a view is already open and `opts.new = true`, -- a new view will be created. ---@param opts? trouble.Mode | { new?: boolean, refresh?: boolean } | string ---@return trouble.View? require("trouble").open(opts) -- Closes the last open view matching the filter. ---@param opts? trouble.Mode|string ---@return trouble.View? require("trouble").close(opts) -- Toggle the view with the given mode. ---@param opts? trouble.Mode|string ---@return trouble.View? require("trouble").toggle(opts) -- Returns true if there is an open view matching the mode. ---@param opts? trouble.Mode|string require("trouble").is_open(opts) -- Refresh all open views. Normally this is done automatically, -- unless you disabled auto refresh. ---@param opts? trouble.Mode|string require("trouble").refresh(opts) -- Get all items from the active view for a given mode. ---@param opts? trouble.Mode|string require("trouble").get_items(opts) -- Renders a trouble list as a statusline component. -- Check the docs for examples. ---@param opts? trouble.Mode|string|{hl_group?:string} ---@return {get: (fun():string), has: (fun():boolean)} require("trouble").statusline(opts) -- Closes the preview and goes to the main window. -- The Trouble window is not closed. ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").cancel(opts) -- Open the preview ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").delete(opts) -- filter ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").filter(opts) -- Go to the first item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").first(opts) -- Focus the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").focus(opts) -- Fold close ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_close(opts) -- fold close all ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_close_all(opts) -- Fold close recursive ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_close_recursive(opts) -- fold disable ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_disable(opts) -- fold enable ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_enable(opts) -- fold more ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_more(opts) -- Fold open ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_open(opts) -- fold open all ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_open_all(opts) -- Fold open recursive ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_open_recursive(opts) -- fold reduce ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_reduce(opts) -- Fold toggle ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_toggle(opts) -- fold toggle enable ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_toggle_enable(opts) -- Fold toggle recursive ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_toggle_recursive(opts) -- fold update ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_update(opts) -- fold update all ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").fold_update_all(opts) -- Show the help ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").help(opts) -- Dump the item to the console ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").inspect(opts) -- Jump to the item if on an item, otherwise fold the node ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump(opts) -- Jump to the item and close the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_close(opts) -- Jump to the item if on an item, otherwise do nothing ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_only(opts) -- Open the item in a split ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_split(opts) -- Open the item in a split and close the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_split_close(opts) -- Open the item in a vsplit ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_vsplit(opts) -- Open the item in a vsplit and close the trouble window ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").jump_vsplit_close(opts) -- Go to the last item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").last(opts) -- Go to the next item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").next(opts) -- Go to the previous item ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").prev(opts) -- Open the preview ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").preview(opts) -- Refresh the trouble source ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").refresh(opts) -- Toggle the preview ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").toggle_preview(opts) -- Toggle the auto refresh ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").toggle_refresh(opts) < TELESCOPE ~ You can easily open any search results in **Trouble**, by defining a custom action: >lua local actions = require("telescope.actions") local open_with_trouble = require("trouble.sources.telescope").open -- Use this to add more results without clearing the trouble list local add_to_trouble = require("trouble.sources.telescope").add local telescope = require("telescope") telescope.setup({ defaults = { mappings = { i = { [""] = open_with_trouble }, n = { [""] = open_with_trouble }, }, }, }) < When you open telescope, you can now hit `` to open the results in **Trouble** FZF-LUA ~ You can easily open any search results in **Trouble**, by defining a custom action: >lua local config = require("fzf-lua.config") local actions = require("trouble.sources.fzf").actions config.defaults.actions.files["ctrl-t"] = actions.open < When you open fzf-lua, you can now hit `` to open the results in **Trouble** STATUSLINE COMPONENT ~ Example for lualine.nvim : >lua { "nvim-lualine/lualine.nvim", opts = function(_, opts) local trouble = require("trouble") local symbols = trouble.statusline({ mode = "lsp_document_symbols", groups = {}, title = false, filter = { range = true }, format = "{kind_icon}{symbol.name:Normal}", -- The following line is needed to fix the background color -- Set it to the lualine section you want to use hl_group = "lualine_c_normal", }) table.insert(opts.sections.lualine_c, { symbols.get, cond = symbols.has, }) end, } < COLORS *trouble.nvim-trouble-colors* The table below shows all the highlight groups defined for Trouble. Highlight Groups ~ Highlight Group Default Group Description -------------------------- ------------------------- ------------- TroubleBasename TroubleFilename TroubleCode Special TroubleCount TabLineSel TroubleDirectory Directory TroubleFilename Directory TroubleIconArray @punctuation.bracket TroubleIconBoolean @boolean TroubleIconClass @type TroubleIconConstant @constant TroubleIconConstructor @constructor TroubleIconDirectory Special TroubleIconEnum @lsp.type.enum TroubleIconEnumMember @lsp.type.enumMember TroubleIconEvent Special TroubleIconField @variable.member TroubleIconFile Normal TroubleIconFunction @function TroubleIconInterface @lsp.type.interface TroubleIconKey @lsp.type.keyword TroubleIconMethod @function.method TroubleIconModule @module TroubleIconNamespace @module TroubleIconNull @constant.builtin TroubleIconNumber @number TroubleIconObject @constant TroubleIconOperator @operator TroubleIconPackage @module TroubleIconProperty @property TroubleIconString @string TroubleIconStruct @lsp.type.struct TroubleIconTypeParameter @lsp.type.typeParameter TroubleIconVariable @variable TroubleIndent LineNr TroubleIndentFoldClosed CursorLineNr TroubleIndentFoldOpen TroubleIndent TroubleIndentLast TroubleIndent TroubleIndentMiddle TroubleIndent TroubleIndentTop TroubleIndent TroubleIndentWs TroubleIndent TroubleNormal NormalFloat TroubleNormalNC NormalFloat TroublePos LineNr TroublePreview Visual TroubleSource Comment TroubleText Normal ============================================================================== 2. Links *trouble.nvim-links* 1. *image*: https://github.com/folke/trouble.nvim/assets/292349/481bc1f7-cb93-432d-8ab6-f54044334b96 Generated by panvimdoc vim:tw=78:ts=8:noet:ft=help:norl: ================================================ FILE: docs/examples.md ================================================ # Examples ## Window Options ### Preview in small float top right ![image](https://github.com/folke/trouble.nvim/assets/292349/f422b8fd-579e-427b-87d3-62daab85d2e0) ```lua { modes = { preview_float = { mode = "diagnostics", preview = { type = "float", relative = "editor", border = "rounded", title = "Preview", title_pos = "center", position = { 0, -2 }, size = { width = 0.3, height = 0.3 }, zindex = 200, }, }, }, } ``` ### Preview in a split to the right of the trouble list ![image](https://github.com/folke/trouble.nvim/assets/292349/adfa02df-b3dd-4c90-af3c-41683c0b5356) ```lua { modes = { test = { mode = "diagnostics", preview = { type = "split", relative = "win", position = "right", size = 0.3, }, }, }, } ``` ### Make Trouble look like v2 ```lua { icons = { indent = { middle = " ", last = " ", top = " ", ws = "│ ", }, }, modes = { diagnostics = { groups = { { "filename", format = "{file_icon} {basename:Title} {count}" }, }, }, }, } ``` ## Filtering ### Diagnostics for the current buffer only ```lua { modes = { diagnostics_buffer = { mode = "diagnostics", -- inherit from diagnostics mode filter = { buf = 0 }, -- filter diagnostics to the current buffer }, } } ``` ### Diagnostics for the current buffer and errors from the current project ```lua { modes = { mydiags = { mode = "diagnostics", -- inherit from diagnostics mode filter = { any = { buf = 0, -- current buffer { severity = vim.diagnostic.severity.ERROR, -- errors only -- limit to files in the current project function(item) return item.filename:find((vim.loop or vim.uv).cwd(), 1, true) end, }, }, }, } } } ``` ### Diagnostics Cascade The following example shows how to create a new mode that shows only the most severe diagnostics. Once those are resolved, less severe diagnostics will be shown. ```lua { modes = { cascade = { mode = "diagnostics", -- inherit from diagnostics mode filter = function(items) local severity = vim.diagnostic.severity.HINT for _, item in ipairs(items) do severity = math.min(severity, item.severity) end return vim.tbl_filter(function(item) return item.severity == severity end, items) end, }, }, } ``` ## Other ### Automatically Open Trouble Quickfix ```lua vim.api.nvim_create_autocmd("QuickFixCmdPost", { callback = function() vim.cmd([[Trouble qflist open]]) end, }) ``` Test with something like `:silent grep vim %` ### Open Trouble Quickfix when the qf list opens > This is **NOT** recommended, since you won't be able to use the quickfix list for other things. ```lua vim.api.nvim_create_autocmd("BufRead", { callback = function(ev) if vim.bo[ev.buf].buftype == "quickfix" then vim.schedule(function() vim.cmd([[cclose]]) vim.cmd([[Trouble qflist open]]) end) end end, }) ``` ================================================ FILE: docs/filter.md ================================================ # Filter ## Examples A simple filter is a table whose keys are item attributes. The following filter keeps items with attribute `buf = 0` **and** `ft = 'lua'`, i.e., diagnostics with severity error to the current buffer when its filetype is `lua`. ```lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = { buf = 0, ft = 'lua' }, }, }, } ``` A filter may be a function that takes `items` as parameter. The following filter keeps items with severity `HINT` ```lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = function(items) return vim.tbl_filter(function(item) return item.severity == vim.diagnostic.severity.HINT end, items) end, }, }, } ``` ## Advanced examples ### The `not` filter The `not` negates filter results. The following filter **removes** diagnostics with severity `INFO` ```lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = { ['not'] = { severity = vim.diagnostic.severity.INFO }, }, }, }, } ``` ### The `any` filter The `any` filter provides logical disjunction. The following filter **keeps** diagnostics for the current buffer **or** diagnostics with severity `ERROR` for the current project. ```lua { modes = { my_diagnostics = { mode = 'diagnostics', filter = { any = { buf = 0, { severity = vim.diagnostic.severity.ERROR, function(item) return item.filename:find((vim.loop or vim.uv).cwd(), 1, true) end, }, }, }, }, }, } ``` ## Item attributes Item attributes are documented in `lua/trouble/item.lua` | Name | Type | Description | | ------------ | -------------------------- | -------------------------------------------------- | | **any** | Logical `or` | Filter result disjunction | | **basename** | `string` | File name. | | **buf** | `number` | Buffer id. | | **dirname** | `string` | Directory path. | | **filename** | `string` | Full file path. | | **ft** | `string` or `string[]` | File types. | | **kind** | `string` | Symbol kind. See `:h symbol`. | | **not** | Logical `not` | Filter result negation. | | **pos** | `{[1]:number, [2]:number}` | Item position. | | **severity** | `number` | Diagnostic severity. See `:h diagnostic-severity`. | | **source** | `string` | Item source. | ================================================ FILE: lua/trouble/api.lua ================================================ local Actions = require("trouble.config.actions") local Config = require("trouble.config") local Util = require("trouble.util") local View = require("trouble.view") ---@alias trouble.ApiFn fun(opts?: trouble.Config|string): trouble.View ---@class trouble.api: trouble.actions local M = {} M.last_mode = nil ---@type string? --- Finds all open views matching the filter. ---@param opts? trouble.Config|string ---@param filter? trouble.View.filter ---@return trouble.View[], trouble.Config function M._find(opts, filter) opts = Config.get(opts) if opts.mode == "last" then opts.mode = M.last_mode opts = Config.get(opts) end M.last_mode = opts.mode or M.last_mode filter = filter or { open = true, mode = opts.mode } return vim.tbl_map(function(v) return v.view end, View.get(filter)), opts end --- Finds the last open view matching the filter. ---@param opts? trouble.Mode|string ---@param filter? trouble.View.filter ---@return trouble.View?, trouble.Mode function M._find_last(opts, filter) local views, _opts = M._find(opts, filter) ---@cast _opts trouble.Mode return views[#views], _opts end -- Opens trouble with the given mode. -- If a view is already open with the same mode, -- it will be focused unless `opts.focus = false`. -- When a view is already open and `opts.new = true`, -- a new view will be created. ---@param opts? trouble.Mode | { new?: boolean, refresh?: boolean } | string ---@return trouble.View? function M.open(opts) opts = opts or {} local view, _opts = M._find_last(opts) if not view or _opts.new then if not _opts.mode then return Util.error("No mode specified") elseif not vim.tbl_contains(Config.modes(), _opts.mode) then return Util.error("Invalid mode `" .. _opts.mode .. "`") end view = View.new(_opts) end if view then if view:is_open() then if opts.refresh ~= false then view:refresh() end else view:open() end if _opts.focus ~= false then view:wait(function() view.win:focus() end) end end return view end -- Closes the last open view matching the filter. ---@param opts? trouble.Mode|string ---@return trouble.View? function M.close(opts) local view = M._find_last(opts) if view then view:close() return view end end -- Toggle the view with the given mode. ---@param opts? trouble.Mode|string ---@return trouble.View? function M.toggle(opts) if M.is_open(opts) then ---@diagnostic disable-next-line: return-type-mismatch return M.close(opts) else return M.open(opts) end end -- Returns true if there is an open view matching the mode. ---@param opts? trouble.Mode|string function M.is_open(opts) return M._find_last(opts) ~= nil end -- Refresh all open views. Normally this is done automatically, -- unless you disabled auto refresh. ---@param opts? trouble.Mode|string function M.refresh(opts) for _, view in ipairs(M._find(opts)) do view:refresh() end end -- Proxy to last view's action. ---@param action trouble.Action.spec function M._action(action) return function(opts) opts = opts or {} if type(opts) == "string" then opts = { mode = opts } end opts = vim.tbl_deep_extend("force", { refresh = false, }, opts) local view = M.open(opts) if view then view:action(action, opts) end return view end end -- Get all items from the active view for a given mode. ---@param opts? trouble.Mode|string function M.get_items(opts) local view = M._find_last(opts) local ret = {} ---@type trouble.Item[] if view then for _, source in pairs(view.sections) do vim.list_extend(ret, source.items or {}) end end return ret end -- Renders a trouble list as a statusline component. -- Check the docs for examples. ---@param opts? trouble.Mode|string|{hl_group?:string} ---@return {get: (fun():string), has: (fun():boolean)} function M.statusline(opts) local Spec = require("trouble.spec") local Section = require("trouble.view.section") local Render = require("trouble.view.render") opts = Config.get(opts) opts.indent_guides = false opts.icons.indent.ws = "" local renderer = Render.new(opts, { multiline = false, indent = false, }) local status = nil ---@type string? ---@cast opts trouble.Mode local s = Spec.section(opts) s.max_items = s.max_items or opts.max_items local section = Section.new(s, opts) section.on_update = function() status = nil if package.loaded["lualine"] then vim.schedule(function() require("lualine").refresh() end) else vim.cmd.redrawstatus() end end section:listen() section:refresh() return { has = function() return section.node and section.node:count() > 0 end, get = function() if status then return status end renderer:clear() renderer:sections({ section }) status = renderer:statusline() if opts.hl_group then status = require("trouble.config.highlights").fix_statusline(status, opts.hl_group) end return status end, } end return setmetatable(M, { __index = function(_, k) if k == "last_mode" then return nil end return M._action(k) end, }) ================================================ FILE: lua/trouble/async.lua ================================================ local M = {} local uv = vim.loop or vim.uv M.budget = 1 local Scheduler = {} Scheduler._queue = {} Scheduler._executor = assert(uv.new_check()) function Scheduler.step() local budget = M.budget * 1e6 local start = uv.hrtime() while #Scheduler._queue > 0 and uv.hrtime() - start < budget do local a = table.remove(Scheduler._queue, 1) a:_step() if a.running then table.insert(Scheduler._queue, a) end end if #Scheduler._queue == 0 then return Scheduler._executor:stop() end end ---@param a Async function Scheduler.add(a) table.insert(Scheduler._queue, a) if not Scheduler._executor:is_active() then Scheduler._executor:start(vim.schedule_wrap(Scheduler.step)) end end --- @alias AsyncCallback fun(result?:any, error?:string) --- @class Async --- @field running boolean --- @field result? any --- @field error? string --- @field callbacks AsyncCallback[] --- @field thread thread local Async = {} Async.__index = Async function Async.new(fn) local self = setmetatable({}, Async) self.callbacks = {} self.running = true self.thread = coroutine.create(fn) Scheduler.add(self) return self end ---@param result? any ---@param error? string function Async:_done(result, error) if self.running then self.running = false self.result = result self.error = error end for _, callback in ipairs(self.callbacks) do callback(result, error) end -- only run each callback once. -- _done can possibly be called multiple times. -- so we need to clear callbacks after executing them. self.callbacks = {} end function Async:_step() local ok, res = coroutine.resume(self.thread) if not ok then return self:_done(nil, res) elseif res == "abort" then return self:_done(nil, "abort") elseif coroutine.status(self.thread) == "dead" then return self:_done(res) end end function Async:cancel() self:_done(nil, "abort") end ---@param cb AsyncCallback function Async:await(cb) if not cb then error("callback is required") end if self.running then table.insert(self.callbacks, cb) else cb(self.result, self.error) end end function Async:sync() while self.running do vim.wait(10) end return self.error and error(self.error) or self.result end --- @return boolean function M.is_async(obj) return obj and type(obj) == "table" and getmetatable(obj) == Async end ---@generic F ---@param fn F ---@return F|fun(...): Async function M.wrap(fn) return function(...) local args = { ... } return Async.new(function() return fn(unpack(args)) end) end end -- This will yield when called from a coroutine ---@async function M.yield(...) if coroutine.running() == nil then error("Trying to yield from a non-yieldable context") return ... end return coroutine.yield(...) end ---@async function M.abort() return M.yield("abort") end return M ================================================ FILE: lua/trouble/cache.lua ================================================ ---@class trouble.CacheM: {[string]: trouble.Cache} local M = {} ---@type table M.stats = {} ---@class trouble.Cache: {[string]: any} ---@field data table ---@field name string ---@field size number local C = {} function C:__index(key) local ret = C[key] if ret then return ret end ret = self.data[key] M.stats[self.name] = M.stats[self.name] or { name = self.name, hit = 0, miss = 0 } local stats = M.stats[self.name] if ret ~= nil then stats.hit = stats.hit + 1 else stats.miss = stats.miss + 1 end return ret end function C:__newindex(key, value) if self.data[key] ~= nil and value == nil then self.size = self.size - 1 elseif self.data[key] == nil and value ~= nil then self.size = self.size + 1 end self.data[key] = value end function C:clear() self.data = {} self.size = 0 end function M.new(name) return setmetatable({ data = {}, name = name, size = 0 }, C) end function M.report() for _, v in pairs(M.stats) do v.ratio = math.ceil(v.hit / (v.hit + v.miss) * 100) end return M.stats end function M.__index(_, k) M[k] = M.new(k) return M[k] end local ret = setmetatable(M, M) return ret ================================================ FILE: lua/trouble/command.lua ================================================ local Config = require("trouble.config") local Parser = require("trouble.config.parser") local Util = require("trouble.util") local M = {} ---@param prefix string ---@param line string ---@param col number function M.complete(prefix, line, col) line = line:sub(1, col):match("Trouble%s*(.*)$") local parsed = M.parse(line) local candidates = {} ---@type string[] if vim.tbl_isempty(parsed.opts) then if not parsed.mode then vim.list_extend(candidates, Config.modes()) else if not parsed.action then vim.list_extend(candidates, M.actions()) end vim.list_extend(candidates, M.complete_opts()) end else vim.list_extend(candidates, M.complete_opts()) end candidates = vim.tbl_filter(function(x) return tostring(x):find(prefix, 1, true) == 1 end, candidates) table.sort(candidates) return candidates end function M.complete_opts() local candidates = {} ---@type string[] local stack = { { k = "", t = Config.get() } } while #stack > 0 do local top = table.remove(stack) for k, v in pairs(top.t) do if type(k) == "number" then k = "[" .. k .. "]" elseif k:match("^[a-z_]+$") then k = "." .. k else k = ("[%q]"):format(k) end local kk = top.k .. k candidates[#candidates + 1] = kk:gsub("^%.", "") .. "=" if type(v) == "table" and not Util.islist(v) then table.insert(stack, { k = kk, t = v }) end end end vim.list_extend(candidates, { "new=true", }) for _, w in ipairs({ "win", "preview" }) do local winopts = { "type=float", "type=split", "position=top", "position=bottom", "position=left", "position=right", "relative=editor", "relative=win", } vim.list_extend( candidates, vim.tbl_map(function(x) return w .. "." .. x end, winopts) ) end return candidates end function M.actions() local actions = vim.tbl_keys(require("trouble.api")) vim.list_extend(actions, vim.tbl_keys(require("trouble.config.actions"))) return actions end ---@param input string function M.parse(input) ---@type {mode: string, action: string, opts: trouble.Config, errors: string[], args: string[]} local ret = Parser.parse(input) local modes = Config.modes() local actions = M.actions() -- Args can be mode and/or action for _, a in ipairs(ret.args) do if vim.tbl_contains(modes, a) then ret.mode = a elseif vim.tbl_contains(actions, a) then ret.action = a else table.insert(ret.errors, "Unknown argument: " .. a) end end return ret end function M.execute(input) if input.args:match("^%s*$") then ---@type {name: string, desc: string}[] local modes = vim.tbl_map(function(x) local m = Config.get(x) local desc = m.desc or x:gsub("^%l", string.upper) desc = Util.camel(desc, " ") return { name = x, desc = desc } end, Config.modes()) vim.ui.select(modes, { prompt = "Select Trouble Mode:", format_item = function(x) return x.desc and (x.desc .. " (" .. x.name .. ")") or x.name end, }, function(mode) if mode then require("trouble").open({ mode = mode.name }) end end) else local ret = M.parse(input.args) ret.action = ret.action or "open" ret.opts.mode = ret.opts.mode or ret.mode if #ret.errors > 0 then Util.error("Error parsing command:\n- input: `" .. input.args .. "`\nErrors:\n" .. table.concat(ret.errors, "\n")) return end require("trouble")[ret.action](ret.opts) end end return M ================================================ FILE: lua/trouble/config/actions.lua ================================================ local Util = require("trouble.util") ---@alias trouble.Action.ctx {item?: trouble.Item, node?: trouble.Node, opts?: table} ---@alias trouble.ActionFn fun(view:trouble.View, ctx:trouble.Action.ctx) ---@alias trouble.Action {action: trouble.ActionFn, desc?: string, mode?: string} ---@alias trouble.Action.spec string|trouble.ActionFn|trouble.Action|{action: string} ---@class trouble.actions: {[string]: trouble.ActionFn} local M = { -- Refresh the trouble source refresh = function(self) self:refresh() end, -- Close the trouble window close = function(self) self:close() end, -- Closes the preview and goes to the main window. -- The Trouble window is not closed. cancel = function(self) self:goto_main() end, -- Focus the trouble window focus = function(self) self.win:focus() end, -- Open the preview preview = function(self, ctx) local Preview = require("trouble.view.preview") if Preview.is_open() then Preview.close() else self:preview(ctx.item) end end, -- Open the preview delete = function(self) local enabled = self.opts.auto_refresh self:delete() if enabled and not self.opts.auto_refresh then Util.warn("Auto refresh **disabled**", { id = "toggle_refresh" }) end end, -- Toggle the preview toggle_preview = function(self, ctx) self.opts.auto_preview = not self.opts.auto_preview local enabled = self.opts.auto_preview and "enabled" or "disabled" local notify = (enabled == "enabled") and Util.info or Util.warn notify("Auto preview **" .. enabled .. "**", { id = "toggle_preview" }) local Preview = require("trouble.view.preview") if self.opts.auto_preview then if ctx.item then self:preview() end else Preview.close() end end, -- Toggle the auto refresh toggle_refresh = function(self) self.opts.auto_refresh = not self.opts.auto_refresh local enabled = self.opts.auto_refresh and "enabled" or "disabled" local notify = (enabled == "enabled") and Util.info or Util.warn notify("Auto refresh **" .. enabled .. "**", { id = "toggle_refresh" }) end, filter = function(self, ctx) self:filter(ctx.opts.filter) end, -- Show the help help = function(self) self:help() end, -- Go to the next item next = function(self, ctx) self:move({ down = vim.v.count1, jump = ctx.opts.jump }) end, -- Go to the previous item prev = function(self, ctx) self:move({ up = vim.v.count1, jump = ctx.opts.jump }) end, -- Go to the first item first = function(self, ctx) self:move({ idx = vim.v.count1, jump = ctx.opts.jump }) end, -- Go to the last item last = function(self, ctx) self:move({ idx = -vim.v.count1, jump = ctx.opts.jump }) end, -- Jump to the item if on an item, otherwise do nothing jump_only = function(self, ctx) if ctx.item then self:jump(ctx.item) end end, -- Jump to the item if on an item, otherwise fold the node jump = function(self, ctx) if ctx.item then self:jump(ctx.item) elseif ctx.node then self:fold(ctx.node) end end, -- Jump to the item and close the trouble window jump_close = function(self, ctx) if ctx.item then self:jump(ctx.item) self:close() end end, -- Open the item in a split jump_split = function(self, ctx) if ctx.item then self:jump(ctx.item, { split = true }) end end, -- Open the item in a split and close the trouble window jump_split_close = function(self, ctx) if ctx.item then self:jump(ctx.item, { split = true }) self:close() end end, -- Open the item in a vsplit jump_vsplit = function(self, ctx) if ctx.item then self:jump(ctx.item, { vsplit = true }) end end, -- Open the item in a vsplit and close the trouble window jump_vsplit_close = function(self, ctx) if ctx.item then self:jump(ctx.item, { vsplit = true }) self:close() end end, -- Dump the item to the console inspect = function(_, ctx) vim.print(ctx.item or (ctx.node and ctx.node.item)) end, fold_reduce = function(self) self:fold_level({ add = vim.v.count1 }) end, fold_open_all = function(self) self:fold_level({ level = 1000 }) end, fold_more = function(self) self:fold_level({ add = -vim.v.count1 }) end, fold_close_all = function(self) self:fold_level({ level = 0 }) end, fold_update = function(self, ctx) self:fold_level({}) self:fold(ctx.node, { action = "open" }) end, fold_update_all = function(self) self:fold_level({}) end, fold_disable = function(self) self.renderer.foldenable = false self:render() end, fold_enable = function(self) self.renderer.foldenable = true self:render() end, fold_toggle_enable = function(self) self.renderer.foldenable = not self.renderer.foldenable self:render() end, } for _, fold_action in ipairs({ "toggle", "open", "close" }) do for _, recursive in ipairs({ true, false }) do local desc = "Fold " .. fold_action .. " " .. (recursive and "recursive" or "") local name = "fold_" .. fold_action .. (recursive and "_recursive" or "") M[name] = { action = function(self, ctx) self:fold(ctx.node, { action = fold_action, recursive = recursive }) end, desc = desc, } end end return setmetatable(M, { __index = function(_, k) if k == "previous" then Util.warn("`previous` is deprecated, use `prev` instead") else Util.error("Action not found: " .. k) end end, }) ================================================ FILE: lua/trouble/config/highlights.lua ================================================ local Util = require("trouble.util") local M = {} -- stylua: ignore M.colors = { -- General Normal = "NormalFloat", NormalNC = "NormalFloat", Text = "Normal", Preview = "Visual", -- Item Filename = "Directory", Basename = "TroubleFilename", Directory = "Directory", IconDirectory = "Special", Source = "Comment", Code = "Special", Pos = "LineNr", Count = "TabLineSel", -- Indent Guides Indent = "LineNr", IndentFoldClosed = "CursorLineNr", IndentFoldOpen = "TroubleIndent", IndentTop = "TroubleIndent", IndentMiddle = "TroubleIndent", IndentLast = "TroubleIndent", IndentWs = "TroubleIndent", -- LSP Symbol Kinds IconArray = "@punctuation.bracket", IconBoolean = "@boolean", IconClass = "@type", IconConstant = "@constant", IconConstructor = "@constructor", IconEnum = "@lsp.type.enum", IconEnumMember = "@lsp.type.enumMember", IconEvent = "Special", IconField = "@variable.member", IconFile = "Normal", IconFunction = "@function", IconInterface = "@lsp.type.interface", IconKey = "@lsp.type.keyword", IconMethod = "@function.method", IconModule = "@module", IconNamespace = "@module", IconNull = "@constant.builtin", IconNumber = "@number", IconObject = "@constant", IconOperator = "@operator", IconPackage = "@module", IconProperty = "@property", IconString = "@string", IconStruct = "@lsp.type.struct", IconTypeParameter = "@lsp.type.typeParameter", IconVariable = "@variable", } function M.setup() M.link(M.colors) M.source("fs") vim.api.nvim_create_autocmd("ColorScheme", { group = vim.api.nvim_create_augroup("trouble.colorscheme", { clear = true }), callback = function() M._fixed = {} end, }) end ---@param prefix? string ---@param links table function M.link(links, prefix) for k, v in pairs(links) do k = (prefix or "Trouble") .. k vim.api.nvim_set_hl(0, k, { link = v, default = true }) end end ---@param source string ---@param links? table function M.source(source, links) ---@type table links = vim.tbl_extend("force", { Filename = "TroubleFilename", Basename = "TroubleFilename", Source = "TroubleSource", Pos = "TroublePos", Count = "TroubleCount", }, links or {}) M.link(links, "Trouble" .. Util.camel(source)) end M._fixed = {} ---@type table ---@param sl string function M.fix_statusline(sl, statusline_hl) local bg = vim.api.nvim_get_hl(0, { name = statusline_hl, link = false }) bg = bg and bg.bg or nil return sl:gsub("%%#(.-)#", function(hl) if not M._fixed[hl] then local opts = vim.api.nvim_get_hl(0, { name = hl, link = false }) or {} opts.bg = bg local group = "TroubleStatusline" .. vim.tbl_count(M._fixed) vim.api.nvim_set_hl(0, group, opts) M._fixed[hl] = group end return "%#" .. M._fixed[hl] .. "#" end) end return M ================================================ FILE: lua/trouble/config/init.lua ================================================ ---@class trouble.Config.mod: trouble.Config local M = {} ---@class trouble.Mode: trouble.Config,trouble.Section.spec ---@field desc? string ---@field sections? string[] ---@class trouble.Config ---@field mode? string ---@field config? fun(opts:trouble.Config) ---@field formatters? table custom formatters ---@field filters? table custom filters ---@field sorters? table custom sorters local defaults = { debug = false, auto_close = false, -- auto close when there are no items auto_open = false, -- auto open when there are items auto_preview = true, -- automatically open preview when on an item auto_refresh = true, -- auto refresh when open auto_jump = false, -- auto jump to the item when there's only one focus = false, -- Focus the window when opened restore = true, -- restores the last location in the list when opening follow = true, -- Follow the current item indent_guides = true, -- show indent guides max_items = 200, -- limit number of items that can be displayed per section multiline = true, -- render multi-line messages pinned = false, -- When pinned, the opened trouble window will be bound to the current buffer warn_no_results = true, -- show a warning when there are no results open_no_results = false, -- open the trouble window when there are no results ---@type trouble.Window.opts win = {}, -- window options for the results window. Can be a split or a floating window. -- Window options for the preview window. Can be a split, floating window, -- or `main` to show the preview in the main editor window. ---@type trouble.Window.opts preview = { type = "main", -- when a buffer is not yet loaded, the preview window will be created -- in a scratch buffer with only syntax highlighting enabled. -- Set to false, if you want the preview to always be a real loaded buffer. scratch = true, }, -- Throttle/Debounce settings. Should usually not be changed. ---@type table throttle = { refresh = 20, -- fetches new data when needed update = 10, -- updates the window render = 10, -- renders the window follow = 100, -- follows the current item preview = { ms = 100, debounce = true }, -- shows the preview for the current item }, -- Key mappings can be set to the name of a builtin action, -- or you can define your own custom action. ---@type table keys = { ["?"] = "help", r = "refresh", R = "toggle_refresh", q = "close", o = "jump_close", [""] = "cancel", [""] = "jump", ["<2-leftmouse>"] = "jump", [""] = "jump_split", [""] = "jump_vsplit", -- go down to next item (accepts count) -- j = "next", ["}"] = "next", ["]]"] = "next", -- go up to prev item (accepts count) -- k = "prev", ["{"] = "prev", ["[["] = "prev", dd = "delete", d = { action = "delete", mode = "v" }, i = "inspect", p = "preview", P = "toggle_preview", zo = "fold_open", zO = "fold_open_recursive", zc = "fold_close", zC = "fold_close_recursive", za = "fold_toggle", zA = "fold_toggle_recursive", zm = "fold_more", zM = "fold_close_all", zr = "fold_reduce", zR = "fold_open_all", zx = "fold_update", zX = "fold_update_all", zn = "fold_disable", zN = "fold_enable", zi = "fold_toggle_enable", gb = { -- example of a custom action that toggles the active view filter action = function(view) view:filter({ buf = 0 }, { toggle = true }) end, desc = "Toggle Current Buffer Filter", }, s = { -- example of a custom action that toggles the severity action = function(view) local f = view:get_filter("severity") local severity = ((f and f.filter.severity or 0) + 1) % 5 view:filter({ severity = severity }, { id = "severity", template = "{hl:Title}Filter:{hl} {severity}", del = severity == 0, }) end, desc = "Toggle Severity Filter", }, }, ---@type table modes = { -- sources define their own modes, which you can use directly, -- or override like in the example below lsp_references = { -- some modes are configurable, see the source code for more details params = { include_declaration = true, }, }, -- The LSP base mode for: -- * lsp_definitions, lsp_references, lsp_implementations -- * lsp_type_definitions, lsp_declarations, lsp_command lsp_base = { params = { -- don't include the current location in the results include_current = false, }, }, -- more advanced example that extends the lsp_document_symbols symbols = { desc = "document symbols", mode = "lsp_document_symbols", focus = false, win = { position = "right" }, filter = { -- remove Package since luals uses it for control flow structures ["not"] = { ft = "lua", kind = "Package" }, any = { -- all symbol kinds for help / markdown files ft = { "help", "markdown" }, -- default set of symbol kinds kind = { "Class", "Constructor", "Enum", "Field", "Function", "Interface", "Method", "Module", "Namespace", "Package", "Property", "Struct", "Trait", }, }, }, }, }, -- stylua: ignore icons = { ---@type trouble.Indent.symbols indent = { top = "│ ", middle = "├╴", last = "└╴", -- last = "-╴", -- last = "╰╴", -- rounded fold_open = " ", fold_closed = " ", ws = " ", }, folder_closed = " ", folder_open = " ", kinds = { Array = " ", Boolean = "󰨙 ", Class = " ", Constant = "󰏿 ", Constructor = " ", Enum = " ", EnumMember = " ", Event = " ", Field = " ", File = " ", Function = "󰊕 ", Interface = " ", Key = " ", Method = "󰊕 ", Module = " ", Namespace = "󰦮 ", Null = " ", Number = "󰎠 ", Object = " ", Operator = " ", Package = " ", Property = " ", String = " ", Struct = "󰆼 ", TypeParameter = " ", Variable = "󰀫 ", }, }, } ---@type trouble.Config local options ---@param opts? trouble.Config function M.setup(opts) if vim.fn.has("nvim-0.9.2") == 0 then local msg = "trouble.nvim requires Neovim >= 0.9.2" vim.notify_once(msg, vim.log.levels.ERROR, { title = "trouble.nvim" }) error(msg) return end opts = opts or {} if opts.auto_open then require("trouble.util").warn({ "You specified `auto_open = true` in your global config.", "This is probably not what you want.", "Add it to the mode you want to auto open instead.", "```lua", "opts = {", " modes = {", " diagnostics = { auto_open = true },", " }", "}", "```", "Disabling global `auto_open`.", }) opts.auto_open = nil end opts.mode = nil options = {} options = M.get(opts) require("trouble.config.highlights").setup() vim.api.nvim_create_user_command("Trouble", function(input) require("trouble.command").execute(input) end, { nargs = "*", complete = function(...) return require("trouble.command").complete(...) end, desc = "Trouble", }) require("trouble.view.main").setup() vim.schedule(function() for mode, mode_opts in pairs(options.modes) do if mode_opts.auto_open then require("trouble.view").new(M.get(mode)) end end end) return options end --- Update the default config. --- Should only be used by source to extend the default config. ---@param config trouble.Config function M.defaults(config) options = vim.tbl_deep_extend("force", config, options) end function M.modes() require("trouble.sources").load() local ret = {} ---@type string[] for k, v in pairs(options.modes) do if v.source or v.mode or v.sections then ret[#ret + 1] = k end end table.sort(ret) return ret end ---@param ...? trouble.Config|string ---@return trouble.Config function M.get(...) options = options or M.setup() -- check if we need to load sources for i = 1, select("#", ...) do ---@type trouble.Config? local opts = select(i, ...) if type(opts) == "string" or (type(opts) == "table" and opts.mode) then M.modes() -- trigger loading of sources break end end ---@type trouble.Config[] local all = { {}, defaults, options or {} } ---@type table local modes = {} local first_mode ---@type string? for i = 1, select("#", ...) do ---@type trouble.Config? local opts = select(i, ...) if type(opts) == "string" then opts = { mode = opts } end if opts then table.insert(all, opts) local idx = #all while opts.mode and not modes[opts.mode] do first_mode = first_mode or opts.mode modes[opts.mode or ""] = true opts = options.modes[opts.mode] or {} table.insert(all, idx, opts) end end end local ret = vim.tbl_deep_extend("force", unpack(all)) if type(ret.config) == "function" then ret.config(ret) end ret.mode = first_mode return ret end return setmetatable(M, { __index = function(_, key) options = options or M.setup() assert(options, "should be setup") return options[key] end, }) ================================================ FILE: lua/trouble/config/parser.lua ================================================ local M = {} ---@param t table ---@param dotted_key string ---@param value any function M.dotset(t, dotted_key, value) local keys = vim.split(dotted_key, ".", { plain = true }) for i = 1, #keys - 1 do local key = keys[i] t[key] = t[key] or {} if type(t[key]) ~= "table" then t[key] = {} end t = t[key] end ---@diagnostic disable-next-line: no-unknown t[keys[#keys]] = value end ---@return {args: string[], opts: table, errors: string[]} function M.parse(input) ---@type string?, string? local positional, options = input:match("^%s*(.-)%s*([a-z%._]+%s*=.*)$") positional = positional or input positional = vim.trim(positional) local ret = { args = positional == "" and {} or vim.split(positional, "%s+"), opts = {}, errors = {}, } if not options then return ret end input = options local parser = vim.treesitter.get_string_parser(input, "lua") parser:parse() local query = vim.treesitter.query.parse( "lua", [[ (ERROR) @error (assignment_statement (variable_list name: (_)) @name) (assignment_statement (expression_list value: (_)) @value) (_ value: (identifier) @global (#has-ancestor? @global expression_list)) ]] ) ---@type table local env = { dotset = M.dotset, opts = ret.opts, } local lines = {} ---@type string[] local name = "" ---@diagnostic disable-next-line: missing-parameter for id, node in query:iter_captures(parser:trees()[1]:root(), input) do local capture = query.captures[id] local text = vim.treesitter.get_node_text(node, input) if capture == "name" then name = text elseif capture == "value" then table.insert(lines, ("dotset(opts, %q, %s)"):format(name, text)) elseif capture == "global" then env[text] = text elseif capture == "error" then table.insert(ret.errors, text) end end local ok, err = pcall(function() local code = table.concat(lines, "\n") env.vim = vim -- selene: allow(incorrect_standard_library_use) local chunk = load(code, "trouble", "t", env) chunk() end) if not ok then table.insert(ret.errors, err) end return ret end return M ================================================ FILE: lua/trouble/docs.lua ================================================ local Config = require("trouble.config") local Docs = require("lazy.docs") local LazyUtil = require("lazy.util") local M = {} function M.update() local config = Docs.extract("lua/trouble/config/init.lua", "\n(--@class trouble%.Mode.-\n})") config = config:gsub("%s*debug = false.\n", "\n") Docs.save({ config = config, colors = Docs.colors({ modname = "trouble.config.highlights", path = "lua/trouble/config/highlights.lua", name = "Trouble", }), modes = M.modes(), api = M.api(), }) end ---@return ReadmeBlock function M.modes() ---@type string[] local lines = {} local exclude = { "fs", "todo" } local modes = Config.modes() for _, mode in ipairs(modes) do if not vim.tbl_contains(exclude, mode) then local m = Config.get(mode) lines[#lines + 1] = ("- **%s**: %s"):format(mode, m.desc or "") end end return { content = table.concat(lines, "\n") } end ---@return ReadmeBlock function M.api() local lines = vim.split(LazyUtil.read_file("lua/trouble/api.lua"), "\n") local funcs = {} ---@type string[] local f = {} for _, line in ipairs(lines) do if line:match("^%-%-") then f[#f + 1] = line elseif line:match("^function") and not line:match("^function M%._") then f[#f + 1] = line:gsub("^function M", [[require("trouble")]]) funcs[#funcs + 1] = table.concat(f, "\n") f = {} else f = {} end end lines = vim.split(LazyUtil.read_file("lua/trouble/config/actions.lua"), "\n") f = {} ---@type table local comments = {} for _, line in ipairs(lines) do if line:match("^%s*%-%-") then f[#f + 1] = line:gsub("^%s*[%-]*%s*", "") elseif line:match("^%s*[%w_]+ = function") then local name = line:match("^%s*([%w_]+)") if not name:match("^_") and #f > 0 then comments[name] = table.concat(f, "\n") end f = {} else f = {} end end local Actions = require("trouble.config.actions") local names = vim.tbl_keys(Actions) table.sort(names) local exclude = { "close" } for _, k in ipairs(names) do local desc = comments[k] or k:gsub("_", " ") local action = Actions[k] if type(Actions[k]) == "table" then desc = action.desc or desc action = action.action end desc = table.concat( vim.tbl_map(function(line) return ("-- %s"):format(line) end, vim.split(desc, "\n")), "\n" ) if type(action) == "function" and not vim.tbl_contains(exclude, k) then funcs[#funcs + 1] = ([[ %s ---@param opts? trouble.Mode | { new? : boolean } | string ---@return trouble.View require("trouble").%s(opts)]]):format(desc, k) end end return { content = table.concat(funcs, "\n\n"), lang = "lua" } end M.update() print("Updated docs") -- M.api() return M ================================================ FILE: lua/trouble/filter.lua ================================================ local Util = require("trouble.util") local M = {} ---@class trouble.ViewFilter.opts ---@field id? string ---@field template? string ---@field data? table ---@field toggle? boolean ---@field del? boolean ---@class trouble.ViewFilter ---@field id string ---@field filter trouble.Filter ---@field template? string ---@field data? table ---@param opts? {lines:boolean} ---@param range trouble.Range ---@param pos trouble.Pos function M.overlaps(pos, range, opts) if opts and opts.lines then return pos[1] >= range.pos[1] and pos[1] <= range.end_pos[1] else return (pos[1] > range.pos[1] or (pos[1] == range.pos[1] and pos[2] >= range.pos[2])) and (pos[1] < range.end_pos[1] or (pos[1] == range.end_pos[1] and pos[2] <= range.end_pos[2])) end end ---@alias trouble.Filter.ctx {opts:trouble.Config, main?:trouble.Main} ---@alias trouble.FilterFn fun(item:trouble.Item, value: any, ctx:trouble.Filter.ctx): boolean ---@class trouble.Filters: {[string]: trouble.FilterFn} M.filters = { buf = function(item, buf, ctx) if buf == 0 then return ctx.main and ctx.main.filename == item.filename or false end return item.buf == buf end, ---@param fts string|string[] ft = function(item, fts, _) fts = type(fts) == "table" and fts or { fts } local ft = item.buf and vim.bo[item.buf].filetype return ft and vim.tbl_contains(fts, ft) or false end, range = function(item, buf, ctx) local main = ctx.main if not main or (main.buf ~= item.buf) then return false end local range = item.range --[[@as trouble.Range]] if range then return M.overlaps(main.cursor, range, { lines = true }) else return M.overlaps(main.cursor, item, { lines = true }) end end, ["not"] = function(item, filter, ctx) ---@cast filter trouble.Filter return not M.is(item, filter, ctx) end, any = function(item, any, ctx) ---@cast any trouble.Filter[] for k, f in pairs(any) do if type(k) == "string" then f = { [k] = f } end if M.is(item, f, ctx) then return true end end return false end, } ---@param item trouble.Item ---@param filter trouble.Filter ---@param ctx trouble.Filter.ctx function M.is(item, filter, ctx) if type(filter) == "table" and Util.islist(filter) then for _, f in ipairs(filter) do if not M.is(item, f, ctx) then return false end end return true end filter = type(filter) == "table" and filter or { filter } for k, v in pairs(filter) do ---@type trouble.FilterFn? local filter_fn = ctx.opts.filters and ctx.opts.filters[k] or M.filters[k] if filter_fn then if not filter_fn(item, v, ctx) then return false end elseif type(k) == "number" then if type(v) == "function" then if not v(item) then return false end elseif not item[v] then return false end elseif type(v) == "table" then if not vim.tbl_contains(v, item[k]) then return false end elseif item[k] ~= v then return false end end return true end ---@param items trouble.Item[] ---@param filter? trouble.Filter ---@param ctx trouble.Filter.ctx function M.filter(items, filter, ctx) -- fast path for empty filter if not filter or (type(filter) == "table" and vim.tbl_isempty(filter)) then return items, {} end if type(filter) == "function" then return filter(items) end local ret = {} ---@type trouble.Item[] for _, item in ipairs(items) do if M.is(item, filter, ctx) then ret[#ret + 1] = item end end return ret end return M ================================================ FILE: lua/trouble/format.lua ================================================ local Cache = require("trouble.cache") local Util = require("trouble.util") local M = {} ---@alias trouble.spec.format string|trouble.Format|(string|trouble.Format)[] ---@alias trouble.Format {text:string, hl?:string} ---@alias trouble.Formatter fun(ctx: trouble.Formatter.ctx): trouble.spec.format? ---@alias trouble.Formatter.ctx {item: trouble.Item, node:trouble.Node, field:string, value:string, opts:trouble.Config} ---@param source string ---@param field string function M.default_hl(source, field) if not source then return "Trouble" .. Util.camel(field) end local key = source .. field local value = Cache.default_hl[key] if value then return value end local hl = "Trouble" .. Util.camel(source) .. Util.camel(field) Cache.default_hl[key] = hl return hl end ---@type (fun(file: string, ext: string): string, string)[] local icons = { function(file) return require("mini.icons").get("file", file) end, function(file, ext) return require("nvim-web-devicons").get_icon(file, ext, { default = true }) end, } function M.get_icon(file, ext) while #icons > 0 do local ok, icon, hl = pcall(icons[1], file, ext) if ok then return icon, hl end table.remove(icons, 1) end end ---@param fn trouble.Formatter ---@param field string function M.cached_formatter(fn, field) local cache = {} ---@param ctx trouble.Formatter.ctx return function(ctx) local key = ctx.item.source .. field .. (ctx.item[field] or "") local result = cache[key] if result then return result end result = fn(ctx) cache[key] = result return result end end ---@type table M.formatters = { pos = function(ctx) return { text = "[" .. ctx.item.pos[1] .. ", " .. (ctx.item.pos[2] + 1) .. "]", } end, code = function(ctx) if not ctx.item.code or ctx.item.code == vim.NIL then return end return { text = "(" .. ctx.item.code .. ")", hl = "TroubleCode", } end, severity = function(ctx) local severity = ctx.item.severity or vim.diagnostic.severity.ERROR local name = vim.diagnostic.severity[severity] or "OTHER" return { text = name, hl = "Diagnostic" .. Util.camel(name:lower()), } end, severity_icon = function(ctx) local severity = ctx.item.severity or vim.diagnostic.severity.ERROR if not vim.diagnostic.severity[severity] then return end if type(severity) == "string" then severity = vim.diagnostic.severity[severity:upper()] or vim.diagnostic.severity.ERROR end local name = Util.camel(vim.diagnostic.severity[severity]:lower()) local sign = vim.fn.sign_getdefined("DiagnosticSign" .. name)[1] if vim.fn.has("nvim-0.10.0") == 1 then local config = vim.diagnostic.config() or {} if config.signs == nil or type(config.signs) == "boolean" then return { text = sign and sign.text or name:sub(1, 1), hl = "DiagnosticSign" .. name } end local signs = config.signs or {} if type(signs) == "function" then signs = signs(0, 0) --[[@as vim.diagnostic.Opts.Signs]] end return { text = type(signs) == "table" and signs.text and signs.text[severity] or sign and sign.text or name:sub(1, 1), hl = "DiagnosticSign" .. name, } else return sign and { text = sign.text, hl = sign.texthl } or { text = name } or nil end end, file_icon = function(ctx) local item = ctx.item --[[@as Diagnostic|trouble.Item]] local file = vim.fn.fnamemodify(item.filename, ":t") local ext = vim.fn.fnamemodify(item.filename, ":e") local icon, color = M.get_icon(file, ext) return icon and { text = icon .. " ", hl = color } or "" end, count = function(ctx) return { text = (" %d "):format(ctx.node:count()), } end, filename = function(ctx) return { text = vim.fn.fnamemodify(ctx.item.filename, ":p:~:."), } end, dirname = function(ctx) return { text = vim.fn.fnamemodify(ctx.item.dirname, ":p:~:."), } end, filter = function(ctx) return { text = vim.inspect(ctx.item.filter):gsub("%s+", " "), hl = "ts.lua", } end, kind_icon = function(ctx) if not ctx.item.kind then return end local icon = ctx.opts.icons.kinds[ctx.item.kind] if icon then return { text = icon, hl = "TroubleIcon" .. ctx.item.kind, } end end, directory = function(ctx) if ctx.node:source() == "fs" then local directory = ctx.item.directory or "" local parent = ctx.node:parent_item() if parent and parent.directory then directory = directory:sub(#parent.directory + 1) return { text = directory, hl = "TroubleDirectory" } end return { text = vim.fn.fnamemodify(directory, ":~"), hl = "TroubleDirectory" } end end, directory_icon = function(ctx) if ctx.node:source() == "fs" then local text = ctx.node.folded and ctx.opts.icons.folder_closed or ctx.opts.icons.folder_open return { text = text, hl = "TroubleIconDirectory" } end end, } M.formatters.severity_icon = M.cached_formatter(M.formatters.severity_icon, "severity") M.formatters.severity = M.cached_formatter(M.formatters.severity, "severity") ---@param ctx trouble.Formatter.ctx function M.field(ctx) ---@type trouble.Format[] local format = { { fi = ctx.field, text = vim.trim(tostring(ctx.item[ctx.field] or "")) } } local opts = ctx.opts local formatter = opts.formatters and opts.formatters[ctx.field] or M.formatters[ctx.field] if formatter then local result = formatter(ctx) if not result then return end result = type(result) == "table" and Util.islist(result) and result or { result } format = {} ---@cast result (string|trouble.Format)[] for _, f in ipairs(result) do ---@diagnostic disable-next-line: assign-type-mismatch format[#format + 1] = type(f) == "string" and { text = f } or f end end for _, f in ipairs(format) do f.hl = f.hl or M.default_hl(ctx.item.source, ctx.field) end return format end ---@param format string ---@param ctx {item: trouble.Item, node:trouble.Node, opts:trouble.Config} function M.format(format, ctx) ---@type trouble.Format[] local ret = {} local hl ---@type string? while true do ---@type string?,string,string local before, fields, after = format:match("^(.-){(.-)}(.*)$") if not before then break end format = after if #before > 0 then ret[#ret + 1] = { text = before, hl = hl } end for _, field in Util.split(fields, "|") do ---@type string,string local field_name, field_hl = field:match("^(.-):(.+)$") if field_name then field = field_name end if field == "hl" then hl = field_hl else ---@cast ctx trouble.Formatter.ctx ctx.field = field ctx.value = ctx.item[field] local ff = M.field(ctx) if ff then for _, f in ipairs(ff) do if hl or field_hl then f.hl = field_hl or hl end ret[#ret + 1] = f end -- only render the first field break end end end end if #format > 0 then ret[#ret + 1] = { text = format, hl = hl } end return ret end return M ================================================ FILE: lua/trouble/init.lua ================================================ ---@class trouble: trouble.api local M = {} ---@param opts? trouble.Config function M.setup(opts) require("trouble.config").setup(opts) end return setmetatable(M, { __index = function(_, k) return require("trouble.api")[k] end, }) ================================================ FILE: lua/trouble/item.lua ================================================ local Cache = require("trouble.cache") local Util = require("trouble.util") ---@alias trouble.Pos {[1]:number, [2]:number} ---@class trouble.Range ---@field pos trouble.Pos ---@field end_pos trouble.Pos ---@class trouble.Item: {[string]: any} ---@field id? string ---@field parent? trouble.Item ---@field buf? number ---@field filename string ---@field pos trouble.Pos (1,0)-indexed ---@field end_pos? trouble.Pos (1,0)-indexed ---@field item table ---@field source string ---@field cache table ---@field range? trouble.Range local M = {} ---@param opts trouble.Item | {filename?:string} function M.new(opts) local self = opts assert(self.source, "source is required") self.pos = self.pos or { 1, 0 } self.pos[1] = math.max(self.pos[1] or 1, 1) self.pos[2] = math.max(self.pos[2] or 0, 0) self.end_pos = self.end_pos or self.pos self.item = self.item or {} if self.buf and not self.filename then self.filename = vim.api.nvim_buf_get_name(self.buf) if self.filename == "" then self.filename = "[buffer:" .. self.buf .. "]" end end assert(self.filename, "filename is required") if self.filename then self.filename = vim.fs.normalize(self.filename) local parts = vim.split(self.filename, "/", { plain = true }) self.basename = table.remove(parts) self.dirname = table.concat(parts, "/") end self.cache = Cache.new("item") return setmetatable(self, M) end ---@param items trouble.Item[] ---@param fields? string[] function M.add_id(items, fields) for _, item in ipairs(items) do if not item.id then local id = { item.source, item.filename, item.pos[1] or "", item.pos[2] or "", item.end_pos[1] or "", item.end_pos[2] or "", } for _, field in ipairs(fields or {}) do table.insert(id, item[field] or "") end item.id = table.concat(id, ":") end end end ---@return string? function M:get_ft(buf) if self.buf and vim.api.nvim_buf_is_loaded(self.buf) then return vim.bo[self.buf].filetype end if not self.filename then return end local ft = Cache.ft[self.filename] if ft == nil then -- HACK: make sure we always pass a valid buf, -- otherwise some detectors will fail hard (like ts) ft = vim.filetype.match({ filename = self.filename, buf = buf or 0 }) Cache.ft[self.filename] = ft or false -- cache misses too end return ft end function M:get_lang(buf) local ft = self:get_ft(buf) return ft and ft ~= "" and vim.treesitter.language.get_lang(ft) or nil end function M:__index(k) if type(k) ~= "string" then return end if M[k] then return M[k] end local item = rawget(self, "item") ---@cast k string if item and item[k] ~= nil then return item[k] end local obj = self local start = 1 while type(obj) == "table" do local dot = k:find(".", start, true) if not dot then if start == 1 then return end local ret = obj[k:sub(start)] rawset(self, k, ret) return ret end local key = k:sub(start, dot - 1) obj = obj[key] start = dot + 1 end end ---@param item trouble.Item function M:add_child(item) item.parent = self end ---@param items trouble.Item[] ---@param opts? {mode?:"range"|"full"|"after", multiline?:boolean} function M.add_text(items, opts) opts = opts or {} opts.mode = opts.mode or "range" local todo = {} ---@type table for _, item in ipairs(items) do if not item.item.text and item.filename then -- schedule to get the lines todo[item.filename] = todo[item.filename] or { rows = {} } todo[item.filename].buf = todo[item.filename].buf or item.buf for r = item.pos[1], item.end_pos and item.end_pos[1] or item.pos[1] do table.insert(todo[item.filename].rows, r) if not opts.multiline then break end end end end -- get the lines and range text local buf_lines = {} ---@type table> for path, t in pairs(todo) do buf_lines[path] = Util.get_lines({ rows = t.rows, buf = t.buf, path = path, }) or {} end for _, item in ipairs(items) do if not item.item.text and item.filename then local lines = {} ---@type string[] for row = item.pos[1], item.end_pos[1] do local line = buf_lines[item.filename][row] or "" if row == item.pos[1] and row == item.end_pos[1] then if opts.mode == "after" then line = line:sub(item.pos[2] + 1) elseif opts.mode == "range" then line = line:sub(item.pos[2] + 1, item.end_pos[2]) end elseif row == item.pos[1] then line = line:sub(item.pos[2] + 1) elseif row == item.end_pos[1] then line = line:sub(1, item.end_pos[2]) --[[@as string]] end if line ~= "" then lines[#lines + 1] = line end end item.item.text = table.concat(lines, "\n") end end return items end return M ================================================ FILE: lua/trouble/promise.lua ================================================ local Util = require("trouble.util") ---@alias trouble.Promise.state "pending" | "fulfilled" | "rejected" ---@class trouble.Promise ---@field state trouble.Promise.state ---@field value any? ---@field queue (fun())[] ---@field resolve fun(value) ---@field reject fun(reason) ---@field has_next boolean local P = {} P.__index = P --- Creates a new promise ---@param executor fun(resolve: fun(value), reject: fun(reason)) ---@return trouble.Promise function P.new(executor) local self = setmetatable({}, P) self.state = "pending" self.value = nil self.queue = {} self.has_next = false ---@param state trouble.Promise.state local function transition(state, result) if self.state == "pending" then self.state = state self.value = result for _, cb in ipairs(self.queue) do cb() end if state == "rejected" and not self.has_next then local bt = debug.traceback() vim.schedule(function() if not self.has_next then Util.error("Unhandled promise rejection:\n```lua\n" .. tostring(result) .. "\n\n" .. bt .. "```") end end) end end end self.resolve = function(value) transition("fulfilled", value) end self.reject = function(reason) transition("rejected", reason) end xpcall(function() executor(self.resolve, self.reject) end, function(err) self.reject(err) end) return self end --- Adds fulfillment and rejection handlers to the promise ---@param on_fulfilled? fun(value):any ---@param on_rejected? fun(reason):any ---@return trouble.Promise function P:next(on_fulfilled, on_rejected) local next = P.new(function() end) local function handle() local callback = on_fulfilled if self.state == "rejected" then callback = on_rejected end if callback then local ok, ret = pcall(callback, self.value) if ok then if ret and type(ret) == "table" and getmetatable(ret) == P then ret:next(next.resolve, next.reject) else next.resolve(ret) end else next.reject(ret) -- reject the next promise with the error end else if self.state == "fulfilled" then next.resolve(self.value) else next.reject(self.value) end end end if self.state ~= "pending" then vim.schedule(handle) -- ensure the callback is called in the next event loop tick else table.insert(self.queue, handle) end self.has_next = true -- self.has_rejection_handler or (on_rejected ~= nil) return next end function P:catch(on_rejected) return self:next(nil, on_rejected) end function P:finally(on_finally) return self:next(function(value) return P.new(function(resolve) on_finally() resolve(value) end) end, function(reason) return P.new(function(_, reject) on_finally() reject(reason) end) end) end function P:is_pending() return self.state == "pending" end function P:timeout(ms) return P.new(function(resolve, reject) local timer = (vim.uv or vim.loop).new_timer() timer:start(ms, 0, function() timer:close() vim.schedule(function() reject("timeout") end) end) self:next(resolve, reject) end) end local M = {} function M.resolve(value) return P.new(function(resolve) resolve(value) end) end function M.reject(reason) return P.new(function(_, reject) reject(reason) end) end ---@param promises trouble.Promise[] function M.all(promises) return P.new(function(resolve, reject) local results = {} local pending = #promises if pending == 0 then return resolve(results) end for i, promise in ipairs(promises) do promise:next(function(value) results[i] = value pending = pending - 1 if pending == 0 then resolve(results) end end, reject) end end) end ---@param promises trouble.Promise[] function M.all_settled(promises) return P.new(function(resolve) local results = {} local pending = #promises if pending == 0 then return resolve(results) end for i, promise in ipairs(promises) do promise:next(function(value) results[i] = { status = "fulfilled", value = value } pending = pending - 1 if pending == 0 then resolve(results) end end, function(reason) results[i] = { status = "rejected", reason = reason } pending = pending - 1 if pending == 0 then resolve(results) end end) end end) end M.new = P.new -- M.new(function() end):timeout(1000) return M ================================================ FILE: lua/trouble/providers/telescope.lua ================================================ local T = require("trouble.sources.telescope") return setmetatable({}, { __index = function(_, k) require("trouble.util").warn( ([[ `%s()` is deprecated ```lua -- Use this: require("trouble.sources.telescope").open() -- Instead of: require("trouble.providers.telescope").%s() ]]):format(k, k), { once = true } ) return T.open end, }) ================================================ FILE: lua/trouble/sort.lua ================================================ local Filter = require("trouble.filter") local M = {} ---@alias trouble.Sort.ctx {opts:trouble.Config, main?:trouble.Main} ---@type table M.sorters = { pos = function(obj) -- Use large multipliers for higher priority fields to ensure their precedence in sorting local primaryScore = obj.pos[1] * 1000000 + obj.pos[2] * 1000 local secondaryScore = obj.end_pos[1] * 1000000 + obj.end_pos[2] * 1000 return primaryScore + secondaryScore end, } ---@param items trouble.Item[] ---@param opts? trouble.Sort[] ---@param ctx trouble.Sort.ctx function M.sort(items, opts, ctx) if not opts or #opts == 0 then return items end local keys = {} ---@type table local desc = {} ---@type boolean[] -- pre-compute fields local fields = {} ---@type trouble.Sort[] for f, field in ipairs(opts) do if field.field then ---@diagnostic disable-next-line: no-unknown local sorter = ctx.opts.sorters and ctx.opts.sorters[field.field] or M.sorters[field.field] if sorter then fields[f] = { sorter = sorter } else fields[f] = { field = field.field } end else fields[f] = field end desc[f] = field.desc or false end -- pre-compute keys for _, item in ipairs(items) do local item_keys = {} ---@type any[] for f, field in ipairs(fields) do local key = nil if field.sorter then key = field.sorter(item) elseif field.field then ---@diagnostic disable-next-line: no-unknown key = item[field.field] elseif field.filter then key = Filter.is(item, field.filter, ctx) end if type(key) == "boolean" then key = key and 0 or 1 end item_keys[f] = key end keys[item] = item_keys end -- sort items table.sort(items, function(a, b) local ka = keys[a] local kb = keys[b] for i = 1, #ka do local fa = ka[i] local fb = kb[i] if fa ~= fb then if desc[i] then return fa > fb else return fa < fb end end end return false end) return items end return M ================================================ FILE: lua/trouble/sources/diagnostics.lua ================================================ ---@diagnostic disable: inject-field local Item = require("trouble.item") ---@class trouble.Source.diagnostics: trouble.Source local M = {} M.highlights = { Message = "TroubleText", ItemSource = "Comment", Code = "Comment", } M.config = { modes = { diagnostics = { desc = "diagnostics", events = { "DiagnosticChanged", "BufEnter" }, -- Trouble classic for other buffers, -- but only if they are in the current directory source = "diagnostics", groups = { -- { format = "{hl:Special}󰚢 {hl} {hl:Title}Diagnostics{hl} {count}" }, -- { "severity", format = "{severity_icon} {severity} {count}" }, -- { "dirname", format = "{hl:Special} {hl} {dirname} {count}" }, { "directory" }, { "filename", format = "{file_icon} {basename} {count}" }, }, sort = { "severity", "filename", "pos", "message" }, format = "{severity_icon} {message:md} {item.source} {code} {pos}", -- filter = { -- ["not"] = { -- any = { -- { severity = vim.diagnostic.severity.ERROR }, -- { buf = 0 }, -- }, -- }, -- function(item) -- return item.filename:find((vim.loop or vim.uv).cwd(), 1, true) -- end, -- }, }, -- { -- -- error from all files -- source = "diagnostics", -- groups = { "severity", "code", "filename" }, -- filter = { -- -- severity = 1, -- }, -- sort = { "filename", "pos" }, -- format = "sig {severity_sign} {severity} file: {filename} pos: {pos}", -- }, -- { -- -- diagnostics from current buffer -- source = "diagnostics", -- groups = { "severity", "filename" }, -- filter = { -- buf = 0, -- }, -- sort = { "pos" }, -- }, }, } ---@type table local cache = {} function M.setup() vim.api.nvim_create_autocmd("DiagnosticChanged", { group = vim.api.nvim_create_augroup("trouble.diagnostics", { clear = true }), callback = function(event) -- NOTE: unfortunately, we can't use the event.data.diagnostics table here, -- since multiple namespaces exist and we can't tell which namespace the -- diagnostics are from. cache[event.buf] = vim.tbl_map(M.item, vim.diagnostic.get(event.buf)) cache[0] = nil end, }) for _, diag in ipairs(vim.diagnostic.get()) do local buf = diag.bufnr if buf and vim.api.nvim_buf_is_valid(buf) then cache[buf] = cache[buf] or {} table.insert(cache[buf], M.item(diag)) Item.add_id(cache[buf], { "item.source", "severity", "code" }) end end end ---@param diag vim.Diagnostic function M.item(diag) return Item.new({ source = "diagnostics", buf = diag.bufnr, pos = { diag.lnum + 1, diag.col }, end_pos = { diag.end_lnum and (diag.end_lnum + 1) or nil, diag.end_col }, item = diag, }) end ---@param cb trouble.Source.Callback ---@param ctx trouble.Source.ctx) function M.get(cb, ctx) -- PERF: pre-filter when possible local buf = type(ctx.opts.filter) == "table" and ctx.opts.filter.buf or nil if buf == 0 then buf = ctx.main.buf end if buf then cb(cache[buf] or {}) else if not cache[0] then cache[0] = {} for b, items in pairs(cache) do if b ~= 0 then if vim.api.nvim_buf_is_valid(b) then for _, item in ipairs(items) do table.insert(cache[0], item) end else cache[b] = nil end end end end cb(cache[0]) end end return M ================================================ FILE: lua/trouble/sources/fzf.lua ================================================ ---@diagnostic disable: inject-field local Item = require("trouble.item") ---Represents an item in a Neovim quickfix/loclist. ---@class fzf.Item ---@field stripped string the fzf item without any highlighting. ---@field bufnr? number The buffer number of the item. ---@field bufname? string ---@field terminal? boolean ---@field path string ---@field uri? string ---@field line number 1-indexed line number ---@field col number 1-indexed column number ---@class fzf.Opts ---@class trouble.Source.fzf: trouble.Source local M = {} ---@type trouble.Item[] M.items = {} M.config = { modes = { fzf = { desc = "FzfLua results previously opened with `require('trouble.sources.fzf').open()`.", source = "fzf", groups = { { "cmd", format = "{hl:Title}fzf{hl} {cmd:Comment} {count}" }, { "filename", format = "{file_icon} {filename} {count}" }, }, sort = { "filename", "pos" }, format = "{text:ts} {pos}", }, fzf_files = { desc = "FzfLua results previously opened with `require('trouble.sources.fzf').open()`.", source = "fzf", groups = { { "cmd", format = "{hl:Title}fzf{hl} {cmd:Comment} {count}" }, }, sort = { "filename", "pos" }, format = "{file_icon} {filename}", }, }, } ---@param item fzf.Item function M.item(item) item.text = item.stripped:match(":%d+:%d?%d?%d?%d?:?(.*)$") local word = item.text and item.text:sub(item.col):match("%S+") return Item.new({ source = "fzf", buf = item.bufnr, filename = item.bufname or item.path or item.uri, pos = { item.line, item.col - 1 }, end_pos = word and { item.line, item.col - 1 + #word } or nil, item = item, }) end ---@param cb trouble.Source.Callback ---@param _ctx trouble.Source.ctx) function M.get(cb, _ctx) cb(M.items) end -- Returns the mode based on the items. function M.mode() for _, item in ipairs(M.items) do if item.text then return "fzf" end end return "fzf_files" end -- Append the current fzf buffer to the trouble list. ---@param selected string[] ---@param fzf_opts fzf.Opts ---@param opts? trouble.Mode|string function M.add(selected, fzf_opts, opts) local cmd = fzf_opts.__INFO.cmd local path = require("fzf-lua.path") for _, line in ipairs(selected) do local item = M.item(path.entry_to_file(line, fzf_opts)) item.item.cmd = cmd table.insert(M.items, item) end vim.schedule(function() opts = opts or {} if type(opts) == "string" then opts = { mode = opts } end opts = vim.tbl_extend("force", { mode = M.mode() }, opts) require("trouble").open(opts) end) end -- Opens the current fzf buffer in the trouble list. -- This will clear the existing items. ---@param selected string[] ---@param fzf_opts fzf.Opts ---@param opts? trouble.Mode|string function M.open(selected, fzf_opts, opts) M.items = {} M.add(selected, fzf_opts, opts) end local smart_prefix = require("trouble.util").is_win() and "transform(IF %FZF_SELECT_COUNT% LEQ 0 (echo select-all))" or "transform([ $FZF_SELECT_COUNT -eq 0 ] && echo select-all)" M.actions = { -- Open selected or all items in the trouble list. open = { fn = M.open, prefix = smart_prefix, desc = "smart-open-with-trouble" }, -- Open selected items in the trouble list. open_selected = { fn = M.open, desc = "open-with-trouble" }, -- Open all items in the trouble list. open_all = { fn = M.open, prefix = "select-all", desc = "open-all-with-trouble" }, -- Add selected or all items to the trouble list. add = { fn = M.add, prefix = smart_prefix, desc = "smart-add-to-trouble" }, -- Add selected items to the trouble list. add_selected = { fn = M.add, desc = "add-to-trouble" }, -- Add all items to the trouble list. add_all = { fn = M.add, prefix = "select-all", desc = "add-all-to-trouble" }, } return M ================================================ FILE: lua/trouble/sources/init.lua ================================================ local Config = require("trouble.config") local Util = require("trouble.util") ---@class trouble.Source ---@field highlights? table ---@field config? trouble.Config ---@field setup? fun() ---@field get trouble.Source.get|table ---@field preview? fun(item:trouble.Item, ctx:trouble.Preview) ---@alias trouble.Source.ctx {main: trouble.Main, opts:trouble.Mode} ---@alias trouble.Source.Callback fun(items:trouble.Item[]) ---@alias trouble.Source.get fun(cb:trouble.Source.Callback, ctx:trouble.Source.ctx) local M = {} ---@type table M.sources = {} ---@param name string ---@param source? trouble.Source function M.register(name, source) if M.sources[name] then error("source already registered: " .. name) end source = source or require("trouble.sources." .. name) if source then if source.setup then source.setup() end require("trouble.config.highlights").source(name, source.highlights) if source.config then Config.defaults(source.config) end end M.sources[name] = source return source end ---@param source string function M.get(source) local parent, child = source:match("^(.-)%.(.*)$") source = parent or source local s = M.sources[source] or M.register(source) if child and type(s.get) ~= "table" then error("source does not support sub-sources: " .. source) elseif child and type(s.get[child]) ~= "function" then error("source does not support sub-source: " .. source .. "." .. child) end return (child and s.get[child] or s.get), s end function M.load() local rtp = vim.api.nvim_get_runtime_file("lua/trouble/sources/*.lua", true) for _, file in ipairs(rtp) do local name = file:match("lua[/\\]trouble[/\\]sources[/\\](.*)%.lua") if name and name ~= "init" and not M.sources[name] then Util.try(function() M.register(name) end, { msg = "Error loading source: " .. name }) end end end return M ================================================ FILE: lua/trouble/sources/lsp.lua ================================================ local Cache = require("trouble.cache") local Config = require("trouble.config") local Filter = require("trouble.filter") local Item = require("trouble.item") local Promise = require("trouble.promise") local Util = require("trouble.util") local str_byteindex_new = pcall(vim.str_byteindex, "aa", "utf-8", 1) ---@param s string line to be indexed ---@param index integer UTF index ---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16 ---@return integer byte (utf-8) index of `encoding` index `index` in `line` local function get_line_col(s, index, encoding) if str_byteindex_new then return vim.str_byteindex(s, encoding, index, false) elseif vim.str_byteindex then ---@diagnostic disable-next-line: param-type-mismatch return vim.str_byteindex(s, index, encoding == "utf-16") elseif vim.lsp.util._str_byteindex then return vim.lsp.util._str_byteindex(s, index, encoding) end error("No str_byteindex function available") end ---@class trouble.Source.lsp: trouble.Source ---@diagnostic disable-next-line: missing-fields local M = {} function M.setup() vim.api.nvim_create_autocmd({ "LspAttach", "LspDetach" }, { group = vim.api.nvim_create_augroup("trouble.lsp.dattach", { clear = true }), callback = function() Cache.symbols:clear() Cache.locations:clear() end, }) vim.api.nvim_create_autocmd({ "BufDelete", "TextChanged", "TextChangedI" }, { group = vim.api.nvim_create_augroup("trouble.lsp.buf", { clear = true }), callback = function(ev) local buf = ev.buf Cache.symbols[buf] = nil if vim.api.nvim_buf_is_valid(ev.buf) and vim.api.nvim_buf_is_loaded(ev.buf) and vim.bo[ev.buf].buftype == "" then Cache.locations:clear() end end, }) end M.config = { modes = { lsp_document_symbols = { title = "{hl:Title}Document Symbols{hl} {count}", desc = "document symbols", events = { "BufEnter", -- symbols are cached on changedtick, -- so it's ok to refresh often { event = "TextChanged", main = true }, { event = "CursorMoved", main = true }, { event = "LspAttach", main = true }, }, source = "lsp.document_symbols", groups = { { "filename", format = "{file_icon} {filename} {count}" }, }, sort = { "filename", "pos", "text" }, -- sort = { { buf = 0 }, { kind = "Function" }, "filename", "pos", "text" }, format = "{kind_icon} {symbol.name} {text:Comment} {pos}", }, lsp_base = { events = { "BufEnter", { event = "CursorHold", main = true }, { event = "LspAttach", main = true }, }, groups = { { "filename", format = "{file_icon} {filename} {count}" }, }, sort = { "filename", "pos", "text" }, format = "{text:ts} ({item.client}) {pos}", }, lsp = { desc = "LSP definitions, references, implementations, type definitions, and declarations", sections = { "lsp_definitions", "lsp_references", "lsp_implementations", "lsp_type_definitions", "lsp_declarations", "lsp_incoming_calls", "lsp_outgoing_calls", }, }, }, } for _, mode in ipairs({ "incoming_calls", "outgoing_calls" }) do M.config.modes["lsp_" .. mode] = { mode = "lsp_base", title = "{hl:Title}" .. Util.camel(mode, " ") .. "{hl} {count}", desc = Util.camel(mode, " "), source = "lsp." .. mode, format = "{kind_icon} {text:ts} {pos} {hl:Title}{item.client:Title}{hl}", } end for _, mode in ipairs({ "definitions", "references", "implementations", "type_definitions", "declarations", "command" }) do M.config.modes["lsp_" .. mode] = { auto_jump = true, mode = "lsp_base", title = "{hl:Title}" .. Util.camel(mode, " ") .. "{hl} {count}", source = "lsp." .. mode, desc = Util.camel(mode, " "):lower(), } end ---@class trouble.lsp.Response: {client: vim.lsp.Client, result: R, err: lsp.ResponseError, params: P} ---@param method string ---@param params? table|fun(client:vim.lsp.Client):table ---@param opts? {client?:vim.lsp.Client} function M.request(method, params, opts) opts = opts or {} local buf = vim.api.nvim_get_current_buf() ---@type vim.lsp.Client[] local clients = {} if opts.client then clients = { opts.client } else if vim.lsp.get_clients then clients = vim.lsp.get_clients({ method = method, bufnr = buf }) else ---@diagnostic disable-next-line: deprecated clients = vim.lsp.get_active_clients({ bufnr = buf }) ---@param client vim.lsp.Client clients = vim.tbl_filter(function(client) return client.supports_method(method) end, clients) end end ---@param client vim.lsp.Client return Promise.all(vim.tbl_map(function(client) return Promise.new(function(resolve) local p = type(params) == "function" and params(client) or params --[[@as table]] local request = vim.fn.has("nvim-0.11") == 0 and function(_, ...) return client.request(...) end or client.request request(client, method, p, function(err, result) resolve({ client = client, result = result, err = err, params = p }) end, buf) end) end, clients)):next(function(results) ---@param v trouble.lsp.Response return vim.tbl_filter(function(v) return v.result end, results) end) end ---@param method string ---@param cb trouble.Source.Callback ---@param ctx trouble.Source.ctx ---@param opts? {context?:any, params?:table} function M.get_locations(method, cb, ctx, opts) local win = vim.api.nvim_get_current_win() local buf = vim.api.nvim_get_current_buf() local cursor = vim.api.nvim_win_get_cursor(win) local col = cursor[2] local line = vim.api.nvim_get_current_line() while col > 1 and vim.fn.strcharpart(line, col - 1, 1):match("^[a-zA-Z_]$") do col = col - 1 end opts = opts or {} ---@type fun(client:vim.lsp.Client):lsp.TextDocumentPositionParams local params = function(client) local ret = opts.params or vim.lsp.util.make_position_params(win, client.offset_encoding) ---@diagnostic disable-next-line: inject-field ret.context = ret.context or opts.context or nil return ret end local id = table.concat({ buf, cursor[1], col, method, vim.inspect(vim.lsp.util.make_position_params(win, "utf-16")) }, "-") if Cache.locations[id] then return cb(Cache.locations[id]) end M.request(method, params):next( ---@param results trouble.lsp.Response[] function(results) local items = {} ---@type trouble.Item[] for _, resp in ipairs(results) do vim.list_extend(items, M.get_items(resp.client, resp.result, ctx.opts.params)) end Cache.locations[id] = items cb(items) end ) end M.get = {} ---@param cb trouble.Source.Callback function M.get.document_symbols(cb) local buf = vim.api.nvim_get_current_buf() ---@type trouble.Item[] local ret = Cache.symbols[buf] if ret then return cb(ret) end ---@type lsp.DocumentSymbolParams local params = { textDocument = vim.lsp.util.make_text_document_params() } ---@alias lsp.Symbol lsp.SymbolInformation|lsp.DocumentSymbol M.request("textDocument/documentSymbol", params):next( ---@param results trouble.lsp.Response[] function(results) if vim.tbl_isempty(results) then return cb({}) end if not vim.api.nvim_buf_is_valid(buf) then return end local items = {} ---@type trouble.Item[] for _, res in ipairs(results) do vim.list_extend(items, M.results_to_items(res.client, res.result, params.textDocument.uri)) end Item.add_text(items, { mode = "after" }) ---@diagnostic disable-next-line: no-unknown Cache.symbols[buf] = items cb(items) end ) end ---@param cb trouble.Source.Callback function M.call_hierarchy(cb, incoming) local win = vim.api.nvim_get_current_win() M.request("textDocument/prepareCallHierarchy", function(client) return vim.lsp.util.make_position_params(win, client.offset_encoding) end) :next( ---@param results trouble.lsp.Response[] function(results) local requests = {} ---@type trouble.Promise[] for _, res in ipairs(results or {}) do for _, chi in ipairs(res.result) do requests[#requests + 1] = M.request( ("callHierarchy/%sCalls"):format(incoming and "incoming" or "outgoing"), { item = chi }, { client = res.client } ) end end return Promise.all(requests) end ) :next( ---@param responses trouble.lsp.Response<(lsp.CallHierarchyIncomingCall|lsp.CallHierarchyOutgoingCall)[]>[][] function(responses) local items = {} ---@type trouble.Item[] for _, results in ipairs(responses) do for _, res in ipairs(results) do local client = res.client local calls = res.result local todo = {} ---@type lsp.ResultItem[] for _, call in ipairs(calls) do todo[#todo + 1] = call.to or call.from end vim.list_extend(items, M.results_to_items(client, todo)) end end Item.add_text(items, { mode = "after" }) if incoming then -- for incoming calls, we actually want the call locations, not just the caller -- but we use the caller's item text as the call location text local texts = {} ---@type table for _, item in ipairs(items) do texts[item.item.symbol] = item.item.text end items = {} for _, results in ipairs(responses) do for _, res in ipairs(results) do local client = res.client local calls = res.result local todo = {} ---@type lsp.ResultItem[] for _, call in ipairs(calls) do for _, r in ipairs(call.fromRanges or {}) do local t = vim.deepcopy(call.from) --[[@as lsp.ResultItem]] t.location = { range = r or call.from.selectionRange or call.from.range, uri = call.from.uri } t.text = texts[call.from] todo[#todo + 1] = t end end vim.list_extend(items, M.results_to_items(client, todo)) end end end cb(items) end ) -- :catch(Util.error) end ---@param cb trouble.Source.Callback function M.get.incoming_calls(cb) M.call_hierarchy(cb, true) end ---@param cb trouble.Source.Callback function M.get.outgoing_calls(cb) M.call_hierarchy(cb, false) end ---@param client vim.lsp.Client ---@param locations? lsp.Location[]|lsp.LocationLink[]|lsp.Location ---@param opts? {include_current?:boolean} function M.get_items(client, locations, opts) opts = opts or {} locations = locations or {} locations = Util.islist(locations) and locations or { locations } ---@cast locations (lsp.Location|lsp.LocationLink)[] locations = vim.list_slice(locations, 1, Config.max_items) local items = M.locations_to_items(client, locations) local cursor = vim.api.nvim_win_get_cursor(0) local fname = vim.api.nvim_buf_get_name(0) fname = vim.fs.normalize(fname) if not opts.include_current then ---@param item trouble.Item items = vim.tbl_filter(function(item) return not (item.filename == fname and Filter.overlaps(cursor, item, { lines = true })) end, items) end -- Item.add_text(items, { mode = "full" }) return items end ---@alias lsp.Loc lsp.Location|lsp.LocationLink ---@param client vim.lsp.Client ---@param locs lsp.Loc[] ---@return trouble.Item[] function M.locations_to_items(client, locs) local ranges = M.locations_to_ranges(client, locs) ---@param range trouble.Range.lsp return vim.tbl_map(function(range) return M.range_to_item(client, range) end, vim.tbl_values(ranges)) end ---@param client vim.lsp.Client ---@param range trouble.Range.lsp ---@return trouble.Item function M.range_to_item(client, range) return Item.new({ buf = range.buf, filename = range.filename, pos = range.pos, end_pos = range.end_pos, source = "lsp", item = { client_id = client.id, client = client.name, location = range.location, text = range.line and vim.trim(range.line) or nil, }, }) end local kinds = nil ---@type table --- Gets the original symbol kind name from its number. --- Some plugins override the symbol kind names, so this function is needed to get the original name. ---@param kind lsp.SymbolKind ---@return string function M.symbol_kind(kind) if not kinds then kinds = {} for k, v in pairs(vim.lsp.protocol.SymbolKind) do if type(v) == "number" then kinds[v] = k end end end return kinds[kind] end ---@alias lsp.ResultItem lsp.Symbol|lsp.CallHierarchyItem|{text?:string} ---@param client vim.lsp.Client ---@param results lsp.ResultItem[] ---@param default_uri? string function M.results_to_items(client, results, default_uri) local items = {} ---@type trouble.Item[] local locs = {} ---@type lsp.Loc[] local processed = {} ---@type table ---@param result lsp.ResultItem local function process(result) local uri = result.location and result.location.uri or result.uri or default_uri local loc = result.location or { range = result.selectionRange or result.range, uri = uri } loc.uri = loc.uri or uri if not loc.uri then assert(loc.uri, "missing uri in result:\n" .. vim.inspect(result)) end -- the range enclosing this symbol. Useful to get the symbol of the current cursor position ---@type lsp.Location? local range = result.range and { range = result.range, uri = uri } or nil processed[result] = { uri = uri, loc = loc, range = range } locs[#locs + 1] = loc if range then locs[#locs + 1] = range end for _, child in ipairs(result.children or {}) do process(child) end end for _, result in ipairs(results) do process(result) end local ranges = M.locations_to_ranges(client, locs) ---@param result lsp.ResultItem local function add(result) local loc = processed[result].loc local range = processed[result].range local item = M.range_to_item(client, ranges[loc]) local id = { item.buf, item.pos[1], item.pos[2], item.end_pos[1], item.end_pos[2], item.kind } item.id = table.concat(id, "|") -- item.text = nil -- the range enclosing this symbol. Useful to get the symbol of the current cursor position item.range = range and ranges[range] or nil item.item.kind = M.symbol_kind(result.kind) or tostring(result.kind) item.item.symbol = result item.item.text = result.text items[#items + 1] = item for _, child in ipairs(result.children or {}) do item:add_child(add(child)) end result.children = nil return item end for _, result in ipairs(results) do add(result) end return items end ---@class trouble.Range.lsp: trouble.Range ---@field buf? number ---@field filename string ---@field location lsp.Loc ---@field client vim.lsp.Client ---@field line string ---@param client vim.lsp.Client ---@param locs lsp.Loc[] function M.locations_to_ranges(client, locs) local todo = {} ---@type table}> for _, d in ipairs(locs) do local uri = d.uri or d.targetUri local range = d.range or d.targetSelectionRange todo[uri] = todo[uri] or { locs = {}, rows = {} } table.insert(todo[uri].locs, d) local from = range.start.line + 1 local to = range["end"].line + 1 todo[uri].rows[from] = from todo[uri].rows[to] = to end local ret = {} ---@type table for uri, t in pairs(todo) do local buf = vim.uri_to_bufnr(uri) local filename = vim.uri_to_fname(uri) local lines = Util.get_lines({ rows = vim.tbl_keys(t.rows), buf = buf }) or {} for _, loc in ipairs(t.locs) do local range = loc.range or loc.targetSelectionRange local line = lines[range.start.line + 1] or "" local end_line = lines[range["end"].line + 1] or "" local pos = { range.start.line + 1, get_line_col(line, range.start.character, client.offset_encoding) } local end_pos = { range["end"].line + 1, get_line_col(end_line, range["end"].character, client.offset_encoding) } ret[loc] = { buf = buf, filename = filename, pos = pos, end_pos = end_pos, source = "lsp", client = client, location = loc, line = line, } end end return ret end ---@param cb trouble.Source.Callback ---@param ctx trouble.Source.ctx function M.get.command(cb, ctx) local err = "Missing command params for `lsp_command`.\n" .. "You need to specify `opts.params = {command = 'the_command', arguments = {}}`" if not ctx.opts.params then return Util.error(err) end ---@type lsp.ExecuteCommandParams local params = ctx.opts.params if not params.command then return Util.error(err) end M.get_locations("workspace/executeCommand", cb, ctx, { params = params }) end ---@param ctx trouble.Source.ctx ---@param cb trouble.Source.Callback function M.get.references(cb, ctx) local params = ctx.opts.params or {} M.get_locations("textDocument/references", cb, ctx, { context = { includeDeclaration = params.include_declaration ~= false, }, }) end ---@param cb trouble.Source.Callback ---@param ctx trouble.Source.ctx function M.get.definitions(cb, ctx) M.get_locations("textDocument/definition", cb, ctx) end ---@param cb trouble.Source.Callback ---@param ctx trouble.Source.ctx function M.get.implementations(cb, ctx) M.get_locations("textDocument/implementation", cb, ctx) end -- Type Definitions ---@param cb trouble.Source.Callback ---@param ctx trouble.Source.ctx function M.get.type_definitions(cb, ctx) M.get_locations("textDocument/typeDefinition", cb, ctx) end -- Declaration ---@param cb trouble.Source.Callback ---@param ctx trouble.Source.ctx function M.get.declarations(cb, ctx) M.get_locations("textDocument/declaration", cb, ctx) end return M ================================================ FILE: lua/trouble/sources/qf.lua ================================================ ---@diagnostic disable: inject-field local Item = require("trouble.item") ---Represents an item in a Neovim quickfix/loclist. ---@class qf.item ---@field bufnr? number The buffer number where the item originates. ---@field filename? string ---@field lnum number The start line number for the item. ---@field end_lnum? number The end line number for the item. ---@field pattern string A pattern related to the item. It can be a search pattern or any relevant string. ---@field col? number The column number where the item starts. ---@field end_col? number The column number where the item ends. ---@field module? string Module information (if any) associated with the item. ---@field nr? number A unique number or ID for the item. ---@field text? string A description or message related to the item. ---@field type? string The type of the item. E.g., "W" might stand for "Warning". ---@field valid number A flag indicating if the item is valid (1) or not (0). ---@field user_data? any Any user data associated with the item. ---@field vcol? number Visual column number. Indicates if the column number is a visual column number (when set to 1) or a byte index (when set to 0). ---@class trouble.Source.qf: trouble.Source local M = {} M.config = { modes = { qflist = { desc = "Quickfix List", events = { "QuickFixCmdPost", { event = "TextChanged", main = true }, }, source = "qf.qflist", groups = { { "filename", format = "{file_icon} {filename} {count}" }, }, sort = { "severity", "filename", "pos", "message" }, format = "{severity_icon|item.type:DiagnosticSignWarn} {text:ts} {pos}", }, loclist = { desc = "Location List", events = { "BufEnter", { event = "TextChanged", main = true }, }, source = "qf.loclist", groups = { { "filename", format = "{file_icon} {filename} {count}" }, }, sort = { "severity", "filename", "pos", "message" }, format = "{severity_icon|item.type:DiagnosticSignWarn} {text:ts} {pos}", }, }, } M.config.modes.quickfix = M.config.modes.qflist local severities = { E = vim.diagnostic.severity.ERROR, W = vim.diagnostic.severity.WARN, I = vim.diagnostic.severity.INFO, H = vim.diagnostic.severity.HINT, N = vim.diagnostic.severity.HINT, } M.get = { qflist = function(cb) cb(M.get_list()) end, loclist = function(cb) cb(M.get_list({ win = vim.api.nvim_get_current_win() })) end, } ---@param opts? {win:number} function M.get_list(opts) opts = opts or {} local list = opts.win == nil and vim.fn.getqflist({ all = true }) or vim.fn.getloclist(opts.win, { all = true }) ---@cast list {items?:qf.item[]}? local ret = {} ---@type trouble.Item[] for _, item in pairs(list and list.items or {}) do local row = item.lnum == 0 and 1 or item.lnum local col = (item.col == 0 and 1 or item.col) - 1 local end_row = item.end_lnum == 0 and row or item.end_lnum local end_col = item.end_col == 0 and col or (item.end_col - 1) if item.valid == 1 then ret[#ret + 1] = Item.new({ pos = { row, col }, end_pos = { end_row, end_col }, text = item.text, severity = severities[item.type] or 0, buf = item.bufnr, filename = item.filename, item = item, source = "qf", }) elseif #ret > 0 and ret[#ret].item.text and item.text then ret[#ret].item.text = ret[#ret].item.text .. "\n" .. item.text end end Item.add_id(ret, { "severity" }) Item.add_text(ret, { mode = "full" }) return ret end return M ================================================ FILE: lua/trouble/sources/snacks.lua ================================================ ---@diagnostic disable: inject-field local Item = require("trouble.item") ---@module 'snacks' ---@class trouble.Source.snacks: trouble.Source local M = {} ---@type trouble.Item[] M.items = {} M.config = { modes = { snacks = { desc = "Snacks results previously opened with `require('trouble.sources.snacks').open()`.", source = "snacks", groups = { { "cmd", format = "{hl:Title}Snacks{hl} {cmd:Comment} {count}" }, { "filename", format = "{file_icon} {filename} {count}" }, }, sort = { "filename", "pos" }, format = "{text:ts} {pos}", }, snacks_files = { desc = "Snacks results previously opened with `require('trouble.sources.snacks').open()`.", source = "snacks", groups = { { "cmd", format = "{hl:Title}Snacks{hl} {cmd:Comment} {count}" }, }, sort = { "filename", "pos" }, format = "{file_icon} {filename}", }, }, } ---@param item snacks.picker.Item function M.item(item) return Item.new({ source = "snacks", buf = item.buf, filename = item.file, pos = item.pos, end_pos = item.end_pos, text = item.line or item.comment or item.label or item.name or item.detail or false, item = item, }) end ---@param cb trouble.Source.Callback ---@param _ctx trouble.Source.ctx) function M.get(cb, _ctx) cb(M.items) end -- Returns the mode based on the items. function M.mode() for _, item in ipairs(M.items) do if item.text then return "snacks" end end return "snacks_files" end ---@param picker snacks.Picker ---@param opts? { type?: "all" | "selected" | "smart", add?: boolean } function M.open(picker, opts) opts = opts or {} if not opts.add then M.items = {} end local sitems = {} ---@type snacks.picker.Item[] local selected = picker:selected() opts.type = opts.type or "smart" if opts.type == "smart" then opts.type = #selected == 0 and "all" or "selected" end if opts.type == "all" then vim.list_extend(sitems, picker:items()) else vim.list_extend(sitems, selected) end for _, i in ipairs(sitems) do local item = M.item(i) table.insert(M.items, item) end picker:close() vim.schedule(function() require("trouble").open({ mode = M.mode() }) end) end ---@param opts? { type?: "all" | "selected" | "smart", add?: boolean } function M.wrap(opts) ---@param picker snacks.Picker return function(picker) M.open(picker, vim.deepcopy(opts or {})) end end ---@type table M.actions = { -- Open selected or all items in the trouble list. trouble_open = { action = M.wrap({ type = "smart" }), desc = "smart-open-with-trouble" }, -- Open selected items in the trouble list. trouble_open_selected = { action = M.wrap({ type = "selected" }), desc = "open-with-trouble" }, -- Open all items in the trouble list. trouble_open_all = { action = M.wrap({ type = "all" }), desc = "open-all-with-trouble" }, -- Add selected or all items to the trouble list. trouble_add = { action = M.wrap({ type = "smart", add = true }), desc = "smart-add-to-trouble" }, -- Add selected items to the trouble list. trouble_add_selected = { action = M.wrap({ type = "selected", add = true }), desc = "add-to-trouble" }, -- Add all items to the trouble list. trouble_add_all = { action = M.wrap({ type = "all" }), desc = "add-all-to-trouble" }, } return M ================================================ FILE: lua/trouble/sources/telescope.lua ================================================ ---@diagnostic disable: inject-field local Item = require("trouble.item") local Util = require("trouble.util") ---Represents an item in a Neovim quickfix/loclist. ---@class telescope.Item ---@field lnum? number The start line number for the item. ---@field col? number The column number where the item starts. ---@field bufnr? number The buffer number where the item originates. ---@field filename? string The filename of the item. ---@field text? string The text of the item. ---@field cwd? string The current working directory of the item. ---@field path? string The path of the item. ---@class trouble.Source.telescope: trouble.Source local M = {} ---@type trouble.Item[] M.items = {} M.config = { modes = { telescope = { desc = "Telescope results previously opened with `require('trouble.sources.telescope').open()`.", source = "telescope", title = "{hl:Title}Telescope{hl} {count}", groups = { { "filename", format = "{file_icon} {filename} {count}" }, }, sort = { "filename", "pos" }, format = "{text:ts} {pos}", }, telescope_files = { desc = "Telescope results previously opened with `require('trouble.sources.telescope').open()`.", source = "telescope", title = "{hl:Title}Telescope{hl} {count}", sort = { "filename", "pos" }, format = "{file_icon} {filename}", }, }, } ---@param item telescope.Item function M.item(item) ---@type string local filename if item.path then filename = item.path else filename = item.filename if item.cwd then filename = item.cwd .. "/" .. filename end end local word = item.text and item.col and item.text:sub(item.col):match("%S+") local pos = item.lnum and { item.lnum, item.col and item.col - 1 or 0 } or nil return Item.new({ source = "telescope", buf = item.bufnr, filename = filename, pos = pos, end_pos = word and pos and { pos[1], pos[2] + #word } or nil, item = item, }) end ---@param cb trouble.Source.Callback ---@param _ctx trouble.Source.ctx) function M.get(cb, _ctx) cb(M.items) end -- Returns the mode based on the items. function M.mode() for _, item in ipairs(M.items) do if item.text then return "telescope" end end return "telescope_files" end -- Append the current telescope buffer to the trouble list. ---@param opts? trouble.Mode|string function M.add(prompt_bufnr, opts) local action_state = require("telescope.actions.state") ---@type Picker local picker = action_state.get_current_picker(prompt_bufnr) if not picker then return Util.error("No Telescope picker found?") end if #picker:get_multi_selection() > 0 then for _, item in ipairs(picker:get_multi_selection()) do table.insert(M.items, M.item(item)) end else for item in picker.manager:iter() do table.insert(M.items, M.item(item)) end end -- Item.add_text(M.items, { mode = "after" }) vim.schedule(function() require("telescope.actions").close(prompt_bufnr) opts = opts or {} if type(opts) == "string" then opts = { mode = opts } end opts = vim.tbl_extend("force", { mode = M.mode() }, opts) require("trouble").open(opts) end) end -- Opens the current telescope buffer in the trouble list. -- This will clear the existing items. ---@param opts? trouble.Mode|string function M.open(prompt_bufnr, opts) M.items = {} M.add(prompt_bufnr, opts) end return M ================================================ FILE: lua/trouble/spec.lua ================================================ local Config = require("trouble.config") local Util = require("trouble.util") ---@alias trouble.SorterFn fun(item: trouble.Item): any? ---@alias trouble.Sort.spec string|trouble.SorterFn|(string|trouble.SorterFn|trouble.Filter.spec)[] ---@alias trouble.Filter.spec table|fun(items: trouble.Item[]): trouble.Item[] ---@alias trouble.Group.spec string|string[]|{format?:string} ---@alias trouble.Sections.spec (trouble.Section.spec|string)[] ---@class trouble.Section.spec ---@field source string ---@field title? string|boolean ---@field events? (string|trouble.Event)[] ---@field groups? trouble.Group.spec[]|trouble.Group.spec ---@field sort? trouble.Sort.spec ---@field filter? trouble.Filter.spec ---@field flatten? boolean when true, items with a natural hierarchy will be flattened ---@field format? string ---@field max_items? number ---@field params? table ---@alias trouble.Filter table|fun(items: trouble.Item[]): trouble.Item[] ---@class trouble.Event ---@field event string|string[] ---@field pattern? string|string[] ---@field main? boolean When true, this event will refresh only when it is the main window ---@class trouble.Sort ---@field field? string ---@field sorter? trouble.SorterFn ---@field filter? trouble.Filter ---@field desc? boolean ---@class trouble.Group ---@field fields? string[] ---@field format? string ---@field directory? boolean ---@class trouble.Section.opts ---@field source string ---@field groups trouble.Group[] ---@field format string ---@field flatten? boolean when true, items with a natural hierarchy will be flattened ---@field events trouble.Event[] ---@field sort? trouble.Sort[] ---@field filter? trouble.Filter ---@field max_items? number ---@field params? table local M = {} ---@param spec trouble.Section.spec|string ---@return trouble.Section.opts function M.section(spec) local groups = type(spec.groups) == "string" and { spec.groups } or spec.groups ---@cast groups trouble.Group.spec[] local events = {} ---@type trouble.Event[] for _, e in ipairs(spec.events or {}) do if type(e) == "string" then local event, pattern = e:match("^(%w+)%s+(.*)$") event = event or e events[#events + 1] = { event = event, pattern = pattern } elseif type(e) == "table" and e.event then events[#events + 1] = e else error("invalid event: " .. vim.inspect(e)) end end local ret = { source = spec.source, groups = vim.tbl_map(M.group, groups or {}), sort = spec.sort and M.sort(spec.sort) or nil, filter = spec.filter, format = spec.format or "{filename} {pos}", events = events, flatten = spec.flatten, params = spec.params, } -- A title is just a group without fields if spec.title then table.insert(ret.groups, 1, { fields = {}, format = spec.title }) end return ret end ---@param action trouble.Action.spec ---@return trouble.Action function M.action(action) if type(action) == "string" then action = { action = action, desc = action:gsub("_", " ") } end if type(action) == "function" then action = { action = action } end if type(action.action) == "string" then local desc = action.action:gsub("_", " ") action.action = require("trouble.config.actions")[action.action] if type(action.action) == "table" then action = action.action end action.desc = action.desc or desc end ---@cast action trouble.Action return action end ---@param mode trouble.Mode ---@return trouble.Section.opts[] function M.sections(mode) local ret = {} ---@type trouble.Section.opts[] if mode.sections then for _, s in ipairs(mode.sections) do ret[#ret + 1] = M.section(Config.get(mode, { sections = false }, s) --[[@as trouble.Mode]]) end else local section = M.section(mode) section.max_items = section.max_items or mode.max_items ret[#ret + 1] = section end return ret end ---@param spec trouble.Sort.spec ---@return trouble.Sort[] function M.sort(spec) spec = type(spec) == "table" and Util.islist(spec) and spec or { spec } ---@cast spec (string|trouble.SorterFn|trouble.Filter.spec)[] local fields = {} ---@type trouble.Sort[] for f, field in ipairs(spec) do if type(field) == "function" then ---@cast field trouble.SorterFn fields[f] = { sorter = field } elseif type(field) == "table" and field.field then ---@cast field {field:string, desc?:boolean} fields[f] = field elseif type(field) == "table" then fields[f] = { filter = field } elseif type(field) == "string" then local desc = field:sub(1, 1) == "-" fields[f] = { field = desc and field:sub(2) or field, desc = desc and true or nil, } else error("invalid sort field: " .. vim.inspect(field)) end end return fields end ---@param spec trouble.Group.spec ---@return trouble.Group function M.group(spec) spec = type(spec) == "string" and { spec } or spec ---@cast spec string[]|{format?:string} ---@type trouble.Group local ret = { fields = {}, format = "" } for k, v in pairs(spec) do if type(k) == "number" then ---@cast v string ret.fields[#ret.fields + 1] = v elseif k == "format" then ---@cast v string ret[k] = v else error("invalid `group` key: " .. k) end end if vim.tbl_contains(ret.fields, "directory") then ret.directory = true ret.format = ret.format == "" and "{directory_icon} {directory} {count}" or ret.format if #ret.fields > 1 then error("group: cannot specify other fields with `directory`") end ret.fields = nil end if ret.format == "" then ret.format = table.concat( ---@param f string vim.tbl_map(function(f) return "{" .. f .. "}" end, ret.fields), " " ) end return ret end return M ================================================ FILE: lua/trouble/tree.lua ================================================ local Item = require("trouble.item") local Util = require("trouble.util") ---@class trouble.Node ---@field id string ---@field parent? trouble.Node ---@field item? trouble.Item ---@field index? table ---@field group? trouble.Group ---@field folded? boolean ---@field children? trouble.Node[] ---@field private _depth number ---@field private _count? number ---@field private _degree? number local M = {} ---@alias trouble.GroupFn fun(item: trouble.Item, parent: trouble.Node, group: trouble.Group): trouble.Node ---@param opts {id: string, item?: trouble.Item} function M.new(opts) local self = setmetatable(opts, { __index = M }) self.id = self.id or self.item and self.item.id or nil self.children = {} self.index = {} return self end function M:delete() local parent = self.parent if not parent then return end if parent.children then parent.children = vim.tbl_filter(function(c) return c ~= self end, parent.children) end if parent.index and self.id then parent.index[self.id] = nil end parent._count = nil parent._degree = nil if parent:count() == 0 then parent:delete() end end -- Max depth of the tree function M:degree() if not self._degree then self._degree = 0 for _, child in ipairs(self.children or {}) do self._degree = math.max(self._degree, child:degree()) end self._degree = self._degree + 1 end return self._degree end -- Depth of this node function M:depth() if not self._depth then self._depth = self.parent and (self.parent:depth() + 1) or 0 end return self._depth end -- Number of actual items in the tree -- This excludes internal group nodes function M:count() if not self._count then self._count = 0 for _, child in ipairs(self.children or {}) do self._count = self._count + child:count() end if not self.group and self.item then self._count = self._count + 1 end end return self._count end --- Gets all the items in the tree, recursively. ---@param ret trouble.Item[]? function M:flatten(ret) ret = ret or {} for _, child in ipairs(self.children or {}) do child:flatten(ret) end if not self.group and self.item then ret[#ret + 1] = self.item end return ret end ---@param idx number|string ---@return trouble.Node? function M:get(idx) return type(idx) == "number" and self.children[idx] or self.index[idx] end -- Source of the item of this node function M:source() return self.item and self.item.source end -- Width of the node (number of children) function M:width() return self.children and #self.children or 0 end -- Item of the parent node function M:parent_item() return self.parent and self.parent.item end function M:add(node) if node.id then if self.index[node.id] then Util.debug("node already exists:\n" .. node.id) node.id = node.id .. "_" end self.index[node.id] = node end node.parent = self table.insert(self.children, node) return node end function M:is_leaf() return self.children == nil or #self.children == 0 end ---@param other? trouble.Node function M:is(other) if not other then return false end if self == other then return true end if self.id ~= other.id then return false end if self.group ~= other.group then return false end if self.group then return true end assert(self.item, "missing item") if not other.item then return false end if self.item == other.item then return true end return self.item.id and (self.item.id == other.item.id) end --- Build a tree from a list of items and a section. ---@param items trouble.Item[] ---@param section trouble.Section.opts function M.build(items, section) local root = M.new({ id = "$root" }) local node_items = {} ---@type table -- create the group nodes for i, item in ipairs(items) do if section.max_items and i > section.max_items then break end local node = root for _, group in ipairs(section.groups) do local builder = M.builders[group.directory and "directory" or "fields"] if not builder then assert(builder, "unknown group type: " .. vim.inspect(group)) end node = builder.group(item, node, group) end node_items[node] = node_items[node] or {} table.insert(node_items[node], item) end -- add the items to the nodes. -- this will structure items by their parent node unless flatten is true for node, nitems in pairs(node_items) do M.add_items(node, nitems, { flatten = section.flatten }) end -- post process the tree for _, group in ipairs(section.groups) do local builder = M.builders[group.directory and "directory" or "fields"] if builder.post then root = builder.post(root) or root end end return root end --- This will add all the items to the root node, --- structured by their parent item, unless flatten is true. ---@param root trouble.Node ---@param items trouble.Item[] ---@param opts? {flatten?: boolean} function M.add_items(root, items, opts) opts = opts or {} local item_nodes = {} ---@type table for _, item in ipairs(items) do item_nodes[item] = M.new({ item = item }) end for _, item in ipairs(items) do local node = item_nodes[item] local parent_node = root if not opts.flatten then local parent = item.parent while parent do if item_nodes[parent] then parent_node = item_nodes[parent] break end parent = parent.parent end end parent_node:add(node) end end ---@alias trouble.Group.builder {group:trouble.GroupFn, post?:(fun(node: trouble.Node):trouble.Node?)} ---@type table<"directory"|"fields", trouble.Group.builder> M.builders = { fields = { group = function(item, parent, group) -- id is based on the parent id and the group fields local id = group.format if #group.fields > 0 then local values = {} ---@type string[] for i = 1, #group.fields do values[#values + 1] = tostring(item[group.fields[i]]) end id = table.concat(values, "|") end id = parent.id .. "#" .. id local child = parent:get(id) if not child then child = M.new({ id = id, item = item, group = group }) parent:add(child) end return child end, }, directory = { group = function(item, root, group) if not item.dirname then return root end local directory = "" local parent = root for _, part in Util.split(item.dirname, "/") do directory = directory .. part .. "/" local id = (root.id or "") .. "#" .. directory local child = parent:get(id) if not child then local dir = Item.new({ filename = directory, source = "fs", id = id, pos = { 1, 0 }, end_pos = { 1, 0 }, dirname = directory, item = { directory = directory, type = "directory" }, }) child = M.new({ id = id, item = dir, group = group }) parent:add(child) end parent = child end return parent end, post = function(root) ---@param node trouble.Node local function collapse(node) if node:source() == "fs" then if node:width() == 1 then local child = node.children[1] if child:source() == "fs" and child.item.type == "directory" then child.parent = node.parent return collapse(child) end end end for c, child in ipairs(node.children or {}) do node.children[c] = collapse(child) end return node end return collapse(root) end, }, } return M ================================================ FILE: lua/trouble/util.lua ================================================ local Config = require("trouble.config") local uv = vim.loop or vim.uv local M = {} ---@param fn function function M.noautocmd(fn) local ei = vim.o.eventignore vim.o.eventignore = "all" fn() vim.o.eventignore = ei end function M.is_win() return uv.os_uname().sysname:find("Windows") ~= nil end ---@param opts? {msg?: string} function M.try(fn, opts) local ok, err = pcall(fn) if not ok then local msg = opts and opts.msg or "Something went wrong:" msg = msg .. "\n" .. err M.error(msg) end end M.islist = vim.islist or vim.tbl_islist ---@alias NotifyOpts {level?: number, title?: string, once?: boolean, id?:string} ---@type table local notif_ids = {} ---@param msg string|string[] ---@param opts? NotifyOpts function M.notify(msg, opts) opts = opts or {} msg = type(msg) == "table" and table.concat(msg, "\n") or msg ---@cast msg string msg = vim.trim(msg) local ret = vim[opts.once and "notify_once" or "notify"](msg, opts.level, { replace = opts.id and notif_ids[opts.id] or nil, title = opts.title or "Trouble", on_open = function(win) vim.wo[win].conceallevel = 3 vim.wo[win].concealcursor = "n" vim.wo[win].spell = false vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown") end, }) if opts.id then notif_ids[opts.id] = ret end return ret end ---@param msg string|string[] ---@param opts? NotifyOpts function M.warn(msg, opts) M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.WARN }, opts or {})) end ---@param msg string|string[] ---@param opts? NotifyOpts function M.info(msg, opts) M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.INFO }, opts or {})) end ---@param msg string|string[] ---@param opts? NotifyOpts function M.error(msg, opts) M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.ERROR }, opts or {})) end ---@param msg string|string[] function M.debug(msg, ...) if Config.debug then if select("#", ...) > 0 then local obj = select("#", ...) == 1 and ... or { ... } msg = msg .. "\n```lua\n" .. vim.inspect(obj) .. "\n```" end M.notify(msg, { title = "Trouble (debug)" }) end end ---@param buf number ---@param row number ---@param ns number ---@param col number ---@param opts vim.api.keyset.set_extmark ---@param debug_info? any function M.set_extmark(buf, ns, row, col, opts, debug_info) local ok, err = pcall(vim.api.nvim_buf_set_extmark, buf, ns, row, col, opts) if not ok and Config.debug then M.debug("Failed to set extmark for preview", { info = debug_info, row = row, col = col, opts = opts, error = err }) end end ---@param str string ---@param sep? string function M.camel(str, sep) local parts = vim.split(str, "[%.%-%_]") ---@diagnostic disable-next-line: no-unknown return table.concat( vim.tbl_map(function(part) return part:sub(1, 1):upper() .. part:sub(2) end, parts), sep or "" ) end ---@param opts? {ms?: number, debounce?: boolean}|number ---@param default Throttle.opts ---@return Throttle.opts function M.throttle_opts(opts, default) opts = opts or {} if type(opts) == "number" then opts = { ms = opts } end return vim.tbl_deep_extend("force", default, opts) end ---@alias Throttle.opts {ms:number, debounce?:boolean, is_running?:fun():boolean} -- throttle with trailing execution ---@generic T: fun() ---@param fn T ---@param opts? Throttle.opts ---@return T function M.throttle(fn, opts) opts = opts or {} opts.ms = opts.ms or 20 local last = 0 local args = nil ---@type {n?:number}? local timer = assert(uv.new_timer()) local pending = false -- from run() till end of fn local running = false -- from run() till end of fn with is_running() local t = {} function t.run() pending = true running = true timer:stop() last = uv.now() vim.schedule(function() xpcall(function() if not args then return M.debug("Empty args. This should not happen.") end fn(vim.F.unpack_len(args)) args = nil end, function(err) vim.schedule(function() M.error(err) end) end) pending = false t.check() end) end function t.schedule() local now = uv.now() local delay = opts.debounce and opts.ms or (opts.ms - (now - last)) timer:stop() timer:start(math.max(0, delay), 0, t.run) end function t.check() if running and not pending and not (opts.is_running and opts.is_running()) then running = false if args then -- schedule if there are pending args t.schedule() end end end local check = assert(uv.new_check()) check:start(t.check) return function(...) args = vim.F.pack_len(...) if timer:is_active() and not opts.debounce then return elseif not running then t.schedule() end end end ---@param s string function M.lines(s) return M.split(s, "\n") end ---@param s string ---@param c? string function M.split(s, c) c = c or "\n" local pos = 1 local l = 0 return function() if pos == -1 then return end l = l + 1 local nl = s:find(c, pos, true) if not nl then local lastLine = s:sub(pos) pos = -1 return l, lastLine end local line = s:sub(pos, nl - 1) pos = nl + 1 return l, line end end --- Gets lines from a file or buffer ---@param opts {path?:string, buf?: number, rows?: number[]} ---@return table? function M.get_lines(opts) if opts.buf then local uri = vim.uri_from_bufnr(opts.buf) if uri:sub(1, 4) ~= "file" then vim.fn.bufload(opts.buf) end if vim.api.nvim_buf_is_loaded(opts.buf) then local lines = {} ---@type table if not opts.rows then return vim.api.nvim_buf_get_lines(opts.buf, 0, -1, false) end for _, row in ipairs(opts.rows) do lines[row] = vim.api.nvim_buf_get_lines(opts.buf, row - 1, row, false)[1] end return lines end opts.path = vim.uri_to_fname(uri) elseif not opts.path then error("buf or filename is required") end local fd = uv.fs_open(opts.path, "r", 438) if not fd then return end local stat = assert(uv.fs_fstat(fd)) if not (stat.type == "file" or stat.type == "link") then return end local data = assert(uv.fs_read(fd, stat.size, 0)) --[[@as string]] assert(uv.fs_close(fd)) local todo = 0 local ret = {} ---@type table for _, r in ipairs(opts.rows or {}) do if not ret[r] then todo = todo + 1 ret[r] = true end end for row, line in M.lines(data) do if not opts.rows or ret[row] then if line:sub(-1) == "\r" then line = line:sub(1, -2) end todo = todo - 1 ret[row] = line if todo == 0 then break end end end for i, r in pairs(ret) do if r == true then ret[i] = "" end end return ret end --- Creates a weak reference to an object. --- Calling the returned function will return the object if it has not been garbage collected. ---@generic T: table ---@param obj T ---@return T|fun():T? function M.weak(obj) local weak = { _obj = obj } ---@return table local function get() local ret = rawget(weak, "_obj") return ret == nil and error("Object has been garbage collected", 2) or ret end local mt = { __mode = "v", __call = function(t) return rawget(t, "_obj") end, __index = function(_, k) return get()[k] end, __newindex = function(_, k, v) get()[k] = v end, __pairs = function() return pairs(get()) end, } return setmetatable(weak, mt) end return M ================================================ FILE: lua/trouble/view/indent.lua ================================================ local Config = require("trouble.config") local Util = require("trouble.util") ---@alias trouble.Indent.type "top"|"middle"|"last"|"fold_open"|"fold_closed"|"ws" ---@alias trouble.Indent.symbols table ---@class SymbolSegment: TextSegment ---@field type trouble.Indent.type ---@class trouble.Indent: {[number]: SymbolSegment} ---@field symbols table local M = {} M.__index = M ---@param symbols? trouble.Indent.symbols function M.new(symbols) local self = setmetatable({}, M) symbols = vim.tbl_deep_extend("force", Config.icons.indent, symbols or {}) self.symbols = {} for k, v in pairs(symbols) do local symbol = v self.symbols[k] = type(symbol) == "string" and { str = symbol } or { str = symbol[1], hl = symbol[2] } self.symbols[k].type = k self.symbols[k].hl = self.symbols[k].hl or ("TroubleIndent" .. Util.camel(k)) end return self end ---@return number ---@param opts? {display:boolean} function M:width(opts) local ret = 0 for _, segment in ipairs(self) do ret = ret + (opts and opts.display and vim.fn.strdisplaywidth(segment.str) or #segment.str) end return ret end -- Returns a new indent with all the symbols replaced with whitespace function M:indent() local new = setmetatable({}, M) for k, v in pairs(self) do new[k] = type(k) == "number" and self.symbols.ws or v end return new end function M:clone() local new = setmetatable({}, M) for k, v in pairs(self) do new[k] = v end return new end ---@param other trouble.Indent.type function M:add(other) table.insert(self, self.symbols[other]) return self end ---@return SymbolSegment? function M:del() return table.remove(self) end ---@param other trouble.Indent.type function M:extend(other) return self:clone():add(other) end function M:multi_line() local last = self:del() if last then self:add(last.type == "middle" and "top" or last.type == "last" and "ws" or last.type) end return self end return M ================================================ FILE: lua/trouble/view/init.lua ================================================ local Format = require("trouble.format") local Main = require("trouble.view.main") local Preview = require("trouble.view.preview") local Promise = require("trouble.promise") local Render = require("trouble.view.render") local Section = require("trouble.view.section") local Spec = require("trouble.spec") local Text = require("trouble.view.text") local Util = require("trouble.util") local Window = require("trouble.view.window") ---@class trouble.View ---@field win trouble.Window ---@field preview_win? trouble.Window ---@field opts trouble.Mode ---@field sections trouble.Section[] ---@field renderer trouble.Render ---@field first_render trouble.Promise ---@field first_update trouble.Promise ---@field moving uv_timer_t ---@field clicked uv_timer_t ---@field state table ---@field _filters table ---@field private _main? trouble.Main local M = {} M.__index = M local _idx = 0 ---@type table M._views = setmetatable({}, { __mode = "k" }) ---@type trouble.View[] M._auto = {} ---@type table M._last = {} M.MOVING_DELAY = 4000 local uv = vim.loop or vim.uv ---@param opts trouble.Mode function M.new(opts) local self = setmetatable({}, M) _idx = _idx + 1 M._views[self] = _idx self.state = {} self.opts = opts or {} self._filters = {} self.first_render = Promise.new(function() end) self.first_update = Promise.new(function() end) self.opts.win = vim.tbl_deep_extend("force", self.opts.win or {}, Window.FOLDS) self.opts.win.on_mount = function() self:on_mount() end self.opts.win.on_close = function() if not self.opts.auto_open then for _, section in ipairs(self.sections) do section:stop() end end end self.sections = {} for _, s in ipairs(Spec.sections(self.opts)) do local section = Section.new(s, self.opts) section.on_update = function() self:update() end table.insert(self.sections, section) end self.win = Window.new(self.opts.win) self.opts.win = self.win.opts self.preview_win = Window.new(self.opts.preview) or nil self.renderer = Render.new(self.opts, { padding = vim.tbl_get(self.opts.win, "padding", "left") or 0, multiline = self.opts.multiline, }) self.update = Util.throttle(M.update, Util.throttle_opts(self.opts.throttle.update, { ms = 10 })) self.render = Util.throttle(M.render, Util.throttle_opts(self.opts.throttle.render, { ms = 10 })) self.follow = Util.throttle(M.follow, Util.throttle_opts(self.opts.throttle.follow, { ms = 100 })) if self.opts.auto_open then -- add to a table, so that the view doesn't gc table.insert(M._auto, self) self:listen() self:refresh() end self.moving = uv.new_timer() self.clicked = uv.new_timer() return self end ---@alias trouble.View.filter {debug?: boolean, open?:boolean, mode?: string} ---@param filter? trouble.View.filter function M.get(filter) filter = filter or {} ---@type {idx:number, mode?: string, view: trouble.View, is_open: boolean}[] local ret = {} for view, idx in pairs(M._views) do local is_open = view.win:valid() local opening = view.first_update:is_pending() local ok = is_open or view.opts.auto_open or opening ok = ok and (not filter.mode or filter.mode == view.opts.mode) ok = ok and (not filter.open or is_open or opening) if ok then ret[#ret + 1] = { idx = idx, mode = view.opts.mode, view = not filter.debug and view or {}, is_open = is_open, } end end table.sort(ret, function(a, b) return a.idx < b.idx end) return ret end function M:on_mount() vim.w[self.win.win].trouble = { mode = self.opts.mode, type = self.opts.win.type, relative = self.opts.win.relative, position = self.opts.win.position, } self:listen() self.win:on("WinLeave", function() if self.opts.preview.type == "main" and self.clicked:is_active() and Preview.is_open() then local main = self.preview_win.opts.win local preview = self.preview_win.win if main and preview and vim.api.nvim_win_is_valid(main) and vim.api.nvim_win_is_valid(preview) then local view = vim.api.nvim_win_call(preview, vim.fn.winsaveview) vim.api.nvim_win_call(main, function() vim.fn.winrestview(view) end) vim.api.nvim_set_current_win(main) end end Preview.close() end) local _self = Util.weak(self) local preview = Util.throttle( M.preview, Util.throttle_opts(self.opts.throttle.preview, { ms = 100, debounce = true, }) ) self.win:on("CursorMoved", function() local this = _self() if not this then return true end M._last[self.opts.mode or ""] = self:at() if this.opts.auto_preview then local loc = this:at() if loc and loc.item then preview(this, loc.item) end end end) if self.opts.follow then -- tracking of the current item self.win:on("CursorMoved", function() local this = _self() if not this then return true end if this.win:valid() then this:follow() end end, { buffer = false }) end self.win:on("OptionSet", function() local this = _self() if not this then return true end if this.win:valid() then local foldlevel = vim.wo[this.win.win].foldlevel if foldlevel ~= this.renderer.foldlevel then this:fold_level({ level = foldlevel }) end end end, { pattern = "foldlevel", buffer = false }) for k, v in pairs(self.opts.keys) do if v ~= false then self:map(k, v) end end self.win:map("", function() self.clicked:start(100, 0, function() end) return "" end, { remap = false, expr = true }) end ---@param node? trouble.Node function M:delete(node) local selection = node and { node } or self:selection() if #selection == 0 then return end for _, n in ipairs(selection) do n:delete() end self.opts.auto_refresh = false self:render() end ---@param node? trouble.Node ---@param opts? trouble.Render.fold_opts function M:fold(node, opts) node = node or self:at().node if node then self.renderer:fold(node, opts) self:render() end end ---@param opts {level?:number, add?:number} function M:fold_level(opts) self.renderer:fold_level(opts) self:render() end ---@param item? trouble.Item ---@param opts? {split?: boolean, vsplit?:boolean} function M:jump(item, opts) opts = opts or {} item = item or self:at().item vim.schedule(function() Preview.close() end) if not item then return Util.warn("No item to jump to") end if not (item.buf or item.filename) then Util.warn("No buffer or filename for item") return end item.buf = item.buf or vim.fn.bufadd(item.filename) if not vim.api.nvim_buf_is_loaded(item.buf) then vim.fn.bufload(item.buf) end if not vim.bo[item.buf].buflisted then vim.bo[item.buf].buflisted = true end local main = self:main() local win = main and main.win or 0 vim.api.nvim_win_call(win, function() -- save position in jump list vim.cmd("normal! m'") end) if opts.split then vim.api.nvim_win_call(win, function() vim.cmd("split") win = vim.api.nvim_get_current_win() end) elseif opts.vsplit then vim.api.nvim_win_call(win, function() vim.cmd("vsplit") win = vim.api.nvim_get_current_win() end) end vim.api.nvim_win_set_buf(win, item.buf) -- order of the below seems important with splitkeep=screen vim.api.nvim_set_current_win(win) vim.api.nvim_win_set_cursor(win, item.pos) vim.api.nvim_win_call(win, function() vim.cmd("norm! zzzv") end) return item end function M:wait(fn) self.first_render:next(fn) end ---@param item? trouble.Item function M:preview(item) item = item or self:at().item if not item then return Util.warn("No item to preview") end return Preview.open(self, item, { scratch = self.opts.preview.scratch }) end function M:main() self._main = Main.get(self.opts.pinned and self._main or nil) return self._main end function M:goto_main() local main = self:main() if main then vim.api.nvim_set_current_win(main.win) end end function M:listen() self:main() for _, section in ipairs(self.sections) do section:listen() end end ---@param cursor? number[] function M:at(cursor) if not vim.api.nvim_buf_is_valid(self.win.buf) then return {} end cursor = cursor or vim.api.nvim_win_get_cursor(self.win.win) return self.renderer:at(cursor[1]) end function M:selection() if not vim.fn.mode():lower():find("v") then local ret = self:at() return ret.node and { ret.node } or {} end vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "x", false) local from = vim.api.nvim_buf_get_mark(self.win.buf, "<")[1] local to = vim.api.nvim_buf_get_mark(self.win.buf, ">")[1] ---@type trouble.Node[] local ret = {} for row = from, to do local node = self.renderer:at(row).node if not vim.tbl_contains(ret, node) then ret[#ret + 1] = node end end return ret end ---@param key string ---@param action trouble.Action.spec function M:map(key, action) action = Spec.action(action) local _self = Util.weak(self) self.win:map(key, function() local this = _self() if this then this:action(action) end end, { desc = action.desc, mode = action.mode }) end ---@param opts? {idx?: number, up?:number, down?:number, jump?:boolean} function M:move(opts) -- start the moving timer. Will stop any previous timers, -- so this acts as a debounce. -- This is needed to prevent `follow` from being called self.moving:start(M.MOVING_DELAY, 0, function() end) opts = opts or {} local cursor = vim.api.nvim_win_get_cursor(self.win.win) local from = 1 local to = vim.api.nvim_buf_line_count(self.win.buf) local todo = opts.idx or opts.up or opts.down or 0 if opts.idx and opts.idx < 0 then from, to = to, 1 todo = math.abs(todo) elseif opts.down then from = cursor[1] + 1 elseif opts.up then from = cursor[1] - 1 to = 1 end for row = from, to, from > to and -1 or 1 do local info = self.renderer:at(row) if info.item and info.first_line then todo = todo - 1 if todo == 0 then vim.api.nvim_win_set_cursor(self.win.win, { row, cursor[2] }) if opts.jump then self:jump(info.item) end break end end end end ---@param action trouble.Action.spec ---@param opts? table function M:action(action, opts) action = Spec.action(action) self:wait(function() local at = self:at() or {} action.action(self, { item = at.item, node = at.node, opts = type(opts) == "table" and opts or {}, }) end) end ---@param opts? {update?: boolean, opening?: boolean} function M:refresh(opts) opts = opts or {} if not (opts.opening or self.win:valid() or self.opts.auto_open) then return end ---@param section trouble.Section return Promise.all(vim.tbl_map(function(section) return section:refresh(opts) end, self.sections)) end function M:help() local text = Text.new({ padding = 1 }) text:nl():append("# Help ", "Title"):nl() text:append("Press ", "Comment"):append("", "Special"):append(" to close", "Comment"):nl():nl() text:append("# Keymaps ", "Title"):nl():nl() ---@type string[] local keys = vim.tbl_keys(self.win.keys) table.sort(keys, function(a, b) local lowa = string.lower(a) local lowb = string.lower(b) if lowa == lowb then return a > b -- Preserve original order for equal strings else return lowa < lowb end end) for _, key in ipairs(keys) do local desc = self.win.keys[key] text:append(" - ", "@punctuation.special.markdown") text:append(key, "Special"):append(" "):append(desc):nl() end text:trim() local win = Window.new({ type = "float", size = { width = text:width(), height = text:height() }, border = "rounded", wo = { cursorline = false }, }) win:open():focus() text:render(win.buf) vim.bo[win.buf].modifiable = false win:map("", win.close) win:map("q", win.close) end function M:is_open() return self.win:valid() end function M:open() if self.win:valid() then return self end self :refresh({ update = false, opening = true }) :next(function() local count = self:count() if count == 0 then if not self.opts.open_no_results then if self.opts.warn_no_results then Util.warn({ "No results for **" .. self.opts.mode .. "**", "Buffer: " .. vim.api.nvim_buf_get_name(self:main().buf), }) end return end elseif count == 1 and self.opts.auto_jump then self:jump(self:flatten()[1]) return self:close() end self.win:open() self:update() end) :next(self.first_update.resolve) return self end function M:close() if vim.api.nvim_get_current_win() == self.win.win then self:goto_main() end Preview.close() self.win:close() return self end function M:count() local count = 0 for _, section in ipairs(self.sections) do if section.node then count = count + section.node:count() end end return count end function M:flatten() local ret = {} for _, section in ipairs(self.sections) do section.node:flatten(ret) end return ret end -- called when results are updated function M:update() local is_open = self.win:valid() local count = self:count() if count == 0 and is_open and self.opts.auto_close then return self:close() end if self.opts.auto_open and not is_open and count > 0 then self.win:open() is_open = true end if not is_open then return end self:render() end ---@param filter trouble.Filter ---@param opts? trouble.ViewFilter.opts function M:filter(filter, opts) opts = opts or {} ---@type trouble.ViewFilter local view_filter = vim.tbl_deep_extend("force", { id = vim.inspect(filter), filter = filter, data = opts.data, template = opts.template, }, opts) if opts.del or (opts.toggle and self._filters[view_filter.id]) then self._filters[view_filter.id] = nil else self._filters[view_filter.id] = view_filter end local filters = vim.tbl_count(self._filters) > 0 and vim.tbl_map(function(f) return f.filter end, vim.tbl_values(self._filters)) or nil for _, section in ipairs(self.sections) do section.filter = filters end self:refresh() end function M:header() local ret = {} ---@type trouble.Format[][] for _, filter in pairs(self._filters) do local data = vim.tbl_deep_extend("force", { filter = filter.filter, }, type(filter.filter) == "table" and filter.filter or {}, filter.data or {}) local template = filter.template or "{hl:Title}Filter:{hl} {filter}" ret[#ret + 1] = self:format(template, data) end return ret end ---@param id string function M:get_filter(id) return self._filters[id] end ---@param template string ---@param data table function M:format(template, data) data.source = "view" assert(self.opts, "opts is nil") return Format.format(template, { item = data, opts = self.opts }) end -- render the results function M:render() if not self.win:valid() then return end local loc = self:at() local restore_loc = self.opts.restore and self.first_render:is_pending() and M._last[self.opts.mode or ""] if restore_loc then loc = restore_loc end -- render sections self.renderer:clear() self.renderer:nl() for _ = 1, vim.tbl_get(self.opts.win, "padding", "top") or 0 do self.renderer:nl() end local header = self:header() for _, h in ipairs(header) do for _, ff in ipairs(h) do self.renderer:append(ff.text, ff) end self.renderer:nl() end self.renderer:sections(self.sections) self.renderer:trim() -- calculate initial folds if self.renderer.foldlevel == nil then local level = vim.wo[self.win.win].foldlevel if level < self.renderer.max_depth then self.renderer:fold_level({ level = level }) -- render again to apply folds return self:render() end end vim.schedule(function() self.first_render.resolve() end) -- render extmarks and restore window view local view = vim.api.nvim_win_call(self.win.win, vim.fn.winsaveview) self.renderer:render(self.win.buf) vim.api.nvim_win_call(self.win.win, function() vim.fn.winrestview(view) end) if self.opts.follow and self:follow() then return end -- when window is at top, dont move cursor if not restore_loc and view.topline == 1 then return end -- fast exit when cursor is already on the right item local new_loc = self:at() if new_loc.node and loc.node and new_loc.node.id == loc.node.id then return end -- Move cursor to the same item local cursor = vim.api.nvim_win_get_cursor(self.win.win) local item_row ---@type number? if loc.node then for row, l in pairs(self.renderer._locations) do if loc.node:is(l.node) then item_row = row break end end end -- Move cursor to the actual item when found if item_row and item_row ~= cursor[1] then vim.api.nvim_win_set_cursor(self.win.win, { item_row, cursor[2] }) return end end -- When not in the trouble window, try to show the range function M:follow() if not self.win:valid() then -- trouble is closed return end if self.moving:is_active() then -- dont follow when moving return end local current_win = vim.api.nvim_get_current_win() if current_win == self.win.win then -- inside the trouble window return end local Filter = require("trouble.filter") local ctx = { opts = self.opts, main = self:main() } local fname = vim.api.nvim_buf_get_name(ctx.main.buf or 0) local loc = self:at() -- check if we're already in the file group local in_group = loc.node and loc.node.item and loc.node.item.filename == fname ---@type number[]|nil local cursor_item = nil local cursor_group = cursor_item for row, l in pairs(self.renderer._locations) do -- only return the group if we're not yet in the group -- and the group's filename matches the current file local is_group = not in_group and l.node and l.node.group and l.node.item and l.node.item.filename == fname if is_group then cursor_group = { row, 1 } end -- prefer a full match local is_current = l.item and Filter.is(l.item, { range = true }, ctx) if is_current then cursor_item = { row, 1 } end end local cursor = cursor_item or cursor_group if cursor then -- make sure the cursorline is visible vim.wo[self.win.win].cursorline = true vim.api.nvim_win_set_cursor(self.win.win, cursor) return true end end return M ================================================ FILE: lua/trouble/view/main.lua ================================================ local Preview = require("trouble.view.preview") ---@class trouble.Main ---@field win number ---@field buf number ---@field filename string ---@field cursor trouble.Pos local M = {} M._main = nil ---@type trouble.Main? function M.setup() local group = vim.api.nvim_create_augroup("trouble.main", { clear = true }) vim.api.nvim_create_autocmd({ "BufEnter", "WinEnter" }, { group = group, callback = function() local win = vim.api.nvim_get_current_win() local buf = vim.api.nvim_win_get_buf(win) if M._valid(win, buf) then M.set(M._info(win)) end end, }) M.set(M._find()) end ---@param main trouble.Main function M.set(main) M._main = main end function M._valid(win, buf) if not win or not buf then return false end if not vim.api.nvim_win_is_valid(win) or not vim.api.nvim_buf_is_valid(buf) then return false end if vim.api.nvim_win_get_buf(win) ~= buf then return false end if Preview.is_win(win) or vim.w[win].trouble then return false end if vim.api.nvim_win_get_config(win).relative ~= "" then return false end if vim.bo[buf].buftype ~= "" then return false end return true end ---@private function M._find() local wins = vim.api.nvim_list_wins() table.insert(wins, 1, vim.api.nvim_get_current_win()) for _, win in ipairs(wins) do local b = vim.api.nvim_win_get_buf(win) if M._valid(win, b) then return M._info(win) end end end ---@private ---@return trouble.Main function M._info(win) local b = vim.api.nvim_win_get_buf(win) return { win = win, buf = b, filename = vim.fs.normalize(vim.api.nvim_buf_get_name(b)), cursor = vim.api.nvim_win_get_cursor(win), } end ---@param main? trouble.Main ---@return trouble.Main? function M.get(main) main = main or M._main local valid = main and M._valid(main.win, main.buf) if not valid then main = M._find() end -- Always return a main window even if it is not valid main = main or M._info(vim.api.nvim_get_current_win()) main.cursor = vim.api.nvim_win_get_cursor(main.win) return main end return M ================================================ FILE: lua/trouble/view/preview.lua ================================================ local Render = require("trouble.view.render") local Util = require("trouble.util") ---@alias trouble.Preview {item:trouble.Item, win:number, buf: number, close:fun()} local M = {} M.preview = nil ---@type trouble.Preview? function M.is_open() return M.preview ~= nil end function M.is_win(win) return M.preview and M.preview.win == win end function M.item() return M.preview and M.preview.item end function M.close() local preview = M.preview M.preview = nil if not preview then return end Render.reset(preview.buf) preview.close() end --- Create a preview buffer for an item. --- If the item has a loaded buffer, use that, --- otherwise create a new buffer. ---@param item trouble.Item ---@param opts? {scratch?:boolean} function M.create(item, opts) opts = opts or {} local buf = item.buf or vim.fn.bufnr(item.filename) if item.filename and vim.fn.isdirectory(item.filename) == 1 then return end -- create a scratch preview buffer when needed if not (buf and vim.api.nvim_buf_is_loaded(buf)) then if opts.scratch then buf = vim.api.nvim_create_buf(false, true) vim.bo[buf].bufhidden = "wipe" vim.bo[buf].buftype = "nofile" local lines = Util.get_lines({ path = item.filename, buf = item.buf }) if not lines then return end vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) local ft = item:get_ft(buf) if ft then local lang = vim.treesitter.language.get_lang(ft) if not pcall(vim.treesitter.start, buf, lang) then vim.bo[buf].syntax = ft end end else item.buf = vim.fn.bufadd(item.filename) buf = item.buf if not vim.api.nvim_buf_is_loaded(item.buf) then vim.fn.bufload(item.buf) end if not vim.bo[item.buf].buflisted then vim.bo[item.buf].buflisted = true end end end return buf end ---@param view trouble.View ---@param item trouble.Item ---@param opts? {scratch?:boolean} function M.open(view, item, opts) if M.item() == item then return end if M.preview and M.preview.item.filename ~= item.filename then M.close() end if not M.preview then local buf = M.create(item, opts) if not buf then return end M.preview = M.preview_win(buf, view) M.preview.buf = buf end M.preview.item = item Render.reset(M.preview.buf) -- make sure we highlight at least one character local end_pos = { item.end_pos[1], item.end_pos[2] } if end_pos[1] == item.pos[1] and end_pos[2] == item.pos[2] then end_pos[2] = end_pos[2] + 1 end -- highlight the line Util.set_extmark(M.preview.buf, Render.ns, item.pos[1] - 1, 0, { end_row = end_pos[1], hl_group = "CursorLine", hl_eol = true, strict = false, priority = 150, }) -- highlight the range Util.set_extmark(M.preview.buf, Render.ns, item.pos[1] - 1, item.pos[2], { end_row = end_pos[1] - 1, end_col = end_pos[2], hl_group = "TroublePreview", strict = false, priority = 160, }) local _, source = require("trouble.sources").get(item.source) if source and source.preview then source.preview(item, M.preview) end -- no autocmds should be triggered. So LSP's etc won't try to attach in the preview Util.noautocmd(function() if pcall(vim.api.nvim_win_set_cursor, M.preview.win, item.pos) then vim.api.nvim_win_call(M.preview.win, function() vim.cmd("norm! zzzv") end) end end) return item end ---@param buf number ---@param view trouble.View function M.preview_win(buf, view) if view.opts.preview.type == "main" then local main = view:main() if not main then Util.debug("No main window") return end view.preview_win.opts.win = main.win else view.preview_win.opts.win = view.win.win end view.preview_win:open() Util.noautocmd(function() view.preview_win:set_buf(buf) view.preview_win:set_options("win") vim.w[view.preview_win.win].trouble_preview = true end) return { win = view.preview_win.win, close = function() view.preview_win:close() end, } end return M ================================================ FILE: lua/trouble/view/render.lua ================================================ local Cache = require("trouble.cache") local Format = require("trouble.format") local Indent = require("trouble.view.indent") local Text = require("trouble.view.text") local Util = require("trouble.util") ---@alias trouble.Render.Location {item?: trouble.Item, node?: trouble.Node, first_line?:boolean} ---@class trouble.Render: trouble.Text ---@field _locations trouble.Render.Location[] Maps line numbers to items. ---@field _folded table ---@field root_nodes trouble.Node[] ---@field foldlevel? number ---@field foldenable boolean ---@field max_depth number ---@field view trouble.View ---@field opts trouble.Config local M = setmetatable({}, Text) M.__index = M ---@class trouble.Render.opts: trouble.Text.opts ---@field indent? trouble.Indent.symbols ---@field formatters? table ---@param text_opts trouble.Text.opts ---@param opts trouble.Config function M.new(opts, text_opts) local text = Text.new(text_opts) ---@type trouble.Render ---@diagnostic disable-next-line: assign-type-mismatch local self = setmetatable(text, M) self.opts = opts self._folded = {} self.foldenable = true self:clear() return self end ---@alias trouble.Render.fold_opts {action?: "open"|"close"|"toggle", recursive?: boolean} ---@param node trouble.Node ---@param opts? trouble.Render.fold_opts function M:fold(node, opts) self.foldenable = true opts = opts or {} local action = opts.action or "toggle" if node:is_leaf() and node.parent then node = node.parent end local id = node.id if action == "toggle" then if self._folded[id] then action = "open" else action = "close" end end local stack = { node } while #stack > 0 do local n = table.remove(stack) --[[@as trouble.Node]] if not n:is_leaf() then if action == "open" then self._folded[n.id] = nil local parent = n.parent while parent do self._folded[parent.id] = nil parent = parent.parent end else self._folded[n.id] = true end if opts.recursive then for _, c in ipairs(n.children or {}) do table.insert(stack, c) end end end end end ---@param opts {level?:number, add?:number} function M:fold_level(opts) self.foldenable = true self.foldlevel = self.foldlevel or (self.max_depth - 1) or 0 if opts.level then self.foldlevel = opts.level end if opts.add then self.foldlevel = self.foldlevel + opts.add end self.foldlevel = math.min(self.max_depth - 1, self.foldlevel) self.foldlevel = math.max(0, self.foldlevel) local stack = {} for _, node in ipairs(self.root_nodes) do table.insert(stack, node) end while #stack > 0 do ---@type trouble.Node local node = table.remove(stack) if not node:is_leaf() then if node:depth() > self.foldlevel then self._folded[node.id] = true else self._folded[node.id] = nil end for _, c in ipairs(node.children or {}) do table.insert(stack, c) end end end end function M:clear() Cache.langs:clear() self.max_depth = 0 self._lines = {} self.ts_regions = {} self._locations = {} self.root_nodes = {} end ---@param sections trouble.Section[] function M:sections(sections) for _, section in ipairs(sections) do local nodes = section.node and section.node.children if nodes and #nodes > 0 then self:section(section.section, nodes) end end end ---@param section trouble.Section.opts ---@param nodes trouble.Node[] function M:section(section, nodes) for n, node in ipairs(nodes) do table.insert(self.root_nodes, node) self.max_depth = math.max(self.max_depth, node:degree()) self:node(node, section, Indent.new(self.opts.icons.indent), n == #nodes) end end function M:is_folded(node) return self.foldenable and self._folded[node.id] end ---@param node trouble.Node ---@param section trouble.Section.opts ---@param indent trouble.Indent ---@param is_last boolean function M:node(node, section, indent, is_last) node.folded = self:is_folded(node) if node.item then ---@type trouble.Indent.type local symbol = self:is_folded(node) and "fold_closed" or node:depth() == 1 and "fold_open" or is_last and "last" or "middle" symbol = node:depth() == 1 and node:is_leaf() and "ws" or symbol indent:add(symbol) -- self:item(node.item, node, section.groups[node.depth].format, true, indent) self:item(node, section, indent) indent:del() end if self:is_folded(node) then return -- don't render children end indent:add((is_last or node:depth() == 1) and "ws" or "top") for i, n in ipairs(node.children or {}) do self:node(n, section, indent, i == #node.children) end indent:del() end --- Returns the item and node at the given row. --- For a group, only the node is returned. --- To get the group item used for formatting, use `node.items[1]`. ---@param row number function M:at(row) return self._locations[row] or {} end ---@param node trouble.Node ---@param section trouble.Section.opts ---@param indent trouble.Indent function M:item(node, section, indent) local item = node.item if not item then return end local is_group = node.group ~= nil local row = self:row() local format_string = node.group and node.group.format or section.format local cache_key = "render:" .. node:depth() .. format_string ---@type TextSegment[]? local segments = not is_group and item.cache[cache_key] if not self.opts.indent_guides then indent = indent:indent() end if self._opts.indent ~= false then self:append(indent) end if segments then self:append(segments) else local format = Format.format(format_string, { item = item, node = node, opts = self.opts }) indent:multi_line() for _, ff in ipairs(format) do local text = self._opts.multiline and ff.text or ff.text:gsub("[\n\r]+", " ") local offset ---@type number? start column of the first line local first ---@type string? first line if ff.hl == "ts" then local lang = item:get_lang() if lang then ff.hl = "ts." .. lang else ff.hl = nil end end for l, line in Util.lines(text) do if l == 1 then first = line else -- PERF: most items are single line, so do heavy lifting only when more than one line offset = offset or (self:col({ display = true }) - vim.fn.strdisplaywidth(first or "")) self:nl() self:append(indent) local indent_width = indent:width({ display = true }) -- align to item column if offset > indent_width then self:append((" "):rep(offset - indent_width)) end end self:append(line, { hl = ff.hl, line = l, }) end end -- NOTE: -- * don't cache groups, since they can contain aggregates. -- * don't cache multi-line items -- * don't cache the indent part of the line if not is_group and self:row() == row then item.cache[cache_key] = vim.list_slice(self._lines[#self._lines], self._opts.indent == false and 1 or (#indent + 1)) end end for r = row, self:row() do self._locations[r] = { first_line = r == row, item = not is_group and item or nil, node = node, } end self:nl() end return M ================================================ FILE: lua/trouble/view/section.lua ================================================ local Filter = require("trouble.filter") local Main = require("trouble.view.main") local Preview = require("trouble.view.preview") local Promise = require("trouble.promise") local Sort = require("trouble.sort") local Sources = require("trouble.sources") local Tree = require("trouble.tree") local Util = require("trouble.util") ---@class trouble.Section ---@field section trouble.Section.opts ---@field finder trouble.Source.get ---@field private _main? trouble.Main ---@field opts trouble.Config ---@field items trouble.Item[] ---@field node? trouble.Node ---@field fetching boolean ---@field filter? trouble.Filter ---@field id number ---@field on_update? fun(self: trouble.Section) ---@field _refresh fun() local M = {} M._id = 0 ---@param section trouble.Section.opts ---@param opts trouble.Config function M.new(section, opts) local self = setmetatable({}, { __index = M }) self.section = section self.opts = opts M._id = M._id + 1 self.id = M._id self.finder = Sources.get(section.source) self.items = {} self:main() local _self = Util.weak(self) self._refresh = Util.throttle( M.refresh, Util.throttle_opts(opts.throttle.refresh, { ms = 20, is_running = function() local this = _self() return this and this.fetching end, }) ) return self end ---@param opts? {update?: boolean} function M:refresh(opts) -- if self.section.source ~= "lsp.document_symbols" then -- Util.debug("Section Refresh", { -- id = self.id, -- source = self.section.source, -- }) -- end self.fetching = true return Promise.new(function(resolve) self:main_call(function(main) local ctx = { opts = self.opts, main = main } self.finder(function(items) items = Filter.filter(items, self.section.filter, ctx) if self.filter then items = Filter.filter(items, self.filter, ctx) end items = Sort.sort(items, self.section.sort, ctx) self.items = items self.node = Tree.build(items, self.section) if not (opts and opts.update == false) then self:update() end resolve(self) end, ctx) end) end) :catch(Util.error) :timeout(2000) :catch(function() end) :finally(function() self.fetching = false end) end ---@param fn fun(main: trouble.Main) function M:main_call(fn) local main = self:main() if not main then return end if Preview.is_win(main.win) then return end local current = { win = vim.api.nvim_get_current_win(), buf = vim.api.nvim_get_current_buf(), cursor = vim.api.nvim_win_get_cursor(0), } if vim.deep_equal(current, main) then fn(main) elseif vim.api.nvim_win_get_buf(main.win) == main.buf then vim.api.nvim_win_call(main.win, function() fn(main) end) else Util.debug({ "Main window switched buffers", "Main: " .. vim.api.nvim_buf_get_name(main.buf), "Current: " .. vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(main.win)), }) end end function M:update() if self.on_update then self:on_update() end end function M:main() self._main = Main.get(self.opts.pinned and self._main or nil) return self._main end function M:augroup() return "trouble.section." .. self.section.source .. "." .. self.id end function M:stop() pcall(vim.api.nvim_del_augroup_by_name, self:augroup()) end function M:listen() local _self = Util.weak(self) local group = vim.api.nvim_create_augroup(self:augroup(), { clear = true }) for _, event in ipairs(self.section.events or {}) do vim.api.nvim_create_autocmd(event.event, { group = group, pattern = event.pattern, callback = function(e) local this = _self() if not this then return true end if not this.opts.auto_refresh then return end if not vim.api.nvim_buf_is_valid(e.buf) then return end if event.main then local main = this:main() if main and main.buf ~= e.buf then return end end if e.event == "BufEnter" and vim.bo[e.buf].buftype ~= "" then return end this:_refresh() end, }) end end return M ================================================ FILE: lua/trouble/view/text.lua ================================================ local Util = require("trouble.util") ---@class TextSegment ---@field str string Text ---@field hl? string Extmark hl group ---@field ts? string TreeSitter language ---@field line? number line number in a multiline segment ---@field width? number ---@alias Extmark {hl_group?:string, col?:number, row?:number, end_col?:number} ---@class trouble.Text.opts ---@field padding? number ---@field multiline? boolean ---@field indent? boolean ---@class trouble.Text ---@field _lines TextSegment[][] ---@field _col number ---@field _indents string[] ---@field _opts trouble.Text.opts local M = {} M.__index = M M.ns = vim.api.nvim_create_namespace("trouble.text") function M.reset(buf) if vim.api.nvim_buf_is_valid(buf) then vim.api.nvim_buf_clear_namespace(buf, M.ns, 0, -1) end end ---@param opts? trouble.Text.opts function M.new(opts) local self = setmetatable({}, M) self._lines = {} self._col = 0 self._opts = opts or {} self._opts.padding = self._opts.padding or 0 self._indents = {} for i = 0, 100, 1 do self._indents[i] = (" "):rep(i) end return self end function M:height() return #self._lines end function M:width() local width = 0 for _, line in ipairs(self._lines) do local w = 0 for _, segment in ipairs(line) do w = w + vim.fn.strdisplaywidth(segment.str) end width = math.max(width, w) end return width + ((self._opts.padding or 0) * 2) end ---@param text string|TextSegment[] ---@param opts? string|{ts?:string, hl?:string, line?:number} function M:append(text, opts) opts = opts or {} if #self._lines == 0 then self:nl() end if type(text) == "table" then for _, s in ipairs(text) do s.width = s.width or #s.str self._col = self._col + s.width table.insert(self._lines[#self._lines], s) end return self end opts = type(opts) == "string" and { hl = opts } or opts if opts.hl == "md" then opts.ts = "markdown" elseif opts.hl and opts.hl:sub(1, 3) == "ts." then opts.ts = opts.hl:sub(4) end for l, line in Util.lines(text) do local width = #line self._col = self._col + width table.insert(self._lines[#self._lines], { str = line, width = width, hl = opts.hl, ts = opts.ts, line = opts.line or l, }) end return self end function M:nl() table.insert(self._lines, {}) self._col = 0 return self end ---@param opts? {sep?:string} function M:statusline(opts) local sep = opts and opts.sep or " " local lines = {} ---@type string[] for _, line in ipairs(self._lines) do local parts = {} for _, segment in ipairs(line) do local str = segment.str:gsub("%%", "%%%%") if segment.hl then str = ("%%#%s#%s%%*"):format(segment.hl, str) end parts[#parts + 1] = str end table.insert(lines, table.concat(parts, "")) end return table.concat(lines, sep) end function M:render(buf) local lines = {} local padding = (" "):rep(self._opts.padding) for _, line in ipairs(self._lines) do local parts = { padding } for _, segment in ipairs(line) do parts[#parts + 1] = segment.str end table.insert(lines, table.concat(parts, "")) end vim.bo[buf].modifiable = true vim.api.nvim_buf_clear_namespace(buf, M.ns, 0, -1) vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) local regions = {} ---@type trouble.LangRegions for l, line in ipairs(self._lines) do local col = self._opts.padding or 0 local row = l - 1 for _, segment in ipairs(line) do local width = segment.width if segment.ts then regions[segment.ts or ""] = regions[segment.ts] or {} local ts_regions = regions[segment.ts or ""] local last_region = ts_regions[#ts_regions] if not last_region then last_region = {} table.insert(ts_regions, last_region) end -- combine multiline item segments in one region if segment.line and #last_region ~= segment.line - 1 then last_region = {} table.insert(ts_regions, last_region) end table.insert(last_region, { row, col, row, col + width + 1, }) elseif segment.hl then Util.set_extmark(buf, M.ns, row, col, { hl_group = segment.hl, end_col = col + width, }) end col = col + width end end vim.bo[buf].modifiable = false local changetick = vim.b[buf].changetick vim.schedule(function() if not vim.api.nvim_buf_is_valid(buf) then return end if vim.b[buf].changetick ~= changetick then return end require("trouble.view.treesitter").attach(buf, regions) end) end function M:trim() while #self._lines > 0 and #self._lines[#self._lines] == 0 do table.remove(self._lines) end end function M:row() return #self._lines == 0 and 1 or #self._lines end ---@param opts? {display:boolean} function M:col(opts) if opts and opts.display then local ret = 0 for _, segment in ipairs(self._lines[#self._lines] or {}) do ret = ret + vim.fn.strdisplaywidth(segment.str) end return ret end return self._col end return M ================================================ FILE: lua/trouble/view/treesitter.lua ================================================ ---@alias trouble.LangRegions table local M = {} M.cache = {} ---@type table> local ns = vim.api.nvim_create_namespace("trouble.treesitter") local TSHighlighter = vim.treesitter.highlighter local function wrap(name) return function(_, win, buf, ...) if not M.cache[buf] then return false end for _, hl in pairs(M.cache[buf] or {}) do if hl.enabled then TSHighlighter.active[buf] = hl.highlighter TSHighlighter[name](_, win, buf, ...) end end TSHighlighter.active[buf] = nil end end M.did_setup = false function M.setup() if M.did_setup then return end M.did_setup = true -- https://github.com/neovim/neovim/commit/5edbabdbec0ac3fba33be8afc008845130158583 if TSHighlighter._on_range then vim.api.nvim_set_decoration_provider(ns, { on_win = wrap("_on_win"), on_range = wrap("_on_range"), }) else vim.api.nvim_set_decoration_provider(ns, { on_win = wrap("_on_win"), on_line = wrap("_on_line"), }) end vim.api.nvim_create_autocmd("BufWipeout", { group = vim.api.nvim_create_augroup("trouble.treesitter.hl", { clear = true }), callback = function(ev) M.cache[ev.buf] = nil end, }) end ---@param buf number ---@param regions trouble.LangRegions function M.attach(buf, regions) M.setup() M.cache[buf] = M.cache[buf] or {} for lang in pairs(M.cache[buf]) do M.cache[buf][lang].enabled = regions[lang] ~= nil end for lang in pairs(regions) do M._attach_lang(buf, lang, regions[lang]) end end ---@param buf number ---@param lang? string function M._attach_lang(buf, lang, regions) lang = lang or "markdown" lang = lang == "markdown" and "markdown_inline" or lang M.cache[buf] = M.cache[buf] or {} if not M.cache[buf][lang] then local ok, parser = pcall(vim.treesitter.languagetree.new, buf, lang) if not ok then local msg = "nvim-treesitter parser missing `" .. lang .. "`" vim.notify_once(msg, vim.log.levels.WARN, { title = "trouble.nvim" }) return end parser:set_included_regions(vim.deepcopy(regions)) M.cache[buf][lang] = { parser = parser, highlighter = TSHighlighter.new(parser), } end M.cache[buf][lang].enabled = true local parser = M.cache[buf][lang].parser parser:set_included_regions(vim.deepcopy(regions)) end return M ================================================ FILE: lua/trouble/view/window.lua ================================================ local Main = require("trouble.view.main") local Util = require("trouble.util") ---@class trouble.Window.split ---@field type "split" ---@field relative "editor" | "win" cursor is only valid for float ---@field size number | {width:number, height:number} when a table is provided, either the width or height is used based on the position ---@field position "top" | "bottom" | "left" | "right" ---@class trouble.Window.float ---@field type "float" ---@field relative "editor" | "win" | "cursor" cursor is only valid for float ---@field size {width: number, height: number} ---@field position {[1]: number, [2]: number} ---@field anchor? string ---@field focusable? boolean ---@field zindex? integer ---@field border? any ---@field title? string|{[1]: string, [2]: string} ---@field title_pos? string ---@field footer? string|{[1]: string, [2]: string} ---@field footer_pos? string ---@field fixed? boolean ---@class trouble.Window.main ---@field type "main" ---@class trouble.Window.base ---@field padding? {top?:number, left?:number} ---@field wo? vim.wo ---@field bo? vim.bo ---@field minimal? boolean (defaults to true) ---@field win? number ---@field on_mount? fun(self: trouble.Window) ---@field on_close? fun(self: trouble.Window) ---@alias trouble.Window.opts trouble.Window.base|trouble.Window.split|trouble.Window.float|trouble.Window.main ---@class trouble.Window ---@field opts trouble.Window.opts ---@field win? number ---@field buf number ---@field id number ---@field keys table local M = {} M.__index = M local _id = 0 local function next_id() _id = _id + 1 return _id end local split_commands = { editor = { top = "topleft", right = "vertical botright", bottom = "botright", left = "vertical topleft", }, win = { top = "aboveleft", right = "vertical rightbelow", bottom = "belowright", left = "vertical leftabove", }, } local float_options = { "anchor", "border", "bufpos", "col", "external", "fixed", "focusable", "footer", "footer_pos", "height", "hide", "noautocmd", "relative", "row", "style", "title", "title_pos", "width", "win", "zindex", } ---@type trouble.Window.opts local defaults = { padding = { top = 0, left = 1 }, bo = { bufhidden = "wipe", filetype = "trouble", buftype = "nofile", }, wo = { winbar = "", winblend = 0, }, } M.FOLDS = { wo = { foldcolumn = "0", foldenable = false, foldlevel = 99, foldmethod = "manual", }, } ---@type trouble.Window.opts local minimal = { wo = { cursorcolumn = false, cursorline = true, cursorlineopt = "both", fillchars = "eob: ", list = false, number = false, relativenumber = false, signcolumn = "no", spell = false, winbar = "", statuscolumn = "", winfixheight = true, winfixwidth = true, winhighlight = "Normal:TroubleNormal,NormalNC:TroubleNormalNC,EndOfBuffer:TroubleNormal", wrap = false, }, } ---@param opts? trouble.Window.opts function M.new(opts) local self = setmetatable({}, M) self.id = next_id() opts = opts or {} if opts.minimal == nil then opts.minimal = opts.type ~= "main" end opts = vim.tbl_deep_extend("force", {}, defaults, opts.minimal and minimal or {}, opts or {}) opts.type = opts.type or "split" if opts.type == "split" then opts.relative = opts.relative or "editor" opts.position = opts.position or "bottom" opts.size = opts.size or (opts.position == "bottom" or opts.position == "top") and 10 or 30 opts.win = opts.win or vim.api.nvim_get_current_win() elseif opts.type == "float" then opts.relative = opts.relative or "editor" opts.size = opts.size or { width = 0.8, height = 0.8 } opts.position = type(opts.position) == "table" and opts.position or { 0.5, 0.5 } elseif opts.type == "main" then opts.type = "float" opts.relative = "win" opts.position = { 0, 0 } opts.size = { width = 1, height = 1 } opts.wo.winhighlight = "Normal:Normal" end self.opts = opts return self end ---@param clear? boolean function M:augroup(clear) return vim.api.nvim_create_augroup("trouble.window." .. self.id, { clear = clear == true }) end function M:parent_size() if self.opts.relative == "editor" or self.opts.relative == "cursor" then return { width = vim.o.columns, height = vim.o.lines } end local ret = { width = vim.api.nvim_win_get_width(self.opts.win), height = vim.api.nvim_win_get_height(self.opts.win), } -- account for winbar if vim.wo[self.opts.win].winbar ~= "" then ret.height = ret.height - 1 end return ret end ---@param type "win" | "buf" function M:set_options(type) local opts = type == "win" and self.opts.wo or self.opts.bo ---@diagnostic disable-next-line: no-unknown for k, v in pairs(opts or {}) do ---@diagnostic disable-next-line: no-unknown local ok, err = pcall(vim.api.nvim_set_option_value, k, v, type == "win" and { scope = "local", win = self.win, } or { buf = self.buf }) if not ok then Util.error("Error setting option `" .. k .. "=" .. v .. "`\n\n" .. err) end end end function M:mount() self.keys = {} self.buf = vim.api.nvim_create_buf(false, true) self:set_options("buf") if self.opts.type == "split" then ---@diagnostic disable-next-line: param-type-mismatch self:mount_split(self.opts) else ---@diagnostic disable-next-line: param-type-mismatch self:mount_float(self.opts) end self:set_options("win") self:on({ "BufWinLeave" }, vim.schedule_wrap(self.check_alien)) self:on("WinClosed", function() if self.opts.on_close then self.opts.on_close(self) end self:augroup(true) end, { win = true }) if self.opts.on_mount then self.opts.on_mount(self) end end function M:set_buf(buf) self.buf = buf if self.win and vim.api.nvim_win_is_valid(self.win) then vim.api.nvim_win_set_buf(self.win, buf) end end function M:check_alien() if self.win and vim.api.nvim_win_is_valid(self.win) then local buf = vim.api.nvim_win_get_buf(self.win) if buf ~= self.buf then -- move the alien buffer to another window local main = Main:get() if main then vim.api.nvim_win_set_buf(main.win, buf) -- restore the trouble window self:close() self:open() end end end end function M:close() pcall(vim.api.nvim_win_close, self.win, true) self:augroup(true) self.win = nil end function M:open() if self:valid() then return self end self:close() self:mount() return self end function M:valid() return self.win and vim.api.nvim_win_is_valid(self.win) and self.buf and vim.api.nvim_buf_is_valid(self.buf) and vim.api.nvim_win_get_buf(self.win) == self.buf end ---@param opts trouble.Window.split|trouble.Window.base function M:mount_split(opts) if self.opts.win and not vim.api.nvim_win_is_valid(self.opts.win) then self.opts.win = 0 end local parent_size = self:parent_size() local size = opts.size if type(size) == "table" then size = opts.position == "left" or opts.position == "right" and size.width or size.height end if size <= 1 then local vertical = opts.position == "left" or opts.position == "right" size = math.floor(parent_size[vertical and "width" or "height"] * size) end local cmd = split_commands[opts.relative][opts.position] Util.noautocmd(function() vim.api.nvim_win_call(opts.win, function() vim.cmd("silent noswapfile " .. cmd .. " " .. size .. "split") vim.api.nvim_win_set_buf(0, self.buf) self.win = vim.api.nvim_get_current_win() end) end) end ---@param opts trouble.Window.float|trouble.Window.base function M:mount_float(opts) local parent_size = self:parent_size() ---@type vim.api.keyset.win_config local config = {} for _, v in ipairs(float_options) do ---@diagnostic disable-next-line: no-unknown config[v] = opts[v] end config.focusable = true config.height = opts.size.height <= 1 and math.floor(parent_size.height * opts.size.height) or opts.size.height config.width = opts.size.width <= 1 and math.floor(parent_size.width * opts.size.width) or opts.size.width config.border = opts.border or "none" config.row = math.abs(opts.position[1]) <= 1 and math.floor((parent_size.height - config.height) * opts.position[1]) or opts.position[1] config.row = config.row < 0 and (parent_size.height + config.row) or config.row config.col = math.abs(opts.position[2]) <= 1 and math.floor((parent_size.width - config.width) * opts.position[2]) or opts.position[2] config.col = config.col < 0 and (parent_size.width + config.col) or config.col if config.relative ~= "win" then config.win = nil end self.win = vim.api.nvim_open_win(self.buf, false, config) end function M:focus() if self:valid() then vim.api.nvim_set_current_win(self.win) end end ---@param events string|string[] ---@param fn fun(self:trouble.Window, event:{buf:number}):boolean? ---@param opts? vim.api.keyset.create_autocmd | {buffer: false, win?:boolean} function M:on(events, fn, opts) opts = opts or {} if opts.win then opts.pattern = self.win .. "" opts.win = nil elseif opts.buffer == nil then opts.buffer = self.buf elseif opts.buffer == false then opts.buffer = nil end if opts.pattern then opts.buffer = nil end local _self = Util.weak(self) opts.callback = function(e) local this = _self() if not this then -- delete the autocmd return true end return fn(this, e) end opts.group = self:augroup() vim.api.nvim_create_autocmd(events, opts) end ---@param key string ---@param fn fun(self: trouble.Window):any ---@param opts? string|vim.keymap.set.Opts|{mode?:string} function M:map(key, fn, opts) opts = vim.tbl_deep_extend("force", { buffer = self.buf, nowait = true, mode = "n", }, type(opts) == "string" and { desc = opts } or opts or {}) local mode = opts.mode opts.mode = nil ---@cast opts vim.keymap.set.Opts if not self:valid() then error("Cannot create a keymap for an invalid window") end self.keys[key] = opts.desc or key local weak_self = Util.weak(self) vim.keymap.set(mode, key, function() if weak_self() then return fn(weak_self()) end end, opts) end return M ================================================ FILE: scripts/docs ================================================ #!/bin/env bash nvim -u tests/minit.lua -l lua/trouble/docs.lua ================================================ FILE: scripts/test ================================================ #!/usr/bin/env bash nvim -l tests/minit.lua --minitest "$@" ================================================ FILE: selene.toml ================================================ std="vim" [lints] mixed_table="allow" ================================================ FILE: stylua.toml ================================================ indent_type = "Spaces" indent_width = 2 column_width = 120 [sort_requires] enabled = true ================================================ FILE: tests/minit.lua ================================================ #!/usr/bin/env -S nvim -l vim.env.LAZY_STDPATH = ".tests" vim.env.LAZY_PATH = vim.fs.normalize("~/projects/lazy.nvim") if vim.fn.isdirectory(vim.env.LAZY_PATH) == 1 then loadfile(vim.env.LAZY_PATH .. "/bootstrap.lua")() else load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"), "bootstrap.lua")() end -- Setup lazy.nvim require("lazy.minit").setup({ spec = { { dir = vim.uv.cwd(), opts = {} }, }, }) ================================================ FILE: tests/parser_spec.lua ================================================ local Parser = require("trouble.config.parser") describe("Input is parsed correctly", function() local tests = { { input = [[a = "b" foo = true bar=1 c = "g"]], expected = { opts = { a = "b", foo = true, bar = 1, c = "g" }, errors = {}, args = {} }, }, { input = [[key="value with spaces"]], expected = { opts = { key = "value with spaces" }, errors = {}, args = {} }, }, { input = [[arr={one, two}]], expected = { opts = { arr = { "one", "two" } }, errors = {}, args = {} }, }, { input = [[a.b = "c"]], expected = { opts = { a = { b = "c" } }, errors = {}, args = {} }, }, { input = [[vim=vim.diagnostic.severity.ERROR]], expected = { opts = { vim = vim.diagnostic.severity.ERROR }, errors = {}, args = {} }, }, { input = [[x.y.z = "value" a.b = 2]], expected = { opts = { x = { y = { z = "value" } }, a = { b = 2 } }, errors = {}, args = {} }, }, { input = [[test="hello world"]], expected = { opts = { test = "hello world" }, errors = {}, args = {} }, }, { input = [[one.two.three.four = 4]], expected = { opts = { one = { two = { three = { four = 4 } } } }, errors = {}, args = {} }, }, { input = [[a="b" c="d" e.f="g" h.i.j="k"]], expected = { opts = { a = "b", c = "d", e = { f = "g" }, h = { i = { j = "k" } } }, errors = {}, args = {} }, }, { input = [[empty = "" nonempty="not empty"]], expected = { opts = { empty = "", nonempty = "not empty" }, errors = {}, args = {} }, }, { input = [[win.position="right" win.relative="win"]], expected = { opts = { win = { position = "right", relative = "win" } }, errors = {}, args = {} }, }, { input = [[a.b="c" a = "b"]], expected = { opts = { a = "b" }, errors = {}, args = {} }, -- This test is tricky as it will overwrite the first value of 'a' }, { input = [[a="b" a.b="c"]], expected = { opts = { a = { b = "c" } }, errors = {}, args = {} }, -- This test is tricky as it will overwrite the first value of 'a' }, { input = [[a_b = 1]], expected = { opts = { a_b = 1 }, errors = {}, args = {} }, }, { input = "foo=bar bar=baz", expected = { opts = { foo = "bar", bar = "baz" }, errors = {}, args = {} }, }, { input = "foo=bar bar={ one, two, three} ", expected = { opts = { foo = "bar", bar = { "one", "two", "three" } }, errors = {}, args = {} }, }, { input = [[a.x = 1 a.y = 2 a = {z =3}]], expected = { opts = { a = { z = 3 } }, errors = {}, args = {} }, }, { input = [[ a = {z =3} a.x = 1 a.y = 2]], expected = { opts = { a = { x = 1, y = 2, z = 3 } }, errors = {}, args = {} }, }, { input = "foo", expected = { opts = {}, errors = {}, args = { "foo" } }, }, { input = "foo bar", expected = { opts = {}, errors = {}, args = { "foo", "bar" } } }, { input = "foo bar baz", expected = { opts = {}, errors = {}, args = { "foo", "bar", "baz" } }, }, } for _, test in ipairs(tests) do it("parses " .. test.input, function() local actual = Parser.parse(test.input) assert.same(test.expected, actual) end) end return tests end) ================================================ FILE: tests/spec_spec.lua ================================================ local Spec = require("trouble.spec") describe("parses specs", function() it("parses a sort spec", function() local f1 = function() end ---@type ({input:trouble.Sort.spec, output:trouble.Sort})[] local tests = { { input = "foo", output = { { field = "foo" } }, }, { input = { "foo", "bar" }, output = { { field = "foo" }, { field = "bar" } }, }, { input = { "foo", "-bar" }, output = { { field = "foo" }, { field = "bar", desc = true } }, }, { input = f1, output = { { sorter = f1 } } }, { input = { buf = 0 }, output = { { filter = { buf = 0 } } } }, { input = { { buf = 0 } }, output = { { filter = { buf = 0 } } } }, { input = { f1, "foo" }, output = { { sorter = f1 }, { field = "foo" } } }, } for _, test in ipairs(tests) do assert.same(test.output, Spec.sort(test.input)) end end) it("parses a group spec", function() ---@type ({input:trouble.Group.spec, output:trouble.Group})[] local tests = { { input = "foo", output = { fields = { "foo" }, format = "{foo}" }, }, { input = "foo", output = { fields = { "foo" }, format = "{foo}" }, }, { input = { "foo", "bar" }, output = { fields = { "foo", "bar" }, format = "{foo} {bar}" }, }, { input = { "foo", "bar" }, output = { fields = { "foo", "bar" }, format = "{foo} {bar}" }, }, { input = { "foo", "bar" }, output = { fields = { "foo", "bar" }, format = "{foo} {bar}" }, }, { input = { "directory", format = "{kind_icon} {symbol.name} {text:Comment} {pos}", }, output = { directory = true, format = "{kind_icon} {symbol.name} {text:Comment} {pos}", }, }, } for _, test in ipairs(tests) do assert.same(test.output, Spec.group(test.input)) end end) it("parses a section spec", function() local tests = { { input = { -- error from all files source = "diagnostics", groups = { "filename" }, filter = { severity = 1, }, sort = { "filename", "-pos" }, }, output = { events = {}, source = "diagnostics", groups = { { fields = { "filename" }, format = "{filename}" } }, sort = { { field = "filename" }, { field = "pos", desc = true } }, filter = { severity = 1 }, format = "{filename} {pos}", }, }, } for _, test in ipairs(tests) do assert.same(test.output, Spec.section(test.input)) end end) end) ================================================ FILE: vim.yml ================================================ base: lua51 lua_versions: - luajit globals: Snacks: any: true vim: any: true jit: any: true assert: any: true describe: any: true it: any: true before_each: any: true