summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.envrc3
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/bug_report.yml116
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/feature_request.yml43
-rwxr-xr-xmut/neovim/pack/plugins/start/quicker.nvim/.github/pre-commit3
-rwxr-xr-xmut/neovim/pack/plugins/start/quicker.nvim/.github/pre-push11
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_remove_question_label_on_comment.yml16
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_request_review.yml27
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/install_nvim.sh12
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/tests.yml122
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.gitignore48
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.luacheckrc17
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.luarc.json9
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/.stylua.toml5
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/CHANGELOG.md65
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/LICENSE21
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/Makefile52
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/README.md375
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/doc/quicker.txt181
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/config.lua189
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/context.lua315
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/cursor.lua44
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/display.lua601
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/editor.lua405
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/follow.lua84
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/fs.lua98
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/highlight.lua222
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/init.lua189
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/keys.lua20
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/opts.lua61
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/util.lua95
-rwxr-xr-xmut/neovim/pack/plugins/start/quicker.nvim/run_tests.sh36
-rwxr-xr-xmut/neovim/pack/plugins/start/quicker.nvim/scripts/generate.py102
-rwxr-xr-xmut/neovim/pack/plugins/start/quicker.nvim/scripts/main.py31
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/scripts/requirements.txt4
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/syntax/qf.vim7
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/context_spec.lua134
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/display_spec.lua145
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/editor_spec.lua347
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/fs_spec.lua20
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/minimal_init.lua16
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/opts_spec.lua52
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_15
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_long_11
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_minimal_11
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_110
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace4
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace_qf2
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_delim10
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe10
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_210
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf2
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf_22
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded10
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded_qf4
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail10
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail_qf1
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_invalid1
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_ll10
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_110
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_210
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_qf15
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace4
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace_qf2
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace4
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace_qf2
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_13
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_216
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_319
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_13
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_25
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_loclist4
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_missing4
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_all_whitespace2
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_mixed_whitespace2
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace2
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace_expanded5
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/test_util.lua142
-rw-r--r--mut/neovim/pack/plugins/start/quicker.nvim/tests/whitespace_spec.lua83
78 files changed, 4773 insertions, 0 deletions
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.envrc b/mut/neovim/pack/plugins/start/quicker.nvim/.envrc
new file mode 100644
index 0000000..94b55e4
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.envrc
@@ -0,0 +1,3 @@
+export VIRTUAL_ENV=venv
+layout python
+python -c 'import pyparsing' 2> /dev/null || pip install -r scripts/requirements.txt
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/bug_report.yml b/mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..1fcb4d8
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,116 @@
+name: Bug Report
+description: File a bug/issue
+title: "bug: "
+labels: [bug]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Before reporting a bug, make sure to search [existing issues](https://github.com/stevearc/quicker.nvim/issues)
+ - type: input
+ attributes:
+ label: "Neovim version (nvim -v)"
+ placeholder: "0.10.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.
+ validations:
+ required: true
+ - type: dropdown
+ attributes:
+ label: What is the severity of this bug?
+ options:
+ - minor (annoyance)
+ - tolerable (can work around it)
+ - breaking (some functionality is broken)
+ - blocking (cannot use plugin)
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior.
+ placeholder: |
+ 1. nvim -u repro.lua
+ 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: Minimal example file
+ description: A small example file you are editing that produces the issue
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Minimal init.lua
+ description:
+ Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua`
+ This uses lazy.nvim (a plugin manager).
+ value: |
+ -- DO NOT change the paths and don't remove the colorscheme
+ local root = vim.fn.fnamemodify("./.repro", ":p")
+
+ -- set stdpaths to use .repro
+ for _, name in ipairs({ "config", "data", "state", "cache" }) do
+ vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
+ end
+
+ -- bootstrap lazy
+ local lazypath = root .. "/plugins/lazy.nvim"
+ if not vim.loop.fs_stat(lazypath) then
+ vim.fn.system({
+ "git",
+ "clone",
+ "--filter=blob:none",
+ "--single-branch",
+ "https://github.com/folke/lazy.nvim.git",
+ lazypath,
+ })
+ end
+ vim.opt.runtimepath:prepend(lazypath)
+
+ -- install plugins
+ local plugins = {
+ "folke/tokyonight.nvim",
+ {
+ "stevearc/quicker.nvim",
+ config = function()
+ require("quicker").setup({
+ -- add your config here
+ })
+ end,
+ },
+ -- add any other plugins here
+ }
+ require("lazy").setup(plugins, {
+ root = root .. "/plugins",
+ })
+
+ vim.cmd.colorscheme("tokyonight")
+ -- add anything else here
+ render: Lua
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Additional context
+ description: Any additional information or screenshots you would like to provide
+ validations:
+ required: false
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/feature_request.yml b/mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..f735c8c
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,43 @@
+name: Feature Request
+description: Submit a feature request
+title: "feature request: "
+labels: [enhancement]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Before submitting a feature request, make sure to search for [existing requests](https://github.com/stevearc/quicker.nvim/issues)
+ - type: checkboxes
+ attributes:
+ label: Did you check existing requests?
+ options:
+ - label: I have searched the existing issues
+ required: true
+ - type: textarea
+ attributes:
+ label: Describe the feature
+ description: A short summary of the feature you want
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Provide background
+ description: Describe the reasoning behind why you want the feature.
+ placeholder: I am trying to do X. My current workflow is Y.
+ validations:
+ required: false
+ - type: dropdown
+ attributes:
+ label: What is the significance of this feature?
+ options:
+ - nice to have
+ - strongly desired
+ - cannot use this plugin without it
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Additional details
+ description: Any additional information you would like to provide. Things you've tried, alternatives considered, examples from other plugins, etc.
+ validations:
+ required: false
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/pre-commit b/mut/neovim/pack/plugins/start/quicker.nvim/.github/pre-commit
new file mode 100755
index 0000000..c64fbec
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/pre-commit
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -e
+make fastlint
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/pre-push b/mut/neovim/pack/plugins/start/quicker.nvim/.github/pre-push
new file mode 100755
index 0000000..ecb23a9
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/pre-push
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -e
+IFS=' '
+while read local_ref _local_sha _remote_ref _remote_sha; do
+ remote_main=$( (git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || echo "///master") | cut -f 4 -d / | tr -d "[:space:]")
+ local_ref_short=$(echo "$local_ref" | cut -f 3 -d / | tr -d "[:space:]")
+ if [ "$local_ref_short" = "$remote_main" ]; then
+ make lint
+ make test
+ fi
+done
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_remove_question_label_on_comment.yml b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_remove_question_label_on_comment.yml
new file mode 100644
index 0000000..f99bba8
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_remove_question_label_on_comment.yml
@@ -0,0 +1,16 @@
+name: Remove Question Label on Issue Comment
+
+on: [issue_comment]
+
+jobs:
+ # Remove the "question" label when a new comment is added.
+ # This lets me ask a question, tag the issue with "question", and filter out all "question"-tagged
+ # issues in my "needs triage" filter.
+ remove_question:
+ runs-on: ubuntu-latest
+ if: github.event.sender.login != 'stevearc'
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions-ecosystem/action-remove-labels@v1
+ with:
+ labels: question
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_request_review.yml b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_request_review.yml
new file mode 100644
index 0000000..c31f582
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/automation_request_review.yml
@@ -0,0 +1,27 @@
+name: Request Review
+permissions:
+ pull-requests: write
+on:
+ pull_request_target:
+ types: [opened, reopened, ready_for_review, synchronize]
+ branches-ignore:
+ - "release-please--**"
+
+jobs:
+ # Request review automatically when PRs are opened
+ request_review:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Request Review
+ uses: actions/github-script@v7
+ if: github.actor != 'stevearc'
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ const pr = context.payload.pull_request;
+ github.rest.pulls.requestReviewers({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: pr.number,
+ reviewers: ['stevearc']
+ });
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/install_nvim.sh b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/install_nvim.sh
new file mode 100644
index 0000000..4c0203c
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/install_nvim.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e
+PLUGINS="$HOME/.local/share/nvim/site/pack/plugins/start"
+mkdir -p "$PLUGINS"
+
+wget "https://github.com/neovim/neovim/releases/download/${NVIM_TAG-stable}/nvim.appimage"
+chmod +x nvim.appimage
+./nvim.appimage --appimage-extract >/dev/null
+rm -f nvim.appimage
+mkdir -p ~/.local/share/nvim
+mv squashfs-root ~/.local/share/nvim/appimage
+sudo ln -s "$HOME/.local/share/nvim/appimage/AppRun" /usr/bin/nvim
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/tests.yml b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/tests.yml
new file mode 100644
index 0000000..a053f5d
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.github/workflows/tests.yml
@@ -0,0 +1,122 @@
+name: Run tests
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ luacheck:
+ name: Luacheck
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Prepare
+ run: |
+ sudo apt-get update
+ sudo add-apt-repository universe
+ sudo apt install luarocks -y
+ sudo luarocks install luacheck
+
+ - name: Run Luacheck
+ run: luacheck lua tests
+
+ typecheck:
+ name: typecheck
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: stevearc/nvim-typecheck-action@v2
+ with:
+ path: lua
+
+ stylua:
+ name: StyLua
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+ - name: Stylua
+ uses: JohnnyMorganz/stylua-action@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ version: v0.20.0
+ args: --check lua tests
+
+ run_tests:
+ strategy:
+ matrix:
+ include:
+ - nvim_tag: v0.10.1
+
+ name: Run tests
+ runs-on: ubuntu-22.04
+ env:
+ NVIM_TAG: ${{ matrix.nvim_tag }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Neovim and dependencies
+ run: |
+ bash ./.github/workflows/install_nvim.sh
+
+ - name: Run tests
+ run: |
+ bash ./run_tests.sh
+
+ update_docs:
+ name: Update docs
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Neovim and dependencies
+ run: |
+ bash ./.github/workflows/install_nvim.sh
+
+ - name: Update docs
+ run: |
+ python -m pip install pyparsing==3.0.9
+ make doc
+ - name: Commit changes
+ if: ${{ github.ref == 'refs/heads/master' }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COMMIT_MSG: |
+ [docgen] Update docs
+ skip-checks: true
+ run: |
+ git config user.email "actions@github"
+ git config user.name "Github Actions"
+ git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
+ git add README.md doc
+ # Only commit and push if we have changes
+ git diff --quiet && git diff --staged --quiet || (git commit -m "${COMMIT_MSG}"; git push origin HEAD:${GITHUB_REF})
+
+ release:
+ name: release
+
+ if: ${{ github.ref == 'refs/heads/master' }}
+ needs:
+ - luacheck
+ - stylua
+ - typecheck
+ - run_tests
+ - update_docs
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: googleapis/release-please-action@v4
+ id: release
+ with:
+ release-type: simple
+ - uses: actions/checkout@v4
+ - uses: rickstaa/action-create-tag@v1
+ if: ${{ steps.release.outputs.release_created }}
+ with:
+ tag: stable
+ message: "Current stable release: ${{ steps.release.outputs.tag_name }}"
+ tag_exists_error: false
+ force_push_tag: true
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.gitignore b/mut/neovim/pack/plugins/start/quicker.nvim/.gitignore
new file mode 100644
index 0000000..9e6bcf7
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.gitignore
@@ -0,0 +1,48 @@
+# Compiled Lua sources
+luac.out
+
+# luarocks build files
+*.src.rock
+*.zip
+*.tar.gz
+
+# Object files
+*.o
+*.os
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+*.def
+*.exp
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+.direnv/
+.testenv/
+venv/
+doc/tags
+scripts/nvim_doc_tools
+scripts/nvim-typecheck-action
+tests/tmp
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.luacheckrc b/mut/neovim/pack/plugins/start/quicker.nvim/.luacheckrc
new file mode 100644
index 0000000..5e100b1
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.luacheckrc
@@ -0,0 +1,17 @@
+max_comment_line_length = false
+codes = true
+
+exclude_files = {
+ "tests/",
+}
+
+ignore = {
+ "212", -- Unused argument
+ "631", -- Line is too long
+ "122", -- Setting a readonly global
+ "542", -- Empty if branch
+}
+
+read_globals = {
+ "vim",
+}
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.luarc.json b/mut/neovim/pack/plugins/start/quicker.nvim/.luarc.json
new file mode 100644
index 0000000..68da2f2
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.luarc.json
@@ -0,0 +1,9 @@
+{
+ "runtime": {
+ "version": "LuaJIT",
+ "pathStrict": true
+ },
+ "type": {
+ "checkTableShape": true
+ }
+}
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/.stylua.toml b/mut/neovim/pack/plugins/start/quicker.nvim/.stylua.toml
new file mode 100644
index 0000000..020ce91
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/.stylua.toml
@@ -0,0 +1,5 @@
+column_width = 100
+indent_type = "Spaces"
+indent_width = 2
+[sort_requires]
+enabled = true
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/CHANGELOG.md b/mut/neovim/pack/plugins/start/quicker.nvim/CHANGELOG.md
new file mode 100644
index 0000000..da1f1e5
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/CHANGELOG.md
@@ -0,0 +1,65 @@
+# Changelog
+
+## [1.3.0](https://github.com/stevearc/quicker.nvim/compare/v1.2.0...v1.3.0) (2024-12-24)
+
+
+### Features
+
+* add option to remove all leading whitespace from items ([#26](https://github.com/stevearc/quicker.nvim/issues/26)) ([da7e910](https://github.com/stevearc/quicker.nvim/commit/da7e9104de4ff9303e1c722f7c9216f994622067))
+* option to scroll to closest quickfix item ([#23](https://github.com/stevearc/quicker.nvim/issues/23)) ([cc8bb67](https://github.com/stevearc/quicker.nvim/commit/cc8bb67271c093a089d205def9dd69a188c45ae1))
+* toggle function for context ([#18](https://github.com/stevearc/quicker.nvim/issues/18)) ([049d665](https://github.com/stevearc/quicker.nvim/commit/049d66534d3de5920663ee1b8dd0096d70f55a67))
+
+
+### Bug Fixes
+
+* filter vim.NIL when deserializing buffer variables ([#30](https://github.com/stevearc/quicker.nvim/issues/30)) ([a3cf525](https://github.com/stevearc/quicker.nvim/commit/a3cf5256998f9387ad8e293c6f295d286be6453f))
+
+## [1.2.0](https://github.com/stevearc/quicker.nvim/compare/v1.1.1...v1.2.0) (2024-11-06)
+
+
+### Features
+
+* add command modifiers to the `toggle()` and `open()` APIs ([#24](https://github.com/stevearc/quicker.nvim/issues/24)) ([95a839f](https://github.com/stevearc/quicker.nvim/commit/95a839fafff1c0a7fe970492f5159f41a90974bf))
+
+
+### Bug Fixes
+
+* crash in highlighter ([11f9eb0](https://github.com/stevearc/quicker.nvim/commit/11f9eb0c803bb9ced8c6043805de89c62bd04515))
+* guard against out of date buffer contents ([1fc29de](https://github.com/stevearc/quicker.nvim/commit/1fc29de2172235c076aa1ead6f1ee772398de732))
+* trim_leading_whitespace works with mixed tabs and spaces ([#26](https://github.com/stevearc/quicker.nvim/issues/26)) ([46e0ad6](https://github.com/stevearc/quicker.nvim/commit/46e0ad6c6a1d998a294e13cbb8b7c398e140983a))
+
+## [1.1.1](https://github.com/stevearc/quicker.nvim/compare/v1.1.0...v1.1.1) (2024-08-20)
+
+
+### Bug Fixes
+
+* refresh replaces all item text with buffer source ([f28fca3](https://github.com/stevearc/quicker.nvim/commit/f28fca3863f8d3679e86d8ff30d023a43fba15c8))
+
+## [1.1.0](https://github.com/stevearc/quicker.nvim/compare/v1.0.0...v1.1.0) (2024-08-20)
+
+
+### Features
+
+* better support for lazy loading ([29ab2a6](https://github.com/stevearc/quicker.nvim/commit/29ab2a6d4771ace240f25df028129bfc85e16ffd))
+* display errors as virtual text when expanding context ([#16](https://github.com/stevearc/quicker.nvim/issues/16)) ([6b79167](https://github.com/stevearc/quicker.nvim/commit/6b79167543f1b18e76319217a29bb4e177a5e1ae))
+* quicker.refresh preserves and display diagnostic messages ([#19](https://github.com/stevearc/quicker.nvim/issues/19)) ([349e0de](https://github.com/stevearc/quicker.nvim/commit/349e0def74ddbfc47f64ca52202e84bedf064048))
+
+
+### Bug Fixes
+
+* editor works when filename is truncated ([7a64d4e](https://github.com/stevearc/quicker.nvim/commit/7a64d4ea2b641cc8671443d0ff26de2924894c9f))
+* **editor:** load buffer if necessary before save_changes ([#14](https://github.com/stevearc/quicker.nvim/issues/14)) ([59a610a](https://github.com/stevearc/quicker.nvim/commit/59a610a2163a51a019bde769bf2e2eec1654e4d4))
+* error when quickfix buffer is hidden and items are added ([#8](https://github.com/stevearc/quicker.nvim/issues/8)) ([a8b885b](https://github.com/stevearc/quicker.nvim/commit/a8b885be246666922aca7f296195986a1cae3344))
+* guard against double-replacing a diagnostic line ([2dc0f80](https://github.com/stevearc/quicker.nvim/commit/2dc0f800770f8956c24a6d70fa61e7ec2e102d8a))
+* **highlight:** check if src_line exists before trying to highlight it ([#6](https://github.com/stevearc/quicker.nvim/issues/6)) ([b6a3d2f](https://github.com/stevearc/quicker.nvim/commit/b6a3d2f6aed7882e8bea772f82ba80b5535157a9))
+* include number of files in editor message ([#13](https://github.com/stevearc/quicker.nvim/issues/13)) ([7d2f6d3](https://github.com/stevearc/quicker.nvim/commit/7d2f6d33c7d680b0a18580cfa5feb17302f389d4))
+* missing highlight groups for headers ([5dafd80](https://github.com/stevearc/quicker.nvim/commit/5dafd80225ba462517c38e7b176bd3df52ccfb35))
+* prevent error when treesitter parser is missing ([#4](https://github.com/stevearc/quicker.nvim/issues/4)) ([5cc096a](https://github.com/stevearc/quicker.nvim/commit/5cc096aad4ba1c1e17b6d76cb87fd7155cf9a559))
+* show filename for invalid items ([#11](https://github.com/stevearc/quicker.nvim/issues/11)) ([514817d](https://github.com/stevearc/quicker.nvim/commit/514817dfb0a2828fe2c6183f996a31847c8aa789))
+
+## 1.0.0 (2024-08-07)
+
+
+### Bug Fixes
+
+* guard against race condition in syntax highlighting ([#1](https://github.com/stevearc/quicker.nvim/issues/1)) ([03d9811](https://github.com/stevearc/quicker.nvim/commit/03d9811c8ac037e4e9c8f4ba0dfd1dff0367e0ac))
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/LICENSE b/mut/neovim/pack/plugins/start/quicker.nvim/LICENSE
new file mode 100644
index 0000000..ce6136c
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Steven Arcangeli
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/Makefile b/mut/neovim/pack/plugins/start/quicker.nvim/Makefile
new file mode 100644
index 0000000..8643a8d
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/Makefile
@@ -0,0 +1,52 @@
+## help: print this help message
+.PHONY: help
+help:
+ @echo 'Usage:'
+ @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
+
+## all: generate docs, lint, and run tests
+.PHONY: all
+all: doc lint test
+
+venv:
+ python3 -m venv venv
+ venv/bin/pip install -r scripts/requirements.txt
+
+## doc: generate documentation
+.PHONY: doc
+doc: scripts/nvim_doc_tools venv
+ venv/bin/python scripts/main.py generate
+ venv/bin/python scripts/main.py lint
+
+## test: run tests
+.PHONY: test
+test:
+ ./run_tests.sh
+
+## update_snapshots: Update the test snapshot files
+.PHONY: update_snapshots
+update_snapshots:
+ ./run_tests.sh --update
+
+## lint: run linters and LuaLS typechecking
+.PHONY: lint
+lint: scripts/nvim-typecheck-action fastlint
+ ./scripts/nvim-typecheck-action/typecheck.sh --workdir scripts/nvim-typecheck-action lua
+
+## fastlint: run only fast linters
+.PHONY: fastlint
+fastlint: scripts/nvim_doc_tools venv
+ venv/bin/python scripts/main.py lint
+ luacheck lua tests --formatter plain
+ stylua --check lua tests
+
+scripts/nvim_doc_tools:
+ git clone https://github.com/stevearc/nvim_doc_tools scripts/nvim_doc_tools
+
+scripts/nvim-typecheck-action:
+ git clone https://github.com/stevearc/nvim-typecheck-action scripts/nvim-typecheck-action
+
+## clean: reset the repository to a clean state
+.PHONY: clean
+clean:
+ rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/README.md b/mut/neovim/pack/plugins/start/quicker.nvim/README.md
new file mode 100644
index 0000000..6e07620
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/README.md
@@ -0,0 +1,375 @@
+# quicker.nvim
+
+Improved UI and workflow for the Neovim quickfix
+
+<!-- TOC -->
+
+- [Requirements](#requirements)
+- [Features](#features)
+- [Installation](#installation)
+- [Setup](#setup)
+- [Options](#options)
+- [Highlights](#highlights)
+- [API](#api)
+- [Other Plugins](#other-plugins)
+
+<!-- /TOC -->
+
+## Requirements
+
+- Neovim 0.10+
+
+## Features
+
+- **Improved styling** - including syntax highlighting of grep results.
+- **Show context lines** - easily view lines above and below the quickfix results.
+- **Editable buffer** - make changes across your whole project by editing the quickfix buffer and `:w`.
+- **API helpers** - some helper methods for common tasks, such as toggling the quickfix.
+
+**Improved styling** (colorscheme: [Duskfox](https://github.com/EdenEast/nightfox.nvim/)) \
+Before \
+<img width="695" alt="Screenshot 2024-07-30 at 6 03 39 PM" src="https://github.com/user-attachments/assets/8faa4790-8a7a-4d05-882e-c4e8e7653b00">
+
+After \
+<img width="686" alt="Screenshot 2024-07-30 at 2 05 49 PM" src="https://github.com/user-attachments/assets/90cf87dd-83ec-4967-88aa-5ffe3e1e6623">
+
+**Context lines** around the results \
+<img width="816" alt="Screenshot 2024-07-30 at 2 06 17 PM" src="https://github.com/user-attachments/assets/844445c9-328f-4f18-91d9-b32d32d3ef39">
+
+**Editing the quickfix** to apply changes across multiple files
+
+https://github.com/user-attachments/assets/5065ac4d-ec24-49d1-a95d-232344b17484
+
+## Installation
+
+quicker.nvim supports all the usual plugin managers
+
+<details>
+ <summary>lazy.nvim</summary>
+
+```lua
+{
+ 'stevearc/quicker.nvim',
+ event = "FileType qf",
+ ---@module "quicker"
+ ---@type quicker.SetupOptions
+ opts = {},
+}
+```
+
+</details>
+
+<details>
+ <summary>Packer</summary>
+
+```lua
+require("packer").startup(function()
+ use({
+ "stevearc/quicker.nvim",
+ config = function()
+ require("quicker").setup()
+ end,
+ })
+end)
+```
+
+</details>
+
+<details>
+ <summary>Paq</summary>
+
+```lua
+require("paq")({
+ { "stevearc/quicker.nvim" },
+})
+```
+
+</details>
+
+<details>
+ <summary>vim-plug</summary>
+
+```vim
+Plug 'stevearc/quicker.nvim'
+```
+
+</details>
+
+<details>
+ <summary>dein</summary>
+
+```vim
+call dein#add('stevearc/quicker.nvim')
+```
+
+</details>
+
+<details>
+ <summary>Pathogen</summary>
+
+```sh
+git clone --depth=1 https://github.com/stevearc/quicker.nvim.git ~/.vim/bundle/
+```
+
+</details>
+
+<details>
+ <summary>Neovim native package</summary>
+
+```sh
+git clone --depth=1 https://github.com/stevearc/quicker.nvim.git \
+ "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/quicker/start/quicker.nvim
+```
+
+</details>
+
+## Setup
+
+You will need to call `setup()` for quicker to start working
+
+```lua
+require("quicker").setup()
+```
+
+It's not required to pass in any options, but you may wish to to set some keymaps.
+
+```lua
+vim.keymap.set("n", "<leader>q", function()
+ require("quicker").toggle()
+end, {
+ desc = "Toggle quickfix",
+})
+vim.keymap.set("n", "<leader>l", function()
+ require("quicker").toggle({ loclist = true })
+end, {
+ desc = "Toggle loclist",
+})
+require("quicker").setup({
+ keys = {
+ {
+ ">",
+ function()
+ require("quicker").expand({ before = 2, after = 2, add_to_existing = true })
+ end,
+ desc = "Expand quickfix context",
+ },
+ {
+ "<",
+ function()
+ require("quicker").collapse()
+ end,
+ desc = "Collapse quickfix context",
+ },
+ },
+})
+```
+
+## Options
+
+A complete list of all configuration options
+
+<!-- OPTIONS -->
+```lua
+require("quicker").setup({
+ -- Local options to set for quickfix
+ opts = {
+ buflisted = false,
+ number = false,
+ relativenumber = false,
+ signcolumn = "auto",
+ winfixheight = true,
+ wrap = false,
+ },
+ -- Set to false to disable the default options in `opts`
+ use_default_opts = true,
+ -- Keymaps to set for the quickfix buffer
+ keys = {
+ -- { ">", "<cmd>lua require('quicker').expand()<CR>", desc = "Expand quickfix content" },
+ },
+ -- Callback function to run any custom logic or keymaps for the quickfix buffer
+ on_qf = function(bufnr) end,
+ edit = {
+ -- Enable editing the quickfix like a normal buffer
+ enabled = true,
+ -- Set to true to write buffers after applying edits.
+ -- Set to "unmodified" to only write unmodified buffers.
+ autosave = "unmodified",
+ },
+ -- Keep the cursor to the right of the filename and lnum columns
+ constrain_cursor = true,
+ highlight = {
+ -- Use treesitter highlighting
+ treesitter = true,
+ -- Use LSP semantic token highlighting
+ lsp = true,
+ -- Load the referenced buffers to apply more accurate highlights (may be slow)
+ load_buffers = true,
+ },
+ follow = {
+ -- When quickfix window is open, scroll to closest item to the cursor
+ enabled = false,
+ },
+ -- Map of quickfix item type to icon
+ type_icons = {
+ E = "󰅚 ",
+ W = "󰀪 ",
+ I = " ",
+ N = " ",
+ H = " ",
+ },
+ -- Border characters
+ borders = {
+ vert = "┃",
+ -- Strong headers separate results from different files
+ strong_header = "━",
+ strong_cross = "╋",
+ strong_end = "┫",
+ -- Soft headers separate results within the same file
+ soft_header = "╌",
+ soft_cross = "╂",
+ soft_end = "┨",
+ },
+ -- How to trim the leading whitespace from results. Can be 'all', 'common', or false
+ trim_leading_whitespace = "common",
+ -- Maximum width of the filename column
+ max_filename_width = function()
+ return math.floor(math.min(95, vim.o.columns / 2))
+ end,
+ -- How far the header should extend to the right
+ header_length = function(type, start_col)
+ return vim.o.columns - start_col
+ end,
+})
+```
+
+<!-- /OPTIONS -->
+
+## Highlights
+
+These are the highlight groups that are used to style the quickfix buffer. You can set these
+highlight groups yourself or use `:help winhighlight` in the setup `opts` option to override them
+for just the quickfix window.
+
+- `QuickFixHeaderHard` - Used for the header that divides results from different files
+- `QuickFixHeaderSoft` - Used for the header that divides results within the same file
+- `Delimiter` - Used for the divider between filename, line number, and text
+- `QuickFixLineNr` - Used for the line number
+- `QuickFixFilename` - Used for the filename
+- `QuickFixFilenameInvalid` - Used for the filename when `valid = 0`
+- `DiagnosticSign*` - Used for the signs that display the quickfix error type
+
+## API
+
+<!-- API -->
+
+### expand(opts)
+
+`expand(opts)` \
+Expand the context around the quickfix results.
+
+| Param | Type | Desc |
+| ---------------- | ------------------------- | -------------------------------------------------------------- |
+| opts | `nil\|quicker.ExpandOpts` | |
+| >before | `nil\|integer` | Number of lines of context to show before the line (default 2) |
+| >after | `nil\|integer` | Number of lines of context to show after the line (default 2) |
+| >add_to_existing | `nil\|boolean` | |
+| >loclist_win | `nil\|integer` | |
+
+**Note:**
+<pre>
+If there are multiple quickfix items for the same line of a file, only the first
+one will remain after calling expand().
+</pre>
+
+### collapse()
+
+`collapse()` \
+Collapse the context around quickfix results, leaving only the `valid` items.
+
+
+### toggle_expand(opts)
+
+`toggle_expand(opts)` \
+Toggle the expanded context around the quickfix results.
+
+| Param | Type | Desc |
+| ---------------- | ------------------------- | -------------------------------------------------------------- |
+| opts | `nil\|quicker.ExpandOpts` | |
+| >before | `nil\|integer` | Number of lines of context to show before the line (default 2) |
+| >after | `nil\|integer` | Number of lines of context to show after the line (default 2) |
+| >add_to_existing | `nil\|boolean` | |
+| >loclist_win | `nil\|integer` | |
+
+### refresh(loclist_win, opts)
+
+`refresh(loclist_win, opts)` \
+Update the quickfix list with the current buffer text for each item.
+
+| Param | Type | Desc |
+| ----------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------- |
+| loclist_win | `nil\|integer` | |
+| opts | `nil\|quicker.RefreshOpts` | |
+| >keep_diagnostics | `nil\|boolean` | If a line has a diagnostic type, keep the original text and display it as virtual text after refreshing from source. |
+
+### is_open(loclist_win)
+
+`is_open(loclist_win)`
+
+| Param | Type | Desc |
+| ----------- | -------------- | ---------------------------------------------------------------------- |
+| loclist_win | `nil\|integer` | Check if loclist is open for the given window. If nil, check quickfix. |
+
+### toggle(opts)
+
+`toggle(opts)` \
+Toggle the quickfix or loclist window.
+
+| Param | Type | Desc |
+| -------------- | -------------------------- | ----------------------------------------------------------------------------------- |
+| opts | `nil\|quicker.OpenOpts` | |
+| >loclist | `nil\|boolean` | Toggle the loclist instead of the quickfix list |
+| >focus | `nil\|boolean` | Focus the quickfix window after toggling (default false) |
+| >height | `nil\|integer` | Height of the quickfix window when opened. Defaults to number of items in the list. |
+| >min_height | `nil\|integer` | Minimum height of the quickfix window. Default 4. |
+| >max_height | `nil\|integer` | Maximum height of the quickfix window. Default 10. |
+| >open_cmd_mods | `nil\|quicker.OpenCmdMods` | A table of modifiers for the quickfix or loclist open commands. |
+
+### open(opts)
+
+`open(opts)` \
+Open the quickfix or loclist window.
+
+| Param | Type | Desc |
+| -------------- | -------------------------- | ----------------------------------------------------------------------------------- |
+| opts | `nil\|quicker.OpenOpts` | |
+| >loclist | `nil\|boolean` | Toggle the loclist instead of the quickfix list |
+| >focus | `nil\|boolean` | Focus the quickfix window after toggling (default false) |
+| >height | `nil\|integer` | Height of the quickfix window when opened. Defaults to number of items in the list. |
+| >min_height | `nil\|integer` | Minimum height of the quickfix window. Default 4. |
+| >max_height | `nil\|integer` | Maximum height of the quickfix window. Default 10. |
+| >open_cmd_mods | `nil\|quicker.OpenCmdMods` | A table of modifiers for the quickfix or loclist open commands. |
+
+### close(opts)
+
+`close(opts)` \
+Close the quickfix or loclist window.
+
+| Param | Type | Desc |
+| -------- | ------------------------ | ---------------------------------------------- |
+| opts | `nil\|quicker.CloseOpts` | |
+| >loclist | `nil\|boolean` | Close the loclist instead of the quickfix list |
+<!-- /API -->
+
+## Other Plugins
+
+In general quicker.nvim should play nice with other quickfix plugins (🟢), except if they change the
+format of the quickfix buffer. Quicker.nvim relies on owning the `:help quickfixtextfunc` for the
+other features to function, so some other plugins you may need to disable or not use parts of their
+functionality (🟡). Some plugins have features that completely conflict with quicker.nvim (🔴).
+
+- 🟢 [nvim-bqf](https://github.com/kevinhwang91/nvim-bqf) - Another bundle of several improvements including a floating preview window and fzf integration.
+- 🟢 [vim-qf](https://github.com/romainl/vim-qf) - Adds some useful mappings and default behaviors.
+- 🟡 [trouble.nvim](https://github.com/folke/trouble.nvim) - A custom UI for displaying quickfix and many other lists. Does not conflict with quicker.nvim, but instead presents an alternative way to manage and view the quickfix.
+- 🟡 [listish.nvim](https://github.com/arsham/listish.nvim) - Provides utilities for adding items to the quickfix and theming (which conflicts with quicker.nvim).
+- 🔴 [quickfix-reflector.vim](https://github.com/stefandtw/quickfix-reflector.vim) - Also provides an "editable quickfix". I used this for many years and would recommend it.
+- 🔴 [replacer.nvim](https://github.com/gabrielpoca/replacer.nvim) - Another "editable quickfix" plugin.
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/doc/quicker.txt b/mut/neovim/pack/plugins/start/quicker.nvim/doc/quicker.txt
new file mode 100644
index 0000000..cf3dbe7
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/doc/quicker.txt
@@ -0,0 +1,181 @@
+*quicker.txt*
+*Quicker* *quicker* *quicker.nvim*
+--------------------------------------------------------------------------------
+CONTENTS *quicker-contents*
+
+ 1. Options |quicker-options|
+ 2. Api |quicker-api|
+
+--------------------------------------------------------------------------------
+OPTIONS *quicker-options*
+
+>lua
+ require("quicker").setup({
+ -- Local options to set for quickfix
+ opts = {
+ buflisted = false,
+ number = false,
+ relativenumber = false,
+ signcolumn = "auto",
+ winfixheight = true,
+ wrap = false,
+ },
+ -- Set to false to disable the default options in `opts`
+ use_default_opts = true,
+ -- Keymaps to set for the quickfix buffer
+ keys = {
+ -- { ">", "<cmd>lua require('quicker').expand()<CR>", desc = "Expand quickfix content" },
+ },
+ -- Callback function to run any custom logic or keymaps for the quickfix buffer
+ on_qf = function(bufnr) end,
+ edit = {
+ -- Enable editing the quickfix like a normal buffer
+ enabled = true,
+ -- Set to true to write buffers after applying edits.
+ -- Set to "unmodified" to only write unmodified buffers.
+ autosave = "unmodified",
+ },
+ -- Keep the cursor to the right of the filename and lnum columns
+ constrain_cursor = true,
+ highlight = {
+ -- Use treesitter highlighting
+ treesitter = true,
+ -- Use LSP semantic token highlighting
+ lsp = true,
+ -- Load the referenced buffers to apply more accurate highlights (may be slow)
+ load_buffers = true,
+ },
+ follow = {
+ -- When quickfix window is open, scroll to closest item to the cursor
+ enabled = false,
+ },
+ -- Map of quickfix item type to icon
+ type_icons = {
+ E = "󰅚 ",
+ W = "󰀪 ",
+ I = " ",
+ N = " ",
+ H = " ",
+ },
+ -- Border characters
+ borders = {
+ vert = "┃",
+ -- Strong headers separate results from different files
+ strong_header = "━",
+ strong_cross = "╋",
+ strong_end = "┫",
+ -- Soft headers separate results within the same file
+ soft_header = "╌",
+ soft_cross = "╂",
+ soft_end = "┨",
+ },
+ -- How to trim the leading whitespace from results. Can be 'all', 'common', or false
+ trim_leading_whitespace = "common",
+ -- Maximum width of the filename column
+ max_filename_width = function()
+ return math.floor(math.min(95, vim.o.columns / 2))
+ end,
+ -- How far the header should extend to the right
+ header_length = function(type, start_col)
+ return vim.o.columns - start_col
+ end,
+ })
+<
+
+--------------------------------------------------------------------------------
+API *quicker-api*
+
+expand({opts}) *quicker.expand*
+ Expand the context around the quickfix results.
+
+ Parameters:
+ {opts} `nil|quicker.ExpandOpts`
+ {before} `nil|integer` Number of lines of context to show
+ before the line (default 2)
+ {after} `nil|integer` Number of lines of context to show
+ after the line (default 2)
+ {add_to_existing} `nil|boolean`
+ {loclist_win} `nil|integer`
+
+ Note:
+ If there are multiple quickfix items for the same line of a file, only the first
+ one will remain after calling expand().
+
+collapse() *quicker.collapse*
+ Collapse the context around quickfix results, leaving only the `valid`
+ items.
+
+
+toggle_expand({opts}) *quicker.toggle_expand*
+ Toggle the expanded context around the quickfix results.
+
+ Parameters:
+ {opts} `nil|quicker.ExpandOpts`
+ {before} `nil|integer` Number of lines of context to show
+ before the line (default 2)
+ {after} `nil|integer` Number of lines of context to show
+ after the line (default 2)
+ {add_to_existing} `nil|boolean`
+ {loclist_win} `nil|integer`
+
+refresh({loclist_win}, {opts}) *quicker.refresh*
+ Update the quickfix list with the current buffer text for each item.
+
+ Parameters:
+ {loclist_win} `nil|integer`
+ {opts} `nil|quicker.RefreshOpts`
+ {keep_diagnostics} `nil|boolean` If a line has a diagnostic type, keep
+ the original text and display it as virtual text
+ after refreshing from source.
+
+is_open({loclist_win}) *quicker.is_open*
+
+ Parameters:
+ {loclist_win} `nil|integer` Check if loclist is open for the given window.
+ If nil, check quickfix.
+
+toggle({opts}) *quicker.toggle*
+ Toggle the quickfix or loclist window.
+
+ Parameters:
+ {opts} `nil|quicker.OpenOpts`
+ {loclist} `nil|boolean` Toggle the loclist instead of the
+ quickfix list
+ {focus} `nil|boolean` Focus the quickfix window after toggling
+ (default false)
+ {height} `nil|integer` Height of the quickfix window when
+ opened. Defaults to number of items in the list.
+ {min_height} `nil|integer` Minimum height of the quickfix window.
+ Default 4.
+ {max_height} `nil|integer` Maximum height of the quickfix window.
+ Default 10.
+ {open_cmd_mods} `nil|quicker.OpenCmdMods` A table of modifiers for the
+ quickfix or loclist open commands.
+
+open({opts}) *quicker.open*
+ Open the quickfix or loclist window.
+
+ Parameters:
+ {opts} `nil|quicker.OpenOpts`
+ {loclist} `nil|boolean` Toggle the loclist instead of the
+ quickfix list
+ {focus} `nil|boolean` Focus the quickfix window after toggling
+ (default false)
+ {height} `nil|integer` Height of the quickfix window when
+ opened. Defaults to number of items in the list.
+ {min_height} `nil|integer` Minimum height of the quickfix window.
+ Default 4.
+ {max_height} `nil|integer` Maximum height of the quickfix window.
+ Default 10.
+ {open_cmd_mods} `nil|quicker.OpenCmdMods` A table of modifiers for the
+ quickfix or loclist open commands.
+
+close({opts}) *quicker.close*
+ Close the quickfix or loclist window.
+
+ Parameters:
+ {opts} `nil|quicker.CloseOpts`
+ {loclist} `nil|boolean` Close the loclist instead of the quickfix list
+
+================================================================================
+vim:tw=80:ts=2:ft=help:norl:syntax=help:
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/config.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/config.lua
new file mode 100644
index 0000000..716f010
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/config.lua
@@ -0,0 +1,189 @@
+local default_config = {
+ -- Local options to set for quickfix
+ opts = {
+ buflisted = false,
+ number = false,
+ relativenumber = false,
+ signcolumn = "auto",
+ winfixheight = true,
+ wrap = false,
+ },
+ -- Set to false to disable the default options in `opts`
+ use_default_opts = true,
+ -- Keymaps to set for the quickfix buffer
+ keys = {
+ -- { ">", "<cmd>lua require('quicker').expand()<CR>", desc = "Expand quickfix content" },
+ },
+ -- Callback function to run any custom logic or keymaps for the quickfix buffer
+ on_qf = function(bufnr) end,
+ edit = {
+ -- Enable editing the quickfix like a normal buffer
+ enabled = true,
+ -- Set to true to write buffers after applying edits.
+ -- Set to "unmodified" to only write unmodified buffers.
+ autosave = "unmodified",
+ },
+ -- Keep the cursor to the right of the filename and lnum columns
+ constrain_cursor = true,
+ highlight = {
+ -- Use treesitter highlighting
+ treesitter = true,
+ -- Use LSP semantic token highlighting
+ lsp = true,
+ -- Load the referenced buffers to apply more accurate highlights (may be slow)
+ load_buffers = true,
+ },
+ follow = {
+ -- When quickfix window is open, scroll to closest item to the cursor
+ enabled = false,
+ },
+ -- Map of quickfix item type to icon
+ type_icons = {
+ E = "󰅚 ",
+ W = "󰀪 ",
+ I = " ",
+ N = " ",
+ H = " ",
+ },
+ -- Border characters
+ borders = {
+ vert = "┃",
+ -- Strong headers separate results from different files
+ strong_header = "━",
+ strong_cross = "╋",
+ strong_end = "┫",
+ -- Soft headers separate results within the same file
+ soft_header = "╌",
+ soft_cross = "╂",
+ soft_end = "┨",
+ },
+ -- How to trim the leading whitespace from results. Can be 'all', 'common', or false
+ trim_leading_whitespace = "common",
+ -- Maximum width of the filename column
+ max_filename_width = function()
+ return math.floor(math.min(95, vim.o.columns / 2))
+ end,
+ -- How far the header should extend to the right
+ header_length = function(type, start_col)
+ return vim.o.columns - start_col
+ end,
+}
+
+---@alias quicker.TrimEnum "all"|"common"|false
+
+---@class quicker.Config
+---@field on_qf fun(bufnr: number)
+---@field opts table<string, any>
+---@field keys quicker.Keymap[]
+---@field use_default_opts boolean
+---@field constrain_cursor boolean
+---@field highlight quicker.HighlightConfig
+---@field follow quicker.FollowConfig
+---@field edit quicker.EditConfig
+---@field type_icons table<string, string>
+---@field borders quicker.Borders
+---@field trim_leading_whitespace quicker.TrimEnum
+---@field max_filename_width fun(): integer
+---@field header_length fun(type: "hard"|"soft", start_col: integer): integer
+local M = {}
+
+---@class (exact) quicker.SetupOptions
+---@field on_qf? fun(bufnr: number) Callback function to run any custom logic or keymaps for the quickfix buffer
+---@field opts? table<string, any> Local options to set for quickfix
+---@field keys? quicker.Keymap[] Keymaps to set for the quickfix buffer
+---@field use_default_opts? boolean Set to false to disable the default options in `opts`
+---@field constrain_cursor? boolean Keep the cursor to the right of the filename and lnum columns
+---@field highlight? quicker.SetupHighlightConfig Configure syntax highlighting
+---@field follow? quicker.SetupFollowConfig Configure cursor following
+---@field edit? quicker.SetupEditConfig
+---@field type_icons? table<string, string> Map of quickfix item type to icon
+---@field borders? quicker.SetupBorders Characters used for drawing the borders
+---@field trim_leading_whitespace? quicker.TrimEnum How to trim the leading whitespace from results
+---@field max_filename_width? fun(): integer Maximum width of the filename column
+---@field header_length? fun(type: "hard"|"soft", start_col: integer): integer How far the header should extend to the right
+
+local has_setup = false
+---@param opts? quicker.SetupOptions
+M.setup = function(opts)
+ opts = opts or {}
+ local new_conf = vim.tbl_deep_extend("keep", opts, default_config)
+
+ for k, v in pairs(new_conf) do
+ M[k] = v
+ end
+
+ -- Shim for when this was only a boolean. 'true' meant 'common'
+ if M.trim_leading_whitespace == true then
+ M.trim_leading_whitespace = "common"
+ end
+
+ -- Remove the default opts values if use_default_opts is false
+ if not new_conf.use_default_opts then
+ M.opts = opts.opts or {}
+ end
+ has_setup = true
+end
+
+---@class (exact) quicker.Keymap
+---@field [1] string Key sequence
+---@field [2] any Command to run
+---@field desc? string
+---@field mode? string
+---@field expr? boolean
+---@field nowait? boolean
+---@field remap? boolean
+---@field replace_keycodes? boolean
+---@field silent? boolean
+
+---@class (exact) quicker.Borders
+---@field vert string
+---@field strong_header string
+---@field strong_cross string
+---@field strong_end string
+---@field soft_header string
+---@field soft_cross string
+---@field soft_end string
+
+---@class (exact) quicker.SetupBorders
+---@field vert? string
+---@field strong_header? string Strong headers separate results from different files
+---@field strong_cross? string
+---@field strong_end? string
+---@field soft_header? string Soft headers separate results within the same file
+---@field soft_cross? string
+---@field soft_end? string
+
+---@class (exact) quicker.HighlightConfig
+---@field treesitter boolean
+---@field lsp boolean
+---@field load_buffers boolean
+
+---@class (exact) quicker.SetupHighlightConfig
+---@field treesitter? boolean Enable treesitter syntax highlighting
+---@field lsp? boolean Use LSP semantic token highlighting
+---@field load_buffers? boolean Load the referenced buffers to apply more accurate highlights (may be slow)
+
+---@class (exact) quicker.FollowConfig
+---@field enabled boolean
+
+---@class (exact) quicker.SetupFollowConfig
+---@field enabled? boolean
+
+---@class (exact) quicker.EditConfig
+---@field enabled boolean
+---@field autosave boolean|"unmodified"
+
+---@class (exact) quicker.SetupEditConfig
+---@field enabled? boolean
+---@field autosave? boolean|"unmodified"
+
+return setmetatable(M, {
+ -- If the user hasn't called setup() yet, make sure we correctly set up the config object so there
+ -- aren't random crashes.
+ __index = function(self, key)
+ if not has_setup then
+ M.setup()
+ end
+ return rawget(self, key)
+ end,
+})
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/context.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/context.lua
new file mode 100644
index 0000000..f5bbb87
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/context.lua
@@ -0,0 +1,315 @@
+local util = require("quicker.util")
+
+local M = {}
+
+---@class (exact) quicker.QFContext
+---@field num_before integer
+---@field num_after integer
+
+---@class (exact) quicker.ExpandOpts
+---@field before? integer Number of lines of context to show before the line (default 2)
+---@field after? integer Number of lines of context to show after the line (default 2)
+---@field add_to_existing? boolean
+---@field loclist_win? integer
+
+---@param item QuickFixItem
+---@param new_text string
+local function update_item_text_keep_diagnostics(item, new_text)
+ -- If this is an "error" item, replace the text with the source line and store that text
+ -- in the user data so we can add it as virtual text later
+ if item.type ~= "" and not vim.endswith(new_text, item.text) then
+ local user_data = util.get_user_data(item)
+ if not user_data.error_text then
+ user_data.error_text = item.text
+ item.user_data = user_data
+ end
+ end
+ item.text = new_text
+end
+
+---@param opts? quicker.ExpandOpts
+function M.expand(opts)
+ opts = opts or {}
+ if not opts.loclist_win and util.get_win_type(0) == "l" then
+ opts.loclist_win = vim.api.nvim_get_current_win()
+ end
+ local qf_list
+ if opts.loclist_win then
+ qf_list = vim.fn.getloclist(opts.loclist_win, { all = 0 })
+ else
+ qf_list = vim.fn.getqflist({ all = 0 })
+ end
+ local winid = qf_list.winid
+ if not winid then
+ vim.notify("Cannot find quickfix window", vim.log.levels.ERROR)
+ return
+ end
+ local ctx = qf_list.context or {}
+ if type(ctx) ~= "table" then
+ -- If the quickfix had a non-table context, we're going to have to overwrite it
+ ctx = {}
+ end
+ ---@type quicker.QFContext
+ local quicker_ctx = ctx.quicker
+ if not quicker_ctx then
+ quicker_ctx = { num_before = 0, num_after = 0 }
+ ctx.quicker = quicker_ctx
+ end
+ local curpos = vim.api.nvim_win_get_cursor(winid)[1]
+ local cur_item = qf_list.items[curpos]
+ local newpos
+
+ -- calculate the number of lines to show before and after the current line
+ local num_before = opts.before or 2
+ if opts.add_to_existing then
+ num_before = num_before + quicker_ctx.num_before
+ end
+ num_before = math.max(0, num_before)
+ quicker_ctx.num_before = num_before
+ local num_after = opts.after or 2
+ if opts.add_to_existing then
+ num_after = num_after + quicker_ctx.num_after
+ end
+ num_after = math.max(0, num_after)
+ quicker_ctx.num_after = num_after
+
+ local items = {}
+ ---@type nil|QuickFixItem
+ local prev_item
+ ---@param i integer
+ ---@return nil|QuickFixItem
+ local function get_next_item(i)
+ local item = qf_list.items[i]
+ for j = i + 1, #qf_list.items do
+ local next_item = qf_list.items[j]
+ -- Next valid item that is on a different line (since we dedupe same-line items)
+ if
+ next_item.valid == 1 and (item.bufnr ~= next_item.bufnr or item.lnum ~= next_item.lnum)
+ then
+ return next_item
+ end
+ end
+ end
+
+ for i, item in ipairs(qf_list.items) do
+ (function()
+ ---@cast item QuickFixItem
+ if item.valid == 0 or item.bufnr == 0 then
+ return
+ end
+
+ if not vim.api.nvim_buf_is_loaded(item.bufnr) then
+ vim.fn.bufload(item.bufnr)
+ end
+
+ local overlaps_previous = false
+ local header_type = "hard"
+ local low = math.max(0, item.lnum - 1 - num_before)
+ if prev_item then
+ if prev_item.bufnr == item.bufnr then
+ -- If this is the second match on the same line, skip this item
+ if prev_item.lnum == item.lnum then
+ return
+ end
+ header_type = "soft"
+ if prev_item.lnum + num_after >= low then
+ low = math.min(item.lnum - 1, prev_item.lnum + num_after)
+ overlaps_previous = true
+ end
+ end
+ end
+
+ local high = item.lnum + num_after
+ local next_item = get_next_item(i)
+ if next_item then
+ if next_item.bufnr == item.bufnr and next_item.lnum <= high then
+ high = next_item.lnum - 1
+ end
+ end
+
+ local item_start_idx = #items
+ local lines = vim.api.nvim_buf_get_lines(item.bufnr, low, high, false)
+ for j, line in ipairs(lines) do
+ if j + low == item.lnum then
+ update_item_text_keep_diagnostics(item, line)
+ table.insert(items, item)
+ else
+ table.insert(items, {
+ bufnr = item.bufnr,
+ lnum = low + j,
+ text = line,
+ valid = 0,
+ user_data = { lnum = low + j },
+ })
+ end
+ if cur_item.bufnr == item.bufnr and cur_item.lnum == low + j then
+ newpos = #items
+ end
+ end
+
+ -- Add the header to the first item in this sequence, if one is needed
+ if prev_item and not overlaps_previous then
+ local first_item = items[item_start_idx + 1]
+ if first_item then
+ first_item.user_data = first_item.user_data or {}
+ first_item.user_data.header = header_type
+ end
+ end
+
+ prev_item = item
+ end)()
+
+ if i == curpos and not newpos then
+ newpos = #items
+ end
+ end
+
+ if opts.loclist_win then
+ vim.fn.setloclist(
+ opts.loclist_win,
+ {},
+ "r",
+ { items = items, title = qf_list.title, context = ctx }
+ )
+ else
+ vim.fn.setqflist({}, "r", { items = items, title = qf_list.title, context = ctx })
+ end
+
+ pcall(vim.api.nvim_win_set_cursor, qf_list.winid, { newpos, 0 })
+end
+
+---@class (exact) quicker.CollapseArgs
+---@field loclist_win? integer
+---
+function M.collapse(opts)
+ opts = opts or {}
+ if not opts.loclist_win and util.get_win_type(0) == "l" then
+ opts.loclist_win = vim.api.nvim_get_current_win()
+ end
+ local curpos = vim.api.nvim_win_get_cursor(0)[1]
+ local qf_list
+ if opts.loclist_win then
+ qf_list = vim.fn.getloclist(opts.loclist_win, { all = 0 })
+ else
+ qf_list = vim.fn.getqflist({ all = 0 })
+ end
+ local items = {}
+ local last_item
+ for i, item in ipairs(qf_list.items) do
+ if item.valid == 1 then
+ if item.user_data then
+ -- Clear the header, if present
+ item.user_data.header = nil
+ end
+ table.insert(items, item)
+ if i <= curpos then
+ last_item = #items
+ end
+ end
+ end
+
+ vim.tbl_filter(function(item)
+ return item.valid == 1
+ end, qf_list.items)
+
+ local ctx = qf_list.context or {}
+ if type(ctx) == "table" then
+ local quicker_ctx = ctx.quicker
+ if quicker_ctx then
+ quicker_ctx = { num_before = 0, num_after = 0 }
+ ctx.quicker = quicker_ctx
+ end
+ end
+
+ if opts.loclist_win then
+ vim.fn.setloclist(
+ opts.loclist_win,
+ {},
+ "r",
+ { items = items, title = qf_list.title, context = qf_list.context }
+ )
+ else
+ vim.fn.setqflist({}, "r", { items = items, title = qf_list.title, context = qf_list.context })
+ end
+ if qf_list.winid then
+ if last_item then
+ vim.api.nvim_win_set_cursor(qf_list.winid, { last_item, 0 })
+ end
+ end
+end
+
+---@param opts? quicker.ExpandOpts
+function M.toggle(opts)
+ opts = opts or {}
+ local ctx
+ if opts.loclist_win then
+ ctx = vim.fn.getloclist(opts.loclist_win, { context = 0 }).context
+ else
+ ctx = vim.fn.getqflist({ context = 0 }).context
+ end
+
+ if
+ type(ctx) == "table"
+ and ctx.quicker
+ and (ctx.quicker.num_before > 0 or ctx.quicker.num_after > 0)
+ then
+ M.collapse()
+ else
+ M.expand(opts)
+ end
+end
+
+---@class (exact) quicker.RefreshOpts
+---@field keep_diagnostics? boolean If a line has a diagnostic type, keep the original text and display it as virtual text after refreshing from source.
+
+---@param loclist_win? integer
+---@param opts? quicker.RefreshOpts
+function M.refresh(loclist_win, opts)
+ opts = vim.tbl_extend("keep", opts or {}, { keep_diagnostics = true })
+ if not loclist_win then
+ local ok, qf = pcall(vim.fn.getloclist, 0, { filewinid = 0 })
+ if ok and qf.filewinid and qf.filewinid ~= 0 then
+ loclist_win = qf.filewinid
+ end
+ end
+
+ local qf_list
+ if loclist_win then
+ qf_list = vim.fn.getloclist(loclist_win, { all = 0 })
+ else
+ qf_list = vim.fn.getqflist({ all = 0 })
+ end
+
+ local items = {}
+ for _, item in ipairs(qf_list.items) do
+ if item.bufnr ~= 0 and item.lnum ~= 0 then
+ if not vim.api.nvim_buf_is_loaded(item.bufnr) then
+ vim.fn.bufload(item.bufnr)
+ end
+ local line = vim.api.nvim_buf_get_lines(item.bufnr, item.lnum - 1, item.lnum, false)[1]
+ if line then
+ if opts.keep_diagnostics then
+ update_item_text_keep_diagnostics(item, line)
+ else
+ item.text = line
+ end
+ table.insert(items, item)
+ end
+ else
+ table.insert(items, item)
+ end
+ end
+
+ if loclist_win then
+ vim.fn.setloclist(
+ loclist_win,
+ {},
+ "r",
+ { items = items, title = qf_list.title, context = qf_list.context }
+ )
+ else
+ vim.fn.setqflist({}, "r", { items = items, title = qf_list.title, context = qf_list.context })
+ end
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/cursor.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/cursor.lua
new file mode 100644
index 0000000..58ee587
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/cursor.lua
@@ -0,0 +1,44 @@
+local M = {}
+
+local function constrain_cursor()
+ local display = require("quicker.display")
+ local cur = vim.api.nvim_win_get_cursor(0)
+ local line = vim.api.nvim_buf_get_lines(0, cur[1] - 1, cur[1], true)[1]
+ local idx = line:find(display.EM_QUAD, 1, true)
+ if not idx then
+ return
+ end
+ local min_col = idx + display.EM_QUAD_LEN - 1
+ if cur[2] < min_col then
+ vim.api.nvim_win_set_cursor(0, { cur[1], min_col })
+ end
+end
+
+---@param bufnr number
+function M.constrain_cursor(bufnr)
+ -- HACK: we have to defer this call because sometimes the autocmds don't take effect.
+ vim.schedule(function()
+ if not vim.api.nvim_buf_is_valid(bufnr) then
+ return
+ end
+ local aug = vim.api.nvim_create_augroup("quicker", { clear = false })
+ vim.api.nvim_create_autocmd("InsertEnter", {
+ desc = "Constrain quickfix cursor position",
+ group = aug,
+ nested = true,
+ buffer = bufnr,
+ -- For some reason the cursor bounces back to its original position,
+ -- so we have to defer the call
+ callback = vim.schedule_wrap(constrain_cursor),
+ })
+ vim.api.nvim_create_autocmd({ "CursorMoved", "ModeChanged" }, {
+ desc = "Constrain quickfix cursor position",
+ nested = true,
+ group = aug,
+ buffer = bufnr,
+ callback = constrain_cursor,
+ })
+ end)
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/display.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/display.lua
new file mode 100644
index 0000000..5c551d3
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/display.lua
@@ -0,0 +1,601 @@
+local config = require("quicker.config")
+local fs = require("quicker.fs")
+local highlight = require("quicker.highlight")
+local util = require("quicker.util")
+
+local M = {}
+
+local EM_QUAD = " "
+local EM_QUAD_LEN = EM_QUAD:len()
+M.EM_QUAD = EM_QUAD
+M.EM_QUAD_LEN = EM_QUAD_LEN
+
+---@class (exact) QuickFixUserData
+---@field header? "hard"|"soft" When present, this line is a header
+---@field lnum? integer Encode the lnum separately for valid=0 items
+---@field error_text? string Error text to be added as virtual text on the line
+
+---@class (exact) QuickFixItem
+---@field text string
+---@field type string
+---@field lnum integer line number in the buffer (first line is 1)
+---@field end_lnum integer end of line number if the item is multiline
+---@field col integer column number (first column is 1)
+---@field end_col integer end of column number if the item has range
+---@field vcol 0|1 if true "col" is visual column. If false "col" is byte index
+---@field nr integer error number
+---@field pattern string search pattern used to locate the error
+---@field bufnr integer number of buffer that has the file name
+---@field module string
+---@field valid 0|1
+---@field user_data? any
+
+---@param type string
+---@return string
+local function get_icon(type)
+ return config.type_icons[type:upper()] or "U"
+end
+
+local sign_highlight_map = {
+ E = "DiagnosticSignError",
+ W = "DiagnosticSignWarn",
+ I = "DiagnosticSignInfo",
+ H = "DiagnosticSignHint",
+ N = "DiagnosticSignHint",
+}
+local virt_text_highlight_map = {
+ E = "DiagnosticVirtualTextError",
+ W = "DiagnosticVirtualTextWarn",
+ I = "DiagnosticVirtualTextInfo",
+ H = "DiagnosticVirtualTextHint",
+ N = "DiagnosticVirtualTextHint",
+}
+
+---@param item QuickFixItem
+M.get_filename_from_item = function(item)
+ if item.module and item.module ~= "" then
+ return item.module
+ elseif item.bufnr > 0 then
+ local bufname = vim.api.nvim_buf_get_name(item.bufnr)
+ local path = fs.shorten_path(bufname)
+ local max_len = config.max_filename_width()
+ if max_len == 0 then
+ return ""
+ elseif path:len() > max_len then
+ path = "…" .. path:sub(path:len() - max_len - 1)
+ end
+ return path
+ else
+ return ""
+ end
+end
+
+local _col_width_cache = {}
+---@param id integer
+---@param items QuickFixItem[]
+---@return integer
+local function get_cached_qf_col_width(id, items)
+ local cached = _col_width_cache[id]
+ if not cached or cached[2] ~= #items then
+ local max_len = 0
+ for _, item in ipairs(items) do
+ max_len = math.max(max_len, vim.api.nvim_strwidth(M.get_filename_from_item(item)))
+ end
+
+ cached = { max_len, #items }
+ _col_width_cache[id] = cached
+ end
+ return cached[1]
+end
+
+---@param items QuickFixItem[]
+---@return table<integer, string>
+local function calc_whitespace_prefix(items)
+ local prefixes = {}
+ if config.trim_leading_whitespace ~= "common" then
+ return prefixes
+ end
+
+ for _, item in ipairs(items) do
+ if item.bufnr ~= 0 and not item.text:match("^%s*$") then
+ local prefix = prefixes[item.bufnr]
+ if not prefix or not vim.startswith(item.text, prefix) then
+ local new_prefix = item.text:match("^%s*")
+
+ -- The new line should have strictly less whitespace as the previous line. If not, then
+ -- there is some whitespace disagreement (e.g. tabs vs spaces) and we should not try to trim
+ -- anything.
+ if prefix and not vim.startswith(prefix, new_prefix) then
+ new_prefix = ""
+ end
+ prefixes[item.bufnr] = new_prefix
+
+ if new_prefix == "" then
+ break
+ end
+ end
+ end
+ end
+ return prefixes
+end
+
+-- Highlighting can be slow because it requires loading buffers and parsing them with treesitter, so
+-- we pipeline it and break it up with defers to keep the editor responsive.
+local add_qf_highlights
+-- We have two queues, one to apply "fast" highlights, and one that will load the buffer (slow)
+-- and then apply more correct highlights. The second queue is always processed after the first.
+local _pending_fast_highlights = {}
+local _pending_bufload_highlights = {}
+local _running = false
+local function do_next_highlight()
+ if _running then
+ return
+ end
+ _running = true
+
+ local next_info = table.remove(_pending_fast_highlights, 1)
+ if not next_info then
+ next_info = table.remove(_pending_bufload_highlights, 1)
+ end
+
+ if next_info then
+ local ok, err = xpcall(add_qf_highlights, debug.traceback, next_info)
+ if not ok then
+ vim.api.nvim_err_writeln(err)
+ end
+ else
+ _running = false
+ return
+ end
+
+ vim.defer_fn(function()
+ _running = false
+ do_next_highlight()
+ end, 20)
+end
+
+---@param queue QuickFixTextFuncInfo[]
+---@param info QuickFixTextFuncInfo
+local function add_info_to_queue(queue, info)
+ for _, i in ipairs(queue) do
+ -- If we're already processing a highlight for this quickfix, just expand the range
+ if i.id == info.id and i.winid == info.winid and i.quickfix == info.quickfix then
+ i.start_idx = math.min(i.start_idx, info.start_idx)
+ i.end_idx = math.max(i.end_idx, info.end_idx)
+ return
+ end
+ end
+ table.insert(queue, info)
+end
+
+---@param info QuickFixTextFuncInfo
+local function schedule_highlights(info)
+ -- If this info already has force_bufload, then we don't want to add it to the first queue.
+ if not info.force_bufload then
+ add_info_to_queue(_pending_fast_highlights, info)
+ end
+
+ if config.highlight.load_buffers then
+ local info2 = vim.deepcopy(info)
+ info2.force_bufload = true
+ add_info_to_queue(_pending_bufload_highlights, info2)
+ end
+
+ vim.schedule(do_next_highlight)
+end
+
+---@param qfbufnr integer
+---@param item QuickFixItem
+---@param line string
+---@param lnum integer
+local function add_item_highlights_from_buf(qfbufnr, item, line, lnum)
+ local prefixes = vim.b[qfbufnr].qf_prefixes or {}
+ local ns = vim.api.nvim_create_namespace("quicker_highlights")
+ -- TODO re-apply highlights when a buffer is loaded or a LSP receives semantic tokens
+ local src_line = vim.api.nvim_buf_get_lines(item.bufnr, item.lnum - 1, item.lnum, false)[1]
+ if not src_line then
+ return
+ end
+
+ -- If the lines differ only in leading whitespace, we should add highlights anyway and adjust
+ -- the offset.
+ local item_space = item.text:match("^%s*"):len()
+ local src_space = src_line:match("^%s*"):len()
+
+ -- Only add highlights if the text in the quickfix matches the source line
+ if item.text:sub(item_space + 1) == src_line:sub(src_space + 1) then
+ local offset = line:find(EM_QUAD, 1, true) + EM_QUAD_LEN - 1
+ local prefix = prefixes[item.bufnr]
+ if type(prefix) == "string" then
+ -- Since prefixes get deserialized from vim.b, if there are holes in the map they get
+ -- filled with `vim.NIL`, so we have to check that the retrieved value is a string.
+ offset = offset - prefix:len()
+ end
+ offset = offset - src_space + item_space
+ if config.trim_leading_whitespace == "all" then
+ offset = offset - item_space
+ end
+
+ -- Add treesitter highlights
+ if config.highlight.treesitter then
+ for _, hl in ipairs(highlight.buf_get_ts_highlights(item.bufnr, item.lnum)) do
+ local start_col, end_col, hl_group = hl[1], hl[2], hl[3]
+ if end_col == -1 then
+ end_col = src_line:len()
+ end
+ -- If the highlight starts at the beginning of the source line, then it might be off the
+ -- buffer in the quickfix because we've removed leading whitespace. If so, clamp the value
+ -- to 0. Except, for some reason 0 gives incorrect results, but -1 works properly even
+ -- though -1 should indicate the *end* of the line. Not sure why this work, but it does.
+ local hl_start = math.max(-1, start_col + offset)
+ vim.api.nvim_buf_set_extmark(qfbufnr, ns, lnum - 1, hl_start, {
+ hl_group = hl_group,
+ end_col = end_col + offset,
+ priority = 100,
+ strict = false,
+ })
+ end
+ end
+
+ -- Add LSP semantic token highlights
+ if config.highlight.lsp then
+ for _, hl in ipairs(highlight.buf_get_lsp_highlights(item.bufnr, item.lnum)) do
+ local start_col, end_col, hl_group, priority = hl[1], hl[2], hl[3], hl[4]
+ vim.api.nvim_buf_set_extmark(qfbufnr, ns, lnum - 1, start_col + offset, {
+ hl_group = hl_group,
+ end_col = end_col + offset,
+ priority = vim.highlight.priorities.semantic_tokens + priority,
+ strict = false,
+ })
+ end
+ end
+ end
+end
+
+---@param qfbufnr integer
+---@param info QuickFixTextFuncInfo
+local function highlight_buffer_when_entered(qfbufnr, info)
+ if vim.b[qfbufnr].pending_highlight then
+ return
+ end
+ vim.api.nvim_create_autocmd("BufEnter", {
+ desc = "Highlight quickfix buffer when entered",
+ buffer = qfbufnr,
+ nested = true,
+ once = true,
+ callback = function()
+ vim.b[qfbufnr].pending_highlight = nil
+ info.start_idx = 1
+ info.end_idx = vim.api.nvim_buf_line_count(qfbufnr)
+ schedule_highlights(info)
+ end,
+ })
+ vim.b[qfbufnr].pending_highlight = true
+end
+
+---@param info QuickFixTextFuncInfo
+---@return {qfbufnr: integer, id: integer, context?: any}
+---@overload fun(info: QuickFixTextFuncInfo, all: true): {qfbufnr: integer, id: integer, items: QuickFixItem[], context?: any}
+local function load_qf(info, all)
+ local query
+ if all then
+ query = { all = 0 }
+ else
+ query = { id = info.id, items = 0, qfbufnr = 0, context = 0 }
+ end
+ if info.quickfix == 1 then
+ return vim.fn.getqflist(query)
+ else
+ return vim.fn.getloclist(info.winid, query)
+ end
+end
+
+---@param info QuickFixTextFuncInfo
+add_qf_highlights = function(info)
+ local qf_list = load_qf(info, true)
+ local qfbufnr = qf_list.qfbufnr
+ if not qfbufnr or qfbufnr == 0 then
+ return
+ elseif info.end_idx < info.start_idx then
+ return
+ end
+
+ local lines = vim.api.nvim_buf_get_lines(qfbufnr, 0, -1, false)
+ if #lines == 1 and lines[1] == "" then
+ -- If the quickfix buffer is not visible, it is possible that quickfixtextfunc has run but the
+ -- buffer has not been populated yet. If that is the case, we should exit early and ensure that
+ -- the highlighting task runs again when the buffer is opened in a window.
+ -- see https://github.com/stevearc/quicker.nvim/pull/8
+ highlight_buffer_when_entered(qfbufnr, info)
+ return
+ end
+ local ns = vim.api.nvim_create_namespace("quicker_highlights")
+
+ -- Only clear the error namespace during the first pass of "fast" highlighting
+ if not info.force_bufload then
+ local err_ns = vim.api.nvim_create_namespace("quicker_err")
+ vim.api.nvim_buf_clear_namespace(qfbufnr, err_ns, 0, -1)
+ end
+
+ local start = vim.uv.hrtime() / 1e6
+ for i = info.start_idx, info.end_idx do
+ vim.api.nvim_buf_clear_namespace(qfbufnr, ns, i - 1, i)
+ ---@type nil|QuickFixItem
+ local item = qf_list.items[i]
+ -- If the quickfix list has changed length since the async highlight job has started,
+ -- we should abort and let the next async highlight task pick it up.
+ if not item then
+ return
+ end
+
+ local line = lines[i]
+ if not line then
+ break
+ end
+ if item.bufnr ~= 0 then
+ local loaded = vim.api.nvim_buf_is_loaded(item.bufnr)
+ if not loaded and info.force_bufload then
+ vim.fn.bufload(item.bufnr)
+ loaded = true
+ end
+
+ if loaded then
+ add_item_highlights_from_buf(qfbufnr, item, line, i)
+ elseif config.highlight.treesitter then
+ local filename = vim.split(line, EM_QUAD, { plain = true })[1]
+ local offset = filename:len() + EM_QUAD_LEN
+ local text = line:sub(offset + 1)
+ for _, hl in ipairs(highlight.get_heuristic_ts_highlights(item, text)) do
+ local start_col, end_col, hl_group = hl[1], hl[2], hl[3]
+ start_col = start_col + offset
+ end_col = end_col + offset
+ vim.api.nvim_buf_set_extmark(qfbufnr, ns, i - 1, start_col, {
+ hl_group = hl_group,
+ end_col = end_col,
+ priority = 100,
+ strict = false,
+ })
+ end
+ end
+ end
+
+ local user_data = util.get_user_data(item)
+ -- Set sign if item has a type
+ if item.type and item.type ~= "" then
+ local mark = {
+ sign_text = get_icon(item.type),
+ sign_hl_group = sign_highlight_map[item.type:upper()],
+ invalidate = true,
+ }
+ if user_data.error_text then
+ mark.virt_text = {
+ { user_data.error_text, virt_text_highlight_map[item.type:upper()] or "Normal" },
+ }
+ end
+ vim.api.nvim_buf_set_extmark(qfbufnr, ns, i - 1, 0, mark)
+ end
+
+ -- If we've been processing for too long, defer to preserve editor responsiveness
+ local delta = vim.uv.hrtime() / 1e6 - start
+ if delta > 50 then
+ info.start_idx = i + 1
+ schedule_highlights(info)
+ return
+ end
+ end
+
+ vim.api.nvim_buf_clear_namespace(qf_list.qfbufnr, ns, info.end_idx, -1)
+end
+
+---@param str string
+---@param len integer
+---@return string
+local function rpad(str, len)
+ return str .. string.rep(" ", len - vim.api.nvim_strwidth(str))
+end
+
+---@param items QuickFixItem[]
+---@return integer
+local function get_lnum_width(items)
+ local max_len = 2
+ local max = 99
+ for _, item in ipairs(items) do
+ if item.lnum > max then
+ max_len = tostring(item.lnum):len()
+ max = item.lnum
+ end
+ end
+ return max_len
+end
+
+---@param text string
+---@param prefix? string
+local function remove_prefix(text, prefix)
+ local ret
+ if prefix and prefix ~= "" then
+ ret = text:sub(prefix:len() + 1)
+ else
+ ret = text
+ end
+
+ return ret
+end
+
+---@class QuickFixTextFuncInfo
+---@field id integer
+---@field start_idx integer
+---@field end_idx integer
+---@field winid integer
+---@field quickfix 1|0
+---@field force_bufload? boolean field injected by us to control if we're forcing a bufload for the syntax highlighting
+
+-- TODO when appending to a qflist, the alignment can be thrown off
+-- TODO when appending to a qflist, the prefix could mismatch earlier lines
+---@param info QuickFixTextFuncInfo
+---@return string[]
+function M.quickfixtextfunc(info)
+ local b = config.borders
+ local qf_list = load_qf(info, true)
+ local locations = {}
+ local invalid_filenames = {}
+ local headers = {}
+ local ret = {}
+ local items = qf_list.items
+ local lnum_width = get_lnum_width(items)
+ local col_width = get_cached_qf_col_width(info.id, items)
+ local lnum_fmt = string.format("%%%ds", lnum_width)
+ local prefixes = calc_whitespace_prefix(items)
+ local no_filenames = col_width == 0
+
+ local function get_virt_text(lnum)
+ -- If none of the quickfix items have filenames, we don't need the lnum column and we only need
+ -- to show a single delimiter. Technically we don't need any delimiter, but this maintains some
+ -- of the original qf behavior while being a bit more visually appealing.
+ if no_filenames then
+ return { { b.vert, "Delimiter" } }
+ else
+ return {
+ { b.vert, "Delimiter" },
+ { lnum_fmt:format(lnum), "QuickFixLineNr" },
+ { b.vert, "Delimiter" },
+ }
+ end
+ end
+
+ for i = info.start_idx, info.end_idx do
+ local item = items[i]
+ local user_data = util.get_user_data(item)
+
+ -- First check if there's a header that we need to save to render as virtual text later
+ if user_data.header == "hard" then
+ -- Header when expanded QF list
+ local pieces = {
+ string.rep(b.strong_header, col_width + 1),
+ b.strong_cross,
+ string.rep(b.strong_header, lnum_width),
+ }
+ local header_len = config.header_length("hard", col_width + lnum_width + 2)
+ if header_len > 0 then
+ table.insert(pieces, b.strong_cross)
+ table.insert(pieces, string.rep(b.strong_header, header_len))
+ else
+ table.insert(pieces, b.strong_end)
+ end
+ table.insert(headers, { i, { { table.concat(pieces, ""), "QuickFixHeaderHard" } } })
+ elseif user_data.header == "soft" then
+ -- Soft header when expanded QF list
+ local pieces = {
+ string.rep(b.soft_header, col_width + 1),
+ b.soft_cross,
+ string.rep(b.soft_header, lnum_width),
+ }
+ local header_len = config.header_length("soft", col_width + lnum_width + 2)
+ if header_len > 0 then
+ table.insert(pieces, b.soft_cross)
+ table.insert(pieces, string.rep(b.soft_header, header_len))
+ else
+ table.insert(pieces, b.soft_end)
+ end
+ table.insert(headers, { i, { { table.concat(pieces, ""), "QuickFixHeaderSoft" } } })
+ end
+
+ -- Construct the lines and save the filename + lnum to render as virtual text later
+ local trimmed_text
+ if config.trim_leading_whitespace == "all" then
+ trimmed_text = item.text:gsub("^%s*", "")
+ elseif config.trim_leading_whitespace == "common" then
+ trimmed_text = remove_prefix(item.text, prefixes[item.bufnr])
+ else
+ trimmed_text = item.text
+ end
+ if item.valid == 1 then
+ -- Matching line
+ local lnum = item.lnum == 0 and " " or item.lnum
+ local filename = rpad(M.get_filename_from_item(item), col_width)
+ table.insert(locations, get_virt_text(lnum))
+ table.insert(ret, filename .. EM_QUAD .. trimmed_text)
+ elseif user_data.lnum then
+ -- Non-matching line from quicker.nvim context lines
+ local filename = string.rep(" ", col_width)
+ table.insert(locations, get_virt_text(user_data.lnum))
+ table.insert(ret, filename .. EM_QUAD .. trimmed_text)
+ else
+ -- Other non-matching line
+ local lnum = item.lnum == 0 and " " or item.lnum
+ local filename = rpad(M.get_filename_from_item(item), col_width)
+ table.insert(locations, get_virt_text(lnum))
+ invalid_filenames[#locations] = true
+ table.insert(ret, filename .. EM_QUAD .. trimmed_text)
+ end
+ end
+
+ -- Render the filename+lnum and the headers as virtual text
+ local start_idx = info.start_idx
+ local set_virt_text
+ set_virt_text = function()
+ qf_list = load_qf(info)
+ if qf_list.qfbufnr > 0 then
+ -- Sometimes the buffer is not fully populated yet. If so, we should try again later.
+ local num_lines = vim.api.nvim_buf_line_count(qf_list.qfbufnr)
+ if num_lines < info.end_idx then
+ vim.schedule(set_virt_text)
+ return
+ end
+
+ local ns = vim.api.nvim_create_namespace("quicker_locations")
+ vim.api.nvim_buf_clear_namespace(qf_list.qfbufnr, ns, start_idx - 1, -1)
+ local header_ns = vim.api.nvim_create_namespace("quicker_headers")
+ vim.api.nvim_buf_clear_namespace(qf_list.qfbufnr, header_ns, start_idx - 1, -1)
+ local filename_ns = vim.api.nvim_create_namespace("quicker_filenames")
+ vim.api.nvim_buf_clear_namespace(qf_list.qfbufnr, filename_ns, start_idx - 1, -1)
+
+ local idmap = {}
+ local lines = vim.api.nvim_buf_get_lines(qf_list.qfbufnr, start_idx - 1, -1, false)
+ for i, loc in ipairs(locations) do
+ local end_col = lines[i]:find(EM_QUAD, 1, true) or col_width
+ local lnum = start_idx + i - 1
+ local id =
+ vim.api.nvim_buf_set_extmark(qf_list.qfbufnr, ns, lnum - 1, end_col + EM_QUAD_LEN - 1, {
+ right_gravity = false,
+ virt_text = loc,
+ virt_text_pos = "inline",
+ invalidate = true,
+ })
+ idmap[id] = lnum
+
+ -- Highlight the filename
+ vim.api.nvim_buf_set_extmark(qf_list.qfbufnr, filename_ns, lnum - 1, 0, {
+ hl_group = invalid_filenames[i] and "QuickFixFilenameInvalid" or "QuickFixFilename",
+ right_gravity = false,
+ end_col = end_col,
+ priority = 100,
+ invalidate = true,
+ })
+ end
+ vim.b[qf_list.qfbufnr].qf_ext_id_to_item_idx = idmap
+
+ for _, pair in ipairs(headers) do
+ local i, header = pair[1], pair[2]
+ local lnum = start_idx + i - 1
+ vim.api.nvim_buf_set_extmark(qf_list.qfbufnr, header_ns, lnum - 1, 0, {
+ virt_lines = { header },
+ virt_lines_above = true,
+ })
+ end
+ end
+ end
+ vim.schedule(set_virt_text)
+
+ -- If we just rendered the last item, add highlights
+ if info.end_idx == #items then
+ schedule_highlights(info)
+
+ if qf_list.qfbufnr > 0 then
+ vim.b[qf_list.qfbufnr].qf_prefixes = prefixes
+ end
+ end
+
+ return ret
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/editor.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/editor.lua
new file mode 100644
index 0000000..3f5db65
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/editor.lua
@@ -0,0 +1,405 @@
+local config = require("quicker.config")
+local display = require("quicker.display")
+local util = require("quicker.util")
+local M = {}
+
+---@class (exact) quicker.ParsedLine
+---@field filename? string
+---@field lnum? integer
+---@field text? string
+
+---@param n integer
+---@param base string
+---@param pluralized? string
+---@return string
+local function plural(n, base, pluralized)
+ if n == 1 then
+ return base
+ elseif pluralized then
+ return pluralized
+ else
+ return base .. "s"
+ end
+end
+
+---Replace the text in a quickfix line, preserving the lineno virt text
+---@param bufnr integer
+---@param lnum integer
+---@param new_text string
+local function replace_qf_line(bufnr, lnum, new_text)
+ local old_line = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
+
+ local old_idx = old_line:find(display.EM_QUAD, 1, true)
+ local new_idx = new_text:find(display.EM_QUAD, 1, true)
+
+ -- If we're missing the em quad delimiter in either the old or new text, the best we can do is
+ -- replace the whole line
+ if not old_idx or not new_idx then
+ vim.api.nvim_buf_set_text(bufnr, lnum - 1, 0, lnum - 1, -1, { new_text })
+ return
+ end
+
+ -- Replace first the text after the em quad, then the filename before.
+ -- This keeps the line number virtual text in the same location.
+ vim.api.nvim_buf_set_text(
+ bufnr,
+ lnum - 1,
+ old_idx + display.EM_QUAD_LEN - 1,
+ lnum - 1,
+ -1,
+ { new_text:sub(new_idx + display.EM_QUAD_LEN) }
+ )
+ vim.api.nvim_buf_set_text(bufnr, lnum - 1, 0, lnum - 1, old_idx, { new_text:sub(1, new_idx) })
+end
+
+---@param bufnr integer
+---@param lnum integer
+---@param text string
+---@param text_hl? string
+local function add_qf_error(bufnr, lnum, text, text_hl)
+ local line = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
+ local col = line:find(config.borders.vert, 1, true)
+ if col then
+ col = line:find(config.borders.vert, col + config.borders.vert:len(), true)
+ + config.borders.vert:len()
+ - 1
+ else
+ col = 0
+ end
+ local offset = vim.api.nvim_strwidth(line:sub(1, col))
+ local ns = vim.api.nvim_create_namespace("quicker_err")
+ vim.api.nvim_buf_set_extmark(bufnr, ns, lnum - 1, col, {
+ virt_text = { { config.type_icons.E, "DiagnosticSignError" } },
+ virt_text_pos = "inline",
+ virt_lines = {
+ {
+ { string.rep(" ", offset), "Normal" },
+ { "↳ ", "DiagnosticError" },
+ { text, text_hl or "Normal" },
+ },
+ },
+ })
+end
+
+---@param item QuickFixItem
+---@param needle string
+---@param src_line nil|string
+---@return nil|table text_change
+---@return nil|string error
+local function get_text_edit(item, needle, src_line)
+ if not src_line then
+ return nil
+ elseif item.text == needle then
+ return nil
+ elseif src_line ~= item.text then
+ if item.text:gsub("^%s*", "") == src_line:gsub("^%s*", "") then
+ -- If they only disagree in their leading whitespace, just take the changes after the
+ -- whitespace and assume that the whitespace hasn't changed.
+ -- This can happen if the setqflist caller doesn't use the same whitespace as the source file,
+ -- for example overseer.nvim Grep will convert tabs to spaces because the embedded terminal
+ -- will convert tabs to spaces.
+ needle = src_line:match("^%s*") .. needle:gsub("^%s*", "")
+ else
+ return nil, "buffer text does not match source text"
+ end
+ end
+
+ return {
+ newText = needle,
+ range = {
+ start = {
+ line = item.lnum - 1,
+ character = 0,
+ },
+ ["end"] = {
+ line = item.lnum - 1,
+ character = #src_line,
+ },
+ },
+ }
+end
+
+---Deserialize qf_prefixes from the buffer, converting vim.NIL to nil
+---@param bufnr integer
+---@return table<integer, string>
+local function load_qf_prefixes(bufnr)
+ local prefixes = vim.b[bufnr].qf_prefixes or {}
+ for k, v in pairs(prefixes) do
+ if v == vim.NIL then
+ prefixes[k] = nil
+ end
+ end
+ return prefixes
+end
+
+---@param bufnr integer
+---@param loclist_win? integer
+local function save_changes(bufnr, loclist_win)
+ if not vim.bo[bufnr].modified then
+ return
+ end
+ local ns = vim.api.nvim_create_namespace("quicker_err")
+ vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
+ local qf_list
+ if loclist_win then
+ qf_list = vim.fn.getloclist(loclist_win, { all = 0 })
+ else
+ qf_list = vim.fn.getqflist({ all = 0 })
+ end
+
+ local changes = {}
+ local function add_change(buf, text_edit)
+ if not changes[buf] then
+ changes[buf] = {}
+ end
+ local last_edit = changes[buf][#changes[buf]]
+ if last_edit and vim.deep_equal(last_edit.range, text_edit.range) then
+ if last_edit.newText == text_edit.newText then
+ return
+ else
+ return "conflicting changes on the same line"
+ end
+ end
+ table.insert(changes[buf], text_edit)
+ end
+
+ -- Parse the buffer
+ local winid = util.buf_find_win(bufnr)
+ local new_items = {}
+ local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
+ local errors = {}
+ local exit_early = false
+ local prefixes = load_qf_prefixes(bufnr)
+ local ext_id_to_item_idx = vim.b[bufnr].qf_ext_id_to_item_idx
+ for i, line in ipairs(lines) do
+ (function()
+ local extmarks = util.get_lnum_extmarks(bufnr, i, line:len())
+ assert(#extmarks <= 1, string.format("Found more than one extmark on line %d", i))
+ local found_idx
+ if extmarks[1] then
+ found_idx = ext_id_to_item_idx[extmarks[1][1]]
+ end
+
+ -- If we didn't find a match, the line was most likely added or reordered
+ if not found_idx then
+ add_qf_error(
+ bufnr,
+ i,
+ "quicker.nvim does not support adding or reordering quickfix items",
+ "DiagnosticError"
+ )
+ if winid then
+ vim.api.nvim_win_set_cursor(winid, { i, 0 })
+ end
+ exit_early = true
+ return
+ end
+
+ -- Trim the filename off of the line
+ local idx = string.find(line, display.EM_QUAD, 1, true)
+ if not idx then
+ add_qf_error(
+ bufnr,
+ i,
+ "The delimiter between filename and text has been deleted. Undo, delete line, or :Refresh.",
+ "DiagnosticError"
+ )
+ if winid then
+ vim.api.nvim_win_set_cursor(winid, { i, 0 })
+ end
+ exit_early = true
+ return
+ end
+ local text = line:sub(idx + display.EM_QUAD_LEN)
+
+ local item = qf_list.items[found_idx]
+ if item.bufnr ~= 0 and item.lnum ~= 0 then
+ if not vim.api.nvim_buf_is_loaded(item.bufnr) then
+ vim.fn.bufload(item.bufnr)
+ end
+ local src_line = vim.api.nvim_buf_get_lines(item.bufnr, item.lnum - 1, item.lnum, false)[1]
+
+ -- add the whitespace prefix back to the parsed line text
+ if config.trim_leading_whitespace == "common" then
+ text = (prefixes[item.bufnr] or "") .. text
+ elseif config.trim_leading_whitespace == "all" and src_line then
+ text = src_line:match("^%s*") .. text
+ end
+
+ if src_line and text ~= src_line then
+ if text:gsub("^%s*", "") == src_line:gsub("^%s*", "") then
+ -- If they only disagree in their leading whitespace, just take the changes after the
+ -- whitespace and assume that the whitespace hasn't changed
+ text = src_line:match("^%s*") .. text:gsub("^%s*", "")
+ end
+ end
+
+ local text_edit, err = get_text_edit(item, text, src_line)
+ if text_edit then
+ local chng_err = add_change(item.bufnr, text_edit)
+ if chng_err then
+ add_qf_error(bufnr, i, chng_err, "DiagnosticError")
+ if winid then
+ vim.api.nvim_win_set_cursor(winid, { i, 0 })
+ end
+ exit_early = true
+ return
+ end
+ elseif err then
+ table.insert(new_items, item)
+ errors[#new_items] = line
+ return
+ end
+ end
+
+ -- add item to future qflist
+ item.text = text
+ table.insert(new_items, item)
+ end)()
+ if exit_early then
+ vim.schedule(function()
+ vim.bo[bufnr].modified = true
+ end)
+ return
+ end
+ end
+
+ ---@type table<integer, boolean>
+ local buf_was_modified = {}
+ for _, buf in ipairs(vim.api.nvim_list_bufs()) do
+ buf_was_modified[buf] = vim.bo[buf].modified
+ end
+ local autosave = config.edit.autosave
+ local num_applied = 0
+ local modified_bufs = {}
+ for chg_buf, text_edits in pairs(changes) do
+ modified_bufs[chg_buf] = true
+ num_applied = num_applied + #text_edits
+ vim.lsp.util.apply_text_edits(text_edits, chg_buf, "utf-8")
+ local was_modified = buf_was_modified[chg_buf]
+ local should_save = autosave == true or (autosave == "unmodified" and not was_modified)
+ -- Autosave changed buffers if they were not modified before
+ if should_save then
+ vim.api.nvim_buf_call(chg_buf, function()
+ vim.cmd.update({ mods = { emsg_silent = true, noautocmd = true } })
+ end)
+ end
+ end
+ if num_applied > 0 then
+ local num_files = vim.tbl_count(modified_bufs)
+ local num_errors = vim.tbl_count(errors)
+ if num_errors > 0 then
+ local total = num_errors + num_applied
+ vim.notify(
+ string.format(
+ "Applied %d/%d %s in %d %s",
+ num_applied,
+ total,
+ plural(total, "change"),
+ num_files,
+ plural(num_files, "file")
+ ),
+ vim.log.levels.WARN
+ )
+ else
+ vim.notify(
+ string.format(
+ "Applied %d %s in %d %s",
+ num_applied,
+ plural(num_applied, "change"),
+ num_files,
+ plural(num_files, "file")
+ ),
+ vim.log.levels.INFO
+ )
+ end
+ end
+
+ local view
+ if winid then
+ view = vim.api.nvim_win_call(winid, function()
+ return vim.fn.winsaveview()
+ end)
+ end
+ if loclist_win then
+ vim.fn.setloclist(
+ loclist_win,
+ {},
+ "r",
+ { items = new_items, title = qf_list.title, context = qf_list.context }
+ )
+ else
+ vim.fn.setqflist(
+ {},
+ "r",
+ { items = new_items, title = qf_list.title, context = qf_list.context }
+ )
+ end
+ if winid and view then
+ vim.api.nvim_win_call(winid, function()
+ vim.fn.winrestview(view)
+ end)
+ end
+
+ -- Schedule this so it runs after the save completes, and the buffer will be correctly marked as modified
+ if not vim.tbl_isempty(errors) then
+ vim.schedule(function()
+ -- Mark the lines with changes that could not be applied
+ for lnum, new_text in pairs(errors) do
+ replace_qf_line(bufnr, lnum, new_text)
+ local item = new_items[lnum]
+ local src_line = vim.api.nvim_buf_get_lines(item.bufnr, item.lnum - 1, item.lnum, false)[1]
+ add_qf_error(bufnr, lnum, src_line)
+ if winid and vim.api.nvim_win_is_valid(winid) then
+ vim.api.nvim_win_set_cursor(winid, { lnum, 0 })
+ end
+ end
+ end)
+
+ -- Notify user that some changes could not be applied
+ local cnt = vim.tbl_count(errors)
+ local change_text = cnt == 1 and "change" or "changes"
+ vim.notify(
+ string.format(
+ "%d %s could not be applied due to conflicts in the source buffer. Please :Refresh and try again.",
+ cnt,
+ change_text
+ ),
+ vim.log.levels.ERROR
+ )
+ end
+end
+
+-- TODO add support for undo past last change
+
+---@param bufnr integer
+function M.setup_editor(bufnr)
+ local aug = vim.api.nvim_create_augroup("quicker", { clear = false })
+ local loclist_win
+ vim.api.nvim_buf_call(bufnr, function()
+ local ok, qf = pcall(vim.fn.getloclist, 0, { filewinid = 0 })
+ if ok and qf.filewinid and qf.filewinid ~= 0 then
+ loclist_win = qf.filewinid
+ end
+ end)
+
+ -- Set a name for the buffer so we can save it
+ local bufname = string.format("quickfix-%d", bufnr)
+ if vim.api.nvim_buf_get_name(bufnr) == "" then
+ vim.api.nvim_buf_set_name(bufnr, bufname)
+ end
+ vim.bo[bufnr].modifiable = true
+
+ vim.api.nvim_create_autocmd("BufWriteCmd", {
+ desc = "quicker.nvim apply changes on write",
+ group = aug,
+ buffer = bufnr,
+ nested = true,
+ callback = function(args)
+ save_changes(args.buf, loclist_win)
+ vim.bo[args.buf].modified = false
+ end,
+ })
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/follow.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/follow.lua
new file mode 100644
index 0000000..d5500a2
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/follow.lua
@@ -0,0 +1,84 @@
+local util = require("quicker.util")
+local M = {}
+
+M.seek_to_position = function()
+ if util.is_open(0) then
+ local qf_list = vim.fn.getloclist(0, { winid = 0, items = 0 })
+ local new_pos = M.calculate_pos(qf_list.items)
+ if new_pos then
+ M.set_pos(qf_list.winid, new_pos)
+ end
+ end
+
+ if util.is_open() then
+ local qf_list = vim.fn.getqflist({ winid = 0, items = 0 })
+ local new_pos = M.calculate_pos(qf_list.items)
+ if new_pos then
+ M.set_pos(qf_list.winid, new_pos)
+ end
+ end
+end
+
+---Calculate the current buffer/cursor location in the quickfix list
+---@param list QuickFixItem[]
+---@return nil|integer
+M.calculate_pos = function(list)
+ if vim.bo.buftype ~= "" then
+ return
+ end
+ local bufnr = vim.api.nvim_get_current_buf()
+ local cursor = vim.api.nvim_win_get_cursor(0)
+ local lnum, col = cursor[1], cursor[2] + 1
+ local prev_lnum = -1
+ local prev_col = -1
+ local found_buf = false
+ local ret
+ for i, entry in ipairs(list) do
+ if entry.bufnr ~= bufnr then
+ if found_buf then
+ return ret
+ end
+ else
+ found_buf = true
+
+ -- If we detect that the list isn't sorted, bail.
+ if
+ prev_lnum > -1
+ and (entry.lnum < prev_lnum or (entry.lnum == prev_lnum and entry.col <= prev_col))
+ then
+ return
+ end
+
+ if prev_lnum == -1 or lnum > entry.lnum or (lnum == entry.lnum and col >= entry.col) then
+ ret = i
+ end
+ prev_lnum = entry.lnum
+ prev_col = entry.col
+ end
+ end
+
+ return ret
+end
+
+local timers = {}
+---@param winid integer
+---@param pos integer
+M.set_pos = function(winid, pos)
+ local timer = timers[winid]
+ if timer then
+ timer:close()
+ end
+ timer = assert(vim.uv.new_timer())
+ timers[winid] = timer
+ timer:start(10, 0, function()
+ timer:close()
+ timers[winid] = nil
+ vim.schedule(function()
+ if vim.api.nvim_win_is_valid(winid) then
+ pcall(vim.api.nvim_win_set_cursor, winid, { pos, 0 })
+ end
+ end)
+ end)
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/fs.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/fs.lua
new file mode 100644
index 0000000..f0e94dc
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/fs.lua
@@ -0,0 +1,98 @@
+local M = {}
+
+---@type boolean
+M.is_windows = vim.uv.os_uname().version:match("Windows")
+
+M.is_mac = vim.uv.os_uname().sysname == "Darwin"
+
+M.is_linux = not M.is_windows and not M.is_mac
+
+---@type string
+M.sep = M.is_windows and "\\" or "/"
+
+---@param ... string
+M.join = function(...)
+ return table.concat({ ... }, M.sep)
+end
+
+---Check if OS path is absolute
+---@param dir string
+---@return boolean
+M.is_absolute = function(dir)
+ if M.is_windows then
+ return dir:match("^%a:\\")
+ else
+ return vim.startswith(dir, "/")
+ end
+end
+
+M.abspath = function(path)
+ if not M.is_absolute(path) then
+ path = vim.fn.fnamemodify(path, ":p")
+ end
+ return path
+end
+
+local home_dir = assert(vim.uv.os_homedir())
+
+---@param path string
+---@param relative_to? string Shorten relative to this path (default cwd)
+---@return string
+M.shorten_path = function(path, relative_to)
+ if not relative_to then
+ relative_to = vim.fn.getcwd()
+ end
+ local relpath
+ if M.is_subpath(relative_to, path) then
+ local idx = relative_to:len() + 1
+ -- Trim the dividing slash if it's not included in relative_to
+ if not vim.endswith(relative_to, "/") and not vim.endswith(relative_to, "\\") then
+ idx = idx + 1
+ end
+ relpath = path:sub(idx)
+ if relpath == "" then
+ relpath = "."
+ end
+ end
+ if M.is_subpath(home_dir, path) then
+ local homepath = "~" .. path:sub(home_dir:len() + 1)
+ if not relpath or homepath:len() < relpath:len() then
+ return homepath
+ end
+ end
+ return relpath or path
+end
+
+--- Returns true if candidate is a subpath of root, or if they are the same path.
+---@param root string
+---@param candidate string
+---@return boolean
+M.is_subpath = function(root, candidate)
+ if candidate == "" then
+ return false
+ end
+ root = vim.fs.normalize(M.abspath(root))
+ -- Trim trailing "/" from the root
+ if root:find("/", -1) then
+ root = root:sub(1, -2)
+ end
+ candidate = vim.fs.normalize(M.abspath(candidate))
+ if M.is_windows then
+ root = root:lower()
+ candidate = candidate:lower()
+ end
+ if root == candidate then
+ return true
+ end
+ local prefix = candidate:sub(1, root:len())
+ if prefix ~= root then
+ return false
+ end
+
+ local candidate_starts_with_sep = candidate:find("/", root:len() + 1, true) == root:len() + 1
+ local root_ends_with_sep = root:find("/", root:len(), true) == root:len()
+
+ return candidate_starts_with_sep or root_ends_with_sep
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/highlight.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/highlight.lua
new file mode 100644
index 0000000..a7323aa
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/highlight.lua
@@ -0,0 +1,222 @@
+local M = {}
+
+---@class quicker.TSHighlight
+---@field [1] integer start_col
+---@field [2] integer end_col
+---@field [3] string highlight group
+
+local _cached_queries = {}
+---@param lang string
+---@return vim.treesitter.Query?
+local function get_highlight_query(lang)
+ local query = _cached_queries[lang]
+ if query == nil then
+ query = vim.treesitter.query.get(lang, "highlights") or false
+ _cached_queries[lang] = query
+ end
+ if query then
+ return query
+ end
+end
+
+---@param bufnr integer
+---@param lnum integer
+---@return quicker.TSHighlight[]
+function M.buf_get_ts_highlights(bufnr, lnum)
+ local filetype = vim.bo[bufnr].filetype
+ if not filetype or filetype == "" then
+ filetype = vim.filetype.match({ buf = bufnr }) or ""
+ end
+ local lang = vim.treesitter.language.get_lang(filetype) or filetype
+ if lang == "" then
+ return {}
+ end
+ local ok, parser = pcall(vim.treesitter.get_parser, bufnr, lang)
+ if not ok or not parser then
+ return {}
+ end
+
+ local row = lnum - 1
+ if not parser:is_valid() then
+ parser:parse(true)
+ end
+
+ local highlights = {}
+ parser:for_each_tree(function(tstree, tree)
+ if not tstree then
+ return
+ end
+
+ local root_node = tstree:root()
+ local root_start_row, _, root_end_row, _ = root_node:range()
+
+ -- Only worry about trees within the line range
+ if root_start_row > row or root_end_row < row then
+ return
+ end
+
+ local query = get_highlight_query(tree:lang())
+
+ -- Some injected languages may not have highlight queries.
+ if not query then
+ return
+ end
+
+ for capture, node, metadata in query:iter_captures(root_node, bufnr, row, root_end_row + 1) do
+ if capture == nil then
+ break
+ end
+
+ local range = vim.treesitter.get_range(node, bufnr, metadata[capture])
+ local start_row, start_col, _, end_row, end_col, _ = unpack(range)
+ if start_row > row then
+ break
+ end
+ local capture_name = query.captures[capture]
+ local hl = string.format("@%s.%s", capture_name, tree:lang())
+ if end_row > start_row then
+ end_col = -1
+ end
+ table.insert(highlights, { start_col, end_col, hl })
+ end
+ end)
+
+ return highlights
+end
+
+---@class quicker.LSPHighlight
+---@field [1] integer start_col
+---@field [2] integer end_col
+---@field [3] string highlight group
+---@field [4] integer priority modifier
+
+-- We're accessing private APIs here. This could break in the future.
+local STHighlighter = vim.lsp.semantic_tokens.__STHighlighter
+
+--- Copied from Neovim semantic_tokens.lua
+--- Do a binary search of the tokens in the half-open range [lo, hi).
+---
+--- Return the index i in range such that tokens[j].line < line for all j < i, and
+--- tokens[j].line >= line for all j >= i, or return hi if no such index is found.
+---
+---@private
+local function lower_bound(tokens, line, lo, hi)
+ while lo < hi do
+ local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2).
+ if tokens[mid].line < line then
+ lo = mid + 1
+ else
+ hi = mid
+ end
+ end
+ return lo
+end
+
+---@param bufnr integer
+---@param lnum integer
+---@return quicker.LSPHighlight[]
+function M.buf_get_lsp_highlights(bufnr, lnum)
+ local highlighter = STHighlighter.active[bufnr]
+ if not highlighter then
+ return {}
+ end
+ local ft = vim.bo[bufnr].filetype
+
+ local lsp_highlights = {}
+ for _, client in pairs(highlighter.client_state) do
+ local highlights = client.current_result.highlights
+ if highlights then
+ local idx = lower_bound(highlights, lnum - 1, 1, #highlights + 1)
+ for i = idx, #highlights do
+ local token = highlights[i]
+
+ if token.line >= lnum then
+ break
+ end
+
+ table.insert(
+ lsp_highlights,
+ { token.start_col, token.end_col, string.format("@lsp.type.%s.%s", token.type, ft), 0 }
+ )
+ for modifier, _ in pairs(token.modifiers) do
+ table.insert(
+ lsp_highlights,
+ { token.start_col, token.end_col, string.format("@lsp.mod.%s.%s", modifier, ft), 1 }
+ )
+ table.insert(lsp_highlights, {
+ token.start_col,
+ token.end_col,
+ string.format("@lsp.typemod.%s.%s.%s", token.type, modifier, ft),
+ 2,
+ })
+ end
+ end
+ end
+ end
+
+ return lsp_highlights
+end
+
+---@param item QuickFixItem
+---@param line string
+---@return quicker.TSHighlight[]
+M.get_heuristic_ts_highlights = function(item, line)
+ local filetype = vim.filetype.match({ buf = item.bufnr })
+ if not filetype then
+ return {}
+ end
+
+ local lang = vim.treesitter.language.get_lang(filetype)
+ if not lang then
+ return {}
+ end
+
+ local has_parser, parser = pcall(vim.treesitter.get_string_parser, line, lang)
+ if not has_parser then
+ return {}
+ end
+
+ local root = parser:parse(true)[1]:root()
+ local query = vim.treesitter.query.get(lang, "highlights")
+ if not query then
+ return {}
+ end
+
+ local highlights = {}
+ for capture, node, metadata in query:iter_captures(root, line) do
+ if capture == nil then
+ break
+ end
+
+ local range = vim.treesitter.get_range(node, line, metadata[capture])
+ local start_row, start_col, _, end_row, end_col, _ = unpack(range)
+ local capture_name = query.captures[capture]
+ local hl = string.format("@%s.%s", capture_name, lang)
+ if end_row > start_row then
+ end_col = -1
+ end
+ table.insert(highlights, { start_col, end_col, hl })
+ end
+
+ return highlights
+end
+
+function M.set_highlight_groups()
+ if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = "QuickFixHeaderHard" })) then
+ vim.api.nvim_set_hl(0, "QuickFixHeaderHard", { link = "Delimiter", default = true })
+ end
+ if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = "QuickFixHeaderSoft" })) then
+ vim.api.nvim_set_hl(0, "QuickFixHeaderSoft", { link = "Comment", default = true })
+ end
+ if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = "QuickFixFilename" })) then
+ vim.api.nvim_set_hl(0, "QuickFixFilename", { link = "Directory", default = true })
+ end
+ if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = "QuickFixFilenameInvalid" })) then
+ vim.api.nvim_set_hl(0, "QuickFixFilenameInvalid", { link = "Comment", default = true })
+ end
+ if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = "QuickFixLineNr" })) then
+ vim.api.nvim_set_hl(0, "QuickFixLineNr", { link = "LineNr", default = true })
+ end
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/init.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/init.lua
new file mode 100644
index 0000000..42ae32b
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/init.lua
@@ -0,0 +1,189 @@
+local M = {}
+
+---@param opts? quicker.SetupOptions
+local function setup(opts)
+ local config = require("quicker.config")
+ config.setup(opts)
+
+ local aug = vim.api.nvim_create_augroup("quicker", { clear = true })
+ vim.api.nvim_create_autocmd("FileType", {
+ pattern = "qf",
+ group = aug,
+ desc = "quicker.nvim set up quickfix mappings",
+ callback = function(args)
+ require("quicker.highlight").set_highlight_groups()
+ require("quicker.opts").set_opts(args.buf)
+ require("quicker.keys").set_keymaps(args.buf)
+ vim.api.nvim_buf_create_user_command(args.buf, "Refresh", function()
+ require("quicker.context").refresh()
+ end, {
+ desc = "Update the quickfix list with the current buffer text for each item",
+ })
+
+ if config.constrain_cursor then
+ require("quicker.cursor").constrain_cursor(args.buf)
+ end
+
+ config.on_qf(args.buf)
+ end,
+ })
+ vim.api.nvim_create_autocmd("ColorScheme", {
+ pattern = "*",
+ group = aug,
+ desc = "quicker.nvim set up quickfix highlight groups",
+ callback = function()
+ require("quicker.highlight").set_highlight_groups()
+ end,
+ })
+ if config.edit.enabled then
+ vim.api.nvim_create_autocmd("BufReadPost", {
+ pattern = "quickfix",
+ group = aug,
+ desc = "quicker.nvim set up quickfix editing",
+ callback = function(args)
+ require("quicker.editor").setup_editor(args.buf)
+ end,
+ })
+ end
+ if config.follow.enabled then
+ vim.api.nvim_create_autocmd({ "CursorMoved", "BufEnter" }, {
+ desc = "quicker.nvim scroll to nearest location in quickfix",
+ pattern = "*",
+ group = aug,
+ callback = function()
+ require("quicker.follow").seek_to_position()
+ end,
+ })
+ end
+
+ vim.o.quickfixtextfunc = "v:lua.require'quicker.display'.quickfixtextfunc"
+
+ -- If the quickfix/loclist is already open, refresh it so the quickfixtextfunc will take effect.
+ -- This is required for lazy-loading to work properly.
+ local list = vim.fn.getqflist({ all = 0 })
+ if not vim.tbl_isempty(list.items) then
+ vim.fn.setqflist({}, "r", list)
+ end
+ for _, winid in ipairs(vim.api.nvim_list_wins()) do
+ if vim.api.nvim_win_is_valid(winid) then
+ local llist = vim.fn.getloclist(winid, { all = 0 })
+ if not vim.tbl_isempty(list.items) then
+ vim.fn.setloclist(winid, {}, "r", llist)
+ end
+ end
+ end
+end
+
+M.setup = setup
+
+---Expand the context around the quickfix results.
+---@param opts? quicker.ExpandOpts
+---@note
+--- If there are multiple quickfix items for the same line of a file, only the first
+--- one will remain after calling expand().
+M.expand = function(opts)
+ return require("quicker.context").expand(opts)
+end
+
+---Collapse the context around quickfix results, leaving only the `valid` items.
+M.collapse = function()
+ return require("quicker.context").collapse()
+end
+
+---Toggle the expanded context around the quickfix results.
+---@param opts? quicker.ExpandOpts
+M.toggle_expand = function(opts)
+ return require("quicker.context").toggle(opts)
+end
+
+---Update the quickfix list with the current buffer text for each item.
+---@param loclist_win? integer
+---@param opts? quicker.RefreshOpts
+M.refresh = function(loclist_win, opts)
+ return require("quicker.context").refresh(loclist_win, opts)
+end
+
+---@param loclist_win? integer Check if loclist is open for the given window. If nil, check quickfix.
+M.is_open = function(loclist_win)
+ return require("quicker.util").is_open(loclist_win)
+end
+
+---@class quicker.OpenCmdMods: vim.api.keyset.parse_cmd.mods
+
+---@class (exact) quicker.OpenOpts
+---@field loclist? boolean Toggle the loclist instead of the quickfix list
+---@field focus? boolean Focus the quickfix window after toggling (default false)
+---@field height? integer Height of the quickfix window when opened. Defaults to number of items in the list.
+---@field min_height? integer Minimum height of the quickfix window. Default 4.
+---@field max_height? integer Maximum height of the quickfix window. Default 10.
+---@field open_cmd_mods? quicker.OpenCmdMods A table of modifiers for the quickfix or loclist open commands.
+
+---Toggle the quickfix or loclist window.
+---@param opts? quicker.OpenOpts
+M.toggle = function(opts)
+ ---@type {loclist: boolean, focus: boolean, height?: integer, min_height: integer, max_height: integer, open_cmd_mods?: quicker.OpenCmdMods}
+ opts = vim.tbl_deep_extend("keep", opts or {}, {
+ loclist = false,
+ focus = false,
+ min_height = 4,
+ max_height = 10,
+ open_cmd_mods = {},
+ })
+ local loclist_win = opts.loclist and 0 or nil
+ if M.is_open(loclist_win) then
+ M.close({ loclist = opts.loclist })
+ else
+ M.open(opts)
+ end
+end
+
+---Open the quickfix or loclist window.
+---@param opts? quicker.OpenOpts
+M.open = function(opts)
+ local util = require("quicker.util")
+ ---@type {loclist: boolean, focus: boolean, height?: integer, min_height: integer, max_height: integer, open_cmd_mods?: quicker.OpenCmdMods}
+ opts = vim.tbl_deep_extend("keep", opts or {}, {
+ loclist = false,
+ focus = false,
+ min_height = 4,
+ max_height = 10,
+ open_cmd_mods = {},
+ })
+ local height
+ if opts.loclist then
+ local ok, err = pcall(vim.cmd.lopen, { mods = opts.open_cmd_mods })
+ if not ok then
+ vim.notify(err, vim.log.levels.ERROR)
+ return
+ end
+ height = #vim.fn.getloclist(0)
+ else
+ vim.cmd.copen({ mods = opts.open_cmd_mods })
+ height = #vim.fn.getqflist()
+ end
+
+ -- only set the height if the quickfix is not a full-height vsplit
+ if not util.is_full_height_vsplit(0) then
+ height = math.min(opts.max_height, math.max(opts.min_height, height))
+ vim.api.nvim_win_set_height(0, height)
+ end
+
+ if not opts.focus then
+ vim.cmd.wincmd({ args = { "p" } })
+ end
+end
+
+---@class (exact) quicker.CloseOpts
+---@field loclist? boolean Close the loclist instead of the quickfix list
+
+---Close the quickfix or loclist window.
+---@param opts? quicker.CloseOpts
+M.close = function(opts)
+ if opts and opts.loclist then
+ vim.cmd.lclose()
+ else
+ vim.cmd.cclose()
+ end
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/keys.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/keys.lua
new file mode 100644
index 0000000..17ea331
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/keys.lua
@@ -0,0 +1,20 @@
+local config = require("quicker.config")
+
+local M = {}
+
+---@param bufnr integer
+function M.set_keymaps(bufnr)
+ for _, defn in ipairs(config.keys) do
+ vim.keymap.set(defn.mode or "n", defn[1], defn[2], {
+ buffer = bufnr,
+ desc = defn.desc,
+ expr = defn.expr,
+ nowait = defn.nowait,
+ remap = defn.remap,
+ replace_keycodes = defn.replace_keycodes,
+ silent = defn.silent,
+ })
+ end
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/opts.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/opts.lua
new file mode 100644
index 0000000..1cf77b6
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/opts.lua
@@ -0,0 +1,61 @@
+local config = require("quicker.config")
+local util = require("quicker.util")
+
+local M = {}
+
+---@param bufnr integer
+local function set_buf_opts(bufnr)
+ for k, v in pairs(config.opts) do
+ local opt_info = vim.api.nvim_get_option_info2(k, {})
+ if opt_info.scope == "buf" then
+ local ok, err = pcall(vim.api.nvim_set_option_value, k, v, { buf = bufnr })
+ if not ok then
+ vim.notify(
+ string.format("Error setting quickfix option %s = %s: %s", k, vim.inspect(v), err),
+ vim.log.levels.ERROR
+ )
+ end
+ end
+ end
+end
+
+---@param winid integer
+local function set_win_opts(winid)
+ for k, v in pairs(config.opts) do
+ local opt_info = vim.api.nvim_get_option_info2(k, {})
+ if opt_info.scope == "win" then
+ local ok, err = pcall(vim.api.nvim_set_option_value, k, v, { scope = "local", win = winid })
+ if not ok then
+ vim.notify(
+ string.format("Error setting quickfix window option %s = %s: %s", k, vim.inspect(v), err),
+ vim.log.levels.ERROR
+ )
+ end
+ end
+ end
+end
+
+---@param bufnr integer
+function M.set_opts(bufnr)
+ set_buf_opts(bufnr)
+ local winid = util.buf_find_win(bufnr)
+ if winid then
+ set_win_opts(winid)
+ else
+ local aug = vim.api.nvim_create_augroup("quicker", { clear = false })
+ vim.api.nvim_create_autocmd("BufWinEnter", {
+ desc = "Set quickfix window options",
+ buffer = bufnr,
+ group = aug,
+ callback = function()
+ winid = util.buf_find_win(bufnr)
+ if winid then
+ set_win_opts(winid)
+ end
+ return winid ~= nil
+ end,
+ })
+ end
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/util.lua b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/util.lua
new file mode 100644
index 0000000..3794091
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/lua/quicker/util.lua
@@ -0,0 +1,95 @@
+local M = {}
+
+---@param bufnr integer
+---@return nil|integer
+function M.buf_find_win(bufnr)
+ for _, winid in ipairs(vim.api.nvim_list_wins()) do
+ if vim.api.nvim_win_is_valid(winid) and vim.api.nvim_win_get_buf(winid) == bufnr then
+ return winid
+ end
+ end
+end
+
+---@param loclist_win? integer Check if loclist is open for the given window. If nil, check quickfix.
+M.is_open = function(loclist_win)
+ if loclist_win then
+ return vim.fn.getloclist(loclist_win or 0, { winid = 0 }).winid ~= 0
+ else
+ return vim.fn.getqflist({ winid = 0 }).winid ~= 0
+ end
+end
+
+---@param winid nil|integer
+---@return nil|"c"|"l"
+M.get_win_type = function(winid)
+ if not winid or winid == 0 then
+ winid = vim.api.nvim_get_current_win()
+ end
+ local info = vim.fn.getwininfo(winid)[1]
+ if info.quickfix == 0 then
+ return nil
+ elseif info.loclist == 0 then
+ return "c"
+ else
+ return "l"
+ end
+end
+
+---@param item QuickFixItem
+---@return QuickFixUserData
+M.get_user_data = function(item)
+ if type(item.user_data) == "table" then
+ return item.user_data
+ else
+ return {}
+ end
+end
+
+---Get valid location extmarks for a line in the quickfix
+---@param bufnr integer
+---@param lnum integer
+---@param line_len? integer how long this particular line is
+---@param ns? integer namespace of extmarks
+---@return table[] extmarks
+M.get_lnum_extmarks = function(bufnr, lnum, line_len, ns)
+ if not ns then
+ ns = vim.api.nvim_create_namespace("quicker_locations")
+ end
+ if not line_len then
+ local line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]
+ line_len = line:len()
+ end
+ local extmarks = vim.api.nvim_buf_get_extmarks(
+ bufnr,
+ ns,
+ { lnum - 1, 0 },
+ { lnum - 1, line_len },
+ { details = true }
+ )
+ return vim.tbl_filter(function(mark)
+ return not mark[4].invalid
+ end, extmarks)
+end
+
+---Return true if the window is a full-height leaf window
+---@param winid? integer
+---@return boolean
+M.is_full_height_vsplit = function(winid)
+ if not winid or winid == 0 then
+ winid = vim.api.nvim_get_current_win()
+ end
+ local layout = vim.fn.winlayout()
+ -- If the top layout is not vsplit, then it's not a vertical leaf
+ if layout[1] ~= "row" then
+ return false
+ end
+ for _, v in ipairs(layout[2]) do
+ if v[1] == "leaf" and v[2] == winid then
+ return true
+ end
+ end
+
+ return false
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/run_tests.sh b/mut/neovim/pack/plugins/start/quicker.nvim/run_tests.sh
new file mode 100755
index 0000000..f7b5bab
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/run_tests.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+set -e
+
+for arg in "$@"; do
+ shift
+ case "$arg" in
+ '--update')
+ export UPDATE_SNAPSHOTS=1
+ ;;
+ *)
+ set -- "$@" "$arg"
+ ;;
+ esac
+done
+
+mkdir -p ".testenv/config/nvim"
+mkdir -p ".testenv/data/nvim"
+mkdir -p ".testenv/state/nvim"
+mkdir -p ".testenv/run/nvim"
+mkdir -p ".testenv/cache/nvim"
+PLUGINS=".testenv/data/nvim/site/pack/plugins/start"
+
+if [ ! -e "$PLUGINS/plenary.nvim" ]; then
+ git clone --depth=1 https://github.com/nvim-lua/plenary.nvim.git "$PLUGINS/plenary.nvim"
+else
+ (cd "$PLUGINS/plenary.nvim" && git pull)
+fi
+
+XDG_CONFIG_HOME=".testenv/config" \
+ XDG_DATA_HOME=".testenv/data" \
+ XDG_STATE_HOME=".testenv/state" \
+ XDG_RUNTIME_DIR=".testenv/run" \
+ XDG_CACHE_HOME=".testenv/cache" \
+ nvim --headless -u tests/minimal_init.lua \
+ -c "RunTests ${1-tests}"
+echo "Success"
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/scripts/generate.py b/mut/neovim/pack/plugins/start/quicker.nvim/scripts/generate.py
new file mode 100755
index 0000000..e1ccded
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/scripts/generate.py
@@ -0,0 +1,102 @@
+import os
+import os.path
+import re
+from typing import List
+
+from nvim_doc_tools import (
+ Vimdoc,
+ VimdocSection,
+ generate_md_toc,
+ indent,
+ parse_directory,
+ read_section,
+ render_md_api2,
+ render_vimdoc_api2,
+ replace_section,
+)
+
+HERE = os.path.dirname(__file__)
+ROOT = os.path.abspath(os.path.join(HERE, os.path.pardir))
+README = os.path.join(ROOT, "README.md")
+DOC = os.path.join(ROOT, "doc")
+VIMDOC = os.path.join(DOC, "quicker.txt")
+
+
+def add_md_link_path(path: str, lines: List[str]) -> List[str]:
+ ret = []
+ for line in lines:
+ ret.append(re.sub(r"(\(#)", "(" + path + "#", line))
+ return ret
+
+
+def update_md_api():
+ types = parse_directory(os.path.join(ROOT, "lua"))
+ funcs = types.files["quicker/init.lua"].functions
+ lines = ["\n"] + render_md_api2(funcs, types, 3)[:-1] # trim last newline
+ replace_section(
+ README,
+ r"^<!-- API -->$",
+ r"^<!-- /API -->$",
+ lines,
+ )
+
+
+def update_options():
+ option_lines = ["\n", "```lua\n"]
+ config_file = os.path.join(ROOT, "lua", "quicker", "config.lua")
+ option_lines = read_section(config_file, r"^\s*local default_config =", r"^}$")
+ option_lines.insert(0, 'require("quicker").setup({\n')
+ option_lines.insert(0, "```lua\n")
+ option_lines.extend(["})\n", "```\n", "\n"])
+ replace_section(
+ README,
+ r"^<!-- OPTIONS -->$",
+ r"^<!-- /OPTIONS -->$",
+ option_lines,
+ )
+
+
+def update_readme_toc():
+ toc = ["\n"] + generate_md_toc(README, max_level=1) + ["\n"]
+ replace_section(
+ README,
+ r"^<!-- TOC -->$",
+ r"^<!-- /TOC -->$",
+ toc,
+ )
+
+
+def gen_options_vimdoc() -> VimdocSection:
+ section = VimdocSection("Options", "quicker-options", ["\n", ">lua\n"])
+ config_file = os.path.join(ROOT, "lua", "quicker", "config.lua")
+ option_lines = read_section(config_file, r"^\s*local default_config =", r"^}$")
+ option_lines.insert(0, 'require("quicker").setup({\n')
+ option_lines.extend(["})\n"])
+ section.body.extend(indent(option_lines, 4))
+ section.body.append("<\n")
+ return section
+
+
+def generate_vimdoc():
+ doc = Vimdoc("quicker.txt", "quicker")
+ types = parse_directory(os.path.join(ROOT, "lua"))
+ funcs = types.files["quicker/init.lua"].functions
+ doc.sections.extend(
+ [
+ gen_options_vimdoc(),
+ VimdocSection(
+ "API", "quicker-api", render_vimdoc_api2("quicker", funcs, types)
+ ),
+ ]
+ )
+
+ with open(VIMDOC, "w", encoding="utf-8") as ofile:
+ ofile.writelines(doc.render())
+
+
+def main() -> None:
+ """Update the README"""
+ update_md_api()
+ update_options()
+ update_readme_toc()
+ generate_vimdoc()
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/scripts/main.py b/mut/neovim/pack/plugins/start/quicker.nvim/scripts/main.py
new file mode 100755
index 0000000..4dffddf
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/scripts/main.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+import argparse
+import os
+import sys
+
+HERE = os.path.dirname(__file__)
+ROOT = os.path.abspath(os.path.join(HERE, os.path.pardir))
+DOC = os.path.join(ROOT, "doc")
+
+
+def main() -> None:
+ """Generate docs"""
+ sys.path.append(HERE)
+ parser = argparse.ArgumentParser(description=main.__doc__)
+ parser.add_argument("command", choices=["generate", "lint"])
+ args = parser.parse_args()
+ if args.command == "generate":
+ import generate
+
+ generate.main()
+ elif args.command == "lint":
+ from nvim_doc_tools import lint_md_links
+
+ files = [os.path.join(ROOT, "README.md")] + [
+ os.path.join(DOC, file) for file in os.listdir(DOC) if file.endswith(".md")
+ ]
+ lint_md_links.main(ROOT, files)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/scripts/requirements.txt b/mut/neovim/pack/plugins/start/quicker.nvim/scripts/requirements.txt
new file mode 100644
index 0000000..2c6271f
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/scripts/requirements.txt
@@ -0,0 +1,4 @@
+pyparsing==3.0.9
+black
+isort
+mypy
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/syntax/qf.vim b/mut/neovim/pack/plugins/start/quicker.nvim/syntax/qf.vim
new file mode 100644
index 0000000..8a19536
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/syntax/qf.vim
@@ -0,0 +1,7 @@
+if exists('b:current_syntax')
+ finish
+endif
+
+syn match QuickFixText /^.*/
+
+let b:current_syntax = 'qf'
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/context_spec.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/context_spec.lua
new file mode 100644
index 0000000..4a4ef5a
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/context_spec.lua
@@ -0,0 +1,134 @@
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+describe("context", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("expand results", function()
+ local first = test_util.make_tmp_file("expand_1.txt", 10)
+ local second = test_util.make_tmp_file("expand_2.txt", 10)
+ local first_buf = vim.fn.bufadd(first)
+ local second_buf = vim.fn.bufadd(second)
+ vim.fn.setqflist({
+ {
+ bufnr = first_buf,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ {
+ bufnr = first_buf,
+ text = "line 8",
+ lnum = 8,
+ valid = 1,
+ },
+ {
+ bufnr = second_buf,
+ text = "line 4",
+ lnum = 4,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "expand_1")
+
+ vim.api.nvim_win_set_cursor(0, { 3, 0 })
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_2")
+ -- Cursor stays on the same item
+ assert.equals(12, vim.api.nvim_win_get_cursor(0)[1])
+ vim.api.nvim_win_set_cursor(0, { 14, 0 })
+
+ -- Expanding again will produce the same result
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_2")
+ assert.equals(14, vim.api.nvim_win_get_cursor(0)[1])
+
+ -- Expanding again will produce the same result
+ quicker.expand({ add_to_existing = true })
+ test_util.assert_snapshot(0, "expand_3")
+
+ -- Collapsing will return to the original state
+ quicker.collapse()
+ test_util.assert_snapshot(0, "expand_1")
+ assert.equals(3, vim.api.nvim_win_get_cursor(0)[1])
+ end)
+
+ it("expand loclist results", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("expand_loclist.txt", 10))
+ vim.fn.setloclist(0, {
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ })
+ vim.cmd.lopen()
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_loclist")
+ end)
+
+ it("expand when items missing bufnr", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("expand_missing.txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ {
+ text = "Valid line with no bufnr",
+ lnum = 4,
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "Invalid line with a bufnr",
+ lnum = 5,
+ valid = 0,
+ },
+ {
+ text = "Invalid line with no bufnr",
+ lnum = 6,
+ valid = 0,
+ },
+ })
+ vim.cmd.copen()
+ quicker.expand()
+ -- The last three lines should be stripped after expansion
+ test_util.assert_snapshot(0, "expand_missing")
+ end)
+
+ it("expand removes duplicate line entries", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("expand_dupe.txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 3",
+ lnum = 3,
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 3",
+ lnum = 3,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "expand_dupe_1")
+
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_dupe_2")
+ end)
+end)
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/display_spec.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/display_spec.lua
new file mode 100644
index 0000000..c3404ad
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/display_spec.lua
@@ -0,0 +1,145 @@
+require("plenary.async").tests.add_to_env()
+local config = require("quicker.config")
+local test_util = require("tests.test_util")
+
+local sleep = require("plenary.async.util").sleep
+
+a.describe("display", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("renders quickfix items", function()
+ vim.fn.setqflist({
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ {
+ filename = "README.md",
+ text = "text",
+ lnum = 10,
+ col = 0,
+ end_col = 4,
+ nr = 3,
+ type = "E",
+ valid = 1,
+ },
+ {
+ module = "mod",
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ valid = 1,
+ },
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ valid = 0,
+ },
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ lnum = 1,
+ text = "",
+ valid = 0,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "display_1")
+ end)
+
+ a.it("truncates long filenames", function()
+ config.max_filename_width = function()
+ return 10
+ end
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file(string.rep("f", 10) .. ".txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ -- Wait for highlights to be applied
+ sleep(50)
+ test_util.assert_snapshot(0, "display_long_1")
+ end)
+
+ a.it("renders minimal line when no filenames in results", function()
+ vim.fn.setqflist({
+ {
+ text = "text",
+ },
+ })
+ vim.cmd.copen()
+ -- Wait for highlights to be applied
+ sleep(50)
+ test_util.assert_snapshot(0, "display_minimal_1")
+ end)
+
+ a.it("sets signs for diagnostics", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("sign_test.txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 1,
+ type = "E",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 2,
+ type = "W",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 3,
+ type = "I",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 4,
+ type = "H",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 5,
+ type = "N",
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+
+ -- Wait for highlights to be applied
+ sleep(50)
+ local ns = vim.api.nvim_create_namespace("quicker_highlights")
+ local marks = vim.api.nvim_buf_get_extmarks(0, ns, 0, -1, { type = "sign" })
+ assert.equals(5, #marks)
+ local expected = {
+ { "DiagnosticSignError", config.type_icons.E },
+ { "DiagnosticSignWarn", config.type_icons.W },
+ { "DiagnosticSignInfo", config.type_icons.I },
+ { "DiagnosticSignHint", config.type_icons.H },
+ { "DiagnosticSignHint", config.type_icons.N },
+ }
+ for i, mark_data in ipairs(marks) do
+ local extmark_id, row = mark_data[1], mark_data[2]
+ local mark = vim.api.nvim_buf_get_extmark_by_id(0, ns, extmark_id, { details = true })
+ local hl_group, icon = unpack(expected[i])
+ assert.equals(i - 1, row)
+ assert.equals(hl_group, mark[3].sign_hl_group)
+ assert.equals(icon, mark[3].sign_text)
+ end
+ end)
+end)
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/editor_spec.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/editor_spec.lua
new file mode 100644
index 0000000..0999508
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/editor_spec.lua
@@ -0,0 +1,347 @@
+local config = require("quicker.config")
+local display = require("quicker.display")
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+---@param lnum integer
+---@param line string
+local function replace_text(lnum, line)
+ local prev_line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]
+ local idx = prev_line:find(display.EM_QUAD, 1, true)
+ vim.api.nvim_buf_set_text(0, lnum - 1, idx + display.EM_QUAD_LEN - 1, lnum - 1, -1, { line })
+end
+
+---@param lnum integer
+local function del_line(lnum)
+ vim.cmd.normal({ args = { string.format("%dggdd", lnum) }, bang = true })
+end
+
+local function wait_virt_text()
+ vim.wait(10, function()
+ return false
+ end)
+end
+
+describe("editor", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("can edit one line in file", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_1.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, "new text")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_1")
+ end)
+
+ it("can edit across multiple files", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_multiple_1.txt", 10))
+ vim.fn.bufload(bufnr)
+ local buf2 = vim.fn.bufadd(test_util.make_tmp_file("edit_multiple_2.txt", 10))
+ vim.fn.bufload(buf2)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 9",
+ lnum = 9,
+ },
+ {
+ bufnr = buf2,
+ text = "line 5",
+ lnum = 5,
+ },
+ })
+ vim.cmd.copen()
+ quicker.expand()
+ wait_virt_text()
+ replace_text(2, "new text")
+ replace_text(3, "some text")
+ replace_text(7, "other text")
+ replace_text(11, "final text")
+ local last_line = vim.api.nvim_buf_line_count(0)
+ vim.api.nvim_win_set_cursor(0, { last_line, 0 })
+ vim.cmd.write()
+ test_util.assert_snapshot(0, "edit_multiple_qf")
+ test_util.assert_snapshot(bufnr, "edit_multiple_1")
+ test_util.assert_snapshot(buf2, "edit_multiple_2")
+ -- We should keep the cursor position
+ assert.equals(last_line, vim.api.nvim_win_get_cursor(0)[1])
+ end)
+
+ it("can expand then edit expanded line", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_expanded.txt", 10))
+ vim.fn.bufload(bufnr)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ quicker.expand()
+ wait_virt_text()
+ replace_text(1, "first")
+ replace_text(2, "second")
+ replace_text(3, "third")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_expanded")
+ test_util.assert_snapshot(0, "edit_expanded_qf")
+ end)
+
+ it("fails when source text is different", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_fail.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "buzz buzz",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, "new text")
+ test_util.with(function()
+ local notify = vim.notify
+ ---@diagnostic disable-next-line: duplicate-set-field
+ vim.notify = function() end
+ return function()
+ vim.notify = notify
+ end
+ end, function()
+ vim.cmd.write()
+ end)
+ test_util.assert_snapshot(bufnr, "edit_fail")
+ test_util.assert_snapshot(0, "edit_fail_qf")
+ end)
+
+ it("can handle multiple qf items on same lnum", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_dupe.txt", 10))
+ vim.fn.bufload(bufnr)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ col = 0,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ col = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, "first")
+ replace_text(2, "second")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_dupe")
+ test_util.assert_snapshot(0, "edit_dupe_qf")
+
+ -- If only one of them has a change, it should go through
+ replace_text(1, "line 2")
+ replace_text(2, "second")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_dupe_2")
+ test_util.assert_snapshot(0, "edit_dupe_qf_2")
+ end)
+
+ it("handles deleting lines (shrinks quickfix)", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_delete.txt", 10))
+ vim.fn.bufload(bufnr)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 3",
+ lnum = 3,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 6",
+ lnum = 6,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ del_line(3)
+ del_line(2)
+ vim.cmd.write()
+ assert.are.same({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ col = 0,
+ end_col = 0,
+ vcol = 0,
+ end_lnum = 0,
+ module = "",
+ nr = 0,
+ pattern = "",
+ type = "",
+ valid = 1,
+ },
+ }, vim.fn.getqflist())
+ end)
+
+ it("handles loclist", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_ll.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setloclist(0, {
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.lopen()
+ wait_virt_text()
+ replace_text(1, "new text")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_ll")
+ end)
+
+ it("handles text that contains the delimiter", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_delim.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ local line = "line 2 " .. config.borders.vert .. " text"
+ vim.api.nvim_buf_set_lines(bufnr, 1, 2, false, { line })
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = line,
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, line .. " " .. config.borders.vert .. " more text")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_delim")
+ end)
+
+ it("can edit lines with trimmed common whitespace", function()
+ require("quicker.config").trim_leading_whitespace = "common"
+ vim.cmd.edit({
+ args = {
+ test_util.make_tmp_file("edit_whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ " line 4",
+ }),
+ },
+ })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ test_util.assert_snapshot(0, "edit_whitespace_qf")
+ replace_text(1, "foo")
+ replace_text(2, "bar")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_whitespace")
+ end)
+
+ it("can edit lines with trimmed all whitespace", function()
+ require("quicker.config").trim_leading_whitespace = "all"
+ vim.cmd.edit({
+ args = {
+ test_util.make_tmp_file("edit_whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ " line 4",
+ }),
+ },
+ })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ test_util.assert_snapshot(0, "edit_all_whitespace_qf")
+ replace_text(1, "foo")
+ replace_text(2, "bar")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_all_whitespace")
+ end)
+
+ it("can edit lines with untrimmed whitespace", function()
+ require("quicker.config").trim_leading_whitespace = false
+ vim.cmd.edit({
+ args = {
+ test_util.make_tmp_file("edit_whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ " line 4",
+ }),
+ },
+ })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ test_util.assert_snapshot(0, "edit_none_whitespace_qf")
+ replace_text(1, "foo")
+ replace_text(2, "bar")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_none_whitespace")
+ end)
+end)
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/fs_spec.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/fs_spec.lua
new file mode 100644
index 0000000..2e1e54f
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/fs_spec.lua
@@ -0,0 +1,20 @@
+local fs = require("quicker.fs")
+
+local home = os.getenv("HOME")
+local cwd = vim.fn.getcwd()
+
+describe("fs", function()
+ it("shortens path", function()
+ assert.equals("~/bar/baz.txt", fs.shorten_path(home .. "/bar/baz.txt"))
+ assert.equals("bar/baz.txt", fs.shorten_path(cwd .. "/bar/baz.txt"))
+ assert.equals("/foo/bar.txt", fs.shorten_path("/foo/bar.txt"))
+ end)
+
+ it("finds subpath", function()
+ assert.truthy(fs.is_subpath("/root", "/root/foo"))
+ assert.truthy(fs.is_subpath(cwd, "foo"))
+ assert.falsy(fs.is_subpath("/root", "/foo"))
+ assert.falsy(fs.is_subpath("/root", "/rooter/foo"))
+ assert.falsy(fs.is_subpath("/root", "/root/../foo"))
+ end)
+end)
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/minimal_init.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/minimal_init.lua
new file mode 100644
index 0000000..486b213
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/minimal_init.lua
@@ -0,0 +1,16 @@
+vim.cmd([[set runtimepath+=.]])
+
+vim.o.swapfile = false
+vim.bo.swapfile = false
+require("tests.test_util").reset_editor()
+
+-- TODO test highlighting (both highlight.lua module and adding them in display.lua)
+-- TODO test syntax highlighting when customizing delimiter
+
+vim.api.nvim_create_user_command("RunTests", function(opts)
+ local path = opts.fargs[1] or "tests"
+ require("plenary.test_harness").test_directory(
+ path,
+ { minimal_init = "./tests/minimal_init.lua" }
+ )
+end, { nargs = "?" })
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/opts_spec.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/opts_spec.lua
new file mode 100644
index 0000000..0732da2
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/opts_spec.lua
@@ -0,0 +1,52 @@
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+describe("opts", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("sets buffer opts", function()
+ quicker.setup({
+ opts = {
+ buflisted = true,
+ bufhidden = "wipe",
+ cindent = true,
+ },
+ })
+ vim.fn.setqflist({
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ assert.truthy(vim.bo.buflisted)
+ assert.equals("wipe", vim.bo.bufhidden)
+ assert.truthy(vim.bo.cindent)
+ end)
+
+ it("sets window opts", function()
+ quicker.setup({
+ opts = {
+ wrap = false,
+ number = true,
+ list = true,
+ },
+ })
+ vim.fn.setqflist({
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ assert.falsy(vim.wo.wrap)
+ assert.truthy(vim.wo.number)
+ assert.truthy(vim.wo.list)
+ end)
+end)
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_1 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_1
new file mode 100644
index 0000000..270a164
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_1
@@ -0,0 +1,5 @@
+README.md ┃ 5┃text
+README.md ┃10┃text
+mod  ┃ ┃text
+README.md ┃ ┃text
+README.md ┃ 1┃ \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_long_1 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_long_1
new file mode 100644
index 0000000..d585beb
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_long_1
@@ -0,0 +1 @@
+…ffffffff.txt ┃ 5┃text \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_minimal_1 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_minimal_1
new file mode 100644
index 0000000..46190e7
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/display_minimal_1
@@ -0,0 +1 @@
+ ┃text \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_1 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_1
new file mode 100644
index 0000000..a18ed5a
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_1
@@ -0,0 +1,10 @@
+line 1
+new text
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace
new file mode 100644
index 0000000..998c877
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace
@@ -0,0 +1,4 @@
+ line 1
+ foo
+ bar
+ line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace_qf b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace_qf
new file mode 100644
index 0000000..baf8533
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_all_whitespace_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_whitespace.txt ┃ 2┃line 2
+tests/tmp/edit_whitespace.txt ┃ 3┃line 3 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_delim b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_delim
new file mode 100644
index 0000000..75a9e7f
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_delim
@@ -0,0 +1,10 @@
+line 1
+line 2 ┃ text ┃ more text
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe
new file mode 100644
index 0000000..b3f56ae
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_2 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_2
new file mode 100644
index 0000000..3ae9ccc
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_2
@@ -0,0 +1,10 @@
+line 1
+second
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf
new file mode 100644
index 0000000..7e01207
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_dupe.txt ┃ 2┃first
+tests/tmp/edit_dupe.txt ┃ 2┃second \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf_2 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf_2
new file mode 100644
index 0000000..1acfd8e
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_dupe_qf_2
@@ -0,0 +1,2 @@
+tests/tmp/edit_dupe.txt ┃ 2┃line 2
+tests/tmp/edit_dupe.txt ┃ 2┃second \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded
new file mode 100644
index 0000000..afc39ad
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded
@@ -0,0 +1,10 @@
+first
+second
+third
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded_qf b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded_qf
new file mode 100644
index 0000000..991dd06
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_expanded_qf
@@ -0,0 +1,4 @@
+  ┃ 1┃first
+tests/tmp/edit_expanded.txt ┃ 2┃second
+  ┃ 3┃third
+  ┃ 4┃line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail
new file mode 100644
index 0000000..b3f56ae
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail_qf b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail_qf
new file mode 100644
index 0000000..3eb9f10
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_fail_qf
@@ -0,0 +1 @@
+tests/tmp/edit_fail.txt ┃ 2┃new text \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_invalid b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_invalid
new file mode 100644
index 0000000..386c994
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_invalid
@@ -0,0 +1 @@
+ ┃ ┃new text
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_ll b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_ll
new file mode 100644
index 0000000..a18ed5a
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_ll
@@ -0,0 +1,10 @@
+line 1
+new text
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_1 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_1
new file mode 100644
index 0000000..765403a
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_1
@@ -0,0 +1,10 @@
+line 1
+new text
+some text
+line 4
+line 5
+line 6
+line 7
+line 8
+other text
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_2 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_2
new file mode 100644
index 0000000..c988ea1
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_2
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+final text
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_qf b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_qf
new file mode 100644
index 0000000..3de41ad
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_multiple_qf
@@ -0,0 +1,15 @@
+  ┃ 1┃line 1
+tests/tmp/edit_multiple_1.txt ┃ 2┃new text
+  ┃ 3┃some text
+  ┃ 4┃line 4
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╂╌╌╂╌╌╌╌╌╌╌╌
+  ┃ 7┃line 7
+  ┃ 8┃line 8
+tests/tmp/edit_multiple_1.txt ┃ 9┃other text
+  ┃10┃line 10
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━╋━━━━━━━━
+  ┃ 3┃line 3
+  ┃ 4┃line 4
+tests/tmp/edit_multiple_2.txt ┃ 5┃final text
+  ┃ 6┃line 6
+  ┃ 7┃line 7 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace
new file mode 100644
index 0000000..4b592c2
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace
@@ -0,0 +1,4 @@
+ line 1
+foo
+bar
+ line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace_qf b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace_qf
new file mode 100644
index 0000000..5be47f1
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_none_whitespace_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_whitespace.txt ┃ 2┃ line 2
+tests/tmp/edit_whitespace.txt ┃ 3┃ line 3 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace
new file mode 100644
index 0000000..6a5ca4b
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace
@@ -0,0 +1,4 @@
+ line 1
+ foo
+ bar
+ line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace_qf b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace_qf
new file mode 100644
index 0000000..e26d928
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/edit_whitespace_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_whitespace.txt ┃ 2┃line 2
+tests/tmp/edit_whitespace.txt ┃ 3┃ line 3 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_1 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_1
new file mode 100644
index 0000000..ab1901f
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_1
@@ -0,0 +1,3 @@
+tests/tmp/expand_1.txt ┃ 2┃line 2
+tests/tmp/expand_1.txt ┃ 8┃line 8
+tests/tmp/expand_2.txt ┃ 4┃line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_2 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_2
new file mode 100644
index 0000000..e0a2139
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_2
@@ -0,0 +1,16 @@
+  ┃ 1┃line 1
+tests/tmp/expand_1.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╂╌╌╂╌╌╌╌╌╌╌╌
+  ┃ 6┃line 6
+  ┃ 7┃line 7
+tests/tmp/expand_1.txt ┃ 8┃line 8
+  ┃ 9┃line 9
+  ┃10┃line 10
+━━━━━━━━━━━━━━━━━━━━━━━╋━━╋━━━━━━━━
+  ┃ 2┃line 2
+  ┃ 3┃line 3
+tests/tmp/expand_2.txt ┃ 4┃line 4
+  ┃ 5┃line 5
+  ┃ 6┃line 6 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_3 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_3
new file mode 100644
index 0000000..0a20a1c
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_3
@@ -0,0 +1,19 @@
+  ┃ 1┃line 1
+tests/tmp/expand_1.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4
+  ┃ 5┃line 5
+  ┃ 6┃line 6
+  ┃ 7┃line 7
+tests/tmp/expand_1.txt ┃ 8┃line 8
+  ┃ 9┃line 9
+  ┃10┃line 10
+━━━━━━━━━━━━━━━━━━━━━━━╋━━╋━━━━━━━━
+  ┃ 1┃line 1
+  ┃ 2┃line 2
+  ┃ 3┃line 3
+tests/tmp/expand_2.txt ┃ 4┃line 4
+  ┃ 5┃line 5
+  ┃ 6┃line 6
+  ┃ 7┃line 7
+  ┃ 8┃line 8 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_1 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_1
new file mode 100644
index 0000000..8e32cb4
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_1
@@ -0,0 +1,3 @@
+tests/tmp/expand_dupe.txt ┃ 2┃line 2
+tests/tmp/expand_dupe.txt ┃ 3┃line 3
+tests/tmp/expand_dupe.txt ┃ 3┃line 3 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_2 b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_2
new file mode 100644
index 0000000..b51efa8
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_dupe_2
@@ -0,0 +1,5 @@
+  ┃ 1┃line 1
+tests/tmp/expand_dupe.txt ┃ 2┃line 2
+tests/tmp/expand_dupe.txt ┃ 3┃line 3
+  ┃ 4┃line 4
+  ┃ 5┃line 5 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_loclist b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_loclist
new file mode 100644
index 0000000..66a6207
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_loclist
@@ -0,0 +1,4 @@
+  ┃ 1┃line 1
+tests/tmp/expand_loclist.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_missing b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_missing
new file mode 100644
index 0000000..f29a273
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/expand_missing
@@ -0,0 +1,4 @@
+  ┃ 1┃line 1
+tests/tmp/expand_missing.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_all_whitespace b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_all_whitespace
new file mode 100644
index 0000000..e664f80
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_all_whitespace
@@ -0,0 +1,2 @@
+tests/tmp/whitespace_1.txt ┃ 2┃line 2
+tests/tmp/whitespace_1.txt ┃ 3┃line 3 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_mixed_whitespace b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_mixed_whitespace
new file mode 100644
index 0000000..f6464d4
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_mixed_whitespace
@@ -0,0 +1,2 @@
+tests/tmp/mixed_whitespace.txt ┃ 1┃ line 1
+tests/tmp/mixed_whitespace.txt ┃ 2┃ line 2 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace
new file mode 100644
index 0000000..49a6e20
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace
@@ -0,0 +1,2 @@
+tests/tmp/whitespace.txt ┃ 2┃line 2
+tests/tmp/whitespace.txt ┃ 3┃ line 3 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace_expanded b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace_expanded
new file mode 100644
index 0000000..07b70d1
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/snapshots/trim_whitespace_expanded
@@ -0,0 +1,5 @@
+  ┃ 1┃ line 1
+tests/tmp/whitespace.txt ┃ 2┃line 2
+tests/tmp/whitespace.txt ┃ 3┃ line 3
+  ┃ 4┃
+  ┃ 5┃ line 4 \ No newline at end of file
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/test_util.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/test_util.lua
new file mode 100644
index 0000000..94b0a40
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/test_util.lua
@@ -0,0 +1,142 @@
+require("plenary.async").tests.add_to_env()
+local M = {}
+
+local tmp_files = {}
+M.reset_editor = function()
+ vim.cmd.tabonly({ mods = { silent = true } })
+ for i, winid in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
+ if i > 1 then
+ vim.api.nvim_win_close(winid, true)
+ end
+ end
+ vim.api.nvim_win_set_buf(0, vim.api.nvim_create_buf(false, true))
+ for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
+ vim.api.nvim_buf_delete(bufnr, { force = true })
+ end
+ vim.fn.setqflist({})
+ vim.fn.setloclist(0, {})
+ for _, filename in ipairs(tmp_files) do
+ vim.uv.fs_unlink(filename)
+ end
+ tmp_files = {}
+
+ require("quicker").setup({
+ header_length = function()
+ -- Make this deterministic so the snapshots are stable
+ return 8
+ end,
+ })
+end
+
+---@param basename string
+---@param lines integer|string[]
+---@return string
+M.make_tmp_file = function(basename, lines)
+ vim.fn.mkdir("tests/tmp", "p")
+ local filename = "tests/tmp/" .. basename
+ table.insert(tmp_files, filename)
+ local f = assert(io.open(filename, "w"))
+ if type(lines) == "table" then
+ for _, line in ipairs(lines) do
+ f:write(line .. "\n")
+ end
+ else
+ for i = 1, lines do
+ f:write("line " .. i .. "\n")
+ end
+ end
+ f:close()
+ return filename
+end
+
+---@param name string
+---@return string[]
+local function load_snapshot(name)
+ local path = "tests/snapshots/" .. name
+ if vim.fn.filereadable(path) == 0 then
+ return {}
+ end
+ local f = assert(io.open(path, "r"))
+ local lines = {}
+ for line in f:lines() do
+ table.insert(lines, line)
+ end
+ f:close()
+ return lines
+end
+
+---@param name string
+---@param lines string[]
+local function save_snapshot(name, lines)
+ vim.fn.mkdir("tests/snapshots", "p")
+ local path = "tests/snapshots/" .. name
+ local f = assert(io.open(path, "w"))
+ f:write(table.concat(lines, "\n"))
+ f:close()
+ return lines
+end
+
+---@param bufnr integer
+---@param name string
+M.assert_snapshot = function(bufnr, name)
+ -- Wait for the virtual text extmarks to be set
+ if vim.bo[bufnr].filetype == "qf" then
+ vim.wait(10, function()
+ return false
+ end)
+ end
+ local util = require("quicker.util")
+ local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+
+ -- Add virtual text to lines
+ local headers = {}
+ local header_ns = vim.api.nvim_create_namespace("quicker_headers")
+ for i, v in ipairs(lines) do
+ local extmarks = util.get_lnum_extmarks(bufnr, i, v:len())
+ assert(#extmarks <= 1, "Expected at most one extmark per line")
+ local mark = extmarks[1]
+ if mark then
+ local start_col = mark[3]
+ local data = mark[4]
+ local virt_text = table.concat(
+ vim.tbl_map(function(vt)
+ return vt[1]
+ end, data.virt_text),
+ ""
+ )
+ lines[i] = v:sub(0, start_col) .. virt_text .. v:sub(start_col + 1)
+
+ extmarks = util.get_lnum_extmarks(bufnr, i, v:len(), header_ns)
+ assert(#extmarks <= 1, "Expected at most one extmark per line")
+ mark = extmarks[1]
+ if mark and mark[4].virt_lines then
+ table.insert(headers, { i, mark[4].virt_lines[1][1][1] })
+ end
+ end
+ end
+
+ for i = #headers, 1, -1 do
+ local lnum, header = unpack(headers[i])
+ table.insert(lines, lnum, header)
+ end
+
+ if os.getenv("UPDATE_SNAPSHOTS") then
+ save_snapshot(name, lines)
+ else
+ local expected = load_snapshot(name)
+ assert.are.same(expected, lines)
+ end
+end
+
+---@param context fun(): fun()
+---@param fn fun()
+M.with = function(context, fn)
+ local cleanup = context()
+ local ok, err = pcall(fn)
+ cleanup()
+ if not ok then
+ error(err)
+ end
+end
+
+return M
diff --git a/mut/neovim/pack/plugins/start/quicker.nvim/tests/whitespace_spec.lua b/mut/neovim/pack/plugins/start/quicker.nvim/tests/whitespace_spec.lua
new file mode 100644
index 0000000..0933276
--- /dev/null
+++ b/mut/neovim/pack/plugins/start/quicker.nvim/tests/whitespace_spec.lua
@@ -0,0 +1,83 @@
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+describe("whitespace", function()
+ before_each(function()
+ require("quicker.config").trim_leading_whitespace = "common"
+ end)
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("removes common leading whitespace from valid results", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ "",
+ " line 4",
+ }))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "trim_whitespace")
+ quicker.expand()
+ test_util.assert_snapshot(0, "trim_whitespace_expanded")
+ end)
+
+ it("handles mixed tabs and spaces", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("mixed_whitespace.txt", {
+ " line 1",
+ "\t\tline 2",
+ }))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 1",
+ lnum = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "\t\tline 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "trim_mixed_whitespace")
+ end)
+
+ it("removes all leading whitespace", function()
+ require("quicker.config").trim_leading_whitespace = "all"
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("whitespace_1.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ "",
+ " line 4",
+ }))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "trim_all_whitespace")
+ end)
+end)