├── plugin └── snacks.lua ├── selene.toml ├── scripts ├── test └── docs ├── .gitignore ├── queries └── lua │ └── highlights.scm ├── stylua.toml ├── .editorconfig ├── .github ├── dependabot.yml ├── workflows │ ├── labeler.yml │ ├── ci.yml │ ├── stale.yml │ ├── update.yml │ └── pr.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml ├── PULL_REQUEST_TEMPLATE.md └── labeler.yml ├── vim.toml ├── tests ├── minit.lua ├── terminal_spec.lua └── gitbrowse_spec.lua ├── docs ├── health.md ├── quickfile.md ├── git.md ├── meta.md ├── notify.md ├── statuscolumn.md ├── bufdelete.md ├── words.md ├── bigfile.md ├── scroll.md ├── dim.md ├── rename.md ├── input.md ├── util.md ├── scope.md ├── zen.md ├── animate.md ├── toggle.md ├── debug.md ├── gitbrowse.md ├── init.md ├── lazygit.md ├── indent.md ├── examples │ └── init.lua ├── scratch.md └── terminal.md ├── doc ├── snacks-health.txt ├── snacks-quickfile.txt ├── snacks-meta.txt ├── snacks-git.txt ├── snacks-bigfile.txt ├── snacks-notify.txt ├── snacks-statuscolumn.txt ├── snacks-bufdelete.txt ├── snacks-words.txt ├── snacks-scroll.txt ├── snacks-rename.txt ├── snacks-dim.txt ├── snacks-scope.txt ├── snacks-animate.txt ├── snacks-util.txt ├── snacks-input.txt ├── snacks-zen.txt ├── snacks-init.txt ├── snacks-indent.txt ├── snacks-gitbrowse.txt └── snacks-debug.txt └── lua ├── snacks ├── quickfile.lua ├── notify.lua ├── git.lua ├── meta │ ├── types.lua │ └── init.lua ├── bigfile.lua ├── health.lua ├── rename.lua ├── bufdelete.lua ├── dim.lua ├── words.lua ├── init.lua ├── profiler │ └── loc.lua ├── util.lua ├── input.lua ├── animate │ └── init.lua └── zen.lua └── trouble └── sources └── profiler.lua /plugin/snacks.lua: -------------------------------------------------------------------------------- 1 | require("snacks") 2 | -------------------------------------------------------------------------------- /selene.toml: -------------------------------------------------------------------------------- 1 | std="vim" 2 | 3 | [lints] 4 | mixed_table="allow" 5 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | nvim -l tests/minit.lua --minitest 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /.repro 3 | /.tests 4 | /build 5 | /debug 6 | /doc/tags 7 | foo.* 8 | node_modules 9 | tt.* 10 | -------------------------------------------------------------------------------- /queries/lua/highlights.scm: -------------------------------------------------------------------------------- 1 | ;; extends 2 | 3 | ((identifier) @namespace.builtin 4 | (#eq? @namespace.builtin "Snacks")) 5 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 2 3 | column_width = 120 4 | [sort_requires] 5 | enabled = true 6 | 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "PR Labeler" 2 | on: 3 | - pull_request_target 4 | 5 | jobs: 6 | labeler: 7 | uses: folke/github/.github/workflows/labeler.yml@main 8 | secrets: inherit 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/folke/snacks.nvim/discussions 5 | about: Use Github discussions instead 6 | -------------------------------------------------------------------------------- /vim.toml: -------------------------------------------------------------------------------- 1 | [selene] 2 | base = "lua51" 3 | name = "vim" 4 | 5 | [vim] 6 | any = true 7 | 8 | [jit] 9 | any = true 10 | 11 | [assert] 12 | any = true 13 | 14 | [describe] 15 | any = true 16 | 17 | [it] 18 | any = true 19 | 20 | [before_each.args] 21 | any = true 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | jobs: 9 | ci: 10 | uses: folke/github/.github/workflows/ci.yml@main 11 | secrets: inherit 12 | with: 13 | plugin: snacks.nvim 14 | repo: folke/snacks.nvim 15 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale Issues & PRs 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | stale: 9 | if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner) 10 | uses: folke/github/.github/workflows/stale.yml@main 11 | secrets: inherit 12 | -------------------------------------------------------------------------------- /tests/minit.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S nvim -l 2 | 3 | vim.env.LAZY_STDPATH = ".tests" 4 | load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))() 5 | 6 | -- Setup lazy.nvim 7 | require("lazy.minit").setup({ 8 | spec = { 9 | { dir = vim.uv.cwd() }, 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Update Repo 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # Run every hour 7 | - cron: "0 * * * *" 8 | 9 | jobs: 10 | update: 11 | if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner) 12 | uses: folke/github/.github/workflows/update.yml@main 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR Title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | - reopened 10 | - ready_for_review 11 | 12 | permissions: 13 | pull-requests: read 14 | 15 | jobs: 16 | pr-title: 17 | uses: folke/github/.github/workflows/pr.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /docs/health.md: -------------------------------------------------------------------------------- 1 | # 🍿 health 2 | 3 | 4 | 5 | ## 📦 Module 6 | 7 | ```lua 8 | ---@class snacks.health 9 | ---@field ok fun(msg: string) 10 | ---@field warn fun(msg: string) 11 | ---@field error fun(msg: string) 12 | ---@field info fun(msg: string) 13 | ---@field start fun(msg: string) 14 | Snacks.health = {} 15 | ``` 16 | 17 | ### `Snacks.health.check()` 18 | 19 | ```lua 20 | Snacks.health.check() 21 | ``` 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 5 | 6 | ## Related Issue(s) 7 | 8 | 12 | 13 | ## Screenshots 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/quickfile.md: -------------------------------------------------------------------------------- 1 | # 🍿 quickfile 2 | 3 | When doing `nvim somefile.txt`, it will render the file as quickly as possible, 4 | before loading your plugins. 5 | 6 | 7 | 8 | ## 📦 Setup 9 | 10 | ```lua 11 | -- lazy.nvim 12 | { 13 | "folke/snacks.nvim", 14 | opts = { 15 | quickfile = { 16 | -- your quickfile configuration comes here 17 | -- or leave it empty to use the default settings 18 | -- refer to the configuration section below 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | ## ⚙️ Config 25 | 26 | ```lua 27 | ---@class snacks.quickfile.Config 28 | { 29 | -- any treesitter langs to exclude 30 | exclude = { "latex" }, 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /scripts/docs: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | set -e 4 | 5 | nvim -u tests/minit.lua --headless +'lua require("snacks.meta.docs").build()' +qa 6 | 7 | echo -e "\n\nGenerating Vim Help" 8 | 9 | if [ "$GITHUB_ACTIONS" = "true" ]; then 10 | exit 0 11 | fi 12 | 13 | PANVIMDOC=~/projects/panvimdoc 14 | 15 | for f in docs/*.md; do 16 | $PANVIMDOC/panvimdoc.sh \ 17 | --project-name "snacks-$(basename "$f" .md)" \ 18 | --description "snacks.nvim" \ 19 | --input-file "$f" \ 20 | --vim-version "Neovim" \ 21 | --demojify "true" \ 22 | --shift-heading-level-by -1 23 | done 24 | 25 | nvim -u tests/minit.lua --headless +'lua require("snacks.meta.docs").fix_titles()' +qa 26 | -------------------------------------------------------------------------------- /docs/git.md: -------------------------------------------------------------------------------- 1 | # 🍿 git 2 | 3 | 4 | 5 | ## 🎨 Styles 6 | 7 | ### `blame_line` 8 | 9 | ```lua 10 | { 11 | width = 0.6, 12 | height = 0.6, 13 | border = "rounded", 14 | title = " Git Blame ", 15 | title_pos = "center", 16 | ft = "git", 17 | } 18 | ``` 19 | 20 | ## 📦 Module 21 | 22 | ### `Snacks.git.blame_line()` 23 | 24 | Show git log for the current line. 25 | 26 | ```lua 27 | ---@param opts? snacks.terminal.Opts | {count?: number} 28 | Snacks.git.blame_line(opts) 29 | ``` 30 | 31 | ### `Snacks.git.get_root()` 32 | 33 | Gets the git root for a buffer or path. 34 | Defaults to the current buffer. 35 | 36 | ```lua 37 | ---@param path? number|string buffer or path 38 | Snacks.git.get_root(path) 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/meta.md: -------------------------------------------------------------------------------- 1 | # 🍿 meta 2 | 3 | Meta functions for Snacks 4 | 5 | 6 | 7 | ## 📚 Types 8 | 9 | ```lua 10 | ---@class snacks.meta.Meta 11 | ---@field desc string 12 | ---@field needs_setup? boolean 13 | ---@field hide? boolean 14 | ---@field readme? boolean 15 | ---@field docs? boolean 16 | ---@field health? boolean 17 | ---@field types? boolean 18 | ---@field config? boolean 19 | ``` 20 | 21 | ```lua 22 | ---@class snacks.meta.Plugin 23 | ---@field name string 24 | ---@field file string 25 | ---@field meta snacks.meta.Meta 26 | ---@field health? fun() 27 | ``` 28 | 29 | ## 📦 Module 30 | 31 | ### `Snacks.meta.get()` 32 | 33 | Get the metadata for all snacks plugins 34 | 35 | ```lua 36 | ---@return snacks.meta.Plugin[] 37 | Snacks.meta.get() 38 | ``` 39 | -------------------------------------------------------------------------------- /tests/terminal_spec.lua: -------------------------------------------------------------------------------- 1 | ---@module "luassert" 2 | 3 | local terminal = require("snacks.terminal") 4 | 5 | local tests = { 6 | { "bash", { "bash" } }, 7 | { '"bash"', { "bash" } }, 8 | { 9 | '"C:\\Program Files\\Git\\bin\\bash.exe" -c "echo hello"', 10 | { "C:\\Program Files\\Git\\bin\\bash.exe", "-c", "echo hello" }, 11 | }, 12 | { "pwsh -NoLogo", { "pwsh", "-NoLogo" } }, 13 | { 'echo "foo\tbar"', { "echo", "foo\tbar" } }, 14 | { "echo\tfoo", { "echo", "foo" } }, 15 | { 'this "is \\"a test"', { "this", 'is "a test' } }, 16 | } 17 | 18 | describe("terminal.parse", function() 19 | for _, test in ipairs(tests) do 20 | it("should parse " .. test[1], function() 21 | local result = terminal.parse(test[1]) 22 | assert.are.same(test[2], result) 23 | end) 24 | end 25 | end) 26 | -------------------------------------------------------------------------------- /docs/notify.md: -------------------------------------------------------------------------------- 1 | # 🍿 notify 2 | 3 | 4 | 5 | ## 📚 Types 6 | 7 | ```lua 8 | ---@alias snacks.notify.Opts snacks.notifier.Notif.opts|{once?: boolean} 9 | ``` 10 | 11 | ## 📦 Module 12 | 13 | ### `Snacks.notify()` 14 | 15 | ```lua 16 | ---@type fun(msg: string|string[], opts?: snacks.notify.Opts) 17 | Snacks.notify() 18 | ``` 19 | 20 | ### `Snacks.notify.error()` 21 | 22 | ```lua 23 | ---@param msg string|string[] 24 | ---@param opts? snacks.notify.Opts 25 | Snacks.notify.error(msg, opts) 26 | ``` 27 | 28 | ### `Snacks.notify.info()` 29 | 30 | ```lua 31 | ---@param msg string|string[] 32 | ---@param opts? snacks.notify.Opts 33 | Snacks.notify.info(msg, opts) 34 | ``` 35 | 36 | ### `Snacks.notify.notify()` 37 | 38 | ```lua 39 | ---@param msg string|string[] 40 | ---@param opts? snacks.notify.Opts 41 | Snacks.notify.notify(msg, opts) 42 | ``` 43 | 44 | ### `Snacks.notify.warn()` 45 | 46 | ```lua 47 | ---@param msg string|string[] 48 | ---@param opts? snacks.notify.Opts 49 | Snacks.notify.warn(msg, opts) 50 | ``` 51 | -------------------------------------------------------------------------------- /doc/snacks-health.txt: -------------------------------------------------------------------------------- 1 | *snacks-health.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-health-table-of-contents* 5 | 6 | 1. Module |snacks-health-module| 7 | - Snacks.health.check() |snacks-health-module-snacks.health.check()| 8 | 9 | ============================================================================== 10 | 1. Module *snacks-health-module* 11 | 12 | >lua 13 | ---@class snacks.health 14 | ---@field ok fun(msg: string) 15 | ---@field warn fun(msg: string) 16 | ---@field error fun(msg: string) 17 | ---@field info fun(msg: string) 18 | ---@field start fun(msg: string) 19 | Snacks.health = {} 20 | < 21 | 22 | 23 | `Snacks.health.check()` *Snacks.health.check()* 24 | 25 | >lua 26 | Snacks.health.check() 27 | < 28 | 29 | Generated by panvimdoc 30 | 31 | vim:tw=78:ts=8:noet:ft=help:norl: 32 | -------------------------------------------------------------------------------- /docs/statuscolumn.md: -------------------------------------------------------------------------------- 1 | # 🍿 statuscolumn 2 | 3 | 4 | 5 | ## 📦 Setup 6 | 7 | ```lua 8 | -- lazy.nvim 9 | { 10 | "folke/snacks.nvim", 11 | opts = { 12 | statuscolumn = { 13 | -- your statuscolumn configuration comes here 14 | -- or leave it empty to use the default settings 15 | -- refer to the configuration section below 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | ## ⚙️ Config 22 | 23 | ```lua 24 | ---@class snacks.statuscolumn.Config 25 | ---@field enabled? boolean 26 | { 27 | left = { "mark", "sign" }, -- priority of signs on the left (high to low) 28 | right = { "fold", "git" }, -- priority of signs on the right (high to low) 29 | folds = { 30 | open = false, -- show open fold icons 31 | git_hl = false, -- use Git Signs hl for fold icons 32 | }, 33 | git = { 34 | -- patterns to match Git signs 35 | patterns = { "GitSign", "MiniDiffSign" }, 36 | }, 37 | refresh = 50, -- refresh at most every 50ms 38 | } 39 | ``` 40 | 41 | ## 📦 Module 42 | 43 | ### `Snacks.statuscolumn()` 44 | 45 | ```lua 46 | ---@type fun(): string 47 | Snacks.statuscolumn() 48 | ``` 49 | 50 | ### `Snacks.statuscolumn.get()` 51 | 52 | ```lua 53 | Snacks.statuscolumn.get() 54 | ``` 55 | -------------------------------------------------------------------------------- /lua/snacks/quickfile.lua: -------------------------------------------------------------------------------- 1 | ---@private 2 | ---@class snacks.quickfile 3 | local M = {} 4 | 5 | M.meta = { 6 | desc = "When doing `nvim somefile.txt`, it will render the file as quickly as possible, before loading your plugins.", 7 | needs_setup = true, 8 | } 9 | 10 | ---@class snacks.quickfile.Config 11 | local defaults = { 12 | -- any treesitter langs to exclude 13 | exclude = { "latex" }, 14 | } 15 | 16 | ---@private 17 | function M.setup() 18 | local opts = Snacks.config.get("quickfile", defaults) 19 | -- Skip if we already entered vim 20 | if vim.v.vim_did_enter == 1 then 21 | return 22 | end 23 | if vim.bo.filetype == "bigfile" then 24 | return 25 | end 26 | 27 | local buf = vim.api.nvim_get_current_buf() 28 | 29 | -- Try to guess the filetype (may change later on during Neovim startup) 30 | local ft = vim.filetype.match({ buf = buf }) 31 | if ft then 32 | -- Add treesitter highlights and fallback to syntax 33 | local lang = vim.treesitter.language.get_lang(ft) 34 | 35 | -- disable treesitter for some langs 36 | if vim.tbl_contains(opts.exclude, lang) then 37 | lang = nil 38 | end 39 | 40 | if not (lang and pcall(vim.treesitter.start, buf, lang)) then 41 | vim.bo[buf].syntax = ft 42 | end 43 | 44 | -- Trigger early redraw 45 | vim.cmd([[redraw]]) 46 | end 47 | end 48 | 49 | return M 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new feature 3 | title: "feature: " 4 | labels: [enhancement] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Did you check the docs? 9 | description: Make sure you read all the docs before submitting a feature request 10 | options: 11 | - label: I have read all the snacks.nvim docs 12 | required: true 13 | - type: textarea 14 | validations: 15 | required: true 16 | attributes: 17 | label: Is your feature request related to a problem? Please describe. 18 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 19 | - type: textarea 20 | validations: 21 | required: true 22 | attributes: 23 | label: Describe the solution you'd like 24 | description: A clear and concise description of what you want to happen. 25 | - type: textarea 26 | validations: 27 | required: true 28 | attributes: 29 | label: Describe alternatives you've considered 30 | description: A clear and concise description of any alternative solutions or features you've considered. 31 | - type: textarea 32 | validations: 33 | required: false 34 | attributes: 35 | label: Additional context 36 | description: Add any other context or screenshots about the feature request here. 37 | -------------------------------------------------------------------------------- /docs/bufdelete.md: -------------------------------------------------------------------------------- 1 | # 🍿 bufdelete 2 | 3 | Delete buffers without disrupting window layout. 4 | 5 | If the buffer you want to close has changes, 6 | a prompt will be shown to save or discard. 7 | 8 | 9 | 10 | ## 📚 Types 11 | 12 | ```lua 13 | ---@class snacks.bufdelete.Opts 14 | ---@field buf? number Buffer to delete. Defaults to the current buffer 15 | ---@field force? boolean Delete the buffer even if it is modified 16 | ---@field filter? fun(buf: number): boolean Filter buffers to delete 17 | ---@field wipe? boolean Wipe the buffer instead of deleting it (see `:h :bwipeout`) 18 | ``` 19 | 20 | ## 📦 Module 21 | 22 | ### `Snacks.bufdelete()` 23 | 24 | ```lua 25 | ---@type fun(buf?: number|snacks.bufdelete.Opts) 26 | Snacks.bufdelete() 27 | ``` 28 | 29 | ### `Snacks.bufdelete.all()` 30 | 31 | Delete all buffers 32 | 33 | ```lua 34 | ---@param opts? snacks.bufdelete.Opts 35 | Snacks.bufdelete.all(opts) 36 | ``` 37 | 38 | ### `Snacks.bufdelete.delete()` 39 | 40 | Delete a buffer: 41 | - either the current buffer if `buf` is not provided 42 | - or the buffer `buf` if it is a number 43 | - or every buffer for which `buf` returns true if it is a function 44 | 45 | ```lua 46 | ---@param opts? number|snacks.bufdelete.Opts 47 | Snacks.bufdelete.delete(opts) 48 | ``` 49 | 50 | ### `Snacks.bufdelete.other()` 51 | 52 | Delete all buffers except the current one 53 | 54 | ```lua 55 | ---@param opts? snacks.bufdelete.Opts 56 | Snacks.bufdelete.other(opts) 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/words.md: -------------------------------------------------------------------------------- 1 | # 🍿 words 2 | 3 | Auto-show LSP references and quickly navigate between them 4 | 5 | 6 | 7 | ## 📦 Setup 8 | 9 | ```lua 10 | -- lazy.nvim 11 | { 12 | "folke/snacks.nvim", 13 | opts = { 14 | words = { 15 | -- your words configuration comes here 16 | -- or leave it empty to use the default settings 17 | -- refer to the configuration section below 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ## ⚙️ Config 24 | 25 | ```lua 26 | ---@class snacks.words.Config 27 | ---@field enabled? boolean 28 | { 29 | debounce = 200, -- time in ms to wait before updating 30 | notify_jump = false, -- show a notification when jumping 31 | notify_end = true, -- show a notification when reaching the end 32 | foldopen = true, -- open folds after jumping 33 | jumplist = true, -- set jump point before jumping 34 | modes = { "n", "i", "c" }, -- modes to show references 35 | } 36 | ``` 37 | 38 | ## 📦 Module 39 | 40 | ### `Snacks.words.clear()` 41 | 42 | ```lua 43 | Snacks.words.clear() 44 | ``` 45 | 46 | ### `Snacks.words.disable()` 47 | 48 | ```lua 49 | Snacks.words.disable() 50 | ``` 51 | 52 | ### `Snacks.words.enable()` 53 | 54 | ```lua 55 | Snacks.words.enable() 56 | ``` 57 | 58 | ### `Snacks.words.is_enabled()` 59 | 60 | ```lua 61 | ---@param buf number? 62 | Snacks.words.is_enabled(buf) 63 | ``` 64 | 65 | ### `Snacks.words.jump()` 66 | 67 | ```lua 68 | ---@param count number 69 | ---@param cycle? boolean 70 | Snacks.words.jump(count, cycle) 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/bigfile.md: -------------------------------------------------------------------------------- 1 | # 🍿 bigfile 2 | 3 | `bigfile` adds a new filetype `bigfile` to Neovim that triggers when the file is 4 | larger than the configured size. This automatically prevents things like LSP 5 | and Treesitter attaching to the buffer. 6 | 7 | Use the `setup` config function to further make changes to a `bigfile` buffer. 8 | The context provides the actual filetype. 9 | 10 | The default implementation enables `syntax` for the buffer and disables 11 | [mini.animate](https://github.com/echasnovski/mini.animate) (if used) 12 | 13 | 14 | 15 | ## 📦 Setup 16 | 17 | ```lua 18 | -- lazy.nvim 19 | { 20 | "folke/snacks.nvim", 21 | opts = { 22 | bigfile = { 23 | -- your bigfile configuration comes here 24 | -- or leave it empty to use the default settings 25 | -- refer to the configuration section below 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | ## ⚙️ Config 32 | 33 | ```lua 34 | ---@class snacks.bigfile.Config 35 | ---@field enabled? boolean 36 | { 37 | notify = true, -- show notification when big file detected 38 | size = 1.5 * 1024 * 1024, -- 1.5MB 39 | -- Enable or disable features when big file detected 40 | ---@param ctx {buf: number, ft:string} 41 | setup = function(ctx) 42 | vim.cmd([[NoMatchParen]]) 43 | Snacks.util.wo(0, { foldmethod = "manual", statuscolumn = "", conceallevel = 0 }) 44 | vim.b.minianimate_disable = true 45 | vim.schedule(function() 46 | vim.bo[ctx.buf].syntax = ctx.ft 47 | end) 48 | end, 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /doc/snacks-quickfile.txt: -------------------------------------------------------------------------------- 1 | *snacks-quickfile.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-quickfile-table-of-contents* 5 | 6 | 1. Setup |snacks-quickfile-setup| 7 | 2. Config |snacks-quickfile-config| 8 | When doing `nvim somefile.txt`, it will render the file as quickly as possible, 9 | before loading your plugins. 10 | 11 | 12 | ============================================================================== 13 | 1. Setup *snacks-quickfile-setup* 14 | 15 | >lua 16 | -- lazy.nvim 17 | { 18 | "folke/snacks.nvim", 19 | opts = { 20 | quickfile = { 21 | -- your quickfile configuration comes here 22 | -- or leave it empty to use the default settings 23 | -- refer to the configuration section below 24 | } 25 | } 26 | } 27 | < 28 | 29 | 30 | ============================================================================== 31 | 2. Config *snacks-quickfile-config* 32 | 33 | >lua 34 | ---@class snacks.quickfile.Config 35 | { 36 | -- any treesitter langs to exclude 37 | exclude = { "latex" }, 38 | } 39 | < 40 | 41 | Generated by panvimdoc 42 | 43 | vim:tw=78:ts=8:noet:ft=help:norl: 44 | -------------------------------------------------------------------------------- /lua/snacks/notify.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.notify 2 | ---@overload fun(msg: string|string[], opts?: snacks.notify.Opts) 3 | local M = setmetatable({}, { 4 | __call = function(t, ...) 5 | return t.notify(...) 6 | end, 7 | }) 8 | 9 | M.meta = { 10 | desc = "Utility functions to work with Neovim's `vim.notify`", 11 | } 12 | 13 | ---@alias snacks.notify.Opts snacks.notifier.Notif.opts|{once?: boolean} 14 | 15 | ---@param msg string|string[] 16 | ---@param opts? snacks.notify.Opts 17 | function M.notify(msg, opts) 18 | opts = opts or {} 19 | local notify = vim[opts.once and "notify_once" or "notify"] --[[@as fun(...)]] 20 | notify = vim.in_fast_event() and vim.schedule_wrap(notify) or notify 21 | msg = type(msg) == "table" and table.concat(msg, "\n") or msg --[[@as string]] 22 | msg = vim.trim(msg) 23 | opts.title = opts.title or "Snacks" 24 | return notify(msg, opts.level, opts) 25 | end 26 | 27 | ---@param msg string|string[] 28 | ---@param opts? snacks.notify.Opts 29 | function M.warn(msg, opts) 30 | return M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.WARN }, opts or {})) 31 | end 32 | 33 | ---@param msg string|string[] 34 | ---@param opts? snacks.notify.Opts 35 | function M.info(msg, opts) 36 | return M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.INFO }, opts or {})) 37 | end 38 | 39 | ---@param msg string|string[] 40 | ---@param opts? snacks.notify.Opts 41 | function M.error(msg, opts) 42 | return M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.ERROR }, opts or {})) 43 | end 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /docs/scroll.md: -------------------------------------------------------------------------------- 1 | # 🍿 scroll 2 | 3 | Smooth scrolling for Neovim. 4 | Properly handles `scrolloff` and mouse scrolling. 5 | 6 | Similar plugins: 7 | 8 | - [mini.animate](https://github.com/echasnovski/mini.animate) 9 | - [neoscroll.nvim](https://github.com/karb94/neoscroll.nvim) 10 | 11 | 12 | 13 | ## 📦 Setup 14 | 15 | ```lua 16 | -- lazy.nvim 17 | { 18 | "folke/snacks.nvim", 19 | opts = { 20 | scroll = { 21 | -- your scroll configuration comes here 22 | -- or leave it empty to use the default settings 23 | -- refer to the configuration section below 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | ## ⚙️ Config 30 | 31 | ```lua 32 | ---@class snacks.scroll.Config 33 | ---@field animate snacks.animate.Config 34 | { 35 | animate = { 36 | duration = { step = 15, total = 250 }, 37 | easing = "linear", 38 | }, 39 | -- what buffers to animate 40 | filter = function(buf) 41 | return vim.g.snacks_scroll ~= false and vim.b[buf].snacks_scroll ~= false and vim.bo[buf].buftype ~= "terminal" 42 | end, 43 | } 44 | ``` 45 | 46 | ## 📚 Types 47 | 48 | ```lua 49 | ---@alias snacks.scroll.View {topline:number, lnum:number} 50 | ``` 51 | 52 | ```lua 53 | ---@class snacks.scroll.State 54 | ---@field anim? snacks.animate.Animation 55 | ---@field win number 56 | ---@field buf number 57 | ---@field view vim.fn.winsaveview.ret 58 | ---@field current vim.fn.winsaveview.ret 59 | ---@field target vim.fn.winsaveview.ret 60 | ---@field scrolloff number 61 | ---@field virtualedit? string 62 | ``` 63 | 64 | ## 📦 Module 65 | 66 | ### `Snacks.scroll.disable()` 67 | 68 | ```lua 69 | Snacks.scroll.disable() 70 | ``` 71 | 72 | ### `Snacks.scroll.enable()` 73 | 74 | ```lua 75 | Snacks.scroll.enable() 76 | ``` 77 | -------------------------------------------------------------------------------- /doc/snacks-meta.txt: -------------------------------------------------------------------------------- 1 | *snacks-meta.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-meta-table-of-contents* 5 | 6 | 1. Types |snacks-meta-types| 7 | 2. Module |snacks-meta-module| 8 | - Snacks.meta.get() |snacks-meta-module-snacks.meta.get()| 9 | Meta functions for Snacks 10 | 11 | 12 | ============================================================================== 13 | 1. Types *snacks-meta-types* 14 | 15 | >lua 16 | ---@class snacks.meta.Meta 17 | ---@field desc string 18 | ---@field needs_setup? boolean 19 | ---@field hide? boolean 20 | ---@field readme? boolean 21 | ---@field docs? boolean 22 | ---@field health? boolean 23 | ---@field types? boolean 24 | ---@field config? boolean 25 | < 26 | 27 | >lua 28 | ---@class snacks.meta.Plugin 29 | ---@field name string 30 | ---@field file string 31 | ---@field meta snacks.meta.Meta 32 | ---@field health? fun() 33 | < 34 | 35 | 36 | ============================================================================== 37 | 2. Module *snacks-meta-module* 38 | 39 | 40 | `Snacks.meta.get()` *Snacks.meta.get()* 41 | 42 | Get the metadata for all snacks plugins 43 | 44 | >lua 45 | ---@return snacks.meta.Plugin[] 46 | Snacks.meta.get() 47 | < 48 | 49 | Generated by panvimdoc 50 | 51 | vim:tw=78:ts=8:noet:ft=help:norl: 52 | -------------------------------------------------------------------------------- /lua/snacks/git.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.git 2 | local M = {} 3 | 4 | M.meta = { 5 | desc = "Git utilities", 6 | } 7 | 8 | Snacks.config.style("blame_line", { 9 | width = 0.6, 10 | height = 0.6, 11 | border = "rounded", 12 | title = " Git Blame ", 13 | title_pos = "center", 14 | ft = "git", 15 | }) 16 | 17 | local git_cache = {} ---@type table 18 | local function is_git_root(dir) 19 | if git_cache[dir] == nil then 20 | git_cache[dir] = (vim.uv or vim.loop).fs_stat(dir .. "/.git") ~= nil 21 | end 22 | return git_cache[dir] 23 | end 24 | 25 | --- Gets the git root for a buffer or path. 26 | --- Defaults to the current buffer. 27 | ---@param path? number|string buffer or path 28 | function M.get_root(path) 29 | path = path or 0 30 | path = type(path) == "number" and vim.api.nvim_buf_get_name(path) or path --[[@as string]] 31 | path = vim.fs.normalize(path) 32 | path = path == "" and (vim.uv or vim.loop).cwd() .. "/foo" or path 33 | -- check cache first 34 | for dir in vim.fs.parents(path) do 35 | if git_cache[dir] then 36 | return vim.fs.normalize(dir) or nil 37 | end 38 | end 39 | for dir in vim.fs.parents(path) do 40 | if is_git_root(dir) then 41 | return vim.fs.normalize(dir) or nil 42 | end 43 | end 44 | end 45 | 46 | --- Show git log for the current line. 47 | ---@param opts? snacks.terminal.Opts | {count?: number} 48 | function M.blame_line(opts) 49 | opts = vim.tbl_deep_extend("force", { 50 | count = 5, 51 | interactive = false, 52 | win = { style = "blame_line" }, 53 | }, opts or {}) 54 | local cursor = vim.api.nvim_win_get_cursor(0) 55 | local line = cursor[1] 56 | local file = vim.api.nvim_buf_get_name(0) 57 | local root = M.get_root() 58 | local cmd = { "git", "-C", root, "log", "-n", opts.count, "-u", "-L", line .. ",+1:" .. file } 59 | return Snacks.terminal(cmd, opts) 60 | end 61 | 62 | return M 63 | -------------------------------------------------------------------------------- /doc/snacks-git.txt: -------------------------------------------------------------------------------- 1 | *snacks-git.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-git-table-of-contents* 5 | 6 | 1. Styles |snacks-git-styles| 7 | - blame_line |snacks-git-styles-blame_line| 8 | 2. Module |snacks-git-module| 9 | - Snacks.git.blame_line() |snacks-git-module-snacks.git.blame_line()| 10 | - Snacks.git.get_root() |snacks-git-module-snacks.git.get_root()| 11 | 12 | ============================================================================== 13 | 1. Styles *snacks-git-styles* 14 | 15 | 16 | BLAME_LINE *snacks-git-styles-blame_line* 17 | 18 | >lua 19 | { 20 | width = 0.6, 21 | height = 0.6, 22 | border = "rounded", 23 | title = " Git Blame ", 24 | title_pos = "center", 25 | ft = "git", 26 | } 27 | < 28 | 29 | 30 | ============================================================================== 31 | 2. Module *snacks-git-module* 32 | 33 | 34 | `Snacks.git.blame_line()` *Snacks.git.blame_line()* 35 | 36 | Show git log for the current line. 37 | 38 | >lua 39 | ---@param opts? snacks.terminal.Opts | {count?: number} 40 | Snacks.git.blame_line(opts) 41 | < 42 | 43 | 44 | `Snacks.git.get_root()` *Snacks.git.get_root()* 45 | 46 | Gets the git root for a buffer or path. Defaults to the current buffer. 47 | 48 | >lua 49 | ---@param path? number|string buffer or path 50 | Snacks.git.get_root(path) 51 | < 52 | 53 | Generated by panvimdoc 54 | 55 | vim:tw=78:ts=8:noet:ft=help:norl: 56 | -------------------------------------------------------------------------------- /lua/snacks/meta/types.lua: -------------------------------------------------------------------------------- 1 | ---@meta _ 2 | 3 | ---@class snacks.plugins 4 | ---@field animate snacks.animate 5 | ---@field bigfile snacks.bigfile 6 | ---@field bufdelete snacks.bufdelete 7 | ---@field dashboard snacks.dashboard 8 | ---@field debug snacks.debug 9 | ---@field dim snacks.dim 10 | ---@field git snacks.git 11 | ---@field gitbrowse snacks.gitbrowse 12 | ---@field health snacks.health 13 | ---@field indent snacks.indent 14 | ---@field input snacks.input 15 | ---@field lazygit snacks.lazygit 16 | ---@field meta snacks.meta 17 | ---@field notifier snacks.notifier 18 | ---@field notify snacks.notify 19 | ---@field profiler snacks.profiler 20 | ---@field quickfile snacks.quickfile 21 | ---@field rename snacks.rename 22 | ---@field scope snacks.scope 23 | ---@field scratch snacks.scratch 24 | ---@field scroll snacks.scroll 25 | ---@field statuscolumn snacks.statuscolumn 26 | ---@field terminal snacks.terminal 27 | ---@field toggle snacks.toggle 28 | ---@field util snacks.util 29 | ---@field win snacks.win 30 | ---@field words snacks.words 31 | ---@field zen snacks.zen 32 | 33 | ---@class snacks.plugins.Config 34 | ---@field animate? snacks.animate.Config 35 | ---@field bigfile? snacks.bigfile.Config 36 | ---@field dashboard? snacks.dashboard.Config 37 | ---@field dim? snacks.dim.Config 38 | ---@field gitbrowse? snacks.gitbrowse.Config 39 | ---@field indent? snacks.indent.Config 40 | ---@field input? snacks.input.Config 41 | ---@field lazygit? snacks.lazygit.Config 42 | ---@field notifier? snacks.notifier.Config 43 | ---@field profiler? snacks.profiler.Config 44 | ---@field quickfile? snacks.quickfile.Config 45 | ---@field scope? snacks.scope.Config 46 | ---@field scratch? snacks.scratch.Config 47 | ---@field scroll? snacks.scroll.Config 48 | ---@field statuscolumn? snacks.statuscolumn.Config 49 | ---@field terminal? snacks.terminal.Config 50 | ---@field toggle? snacks.toggle.Config 51 | ---@field win? snacks.win.Config 52 | ---@field words? snacks.words.Config 53 | ---@field zen? snacks.zen.Config 54 | -------------------------------------------------------------------------------- /lua/snacks/bigfile.lua: -------------------------------------------------------------------------------- 1 | ---@private 2 | ---@class snacks.bigfile 3 | local M = {} 4 | 5 | M.meta = { 6 | desc = "Deal with big files", 7 | needs_setup = true, 8 | } 9 | 10 | ---@class snacks.bigfile.Config 11 | ---@field enabled? boolean 12 | local defaults = { 13 | notify = true, -- show notification when big file detected 14 | size = 1.5 * 1024 * 1024, -- 1.5MB 15 | -- Enable or disable features when big file detected 16 | ---@param ctx {buf: number, ft:string} 17 | setup = function(ctx) 18 | vim.cmd([[NoMatchParen]]) 19 | Snacks.util.wo(0, { foldmethod = "manual", statuscolumn = "", conceallevel = 0 }) 20 | vim.b.minianimate_disable = true 21 | vim.schedule(function() 22 | vim.bo[ctx.buf].syntax = ctx.ft 23 | end) 24 | end, 25 | } 26 | 27 | ---@private 28 | function M.setup() 29 | local opts = Snacks.config.get("bigfile", defaults) 30 | 31 | vim.filetype.add({ 32 | pattern = { 33 | [".*"] = { 34 | function(path, buf) 35 | return vim.bo[buf] 36 | and vim.bo[buf].filetype ~= "bigfile" 37 | and path 38 | and vim.fn.getfsize(path) > opts.size 39 | and "bigfile" 40 | or nil 41 | end, 42 | }, 43 | }, 44 | }) 45 | 46 | vim.api.nvim_create_autocmd({ "FileType" }, { 47 | group = vim.api.nvim_create_augroup("snacks_bigfile", { clear = true }), 48 | pattern = "bigfile", 49 | callback = function(ev) 50 | if opts.notify then 51 | local path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(ev.buf), ":p:~:.") 52 | Snacks.notify.warn({ 53 | ("Big file detected `%s`."):format(path), 54 | "Some Neovim features have been **disabled**.", 55 | }, { title = "Big File" }) 56 | end 57 | vim.api.nvim_buf_call(ev.buf, function() 58 | opts.setup({ 59 | buf = ev.buf, 60 | ft = vim.filetype.match({ buf = ev.buf }) or "", 61 | }) 62 | end) 63 | end, 64 | }) 65 | end 66 | 67 | return M 68 | -------------------------------------------------------------------------------- /lua/snacks/meta/init.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.meta 2 | local M = {} 3 | 4 | M.meta = { 5 | desc = "Meta functions for Snacks", 6 | readme = false, 7 | } 8 | 9 | ---@class snacks.meta.Meta 10 | ---@field desc string 11 | ---@field needs_setup? boolean 12 | ---@field hide? boolean 13 | ---@field readme? boolean 14 | ---@field docs? boolean 15 | ---@field health? boolean 16 | ---@field types? boolean 17 | ---@field config? boolean 18 | 19 | ---@class snacks.meta.Plugin 20 | ---@field name string 21 | ---@field file string 22 | ---@field meta snacks.meta.Meta 23 | ---@field health? fun() 24 | 25 | --- Get the metadata for all snacks plugins 26 | ---@return snacks.meta.Plugin[] 27 | function M.get() 28 | local ret = {} ---@type snacks.meta.Plugin[] 29 | local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":h:h") 30 | for file, t in vim.fs.dir(root, { depth = 1 }) do 31 | local name = vim.fn.fnamemodify(file, ":t:r") 32 | file = t == "directory" and ("%s/init.lua"):format(file) or file 33 | file = root .. "/" .. file 34 | local mod = name == "init" and setmetatable({ meta = { desc = "Snacks", hide = true } }, { __index = Snacks }) 35 | or Snacks[name] --[[@as snacks.meta.Plugin]] 36 | assert(type(mod) == "table", ("`Snacks.%s` not found"):format(name)) 37 | assert(type(mod.meta) == "table", ("`Snacks.%s.meta` not found"):format(name)) 38 | assert(type(mod.meta.desc) == "string", ("`Snacks.%s.meta.desc` not found"):format(name)) 39 | 40 | for _, prop in ipairs({ "readme", "docs", "health", "types" }) do 41 | if mod.meta[prop] == nil then 42 | mod.meta[prop] = not mod.meta.hide 43 | end 44 | end 45 | 46 | ret[#ret + 1] = setmetatable({ 47 | name = name, 48 | file = file, 49 | }, { 50 | __index = mod, 51 | __tostring = function(self) 52 | return "snacks." .. self.name 53 | end, 54 | }) 55 | end 56 | table.sort(ret, function(a, b) 57 | return a.name < b.name 58 | end) 59 | return ret 60 | end 61 | 62 | return M 63 | -------------------------------------------------------------------------------- /docs/dim.md: -------------------------------------------------------------------------------- 1 | # 🍿 dim 2 | 3 | Focus on the active scope by dimming the rest. 4 | 5 | Similar plugins: 6 | 7 | - [twilight.nvim](https://github.com/folke/twilight.nvim) 8 | - [limelight.vim](https://github.com/junegunn/limelight.vim) 9 | - [goyo.vim](https://github.com/junegunn/goyo.vim) 10 | 11 | ![image](https://github.com/user-attachments/assets/c0c5ffda-aaeb-4578-8a18-abee2e443a93) 12 | 13 | 14 | 15 | 16 | ## 📦 Setup 17 | 18 | ```lua 19 | -- lazy.nvim 20 | { 21 | "folke/snacks.nvim", 22 | opts = { 23 | dim = { 24 | -- your dim configuration comes here 25 | -- or leave it empty to use the default settings 26 | -- refer to the configuration section below 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | ## ⚙️ Config 33 | 34 | ```lua 35 | ---@class snacks.dim.Config 36 | { 37 | ---@type snacks.scope.Config 38 | scope = { 39 | min_size = 5, 40 | max_size = 20, 41 | siblings = true, 42 | }, 43 | -- animate scopes. Enabled by default for Neovim >= 0.10 44 | -- Works on older versions but has to trigger redraws during animation. 45 | ---@type snacks.animate.Config|{enabled?: boolean} 46 | animate = { 47 | enabled = vim.fn.has("nvim-0.10") == 1, 48 | easing = "outQuad", 49 | duration = { 50 | step = 20, -- ms per step 51 | total = 300, -- maximum duration 52 | }, 53 | }, 54 | -- what buffers to dim 55 | filter = function(buf) 56 | return vim.g.snacks_dim ~= false and vim.b[buf].snacks_dim ~= false and vim.bo[buf].buftype == "" 57 | end, 58 | } 59 | ``` 60 | 61 | ## 📦 Module 62 | 63 | ### `Snacks.dim()` 64 | 65 | ```lua 66 | ---@type fun(opts: snacks.dim.Config) 67 | Snacks.dim() 68 | ``` 69 | 70 | ### `Snacks.dim.animate()` 71 | 72 | Toggle scope animations 73 | 74 | ```lua 75 | Snacks.dim.animate() 76 | ``` 77 | 78 | ### `Snacks.dim.disable()` 79 | 80 | Disable dimming 81 | 82 | ```lua 83 | Snacks.dim.disable() 84 | ``` 85 | 86 | ### `Snacks.dim.enable()` 87 | 88 | ```lua 89 | ---@param opts? snacks.dim.Config 90 | Snacks.dim.enable(opts) 91 | ``` 92 | -------------------------------------------------------------------------------- /lua/snacks/health.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.health 2 | ---@field ok fun(msg: string) 3 | ---@field warn fun(msg: string) 4 | ---@field error fun(msg: string) 5 | ---@field info fun(msg: string) 6 | ---@field start fun(msg: string) 7 | local M = setmetatable({}, { 8 | __index = function(M, k) 9 | return function(msg) 10 | return require("vim.health")[k](M.prefix .. msg) 11 | end 12 | end, 13 | }) 14 | M.prefix = "" 15 | 16 | M.meta = { 17 | desc = "Snacks health checks", 18 | readme = false, 19 | health = false, 20 | } 21 | 22 | function M.check() 23 | M.prefix = "" 24 | M.start("Snacks") 25 | if Snacks.did_setup then 26 | M.ok("setup called") 27 | if Snacks.did_setup_after_vim_enter then 28 | M.warn("setup called *after* `VimEnter`") 29 | end 30 | else 31 | M.error("setup not called") 32 | end 33 | if package.loaded.lazy then 34 | local plugin = require("lazy.core.config").spec.plugins["snacks.nvim"] 35 | if plugin then 36 | if plugin.lazy ~= false then 37 | M.warn("`snacks.nvim` should not be lazy-loaded. Add `lazy=false` to the plugin spec") 38 | end 39 | if (plugin.priority or 0) < 1000 then 40 | M.warn("`snacks.nvim` should have a priority of 1000 or higher. Add `priority=1000` to the plugin spec") 41 | end 42 | else 43 | M.error("`snacks.nvim` not found in lazy") 44 | end 45 | end 46 | for _, plugin in ipairs(Snacks.meta.get()) do 47 | local opts = Snacks.config[plugin.name] or {} --[[@as {enabled?: boolean}]] 48 | if plugin.meta.health ~= false and (plugin.meta.needs_setup or plugin.health) then 49 | M.start(("Snacks.%s"):format(plugin.name)) 50 | -- M.prefix = ("`Snacks.%s` "):format(name) 51 | if plugin.meta.needs_setup then 52 | if opts.enabled then 53 | M.ok("setup {enabled}") 54 | else 55 | M.warn("setup {disabled}") 56 | end 57 | end 58 | if plugin.health then 59 | plugin.health() 60 | end 61 | end 62 | end 63 | end 64 | 65 | return M 66 | -------------------------------------------------------------------------------- /docs/rename.md: -------------------------------------------------------------------------------- 1 | # 🍿 rename 2 | 3 | LSP-integrated file renaming with support for plugins like 4 | [neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim) and [mini.files](https://github.com/echasnovski/mini.files). 5 | 6 | ## 🚀 Usage 7 | 8 | ## [mini.files](https://github.com/echasnovski/mini.files) 9 | 10 | ```lua 11 | vim.api.nvim_create_autocmd("User", { 12 | pattern = "MiniFilesActionRename", 13 | callback = function(event) 14 | Snacks.rename.on_rename_file(event.data.from, event.data.to) 15 | end, 16 | }) 17 | ``` 18 | 19 | ## [neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim) 20 | 21 | ```lua 22 | { 23 | "nvim-neo-tree/neo-tree.nvim", 24 | opts = function(_, opts) 25 | local function on_move(data) 26 | Snacks.rename.on_rename_file(data.source, data.destination) 27 | end 28 | local events = require("neo-tree.events") 29 | opts.event_handlers = opts.event_handlers or {} 30 | vim.list_extend(opts.event_handlers, { 31 | { event = events.FILE_MOVED, handler = on_move }, 32 | { event = events.FILE_RENAMED, handler = on_move }, 33 | }) 34 | end, 35 | } 36 | ``` 37 | 38 | ## [nvim-tree](https://github.com/nvim-tree/nvim-tree.lua) 39 | 40 | ```lua 41 | local prev = { new_name = "", old_name = "" } -- Prevents duplicate events 42 | vim.api.nvim_create_autocmd("User", { 43 | pattern = "NvimTreeSetup", 44 | callback = function() 45 | local events = require("nvim-tree.api").events 46 | events.subscribe(events.Event.NodeRenamed, function(data) 47 | if prev.new_name ~= data.new_name or prev.old_name ~= data.old_name then 48 | data = data 49 | Snacks.rename.on_rename_file(data.old_name, data.new_name) 50 | end 51 | end) 52 | end, 53 | }) 54 | ``` 55 | 56 | 57 | 58 | ## 📦 Module 59 | 60 | ### `Snacks.rename.on_rename_file()` 61 | 62 | Lets LSP clients know that a file has been renamed 63 | 64 | ```lua 65 | ---@param from string 66 | ---@param to string 67 | ---@param rename? fun() 68 | Snacks.rename.on_rename_file(from, to, rename) 69 | ``` 70 | 71 | ### `Snacks.rename.rename_file()` 72 | 73 | Prompt for the new filename, 74 | do the rename, and trigger LSP handlers 75 | 76 | ```lua 77 | Snacks.rename.rename_file() 78 | ``` 79 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # .github/labeler.yml 2 | 3 | # Label for any files under the `doc/` directory 4 | docs-vim: 5 | - changed-files: 6 | - any-glob-to-any-file: "doc/**" 7 | 8 | # Label for any files under the `docs/` directory and `README.md` 9 | docs: 10 | - changed-files: 11 | - any-glob-to-any-file: 12 | - "docs/**" 13 | - "README.md" 14 | 15 | core: 16 | - changed-files: 17 | - any-glob-to-any-file: 18 | - "lua/snacks/init.lua" 19 | - "lua/snacks/health.lua" 20 | - "plugins/**" 21 | - "queries/**" 22 | - "scripts/**" 23 | 24 | # Dynamic labels for each module under `lua/snacks/` 25 | bigfile: 26 | - changed-files: 27 | - any-glob-to-any-file: "lua/snacks/bigfile.lua" 28 | bufdelete: 29 | - changed-files: 30 | - any-glob-to-any-file: "lua/snacks/bufdelete.lua" 31 | dashboard: 32 | - changed-files: 33 | - any-glob-to-any-file: "lua/snacks/dashboard.lua" 34 | debug: 35 | - changed-files: 36 | - any-glob-to-any-file: "lua/snacks/debug.lua" 37 | git: 38 | - changed-files: 39 | - any-glob-to-any-file: "lua/snacks/git.lua" 40 | gitbrowse: 41 | - changed-files: 42 | - any-glob-to-any-file: "lua/snacks/gitbrowse.lua" 43 | init: 44 | - changed-files: 45 | - any-glob-to-any-file: "lua/snacks/init.lua" 46 | lazygit: 47 | - changed-files: 48 | - any-glob-to-any-file: "lua/snacks/lazygit.lua" 49 | notifier: 50 | - changed-files: 51 | - any-glob-to-any-file: "lua/snacks/notifier.lua" 52 | notify: 53 | - changed-files: 54 | - any-glob-to-any-file: "lua/snacks/notify.lua" 55 | quickfile: 56 | - changed-files: 57 | - any-glob-to-any-file: "lua/snacks/quickfile.lua" 58 | rename: 59 | - changed-files: 60 | - any-glob-to-any-file: "lua/snacks/rename.lua" 61 | statuscolumn: 62 | - changed-files: 63 | - any-glob-to-any-file: "lua/snacks/statuscolumn.lua" 64 | terminal: 65 | - changed-files: 66 | - any-glob-to-any-file: "lua/snacks/terminal.lua" 67 | toggle: 68 | - changed-files: 69 | - any-glob-to-any-file: "lua/snacks/toggle.lua" 70 | win: 71 | - changed-files: 72 | - any-glob-to-any-file: "lua/snacks/win.lua" 73 | words: 74 | - changed-files: 75 | - any-glob-to-any-file: "lua/snacks/words.lua" 76 | -------------------------------------------------------------------------------- /lua/trouble/sources/profiler.lua: -------------------------------------------------------------------------------- 1 | ---@module 'trouble' 2 | ---@diagnostic disable: inject-field 3 | local Item = require("trouble.item") 4 | 5 | ---@type trouble.Source 6 | local M = {} 7 | 8 | ---@diagnostic disable-next-line: missing-fields 9 | M.config = { 10 | formatters = { 11 | badges = function(ctx) 12 | local trace = ctx.item.item ---@type snacks.profiler.Trace 13 | local badges = Snacks.profiler.ui.badges(trace, { badges = { "time", "count" } }) 14 | local text = Snacks.profiler.ui.format(badges) 15 | return vim.tbl_map(function(t) 16 | return { text = t[1], hl = t[2] } 17 | end, text) 18 | end, 19 | }, 20 | modes = { 21 | profiler = { 22 | events = { { event = "User", pattern = "SnacksProfilerLoaded" } }, 23 | source = "profiler", 24 | groups = { 25 | -- { "tag", format = "{todo_icon} {tag}" }, 26 | -- { "directory" }, 27 | { "loc.plugin", format = "{file_icon} {loc.plugin} {count}" }, 28 | }, 29 | -- sort = { { buf = 0 }, "filename", "pos", "name" }, 30 | sort = { "-time" }, 31 | format = "{name} {badges} {pos}", 32 | }, 33 | }, 34 | } 35 | 36 | function M.preview(item, ctx) 37 | Snacks.profiler.ui.highlight(ctx.buf, { file = item.item.loc.file }) 38 | end 39 | 40 | function M.get(cb, ctx) 41 | ---@type snacks.profiler.Find 42 | local opts = vim.tbl_deep_extend( 43 | "force", 44 | { group = "name", structure = true }, 45 | type(ctx.opts.params) == "table" and ctx.opts.params or {} 46 | ) 47 | local _, node = Snacks.profiler.find(opts) 48 | local items = {} ---@type trouble.Item[] 49 | local id = 0 50 | 51 | ---@param n snacks.profiler.Node 52 | local function add(n) 53 | if n.trace.def then 54 | id = id + 1 55 | local loc = n.trace.def 56 | local item = Item.new({ 57 | id = id, 58 | pos = { n.trace.def.line, 0 }, 59 | text = n.trace.name, 60 | filename = loc and loc.file, 61 | item = n.trace, 62 | source = "profiler", 63 | }) 64 | items[#items + 1] = item 65 | for _, child in pairs(n.children) do 66 | item:add_child(add(child)) 67 | end 68 | return item 69 | end 70 | end 71 | 72 | for _, child in pairs(node.children or {}) do 73 | add(child) 74 | end 75 | cb(items) 76 | end 77 | 78 | return M 79 | -------------------------------------------------------------------------------- /doc/snacks-bigfile.txt: -------------------------------------------------------------------------------- 1 | *snacks-bigfile.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-bigfile-table-of-contents* 5 | 6 | 1. Setup |snacks-bigfile-setup| 7 | 2. Config |snacks-bigfile-config| 8 | `bigfile` adds a new filetype `bigfile` to Neovim that triggers when the file 9 | is larger than the configured size. This automatically prevents things like LSP 10 | and Treesitter attaching to the buffer. 11 | 12 | Use the `setup` config function to further make changes to a `bigfile` buffer. 13 | The context provides the actual filetype. 14 | 15 | The default implementation enables `syntax` for the buffer and disables 16 | mini.animate (if used) 17 | 18 | 19 | ============================================================================== 20 | 1. Setup *snacks-bigfile-setup* 21 | 22 | >lua 23 | -- lazy.nvim 24 | { 25 | "folke/snacks.nvim", 26 | opts = { 27 | bigfile = { 28 | -- your bigfile configuration comes here 29 | -- or leave it empty to use the default settings 30 | -- refer to the configuration section below 31 | } 32 | } 33 | } 34 | < 35 | 36 | 37 | ============================================================================== 38 | 2. Config *snacks-bigfile-config* 39 | 40 | >lua 41 | ---@class snacks.bigfile.Config 42 | ---@field enabled? boolean 43 | { 44 | notify = true, -- show notification when big file detected 45 | size = 1.5 * 1024 * 1024, -- 1.5MB 46 | -- Enable or disable features when big file detected 47 | ---@param ctx {buf: number, ft:string} 48 | setup = function(ctx) 49 | vim.cmd([[NoMatchParen]]) 50 | Snacks.util.wo(0, { foldmethod = "manual", statuscolumn = "", conceallevel = 0 }) 51 | vim.b.minianimate_disable = true 52 | vim.schedule(function() 53 | vim.bo[ctx.buf].syntax = ctx.ft 54 | end) 55 | end, 56 | } 57 | < 58 | 59 | Generated by panvimdoc 60 | 61 | vim:tw=78:ts=8:noet:ft=help:norl: 62 | -------------------------------------------------------------------------------- /docs/input.md: -------------------------------------------------------------------------------- 1 | # 🍿 input 2 | 3 | Better `vim.ui.input`. 4 | 5 | ![image](https://github.com/user-attachments/assets/f7579302-bea1-4f1c-8b3b-723c3f4ca04b) 6 | 7 | 8 | 9 | ## 📦 Setup 10 | 11 | ```lua 12 | -- lazy.nvim 13 | { 14 | "folke/snacks.nvim", 15 | opts = { 16 | input = { 17 | -- your input configuration comes here 18 | -- or leave it empty to use the default settings 19 | -- refer to the configuration section below 20 | } 21 | } 22 | } 23 | ``` 24 | 25 | ## ⚙️ Config 26 | 27 | ```lua 28 | ---@class snacks.input.Config 29 | ---@field enabled? boolean 30 | ---@field win? snacks.win.Config 31 | ---@field icon? string 32 | { 33 | icon = " ", 34 | icon_hl = "SnacksInputIcon", 35 | win = { style = "input" }, 36 | expand = true, 37 | } 38 | ``` 39 | 40 | ## 🎨 Styles 41 | 42 | ### `input` 43 | 44 | ```lua 45 | { 46 | backdrop = false, 47 | position = "float", 48 | border = "rounded", 49 | title_pos = "center", 50 | height = 1, 51 | width = 60, 52 | relative = "editor", 53 | row = 2, 54 | -- relative = "cursor", 55 | -- row = -3, 56 | -- col = 0, 57 | wo = { 58 | winhighlight = "NormalFloat:SnacksInputNormal,FloatBorder:SnacksInputBorder,FloatTitle:SnacksInputTitle", 59 | }, 60 | keys = { 61 | i_esc = { "", { "cmp_close", "cancel" }, mode = "i" }, 62 | -- i_esc = { "", "stopinsert", mode = "i" }, 63 | i_cr = { "", { "cmp_accept", "confirm" }, mode = "i" }, 64 | i_tab = { "", { "cmp_select_next", "cmp" }, mode = "i" }, 65 | q = "cancel", 66 | }, 67 | } 68 | ``` 69 | 70 | ## 📚 Types 71 | 72 | ```lua 73 | ---@class snacks.input.Opts: snacks.input.Config 74 | ---@field prompt? string 75 | ---@field default? string 76 | ---@field completion? string 77 | ---@field highlight? fun() 78 | ``` 79 | 80 | ## 📦 Module 81 | 82 | ### `Snacks.input()` 83 | 84 | ```lua 85 | ---@type fun(opts: snacks.input.Opts, on_confirm: fun(value?: string)): snacks.win 86 | Snacks.input() 87 | ``` 88 | 89 | ### `Snacks.input.disable()` 90 | 91 | ```lua 92 | Snacks.input.disable() 93 | ``` 94 | 95 | ### `Snacks.input.enable()` 96 | 97 | ```lua 98 | Snacks.input.enable() 99 | ``` 100 | 101 | ### `Snacks.input.input()` 102 | 103 | ```lua 104 | ---@param opts? snacks.input.Opts 105 | ---@param on_confirm fun(value?: string) 106 | Snacks.input.input(opts, on_confirm) 107 | ``` 108 | -------------------------------------------------------------------------------- /lua/snacks/rename.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.rename 2 | local M = {} 3 | 4 | M.meta = { 5 | desc = "LSP-integrated file renaming with support for plugins like [neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim) and [mini.files](https://github.com/echasnovski/mini.files).", 6 | } 7 | 8 | local uv = vim.uv or vim.loop 9 | 10 | ---@param path string 11 | local function realpath(path) 12 | return vim.fs.normalize(uv.fs_realpath(path) or path) 13 | end 14 | 15 | -- Prompt for the new filename, 16 | -- do the rename, and trigger LSP handlers 17 | function M.rename_file() 18 | local buf = vim.api.nvim_get_current_buf() 19 | local old = assert(realpath(vim.api.nvim_buf_get_name(buf))) 20 | local root = assert(realpath(uv.cwd() or ".")) 21 | 22 | if old:find(root, 1, true) ~= 1 then 23 | root = vim.fn.fnamemodify(old, ":p:h") 24 | end 25 | 26 | local extra = old:sub(#root + 2) 27 | 28 | vim.ui.input({ 29 | prompt = "New File Name: ", 30 | default = extra, 31 | completion = "file", 32 | }, function(new) 33 | if not new or new == "" or new == extra then 34 | return 35 | end 36 | new = vim.fs.normalize(root .. "/" .. new) 37 | vim.fn.mkdir(vim.fs.dirname(new), "p") 38 | M.on_rename_file(old, new, function() 39 | vim.fn.rename(old, new) 40 | vim.cmd.edit(new) 41 | vim.api.nvim_buf_delete(buf, { force = true }) 42 | vim.fn.delete(old) 43 | end) 44 | end) 45 | end 46 | 47 | --- Lets LSP clients know that a file has been renamed 48 | ---@param from string 49 | ---@param to string 50 | ---@param rename? fun() 51 | function M.on_rename_file(from, to, rename) 52 | local changes = { files = { { 53 | oldUri = vim.uri_from_fname(from), 54 | newUri = vim.uri_from_fname(to), 55 | } } } 56 | 57 | local clients = (vim.lsp.get_clients or vim.lsp.get_active_clients)() 58 | for _, client in ipairs(clients) do 59 | if client.supports_method("workspace/willRenameFiles") then 60 | local resp = client.request_sync("workspace/willRenameFiles", changes, 1000, 0) 61 | if resp and resp.result ~= nil then 62 | vim.lsp.util.apply_workspace_edit(resp.result, client.offset_encoding) 63 | end 64 | end 65 | end 66 | 67 | if rename then 68 | rename() 69 | end 70 | 71 | for _, client in ipairs(clients) do 72 | if client.supports_method("workspace/didRenameFiles") then 73 | client.notify("workspace/didRenameFiles", changes) 74 | end 75 | end 76 | end 77 | 78 | return M 79 | -------------------------------------------------------------------------------- /doc/snacks-notify.txt: -------------------------------------------------------------------------------- 1 | *snacks-notify.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-notify-table-of-contents* 5 | 6 | 1. Types |snacks-notify-types| 7 | 2. Module |snacks-notify-module| 8 | - Snacks.notify() |snacks-notify-module-snacks.notify()| 9 | - Snacks.notify.error() |snacks-notify-module-snacks.notify.error()| 10 | - Snacks.notify.info() |snacks-notify-module-snacks.notify.info()| 11 | - Snacks.notify.notify() |snacks-notify-module-snacks.notify.notify()| 12 | - Snacks.notify.warn() |snacks-notify-module-snacks.notify.warn()| 13 | 14 | ============================================================================== 15 | 1. Types *snacks-notify-types* 16 | 17 | >lua 18 | ---@alias snacks.notify.Opts snacks.notifier.Notif.opts|{once?: boolean} 19 | < 20 | 21 | 22 | ============================================================================== 23 | 2. Module *snacks-notify-module* 24 | 25 | 26 | `Snacks.notify()` *Snacks.notify()* 27 | 28 | >lua 29 | ---@type fun(msg: string|string[], opts?: snacks.notify.Opts) 30 | Snacks.notify() 31 | < 32 | 33 | 34 | `Snacks.notify.error()` *Snacks.notify.error()* 35 | 36 | >lua 37 | ---@param msg string|string[] 38 | ---@param opts? snacks.notify.Opts 39 | Snacks.notify.error(msg, opts) 40 | < 41 | 42 | 43 | `Snacks.notify.info()` *Snacks.notify.info()* 44 | 45 | >lua 46 | ---@param msg string|string[] 47 | ---@param opts? snacks.notify.Opts 48 | Snacks.notify.info(msg, opts) 49 | < 50 | 51 | 52 | `Snacks.notify.notify()` *Snacks.notify.notify()* 53 | 54 | >lua 55 | ---@param msg string|string[] 56 | ---@param opts? snacks.notify.Opts 57 | Snacks.notify.notify(msg, opts) 58 | < 59 | 60 | 61 | `Snacks.notify.warn()` *Snacks.notify.warn()* 62 | 63 | >lua 64 | ---@param msg string|string[] 65 | ---@param opts? snacks.notify.Opts 66 | Snacks.notify.warn(msg, opts) 67 | < 68 | 69 | Generated by panvimdoc 70 | 71 | vim:tw=78:ts=8:noet:ft=help:norl: 72 | -------------------------------------------------------------------------------- /doc/snacks-statuscolumn.txt: -------------------------------------------------------------------------------- 1 | *snacks-statuscolumn.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-statuscolumn-table-of-contents* 5 | 6 | 1. Setup |snacks-statuscolumn-setup| 7 | 2. Config |snacks-statuscolumn-config| 8 | 3. Module |snacks-statuscolumn-module| 9 | - Snacks.statuscolumn() |snacks-statuscolumn-module-snacks.statuscolumn()| 10 | - Snacks.statuscolumn.get()|snacks-statuscolumn-module-snacks.statuscolumn.get()| 11 | 12 | ============================================================================== 13 | 1. Setup *snacks-statuscolumn-setup* 14 | 15 | >lua 16 | -- lazy.nvim 17 | { 18 | "folke/snacks.nvim", 19 | opts = { 20 | statuscolumn = { 21 | -- your statuscolumn configuration comes here 22 | -- or leave it empty to use the default settings 23 | -- refer to the configuration section below 24 | } 25 | } 26 | } 27 | < 28 | 29 | 30 | ============================================================================== 31 | 2. Config *snacks-statuscolumn-config* 32 | 33 | >lua 34 | ---@class snacks.statuscolumn.Config 35 | ---@field enabled? boolean 36 | { 37 | left = { "mark", "sign" }, -- priority of signs on the left (high to low) 38 | right = { "fold", "git" }, -- priority of signs on the right (high to low) 39 | folds = { 40 | open = false, -- show open fold icons 41 | git_hl = false, -- use Git Signs hl for fold icons 42 | }, 43 | git = { 44 | -- patterns to match Git signs 45 | patterns = { "GitSign", "MiniDiffSign" }, 46 | }, 47 | refresh = 50, -- refresh at most every 50ms 48 | } 49 | < 50 | 51 | 52 | ============================================================================== 53 | 3. Module *snacks-statuscolumn-module* 54 | 55 | 56 | `Snacks.statuscolumn()` *Snacks.statuscolumn()* 57 | 58 | >lua 59 | ---@type fun(): string 60 | Snacks.statuscolumn() 61 | < 62 | 63 | 64 | `Snacks.statuscolumn.get()` *Snacks.statuscolumn.get()* 65 | 66 | >lua 67 | Snacks.statuscolumn.get() 68 | < 69 | 70 | Generated by panvimdoc 71 | 72 | vim:tw=78:ts=8:noet:ft=help:norl: 73 | -------------------------------------------------------------------------------- /docs/util.md: -------------------------------------------------------------------------------- 1 | # 🍿 util 2 | 3 | 4 | 5 | ## 📚 Types 6 | 7 | ```lua 8 | ---@alias snacks.util.hl table 9 | ``` 10 | 11 | ## 📦 Module 12 | 13 | ### `Snacks.util.blend()` 14 | 15 | ```lua 16 | ---@param fg string foreground color 17 | ---@param bg string background color 18 | ---@param alpha number number between 0 and 1. 0 results in bg, 1 results in fg 19 | Snacks.util.blend(fg, bg, alpha) 20 | ``` 21 | 22 | ### `Snacks.util.bo()` 23 | 24 | Set buffer-local options. 25 | 26 | ```lua 27 | ---@param buf number 28 | ---@param bo vim.bo 29 | Snacks.util.bo(buf, bo) 30 | ``` 31 | 32 | ### `Snacks.util.color()` 33 | 34 | ```lua 35 | ---@param group string hl group to get color from 36 | ---@param prop? string property to get. Defaults to "fg" 37 | Snacks.util.color(group, prop) 38 | ``` 39 | 40 | ### `Snacks.util.file_decode()` 41 | 42 | Decodes a file name to a string. 43 | 44 | ```lua 45 | ---@param str string 46 | Snacks.util.file_decode(str) 47 | ``` 48 | 49 | ### `Snacks.util.file_encode()` 50 | 51 | Encodes a string to be used as a file name. 52 | 53 | ```lua 54 | ---@param str string 55 | Snacks.util.file_encode(str) 56 | ``` 57 | 58 | ### `Snacks.util.icon()` 59 | 60 | Get an icon from `mini.icons` or `nvim-web-devicons`. 61 | 62 | ```lua 63 | ---@param name string 64 | ---@param cat? string defaults to "file" 65 | ---@return string, string? 66 | Snacks.util.icon(name, cat) 67 | ``` 68 | 69 | ### `Snacks.util.is_transparent()` 70 | 71 | Check if the colorscheme is transparent. 72 | 73 | ```lua 74 | Snacks.util.is_transparent() 75 | ``` 76 | 77 | ### `Snacks.util.redraw()` 78 | 79 | Redraw the window. 80 | Optimized for Neovim >= 0.10 81 | 82 | ```lua 83 | ---@param win number 84 | Snacks.util.redraw(win) 85 | ``` 86 | 87 | ### `Snacks.util.redraw_range()` 88 | 89 | Redraw the range of lines in the window. 90 | Optimized for Neovim >= 0.10 91 | 92 | ```lua 93 | ---@param win number 94 | ---@param from number -- 1-indexed, inclusive 95 | ---@param to number -- 1-indexed, inclusive 96 | Snacks.util.redraw_range(win, from, to) 97 | ``` 98 | 99 | ### `Snacks.util.set_hl()` 100 | 101 | Ensures the hl groups are always set, even after a colorscheme change. 102 | 103 | ```lua 104 | ---@param groups snacks.util.hl 105 | ---@param opts? { prefix?:string, default?:boolean, managed?:boolean } 106 | Snacks.util.set_hl(groups, opts) 107 | ``` 108 | 109 | ### `Snacks.util.wo()` 110 | 111 | Set window-local options. 112 | 113 | ```lua 114 | ---@param win number 115 | ---@param wo vim.wo 116 | Snacks.util.wo(win, wo) 117 | ``` 118 | -------------------------------------------------------------------------------- /docs/scope.md: -------------------------------------------------------------------------------- 1 | # 🍿 scope 2 | 3 | Scope detection based on treesitter or indent. 4 | 5 | The indent-based algorithm is similar to what is used 6 | in [mini.indentscope](https://github.com/echasnovski/mini.indentscope). 7 | 8 | 9 | 10 | ## 📦 Setup 11 | 12 | ```lua 13 | -- lazy.nvim 14 | { 15 | "folke/snacks.nvim", 16 | opts = { 17 | scope = { 18 | -- your scope configuration comes here 19 | -- or leave it empty to use the default settings 20 | -- refer to the configuration section below 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | ## ⚙️ Config 27 | 28 | ```lua 29 | ---@class snacks.scope.Config 30 | ---@field max_size? number 31 | { 32 | -- absolute minimum size of the scope. 33 | -- can be less if the scope is a top-level single line scope 34 | min_size = 2, 35 | -- try to expand the scope to this size 36 | max_size = nil, 37 | siblings = false, -- expand single line scopes with single line siblings 38 | -- what buffers to attach to 39 | filter = function(buf) 40 | return vim.bo[buf].buftype == "" 41 | end, 42 | -- debounce scope detection in ms 43 | debounce = 30, 44 | treesitter = { 45 | -- detect scope based on treesitter. 46 | -- falls back to indent based detection if not available 47 | enabled = true, 48 | ---@type string[]|false 49 | blocks = { 50 | "function_declaration", 51 | "function_definition", 52 | "method_declaration", 53 | "method_definition", 54 | "class_declaration", 55 | "class_definition", 56 | "do_statement", 57 | "while_statement", 58 | "repeat_statement", 59 | "if_statement", 60 | "for_statement", 61 | }, 62 | }, 63 | } 64 | ``` 65 | 66 | ## 📚 Types 67 | 68 | ```lua 69 | ---@class snacks.scope.Opts: snacks.scope.Config 70 | ---@field buf number 71 | ---@field pos {[1]:number, [2]:number} -- (1,0) indexed 72 | ``` 73 | 74 | ```lua 75 | ---@alias snacks.scope.Attach.cb fun(win: number, buf: number, scope:snacks.scope.Scope?, prev:snacks.scope.Scope?) 76 | ``` 77 | 78 | ```lua 79 | ---@alias snacks.scope.scope {buf: number, from: number, to: number, indent?: number} 80 | ``` 81 | 82 | ## 📦 Module 83 | 84 | ### `Snacks.scope.attach()` 85 | 86 | Attach a scope listener 87 | 88 | ```lua 89 | ---@param cb snacks.scope.Attach.cb 90 | ---@param opts? snacks.scope.Config 91 | ---@return snacks.scope.Listener 92 | Snacks.scope.attach(cb, opts) 93 | ``` 94 | 95 | ### `Snacks.scope.get()` 96 | 97 | ```lua 98 | ---@param opts? snacks.scope.Opts 99 | ---@return snacks.scope.Scope? 100 | Snacks.scope.get(opts) 101 | ``` 102 | -------------------------------------------------------------------------------- /doc/snacks-bufdelete.txt: -------------------------------------------------------------------------------- 1 | *snacks-bufdelete.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-bufdelete-table-of-contents* 5 | 6 | 1. Types |snacks-bufdelete-types| 7 | 2. Module |snacks-bufdelete-module| 8 | - Snacks.bufdelete() |snacks-bufdelete-module-snacks.bufdelete()| 9 | - Snacks.bufdelete.all() |snacks-bufdelete-module-snacks.bufdelete.all()| 10 | - Snacks.bufdelete.delete()|snacks-bufdelete-module-snacks.bufdelete.delete()| 11 | - Snacks.bufdelete.other()|snacks-bufdelete-module-snacks.bufdelete.other()| 12 | Delete buffers without disrupting window layout. 13 | 14 | If the buffer you want to close has changes, a prompt will be shown to save or 15 | discard. 16 | 17 | 18 | ============================================================================== 19 | 1. Types *snacks-bufdelete-types* 20 | 21 | >lua 22 | ---@class snacks.bufdelete.Opts 23 | ---@field buf? number Buffer to delete. Defaults to the current buffer 24 | ---@field force? boolean Delete the buffer even if it is modified 25 | ---@field filter? fun(buf: number): boolean Filter buffers to delete 26 | ---@field wipe? boolean Wipe the buffer instead of deleting it (see `:h :bwipeout`) 27 | < 28 | 29 | 30 | ============================================================================== 31 | 2. Module *snacks-bufdelete-module* 32 | 33 | 34 | `Snacks.bufdelete()` *Snacks.bufdelete()* 35 | 36 | >lua 37 | ---@type fun(buf?: number|snacks.bufdelete.Opts) 38 | Snacks.bufdelete() 39 | < 40 | 41 | 42 | `Snacks.bufdelete.all()` *Snacks.bufdelete.all()* 43 | 44 | Delete all buffers 45 | 46 | >lua 47 | ---@param opts? snacks.bufdelete.Opts 48 | Snacks.bufdelete.all(opts) 49 | < 50 | 51 | 52 | `Snacks.bufdelete.delete()` *Snacks.bufdelete.delete()* 53 | 54 | Delete a buffer: - either the current buffer if `buf` is not provided - or the 55 | buffer `buf` if it is a number - or every buffer for which `buf` returns true 56 | if it is a function 57 | 58 | >lua 59 | ---@param opts? number|snacks.bufdelete.Opts 60 | Snacks.bufdelete.delete(opts) 61 | < 62 | 63 | 64 | `Snacks.bufdelete.other()` *Snacks.bufdelete.other()* 65 | 66 | Delete all buffers except the current one 67 | 68 | >lua 69 | ---@param opts? snacks.bufdelete.Opts 70 | Snacks.bufdelete.other(opts) 71 | < 72 | 73 | Generated by panvimdoc 74 | 75 | vim:tw=78:ts=8:noet:ft=help:norl: 76 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug/issue 3 | title: "bug: " 4 | labels: [bug] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/snacks.nvim) 10 | and search [existing issues](https://github.com/folke/snacks.nvim/issues). 11 | 12 | Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/snacks.nvim/discussions) and will be closed. 13 | - type: checkboxes 14 | attributes: 15 | label: Did you check docs and existing issues? 16 | description: Make sure you checked all of the below before submitting an issue 17 | options: 18 | - label: I have read all the snacks.nvim docs 19 | required: true 20 | - label: I have updated the plugin to the latest version before submitting this issue 21 | required: true 22 | - label: I have searched the existing issues of snacks.nvim 23 | required: true 24 | - label: I have searched the existing issues of plugins related to this issue 25 | required: true 26 | - type: input 27 | attributes: 28 | label: "Neovim version (nvim -v)" 29 | placeholder: "0.8.0 commit db1b0ee3b30f" 30 | validations: 31 | required: true 32 | - type: input 33 | attributes: 34 | label: "Operating system/version" 35 | placeholder: "MacOS 11.5" 36 | validations: 37 | required: true 38 | - type: textarea 39 | attributes: 40 | label: Describe the bug 41 | description: A clear and concise description of what the bug is. Please include any related errors you see in Neovim. 42 | validations: 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: Steps To Reproduce 47 | description: Steps to reproduce the behavior. 48 | placeholder: | 49 | 1. 50 | 2. 51 | 3. 52 | validations: 53 | required: true 54 | - type: textarea 55 | attributes: 56 | label: Expected Behavior 57 | description: A concise description of what you expected to happen. 58 | validations: 59 | required: true 60 | - type: textarea 61 | attributes: 62 | label: Repro 63 | description: Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua` 64 | value: | 65 | vim.env.LAZY_STDPATH = ".repro" 66 | load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))() 67 | 68 | require("lazy.minit").repro({ 69 | spec = { 70 | { "folke/snacks.nvim", opts = {} }, 71 | -- add any other plugins here 72 | }, 73 | }) 74 | render: lua 75 | validations: 76 | required: false 77 | -------------------------------------------------------------------------------- /docs/zen.md: -------------------------------------------------------------------------------- 1 | # 🍿 zen 2 | 3 | Zen mode • distraction-free coding. 4 | Integrates with `Snacks.toggle` to toggle various UI elements 5 | and with `Snacks.dim` to dim code out of scope. 6 | 7 | Similar plugins: 8 | 9 | - [zen-mode.nvim](https://github.com/folke/zen-mode.nvim) 10 | - [true-zen.nvim](https://github.com/pocco81/true-zen.nvim) 11 | 12 | ![image](https://github.com/user-attachments/assets/77c607ec-c354-4e17-bcd1-fdcd4b4c0057) 13 | 14 | 15 | 16 | ## 📦 Setup 17 | 18 | ```lua 19 | -- lazy.nvim 20 | { 21 | "folke/snacks.nvim", 22 | opts = { 23 | zen = { 24 | -- your zen configuration comes here 25 | -- or leave it empty to use the default settings 26 | -- refer to the configuration section below 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | ## ⚙️ Config 33 | 34 | ```lua 35 | ---@class snacks.zen.Config 36 | { 37 | -- You can add any `Snacks.toggle` id here. 38 | -- Toggle state is restored when the window is closed. 39 | -- Toggle config options are NOT merged. 40 | ---@type table 41 | toggles = { 42 | dim = true, 43 | git_signs = false, 44 | mini_diff_signs = false, 45 | -- diagnostics = false, 46 | -- inlay_hints = false, 47 | }, 48 | show = { 49 | statusline = false, -- can only be shown when using the global statusline 50 | tabline = false, 51 | }, 52 | ---@type snacks.win.Config 53 | win = { style = "zen" }, 54 | 55 | --- Options for the `Snacks.zen.zoom()` 56 | ---@type snacks.zen.Config 57 | zoom = { 58 | toggles = {}, 59 | show = { statusline = true, tabline = true }, 60 | win = { 61 | backdrop = false, 62 | width = 0, -- full width 63 | }, 64 | }, 65 | } 66 | ``` 67 | 68 | ## 🎨 Styles 69 | 70 | ### `zen` 71 | 72 | ```lua 73 | { 74 | enter = true, 75 | fixbuf = false, 76 | minimal = false, 77 | width = 120, 78 | height = 0, 79 | backdrop = { transparent = true, blend = 40 }, 80 | keys = { q = false }, 81 | wo = { 82 | winhighlight = "NormalFloat:Normal", 83 | }, 84 | } 85 | ``` 86 | 87 | ### `zoom_indicator` 88 | 89 | fullscreen indicator 90 | only shown when the window is maximized 91 | 92 | ```lua 93 | { 94 | text = "▍ zoom 󰊓 ", 95 | minimal = true, 96 | enter = false, 97 | focusable = false, 98 | height = 1, 99 | row = 0, 100 | col = -1, 101 | backdrop = false, 102 | } 103 | ``` 104 | 105 | ## 📦 Module 106 | 107 | ### `Snacks.zen()` 108 | 109 | ```lua 110 | ---@type fun(opts: snacks.zen.Config): snacks.win 111 | Snacks.zen() 112 | ``` 113 | 114 | ### `Snacks.zen.zen()` 115 | 116 | ```lua 117 | ---@param opts? snacks.zen.Config 118 | Snacks.zen.zen(opts) 119 | ``` 120 | 121 | ### `Snacks.zen.zoom()` 122 | 123 | ```lua 124 | ---@param opts? snacks.zen.Config 125 | Snacks.zen.zoom(opts) 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/animate.md: -------------------------------------------------------------------------------- 1 | # 🍿 animate 2 | 3 | Efficient animation library including over 45 easing functions: 4 | 5 | - [Emmanuel Oga's easing functions](https://github.com/EmmanuelOga/easing) 6 | - [Easing functions overview](https://github.com/kikito/tween.lua?tab=readme-ov-file#easing-functions) 7 | 8 | There's at any given time at most one timer running, that takes 9 | care of all active animations, controlled by the `fps` setting. 10 | 11 | 12 | 13 | ## 📦 Setup 14 | 15 | ```lua 16 | -- lazy.nvim 17 | { 18 | "folke/snacks.nvim", 19 | opts = { 20 | animate = { 21 | -- your animate configuration comes here 22 | -- or leave it empty to use the default settings 23 | -- refer to the configuration section below 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | ## ⚙️ Config 30 | 31 | ```lua 32 | ---@class snacks.animate.Config 33 | ---@field easing? snacks.animate.easing|snacks.animate.easing.Fn 34 | { 35 | ---@type snacks.animate.Duration|number 36 | duration = 20, -- ms per step 37 | easing = "linear", 38 | fps = 60, -- frames per second. Global setting for all animations 39 | } 40 | ``` 41 | 42 | ## 📚 Types 43 | 44 | All easing functions take these parameters: 45 | 46 | * `t` _(time)_: should go from 0 to duration 47 | * `b` _(begin)_: value of the property being ease. 48 | * `c` _(change)_: ending value of the property - beginning value of the property 49 | * `d` _(duration)_: total duration of the animation 50 | 51 | Some functions allow additional modifiers, like the elastic functions 52 | which also can receive an amplitud and a period parameters (defaults 53 | are included) 54 | 55 | ```lua 56 | ---@alias snacks.animate.easing.Fn fun(t: number, b: number, c: number, d: number): number 57 | ``` 58 | 59 | Duration can be specified as the total duration or the duration per step. 60 | When both are specified, the minimum of both is used. 61 | 62 | ```lua 63 | ---@class snacks.animate.Duration 64 | ---@field step? number duration per step in ms 65 | ---@field total? number total duration in ms 66 | ``` 67 | 68 | ```lua 69 | ---@class snacks.animate.Opts: snacks.animate.Config 70 | ---@field int? boolean interpolate the value to an integer 71 | ---@field id? number|string unique identifier for the animation 72 | ``` 73 | 74 | ```lua 75 | ---@class snacks.animate.ctx 76 | ---@field anim snacks.animate.Animation 77 | ---@field prev number 78 | ---@field done boolean 79 | ``` 80 | 81 | ```lua 82 | ---@alias snacks.animate.cb fun(value:number, ctx: snacks.animate.ctx) 83 | ``` 84 | 85 | ## 📦 Module 86 | 87 | ### `Snacks.animate()` 88 | 89 | ```lua 90 | ---@type fun(from: number, to: number, cb: snacks.animate.cb, opts?: snacks.animate.Opts): snacks.animate.Animation 91 | Snacks.animate() 92 | ``` 93 | 94 | ### `Snacks.animate.add()` 95 | 96 | Add an animation 97 | 98 | ```lua 99 | ---@param from number 100 | ---@param to number 101 | ---@param cb snacks.animate.cb 102 | ---@param opts? snacks.animate.Opts 103 | Snacks.animate.add(from, to, cb, opts) 104 | ``` 105 | 106 | ### `Snacks.animate.del()` 107 | 108 | Delete an animation 109 | 110 | ```lua 111 | ---@param id number|string 112 | Snacks.animate.del(id) 113 | ``` 114 | -------------------------------------------------------------------------------- /doc/snacks-words.txt: -------------------------------------------------------------------------------- 1 | *snacks-words.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-words-table-of-contents* 5 | 6 | 1. Setup |snacks-words-setup| 7 | 2. Config |snacks-words-config| 8 | 3. Module |snacks-words-module| 9 | - Snacks.words.clear() |snacks-words-module-snacks.words.clear()| 10 | - Snacks.words.disable() |snacks-words-module-snacks.words.disable()| 11 | - Snacks.words.enable() |snacks-words-module-snacks.words.enable()| 12 | - Snacks.words.is_enabled() |snacks-words-module-snacks.words.is_enabled()| 13 | - Snacks.words.jump() |snacks-words-module-snacks.words.jump()| 14 | Auto-show LSP references and quickly navigate between them 15 | 16 | 17 | ============================================================================== 18 | 1. Setup *snacks-words-setup* 19 | 20 | >lua 21 | -- lazy.nvim 22 | { 23 | "folke/snacks.nvim", 24 | opts = { 25 | words = { 26 | -- your words configuration comes here 27 | -- or leave it empty to use the default settings 28 | -- refer to the configuration section below 29 | } 30 | } 31 | } 32 | < 33 | 34 | 35 | ============================================================================== 36 | 2. Config *snacks-words-config* 37 | 38 | >lua 39 | ---@class snacks.words.Config 40 | ---@field enabled? boolean 41 | { 42 | debounce = 200, -- time in ms to wait before updating 43 | notify_jump = false, -- show a notification when jumping 44 | notify_end = true, -- show a notification when reaching the end 45 | foldopen = true, -- open folds after jumping 46 | jumplist = true, -- set jump point before jumping 47 | modes = { "n", "i", "c" }, -- modes to show references 48 | } 49 | < 50 | 51 | 52 | ============================================================================== 53 | 3. Module *snacks-words-module* 54 | 55 | 56 | `Snacks.words.clear()` *Snacks.words.clear()* 57 | 58 | >lua 59 | Snacks.words.clear() 60 | < 61 | 62 | 63 | `Snacks.words.disable()` *Snacks.words.disable()* 64 | 65 | >lua 66 | Snacks.words.disable() 67 | < 68 | 69 | 70 | `Snacks.words.enable()` *Snacks.words.enable()* 71 | 72 | >lua 73 | Snacks.words.enable() 74 | < 75 | 76 | 77 | `Snacks.words.is_enabled()` *Snacks.words.is_enabled()* 78 | 79 | >lua 80 | ---@param buf number? 81 | Snacks.words.is_enabled(buf) 82 | < 83 | 84 | 85 | `Snacks.words.jump()` *Snacks.words.jump()* 86 | 87 | >lua 88 | ---@param count number 89 | ---@param cycle? boolean 90 | Snacks.words.jump(count, cycle) 91 | < 92 | 93 | Generated by panvimdoc 94 | 95 | vim:tw=78:ts=8:noet:ft=help:norl: 96 | -------------------------------------------------------------------------------- /doc/snacks-scroll.txt: -------------------------------------------------------------------------------- 1 | *snacks-scroll.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-scroll-table-of-contents* 5 | 6 | 1. Setup |snacks-scroll-setup| 7 | 2. Config |snacks-scroll-config| 8 | 3. Types |snacks-scroll-types| 9 | 4. Module |snacks-scroll-module| 10 | - Snacks.scroll.disable() |snacks-scroll-module-snacks.scroll.disable()| 11 | - Snacks.scroll.enable() |snacks-scroll-module-snacks.scroll.enable()| 12 | Smooth scrolling for Neovim. Properly handles `scrolloff` and mouse scrolling. 13 | 14 | Similar plugins: 15 | 16 | - mini.animate 17 | - neoscroll.nvim 18 | 19 | 20 | ============================================================================== 21 | 1. Setup *snacks-scroll-setup* 22 | 23 | >lua 24 | -- lazy.nvim 25 | { 26 | "folke/snacks.nvim", 27 | opts = { 28 | scroll = { 29 | -- your scroll configuration comes here 30 | -- or leave it empty to use the default settings 31 | -- refer to the configuration section below 32 | } 33 | } 34 | } 35 | < 36 | 37 | 38 | ============================================================================== 39 | 2. Config *snacks-scroll-config* 40 | 41 | >lua 42 | ---@class snacks.scroll.Config 43 | ---@field animate snacks.animate.Config 44 | { 45 | animate = { 46 | duration = { step = 15, total = 250 }, 47 | easing = "linear", 48 | }, 49 | -- what buffers to animate 50 | filter = function(buf) 51 | return vim.g.snacks_scroll ~= false and vim.b[buf].snacks_scroll ~= false 52 | end, 53 | } 54 | < 55 | 56 | 57 | ============================================================================== 58 | 3. Types *snacks-scroll-types* 59 | 60 | >lua 61 | ---@alias snacks.scroll.View {topline:number, lnum:number} 62 | < 63 | 64 | >lua 65 | ---@class snacks.scroll.State 66 | ---@field win number 67 | ---@field buf number 68 | ---@field view snacks.scroll.View 69 | ---@field current snacks.scroll.View 70 | ---@field target snacks.scroll.View 71 | ---@field scrolloff number 72 | ---@field mousescroll number 73 | ---@field height number 74 | < 75 | 76 | 77 | ============================================================================== 78 | 4. Module *snacks-scroll-module* 79 | 80 | 81 | `Snacks.scroll.disable()` *Snacks.scroll.disable()* 82 | 83 | >lua 84 | Snacks.scroll.disable() 85 | < 86 | 87 | 88 | `Snacks.scroll.enable()` *Snacks.scroll.enable()* 89 | 90 | >lua 91 | Snacks.scroll.enable() 92 | < 93 | 94 | Generated by panvimdoc 95 | 96 | vim:tw=78:ts=8:noet:ft=help:norl: 97 | -------------------------------------------------------------------------------- /docs/toggle.md: -------------------------------------------------------------------------------- 1 | # 🍿 toggle 2 | 3 | Toggle keymaps integrated with which-key icons / colors 4 | 5 | ![image](https://github.com/user-attachments/assets/6d843acd-1ac1-44fd-b318-58b4c17de2d5) 6 | 7 | 8 | 9 | ## 📦 Setup 10 | 11 | ```lua 12 | -- lazy.nvim 13 | { 14 | "folke/snacks.nvim", 15 | opts = { 16 | toggle = { 17 | -- your toggle configuration comes here 18 | -- or leave it empty to use the default settings 19 | -- refer to the configuration section below 20 | } 21 | } 22 | } 23 | ``` 24 | 25 | ## ⚙️ Config 26 | 27 | ```lua 28 | ---@class snacks.toggle.Config 29 | ---@field icon? string|{ enabled: string, disabled: string } 30 | ---@field color? string|{ enabled: string, disabled: string } 31 | { 32 | map = vim.keymap.set, -- keymap.set function to use 33 | which_key = true, -- integrate with which-key to show enabled/disabled icons and colors 34 | notify = true, -- show a notification when toggling 35 | -- icons for enabled/disabled states 36 | icon = { 37 | enabled = " ", 38 | disabled = " ", 39 | }, 40 | -- colors for enabled/disabled states 41 | color = { 42 | enabled = "green", 43 | disabled = "yellow", 44 | }, 45 | } 46 | ``` 47 | 48 | ## 📚 Types 49 | 50 | ```lua 51 | ---@class snacks.toggle.Opts: snacks.toggle.Config 52 | ---@field id? string 53 | ---@field name string 54 | ---@field get fun():boolean 55 | ---@field set fun(state:boolean) 56 | ``` 57 | 58 | ## 📦 Module 59 | 60 | ### `Snacks.toggle()` 61 | 62 | ```lua 63 | ---@type fun(... :snacks.toggle.Opts): snacks.toggle.Class 64 | Snacks.toggle() 65 | ``` 66 | 67 | ### `Snacks.toggle.diagnostics()` 68 | 69 | ```lua 70 | ---@param opts? snacks.toggle.Config 71 | Snacks.toggle.diagnostics(opts) 72 | ``` 73 | 74 | ### `Snacks.toggle.dim()` 75 | 76 | ```lua 77 | Snacks.toggle.dim() 78 | ``` 79 | 80 | ### `Snacks.toggle.get()` 81 | 82 | ```lua 83 | ---@param id string 84 | ---@return snacks.toggle.Class? 85 | Snacks.toggle.get(id) 86 | ``` 87 | 88 | ### `Snacks.toggle.indent()` 89 | 90 | ```lua 91 | Snacks.toggle.indent() 92 | ``` 93 | 94 | ### `Snacks.toggle.inlay_hints()` 95 | 96 | ```lua 97 | ---@param opts? snacks.toggle.Config 98 | Snacks.toggle.inlay_hints(opts) 99 | ``` 100 | 101 | ### `Snacks.toggle.line_number()` 102 | 103 | ```lua 104 | ---@param opts? snacks.toggle.Config 105 | Snacks.toggle.line_number(opts) 106 | ``` 107 | 108 | ### `Snacks.toggle.new()` 109 | 110 | ```lua 111 | ---@param ... snacks.toggle.Opts 112 | Snacks.toggle.new(...) 113 | ``` 114 | 115 | ### `Snacks.toggle.option()` 116 | 117 | ```lua 118 | ---@param option string 119 | ---@param opts? snacks.toggle.Config | {on?: unknown, off?: unknown} 120 | Snacks.toggle.option(option, opts) 121 | ``` 122 | 123 | ### `Snacks.toggle.profiler()` 124 | 125 | ```lua 126 | Snacks.toggle.profiler() 127 | ``` 128 | 129 | ### `Snacks.toggle.profiler_highlights()` 130 | 131 | ```lua 132 | Snacks.toggle.profiler_highlights() 133 | ``` 134 | 135 | ### `Snacks.toggle.scroll()` 136 | 137 | ```lua 138 | Snacks.toggle.scroll() 139 | ``` 140 | 141 | ### `Snacks.toggle.treesitter()` 142 | 143 | ```lua 144 | ---@param opts? snacks.toggle.Config 145 | Snacks.toggle.treesitter(opts) 146 | ``` 147 | 148 | ### `Snacks.toggle.words()` 149 | 150 | ```lua 151 | Snacks.toggle.words() 152 | ``` 153 | -------------------------------------------------------------------------------- /docs/debug.md: -------------------------------------------------------------------------------- 1 | # 🍿 debug 2 | 3 | Utility functions you can use in your code. 4 | 5 | Personally, I have the code below at the top of my `init.lua`: 6 | 7 | ```lua 8 | _G.dd = function(...) 9 | Snacks.debug.inspect(...) 10 | end 11 | _G.bt = function() 12 | Snacks.debug.backtrace() 13 | end 14 | vim.print = _G.dd 15 | ``` 16 | 17 | What this does: 18 | 19 | - Add a global `dd(...)` you can use anywhere to quickly show a 20 | notification with a pretty printed dump of the object(s) 21 | with lua treesitter highlighting 22 | - Add a global `bt()` to show a notification with a pretty 23 | backtrace. 24 | - Override Neovim's `vim.print`, which is also used by `:= {something = 123}` 25 | 26 | ![image](https://github.com/user-attachments/assets/0517aed7-fbd0-42ee-8058-c213410d80a7) 27 | 28 | 29 | 30 | ## 📚 Types 31 | 32 | ```lua 33 | ---@alias snacks.debug.Trace {name: string, time: number, [number]:snacks.debug.Trace} 34 | ---@alias snacks.debug.Stat {name:string, time:number, count?:number, depth?:number} 35 | ``` 36 | 37 | ## 📦 Module 38 | 39 | ### `Snacks.debug()` 40 | 41 | ```lua 42 | ---@type fun(...) 43 | Snacks.debug() 44 | ``` 45 | 46 | ### `Snacks.debug.backtrace()` 47 | 48 | Show a notification with a pretty backtrace 49 | 50 | ```lua 51 | ---@param msg? string|string[] 52 | ---@param opts? snacks.notify.Opts 53 | Snacks.debug.backtrace(msg, opts) 54 | ``` 55 | 56 | ### `Snacks.debug.inspect()` 57 | 58 | Show a notification with a pretty printed dump of the object(s) 59 | with lua treesitter highlighting and the location of the caller 60 | 61 | ```lua 62 | Snacks.debug.inspect(...) 63 | ``` 64 | 65 | ### `Snacks.debug.log()` 66 | 67 | Log a message to the file `./debug.log`. 68 | - a timestamp will be added to every message. 69 | - accepts multiple arguments and pretty prints them. 70 | - if the argument is not a string, it will be printed using `vim.inspect`. 71 | - if the message is smaller than 120 characters, it will be printed on a single line. 72 | 73 | ```lua 74 | Snacks.debug.log("Hello", { foo = "bar" }, 42) 75 | -- 2024-11-08 08:56:52 Hello { foo = "bar" } 42 76 | ``` 77 | 78 | ```lua 79 | Snacks.debug.log(...) 80 | ``` 81 | 82 | ### `Snacks.debug.profile()` 83 | 84 | Very simple function to profile a lua function. 85 | * **flush**: set to `true` to use `jit.flush` in every iteration. 86 | * **count**: defaults to 100 87 | 88 | ```lua 89 | ---@param fn fun() 90 | ---@param opts? {count?: number, flush?: boolean, title?: string} 91 | Snacks.debug.profile(fn, opts) 92 | ``` 93 | 94 | ### `Snacks.debug.run()` 95 | 96 | Run the current buffer or a range of lines. 97 | Shows the output of `print` inlined with the code. 98 | Any error will be shown as a diagnostic. 99 | 100 | ```lua 101 | ---@param opts? {name?:string, buf?:number, print?:boolean} 102 | Snacks.debug.run(opts) 103 | ``` 104 | 105 | ### `Snacks.debug.stats()` 106 | 107 | ```lua 108 | ---@param opts? {min?: number, show?:boolean} 109 | ---@return {summary:table, trace:snacks.debug.Stat[], traces:snacks.debug.Trace[]} 110 | Snacks.debug.stats(opts) 111 | ``` 112 | 113 | ### `Snacks.debug.trace()` 114 | 115 | ```lua 116 | ---@param name string? 117 | Snacks.debug.trace(name) 118 | ``` 119 | 120 | ### `Snacks.debug.tracemod()` 121 | 122 | ```lua 123 | ---@param modname string 124 | ---@param mod? table 125 | ---@param suffix? string 126 | Snacks.debug.tracemod(modname, mod, suffix) 127 | ``` 128 | -------------------------------------------------------------------------------- /docs/gitbrowse.md: -------------------------------------------------------------------------------- 1 | # 🍿 gitbrowse 2 | 3 | Open the repo of the active file in the browser (e.g., GitHub) 4 | 5 | 6 | 7 | ## 📦 Setup 8 | 9 | ```lua 10 | -- lazy.nvim 11 | { 12 | "folke/snacks.nvim", 13 | opts = { 14 | gitbrowse = { 15 | -- your gitbrowse configuration comes here 16 | -- or leave it empty to use the default settings 17 | -- refer to the configuration section below 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ## ⚙️ Config 24 | 25 | ```lua 26 | ---@class snacks.gitbrowse.Config 27 | ---@field url_patterns? table> 28 | { 29 | notify = true, -- show notification on open 30 | -- Handler to open the url in a browser 31 | ---@param url string 32 | open = function(url) 33 | if vim.fn.has("nvim-0.10") == 0 then 34 | require("lazy.util").open(url, { system = true }) 35 | return 36 | end 37 | vim.ui.open(url) 38 | end, 39 | ---@type "repo" | "branch" | "file" | "commit" 40 | what = "file", -- what to open. not all remotes support all types 41 | branch = nil, ---@type string? 42 | line_start = nil, ---@type number? 43 | line_end = nil, ---@type number? 44 | -- patterns to transform remotes to an actual URL 45 | remote_patterns = { 46 | { "^(https?://.*)%.git$" , "%1" }, 47 | { "^git@(.+):(.+)%.git$" , "https://%1/%2" }, 48 | { "^git@(.+):(.+)$" , "https://%1/%2" }, 49 | { "^git@(.+)/(.+)$" , "https://%1/%2" }, 50 | { "^ssh://git@(.*)$" , "https://%1" }, 51 | { "^ssh://([^:/]+)(:%d+)/(.*)$" , "https://%1/%3" }, 52 | { "^ssh://([^/]+)/(.*)$" , "https://%1/%2" }, 53 | { "ssh%.dev%.azure%.com/v3/(.*)/(.*)$", "dev.azure.com/%1/_git/%2" }, 54 | { "^https://%w*@(.*)" , "https://%1" }, 55 | { "^git@(.*)" , "https://%1" }, 56 | { ":%d+" , "" }, 57 | { "%.git$" , "" }, 58 | }, 59 | url_patterns = { 60 | ["github%.com"] = { 61 | branch = "/tree/{branch}", 62 | file = "/blob/{branch}/{file}#L{line_start}-L{line_end}", 63 | commit = "/commit/{commit}", 64 | }, 65 | ["gitlab%.com"] = { 66 | branch = "/-/tree/{branch}", 67 | file = "/-/blob/{branch}/{file}#L{line_start}-L{line_end}", 68 | commit = "/-/commit/{commit}", 69 | }, 70 | ["bitbucket%.org"] = { 71 | branch = "/src/{branch}", 72 | file = "/src/{branch}/{file}#lines-{line_start}-L{line_end}", 73 | commit = "/commits/{commit}", 74 | }, 75 | }, 76 | } 77 | ``` 78 | 79 | ## 📚 Types 80 | 81 | ```lua 82 | ---@class snacks.gitbrowse.Fields 83 | ---@field branch? string 84 | ---@field file? string 85 | ---@field line_start? number 86 | ---@field line_end? number 87 | ---@field commit? string 88 | ---@field line_count? number 89 | ``` 90 | 91 | ## 📦 Module 92 | 93 | ### `Snacks.gitbrowse()` 94 | 95 | ```lua 96 | ---@type fun(opts?: snacks.gitbrowse.Config) 97 | Snacks.gitbrowse() 98 | ``` 99 | 100 | ### `Snacks.gitbrowse.get_url()` 101 | 102 | ```lua 103 | ---@param repo string 104 | ---@param fields snacks.gitbrowse.Fields 105 | ---@param opts? snacks.gitbrowse.Config 106 | Snacks.gitbrowse.get_url(repo, fields, opts) 107 | ``` 108 | 109 | ### `Snacks.gitbrowse.open()` 110 | 111 | ```lua 112 | ---@param opts? snacks.gitbrowse.Config 113 | Snacks.gitbrowse.open(opts) 114 | ``` 115 | -------------------------------------------------------------------------------- /tests/gitbrowse_spec.lua: -------------------------------------------------------------------------------- 1 | ---@module "luassert" 2 | 3 | local gitbrowse = require("snacks.gitbrowse") 4 | 5 | -- stylua: ignore 6 | local git_remotes_cases = { 7 | ["https://github.com/LazyVim/LazyVim.git"] = "https://github.com/LazyVim/LazyVim", 8 | ["https://github.com/LazyVim/LazyVim"] = "https://github.com/LazyVim/LazyVim", 9 | ["git@github.com:LazyVim/LazyVim"] = "https://github.com/LazyVim/LazyVim", 10 | ["git@ssh.dev.azure.com:v3/neovim-org/owner/repo"] = "https://dev.azure.com/neovim-org/owner/_git/repo", 11 | ["https://folkelemaitre@bitbucket.org/samiulazim/neovim.git"] = "https://bitbucket.org/samiulazim/neovim", 12 | ["git@bitbucket.org:samiulazim/neovim.git"] = "https://bitbucket.org/samiulazim/neovim", 13 | ["git@gitlab.com:inkscape/inkscape.git"] = "https://gitlab.com/inkscape/inkscape", 14 | ["https://gitlab.com/inkscape/inkscape.git"] = "https://gitlab.com/inkscape/inkscape", 15 | ["git@github.com:torvalds/linux.git"] = "https://github.com/torvalds/linux", 16 | ["https://github.com/torvalds/linux.git"] = "https://github.com/torvalds/linux", 17 | ["git@bitbucket.org:team/repo.git"] = "https://bitbucket.org/team/repo", 18 | ["https://bitbucket.org/team/repo.git"] = "https://bitbucket.org/team/repo", 19 | ["git@gitlab.com:example-group/example-project.git"] = "https://gitlab.com/example-group/example-project", 20 | ["https://gitlab.com/example-group/example-project.git"] = "https://gitlab.com/example-group/example-project", 21 | ["git@ssh.dev.azure.com:v3/org/project/repo"] = "https://dev.azure.com/org/project/_git/repo", 22 | ["https://username@dev.azure.com/org/project/_git/repo"] = "https://dev.azure.com/org/project/_git/repo", 23 | ["ssh://git@ghe.example.com:2222/org/repo.git"] = "https://ghe.example.com/org/repo", 24 | ["https://ghe.example.com/org/repo.git"] = "https://ghe.example.com/org/repo", 25 | ["git-codecommit.us-east-1.amazonaws.com/v1/repos/MyDemoRepo"] = "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/MyDemoRepo", 26 | ["https://git-codecommit.us-east-1.amazonaws.com/v1/repos/MyDemoRepo"] = "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/MyDemoRepo", 27 | ["ssh://git@source.developers.google.com:2022/p/project/r/repo"] = "https://source.developers.google.com/p/project/r/repo", 28 | ["https://source.developers.google.com/p/project/r/repo"] = "https://source.developers.google.com/p/project/r/repo", 29 | ["git@git.sr.ht:~user/repo"] = "https://git.sr.ht/~user/repo", 30 | ["https://git.sr.ht/~user/repo"] = "https://git.sr.ht/~user/repo", 31 | ["git@git.sr.ht:~user/another-repo"] = "https://git.sr.ht/~user/another-repo", 32 | ["https://git.sr.ht/~user/another-repo"] = "https://git.sr.ht/~user/another-repo", 33 | } 34 | 35 | describe("util.lazygit", function() 36 | for remote, expected in pairs(git_remotes_cases) do 37 | it("should parse git remote " .. remote, function() 38 | local url = gitbrowse.get_repo(remote) 39 | assert.are.equal(expected, url) 40 | end) 41 | end 42 | end) 43 | -------------------------------------------------------------------------------- /docs/init.md: -------------------------------------------------------------------------------- 1 | # 🍿 init 2 | 3 | 4 | 5 | ## ⚙️ Config 6 | 7 | ```lua 8 | ---@class snacks.Config 9 | ---@field animate? snacks.animate.Config 10 | ---@field bigfile? snacks.bigfile.Config 11 | ---@field dashboard? snacks.dashboard.Config 12 | ---@field dim? snacks.dim.Config 13 | ---@field gitbrowse? snacks.gitbrowse.Config 14 | ---@field indent? snacks.indent.Config 15 | ---@field input? snacks.input.Config 16 | ---@field lazygit? snacks.lazygit.Config 17 | ---@field notifier? snacks.notifier.Config 18 | ---@field profiler? snacks.profiler.Config 19 | ---@field quickfile? snacks.quickfile.Config 20 | ---@field scope? snacks.scope.Config 21 | ---@field scratch? snacks.scratch.Config 22 | ---@field scroll? snacks.scroll.Config 23 | ---@field statuscolumn? snacks.statuscolumn.Config 24 | ---@field terminal? snacks.terminal.Config 25 | ---@field toggle? snacks.toggle.Config 26 | ---@field win? snacks.win.Config 27 | ---@field words? snacks.words.Config 28 | ---@field zen? snacks.zen.Config 29 | ---@field styles? table 30 | { 31 | bigfile = { enabled = false }, 32 | dashboard = { enabled = false }, 33 | indent = { enabled = false }, 34 | input = { enabled = false }, 35 | notifier = { enabled = false }, 36 | quickfile = { enabled = false }, 37 | scroll = { enabled = false }, 38 | statuscolumn = { enabled = false }, 39 | styles = {}, 40 | words = { enabled = false }, 41 | } 42 | ``` 43 | 44 | ## 📚 Types 45 | 46 | ```lua 47 | ---@class snacks.Config.base 48 | ---@field example? string 49 | ---@field config? fun(opts: table, defaults: table) 50 | ``` 51 | 52 | ## 📦 Module 53 | 54 | ```lua 55 | ---@class Snacks 56 | ---@field animate snacks.animate 57 | ---@field bigfile snacks.bigfile 58 | ---@field bufdelete snacks.bufdelete 59 | ---@field dashboard snacks.dashboard 60 | ---@field debug snacks.debug 61 | ---@field dim snacks.dim 62 | ---@field git snacks.git 63 | ---@field gitbrowse snacks.gitbrowse 64 | ---@field health snacks.health 65 | ---@field indent snacks.indent 66 | ---@field input snacks.input 67 | ---@field lazygit snacks.lazygit 68 | ---@field meta snacks.meta 69 | ---@field notifier snacks.notifier 70 | ---@field notify snacks.notify 71 | ---@field profiler snacks.profiler 72 | ---@field quickfile snacks.quickfile 73 | ---@field rename snacks.rename 74 | ---@field scope snacks.scope 75 | ---@field scratch snacks.scratch 76 | ---@field scroll snacks.scroll 77 | ---@field statuscolumn snacks.statuscolumn 78 | ---@field terminal snacks.terminal 79 | ---@field toggle snacks.toggle 80 | ---@field util snacks.util 81 | ---@field win snacks.win 82 | ---@field words snacks.words 83 | ---@field zen snacks.zen 84 | Snacks = {} 85 | ``` 86 | 87 | ### `Snacks.config.example()` 88 | 89 | Get an example config from the docs/examples directory. 90 | 91 | ```lua 92 | ---@param snack string 93 | ---@param name string 94 | ---@param opts? table 95 | Snacks.config.example(snack, name, opts) 96 | ``` 97 | 98 | ### `Snacks.config.get()` 99 | 100 | ```lua 101 | ---@generic T: table 102 | ---@param snack string 103 | ---@param defaults T 104 | ---@param ... T[] 105 | ---@return T 106 | Snacks.config.get(snack, defaults, ...) 107 | ``` 108 | 109 | ### `Snacks.config.style()` 110 | 111 | Register a new window style config. 112 | 113 | ```lua 114 | ---@param name string 115 | ---@param defaults snacks.win.Config 116 | Snacks.config.style(name, defaults) 117 | ``` 118 | 119 | ### `Snacks.setup()` 120 | 121 | ```lua 122 | ---@param opts snacks.Config? 123 | Snacks.setup(opts) 124 | ``` 125 | -------------------------------------------------------------------------------- /lua/snacks/bufdelete.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.bufdelete 2 | ---@overload fun(buf?: number|snacks.bufdelete.Opts) 3 | local M = setmetatable({}, { 4 | __call = function(t, ...) 5 | return t.delete(...) 6 | end, 7 | }) 8 | 9 | M.meta = { 10 | desc = "Delete buffers without disrupting window layout", 11 | } 12 | 13 | ---@class snacks.bufdelete.Opts 14 | ---@field buf? number Buffer to delete. Defaults to the current buffer 15 | ---@field force? boolean Delete the buffer even if it is modified 16 | ---@field filter? fun(buf: number): boolean Filter buffers to delete 17 | ---@field wipe? boolean Wipe the buffer instead of deleting it (see `:h :bwipeout`) 18 | 19 | --- Delete a buffer: 20 | --- - either the current buffer if `buf` is not provided 21 | --- - or the buffer `buf` if it is a number 22 | --- - or every buffer for which `buf` returns true if it is a function 23 | ---@param opts? number|snacks.bufdelete.Opts 24 | function M.delete(opts) 25 | opts = opts or {} 26 | opts = type(opts) == "number" and { buf = opts } or opts 27 | opts = type(opts) == "function" and { filter = opts } or opts 28 | ---@cast opts snacks.bufdelete.Opts 29 | 30 | if type(opts.filter) == "function" then 31 | for _, b in ipairs(vim.tbl_filter(opts.filter, vim.api.nvim_list_bufs())) do 32 | if vim.bo[b].buflisted then 33 | M.delete(vim.tbl_extend("force", {}, opts, { buf = b, filter = false })) 34 | end 35 | end 36 | return 37 | end 38 | 39 | local buf = opts.buf or 0 40 | buf = buf == 0 and vim.api.nvim_get_current_buf() or buf 41 | 42 | vim.api.nvim_buf_call(buf, function() 43 | if vim.bo.modified and not opts.force then 44 | local choice = vim.fn.confirm(("Save changes to %q?"):format(vim.fn.bufname()), "&Yes\n&No\n&Cancel") 45 | if choice == 0 or choice == 3 then -- 0 for / and 3 for Cancel 46 | return 47 | end 48 | if choice == 1 then -- Yes 49 | vim.cmd.write() 50 | end 51 | end 52 | 53 | for _, win in ipairs(vim.fn.win_findbuf(buf)) do 54 | vim.api.nvim_win_call(win, function() 55 | if not vim.api.nvim_win_is_valid(win) or vim.api.nvim_win_get_buf(win) ~= buf then 56 | return 57 | end 58 | -- Try using alternate buffer 59 | local alt = vim.fn.bufnr("#") 60 | if alt ~= buf and vim.fn.buflisted(alt) == 1 then 61 | vim.api.nvim_win_set_buf(win, alt) 62 | return 63 | end 64 | 65 | -- Try using previous buffer 66 | local has_previous = pcall(vim.cmd, "bprevious") 67 | if has_previous and buf ~= vim.api.nvim_win_get_buf(win) then 68 | return 69 | end 70 | 71 | -- Create new listed buffer 72 | local new_buf = vim.api.nvim_create_buf(true, false) 73 | vim.api.nvim_win_set_buf(win, new_buf) 74 | end) 75 | end 76 | if vim.api.nvim_buf_is_valid(buf) then 77 | pcall(vim.cmd, (opts.wipe and "bwipeout! " or "bdelete! ") .. buf) 78 | end 79 | end) 80 | end 81 | 82 | --- Delete all buffers 83 | ---@param opts? snacks.bufdelete.Opts 84 | function M.all(opts) 85 | return M.delete(vim.tbl_extend("force", {}, opts or {}, { 86 | filter = function() 87 | return true 88 | end, 89 | })) 90 | end 91 | 92 | --- Delete all buffers except the current one 93 | ---@param opts? snacks.bufdelete.Opts 94 | function M.other(opts) 95 | return M.delete(vim.tbl_extend("force", {}, opts or {}, { 96 | filter = function(b) 97 | return b ~= vim.api.nvim_get_current_buf() 98 | end, 99 | })) 100 | end 101 | 102 | return M 103 | -------------------------------------------------------------------------------- /docs/lazygit.md: -------------------------------------------------------------------------------- 1 | # 🍿 lazygit 2 | 3 | Automatically configures lazygit with a theme generated based on your Neovim colorscheme 4 | and integrate edit with the current neovim instance. 5 | 6 | ![image](https://github.com/user-attachments/assets/5e5ca232-af65-4ebc-b0ca-02bc9c33d23d) 7 | 8 | 9 | 10 | ## 📦 Setup 11 | 12 | ```lua 13 | -- lazy.nvim 14 | { 15 | "folke/snacks.nvim", 16 | opts = { 17 | lazygit = { 18 | -- your lazygit configuration comes here 19 | -- or leave it empty to use the default settings 20 | -- refer to the configuration section below 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | ## ⚙️ Config 27 | 28 | ```lua 29 | ---@class snacks.lazygit.Config: snacks.terminal.Opts 30 | ---@field args? string[] 31 | ---@field theme? snacks.lazygit.Theme 32 | { 33 | -- automatically configure lazygit to use the current colorscheme 34 | -- and integrate edit with the current neovim instance 35 | configure = true, 36 | -- extra configuration for lazygit that will be merged with the default 37 | -- snacks does NOT have a full yaml parser, so if you need `"test"` to appear with the quotes 38 | -- you need to double quote it: `"\"test\""` 39 | config = { 40 | os = { editPreset = "nvim-remote" }, 41 | gui = { 42 | -- set to an empty string "" to disable icons 43 | nerdFontsVersion = "3", 44 | }, 45 | }, 46 | theme_path = vim.fs.normalize(vim.fn.stdpath("cache") .. "/lazygit-theme.yml"), 47 | -- Theme for lazygit 48 | theme = { 49 | [241] = { fg = "Special" }, 50 | activeBorderColor = { fg = "MatchParen", bold = true }, 51 | cherryPickedCommitBgColor = { fg = "Identifier" }, 52 | cherryPickedCommitFgColor = { fg = "Function" }, 53 | defaultFgColor = { fg = "Normal" }, 54 | inactiveBorderColor = { fg = "FloatBorder" }, 55 | optionsTextColor = { fg = "Function" }, 56 | searchingActiveBorderColor = { fg = "MatchParen", bold = true }, 57 | selectedLineBgColor = { bg = "Visual" }, -- set to `default` to have no background colour 58 | unstagedChangesColor = { fg = "DiagnosticError" }, 59 | }, 60 | win = { 61 | style = "lazygit", 62 | }, 63 | } 64 | ``` 65 | 66 | ## 🎨 Styles 67 | 68 | ### `lazygit` 69 | 70 | ```lua 71 | {} 72 | ``` 73 | 74 | ## 📚 Types 75 | 76 | ```lua 77 | ---@alias snacks.lazygit.Color {fg?:string, bg?:string, bold?:boolean} 78 | ``` 79 | 80 | ```lua 81 | ---@class snacks.lazygit.Theme: table 82 | ---@field activeBorderColor snacks.lazygit.Color 83 | ---@field cherryPickedCommitBgColor snacks.lazygit.Color 84 | ---@field cherryPickedCommitFgColor snacks.lazygit.Color 85 | ---@field defaultFgColor snacks.lazygit.Color 86 | ---@field inactiveBorderColor snacks.lazygit.Color 87 | ---@field optionsTextColor snacks.lazygit.Color 88 | ---@field searchingActiveBorderColor snacks.lazygit.Color 89 | ---@field selectedLineBgColor snacks.lazygit.Color 90 | ---@field unstagedChangesColor snacks.lazygit.Color 91 | ``` 92 | 93 | ## 📦 Module 94 | 95 | ### `Snacks.lazygit()` 96 | 97 | ```lua 98 | ---@type fun(opts?: snacks.lazygit.Config): snacks.win 99 | Snacks.lazygit() 100 | ``` 101 | 102 | ### `Snacks.lazygit.log()` 103 | 104 | Opens lazygit with the log view 105 | 106 | ```lua 107 | ---@param opts? snacks.lazygit.Config 108 | Snacks.lazygit.log(opts) 109 | ``` 110 | 111 | ### `Snacks.lazygit.log_file()` 112 | 113 | Opens lazygit with the log of the current file 114 | 115 | ```lua 116 | ---@param opts? snacks.lazygit.Config 117 | Snacks.lazygit.log_file(opts) 118 | ``` 119 | 120 | ### `Snacks.lazygit.open()` 121 | 122 | Opens lazygit, properly configured to use the current colorscheme 123 | and integrate with the current neovim instance 124 | 125 | ```lua 126 | ---@param opts? snacks.lazygit.Config 127 | Snacks.lazygit.open(opts) 128 | ``` 129 | -------------------------------------------------------------------------------- /docs/indent.md: -------------------------------------------------------------------------------- 1 | # 🍿 indent 2 | 3 | Visualize indent guides and scopes based on treesitter or indent. 4 | 5 | Similar plugins: 6 | 7 | - [indent-blankline.nvim](https://github.com/lukas-reineke/indent-blankline.nvim) 8 | - [mini.indentscope](https://github.com/echasnovski/mini.indentscope) 9 | 10 | ![image](https://github.com/user-attachments/assets/56a99495-05ab-488e-9619-574cb7ff2b7d) 11 | 12 | 13 | 14 | ## 📦 Setup 15 | 16 | ```lua 17 | -- lazy.nvim 18 | { 19 | "folke/snacks.nvim", 20 | opts = { 21 | indent = { 22 | -- your indent configuration comes here 23 | -- or leave it empty to use the default settings 24 | -- refer to the configuration section below 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ## ⚙️ Config 31 | 32 | ```lua 33 | ---@class snacks.indent.Config 34 | ---@field enabled? boolean 35 | { 36 | indent = { 37 | enabled = true, -- enable indent guides 38 | char = "│", 39 | blank = " ", 40 | -- blank = "∙", 41 | only_scope = false, -- only show indent guides of the scope 42 | only_current = false, -- only show indent guides in the current window 43 | hl = "SnacksIndent", ---@type string|string[] hl groups for indent guides 44 | -- can be a list of hl groups to cycle through 45 | -- hl = { 46 | -- "SnacksIndent1", 47 | -- "SnacksIndent2", 48 | -- "SnacksIndent3", 49 | -- "SnacksIndent4", 50 | -- "SnacksIndent5", 51 | -- "SnacksIndent6", 52 | -- "SnacksIndent7", 53 | -- "SnacksIndent8", 54 | -- }, 55 | }, 56 | ---@class snacks.indent.Scope.Config: snacks.scope.Config 57 | scope = { 58 | enabled = true, -- enable highlighting the current scope 59 | -- animate scopes. Enabled by default for Neovim >= 0.10 60 | -- Works on older versions but has to trigger redraws during animation. 61 | ---@type snacks.animate.Config|{enabled?: boolean} 62 | animate = { 63 | enabled = vim.fn.has("nvim-0.10") == 1, 64 | easing = "linear", 65 | duration = { 66 | step = 20, -- ms per step 67 | total = 500, -- maximum duration 68 | }, 69 | }, 70 | char = "│", 71 | underline = false, -- underline the start of the scope 72 | only_current = false, -- only show scope in the current window 73 | hl = "SnacksIndentScope", ---@type string|string[] hl group for scopes 74 | }, 75 | chunk = { 76 | -- when enabled, scopes will be rendered as chunks, except for the 77 | -- top-level scope which will be rendered as a scope. 78 | enabled = false, 79 | -- only show chunk scopes in the current window 80 | only_current = false, 81 | hl = "SnacksIndentChunk", ---@type string|string[] hl group for chunk scopes 82 | char = { 83 | corner_top = "┌", 84 | corner_bottom = "└", 85 | -- corner_top = "╭", 86 | -- corner_bottom = "╰", 87 | horizontal = "─", 88 | vertical = "│", 89 | arrow = ">", 90 | }, 91 | }, 92 | blank = { 93 | char = " ", 94 | -- char = "·", 95 | hl = "SnacksIndentBlank", ---@type string|string[] hl group for blank spaces 96 | }, 97 | -- filter for buffers to enable indent guides 98 | filter = function(buf) 99 | return vim.g.snacks_indent ~= false and vim.b[buf].snacks_indent ~= false and vim.bo[buf].buftype == "" 100 | end, 101 | priority = 200, 102 | } 103 | ``` 104 | 105 | ## 📚 Types 106 | 107 | ```lua 108 | ---@class snacks.indent.Scope: snacks.scope.Scope 109 | ---@field win number 110 | ---@field step? number 111 | ``` 112 | 113 | ## 📦 Module 114 | 115 | ### `Snacks.indent.animate()` 116 | 117 | Toggle scope animations 118 | 119 | ```lua 120 | Snacks.indent.animate() 121 | ``` 122 | 123 | ### `Snacks.indent.disable()` 124 | 125 | Disable indent guides 126 | 127 | ```lua 128 | Snacks.indent.disable() 129 | ``` 130 | 131 | ### `Snacks.indent.enable()` 132 | 133 | Enable indent guides 134 | 135 | ```lua 136 | Snacks.indent.enable() 137 | ``` 138 | -------------------------------------------------------------------------------- /doc/snacks-rename.txt: -------------------------------------------------------------------------------- 1 | *snacks-rename.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-rename-table-of-contents* 5 | 6 | 1. Usage |snacks-rename-usage| 7 | 2. mini.files |snacks-rename-mini.files| 8 | 3. neo-tree.nvim |snacks-rename-neo-tree.nvim| 9 | 4. nvim-tree |snacks-rename-nvim-tree| 10 | 5. Module |snacks-rename-module| 11 | - Snacks.rename.on_rename_file()|snacks-rename-module-snacks.rename.on_rename_file()| 12 | - Snacks.rename.rename_file()|snacks-rename-module-snacks.rename.rename_file()| 13 | LSP-integrated file renaming with support for plugins like neo-tree.nvim 14 | and mini.files 15 | . 16 | 17 | 18 | ============================================================================== 19 | 1. Usage *snacks-rename-usage* 20 | 21 | 22 | ============================================================================== 23 | 2. mini.files *snacks-rename-mini.files* 24 | 25 | >lua 26 | vim.api.nvim_create_autocmd("User", { 27 | pattern = "MiniFilesActionRename", 28 | callback = function(event) 29 | Snacks.rename.on_rename_file(event.data.from, event.data.to) 30 | end, 31 | }) 32 | < 33 | 34 | 35 | ============================================================================== 36 | 3. neo-tree.nvim *snacks-rename-neo-tree.nvim* 37 | 38 | >lua 39 | { 40 | "nvim-neo-tree/neo-tree.nvim", 41 | opts = function(_, opts) 42 | local function on_move(data) 43 | Snacks.rename.on_rename_file(data.source, data.destination) 44 | end 45 | local events = require("neo-tree.events") 46 | opts.event_handlers = opts.event_handlers or {} 47 | vim.list_extend(opts.event_handlers, { 48 | { event = events.FILE_MOVED, handler = on_move }, 49 | { event = events.FILE_RENAMED, handler = on_move }, 50 | }) 51 | end, 52 | } 53 | < 54 | 55 | 56 | ============================================================================== 57 | 4. nvim-tree *snacks-rename-nvim-tree* 58 | 59 | >lua 60 | local prev = { new_name = "", old_name = "" } -- Prevents duplicate events 61 | vim.api.nvim_create_autocmd("User", { 62 | pattern = "NvimTreeSetup", 63 | callback = function() 64 | local events = require("nvim-tree.api").events 65 | events.subscribe(events.Event.NodeRenamed, function(data) 66 | if prev.new_name ~= data.new_name or prev.old_name ~= data.old_name then 67 | data = data 68 | Snacks.rename.on_rename_file(data.old_name, data.new_name) 69 | end 70 | end) 71 | end, 72 | }) 73 | < 74 | 75 | 76 | ============================================================================== 77 | 5. Module *snacks-rename-module* 78 | 79 | 80 | `Snacks.rename.on_rename_file()` *Snacks.rename.on_rename_file()* 81 | 82 | Lets LSP clients know that a file has been renamed 83 | 84 | >lua 85 | ---@param from string 86 | ---@param to string 87 | ---@param rename? fun() 88 | Snacks.rename.on_rename_file(from, to, rename) 89 | < 90 | 91 | 92 | `Snacks.rename.rename_file()` *Snacks.rename.rename_file()* 93 | 94 | Prompt for the new filename, do the rename, and trigger LSP handlers 95 | 96 | >lua 97 | Snacks.rename.rename_file() 98 | < 99 | 100 | Generated by panvimdoc 101 | 102 | vim:tw=78:ts=8:noet:ft=help:norl: 103 | -------------------------------------------------------------------------------- /doc/snacks-dim.txt: -------------------------------------------------------------------------------- 1 | *snacks-dim.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-dim-table-of-contents* 5 | 6 | 1. Setup |snacks-dim-setup| 7 | 2. Config |snacks-dim-config| 8 | 3. Module |snacks-dim-module| 9 | - Snacks.dim() |snacks-dim-module-snacks.dim()| 10 | - Snacks.dim.animate() |snacks-dim-module-snacks.dim.animate()| 11 | - Snacks.dim.disable() |snacks-dim-module-snacks.dim.disable()| 12 | - Snacks.dim.enable() |snacks-dim-module-snacks.dim.enable()| 13 | 4. Links |snacks-dim-links| 14 | Focus on the active scope by dimming the rest. 15 | 16 | Similar plugins: 17 | 18 | - twilight.nvim 19 | - limelight.vim 20 | - goyo.vim 21 | 22 | 23 | ============================================================================== 24 | 1. Setup *snacks-dim-setup* 25 | 26 | >lua 27 | -- lazy.nvim 28 | { 29 | "folke/snacks.nvim", 30 | opts = { 31 | dim = { 32 | -- your dim configuration comes here 33 | -- or leave it empty to use the default settings 34 | -- refer to the configuration section below 35 | } 36 | } 37 | } 38 | < 39 | 40 | 41 | ============================================================================== 42 | 2. Config *snacks-dim-config* 43 | 44 | >lua 45 | ---@class snacks.dim.Config 46 | { 47 | ---@type snacks.scope.Config 48 | scope = { 49 | min_size = 5, 50 | max_size = 20, 51 | siblings = true, 52 | }, 53 | -- animate scopes. Enabled by default for Neovim >= 0.10 54 | -- Works on older versions but has to trigger redraws during animation. 55 | ---@type snacks.animate.Config|{enabled?: boolean} 56 | animate = { 57 | enabled = vim.fn.has("nvim-0.10") == 1, 58 | easing = "outQuad", 59 | duration = { 60 | step = 20, -- ms per step 61 | total = 300, -- maximum duration 62 | }, 63 | }, 64 | -- what buffers to dim 65 | filter = function(buf) 66 | return vim.g.snacks_dim ~= false and vim.b[buf].snacks_dim ~= false and vim.bo[buf].buftype == "" 67 | end, 68 | } 69 | < 70 | 71 | 72 | ============================================================================== 73 | 3. Module *snacks-dim-module* 74 | 75 | 76 | `Snacks.dim()` *Snacks.dim()* 77 | 78 | >lua 79 | ---@type fun(opts: snacks.dim.Config) 80 | Snacks.dim() 81 | < 82 | 83 | 84 | `Snacks.dim.animate()` *Snacks.dim.animate()* 85 | 86 | Toggle scope animations 87 | 88 | >lua 89 | Snacks.dim.animate() 90 | < 91 | 92 | 93 | `Snacks.dim.disable()` *Snacks.dim.disable()* 94 | 95 | Disable dimming 96 | 97 | >lua 98 | Snacks.dim.disable() 99 | < 100 | 101 | 102 | `Snacks.dim.enable()` *Snacks.dim.enable()* 103 | 104 | >lua 105 | ---@param opts? snacks.dim.Config 106 | Snacks.dim.enable(opts) 107 | < 108 | 109 | ============================================================================== 110 | 4. Links *snacks-dim-links* 111 | 112 | 1. *image*: https://github.com/user-attachments/assets/c0c5ffda-aaeb-4578-8a18-abee2e443a93 113 | 114 | Generated by panvimdoc 115 | 116 | vim:tw=78:ts=8:noet:ft=help:norl: 117 | -------------------------------------------------------------------------------- /docs/examples/init.lua: -------------------------------------------------------------------------------- 1 | -- stylua: ignore 2 | return { 3 | "folke/snacks.nvim", 4 | priority = 1000, 5 | lazy = false, 6 | ---@type snacks.Config 7 | opts = { 8 | bigfile = { enabled = true }, 9 | dashboard = { enabled = true }, 10 | indent = { enabled = true }, 11 | input = { enabled = true }, 12 | notifier = { 13 | enabled = true, 14 | timeout = 3000, 15 | }, 16 | quickfile = { enabled = true }, 17 | scroll = { enabled = true }, 18 | statuscolumn = { enabled = true }, 19 | words = { enabled = true }, 20 | styles = { 21 | notification = { 22 | -- wo = { wrap = true } -- Wrap notifications 23 | } 24 | } 25 | }, 26 | keys = { 27 | { "z", function() Snacks.zen() end, desc = "Toggle Zen Mode" }, 28 | { "Z", function() Snacks.zen.zoom() end, desc = "Toggle Zoom" }, 29 | { ".", function() Snacks.scratch() end, desc = "Toggle Scratch Buffer" }, 30 | { "S", function() Snacks.scratch.select() end, desc = "Select Scratch Buffer" }, 31 | { "n", function() Snacks.notifier.show_history() end, desc = "Notification History" }, 32 | { "bd", function() Snacks.bufdelete() end, desc = "Delete Buffer" }, 33 | { "cR", function() Snacks.rename.rename_file() end, desc = "Rename File" }, 34 | { "gB", function() Snacks.gitbrowse() end, desc = "Git Browse" }, 35 | { "gb", function() Snacks.git.blame_line() end, desc = "Git Blame Line" }, 36 | { "gf", function() Snacks.lazygit.log_file() end, desc = "Lazygit Current File History" }, 37 | { "gg", function() Snacks.lazygit() end, desc = "Lazygit" }, 38 | { "gl", function() Snacks.lazygit.log() end, desc = "Lazygit Log (cwd)" }, 39 | { "un", function() Snacks.notifier.hide() end, desc = "Dismiss All Notifications" }, 40 | { "", function() Snacks.terminal() end, desc = "Toggle Terminal" }, 41 | { "", function() Snacks.terminal() end, desc = "which_key_ignore" }, 42 | { "]]", function() Snacks.words.jump(vim.v.count1) end, desc = "Next Reference", mode = { "n", "t" } }, 43 | { "[[", function() Snacks.words.jump(-vim.v.count1) end, desc = "Prev Reference", mode = { "n", "t" } }, 44 | { 45 | "N", 46 | desc = "Neovim News", 47 | function() 48 | Snacks.win({ 49 | file = vim.api.nvim_get_runtime_file("doc/news.txt", false)[1], 50 | width = 0.6, 51 | height = 0.6, 52 | wo = { 53 | spell = false, 54 | wrap = false, 55 | signcolumn = "yes", 56 | statuscolumn = " ", 57 | conceallevel = 3, 58 | }, 59 | }) 60 | end, 61 | } 62 | }, 63 | init = function() 64 | vim.api.nvim_create_autocmd("User", { 65 | pattern = "VeryLazy", 66 | callback = function() 67 | -- Setup some globals for debugging (lazy-loaded) 68 | _G.dd = function(...) 69 | Snacks.debug.inspect(...) 70 | end 71 | _G.bt = function() 72 | Snacks.debug.backtrace() 73 | end 74 | vim.print = _G.dd -- Override print to use snacks for `:=` command 75 | 76 | -- Create some toggle mappings 77 | Snacks.toggle.option("spell", { name = "Spelling" }):map("us") 78 | Snacks.toggle.option("wrap", { name = "Wrap" }):map("uw") 79 | Snacks.toggle.option("relativenumber", { name = "Relative Number" }):map("uL") 80 | Snacks.toggle.diagnostics():map("ud") 81 | Snacks.toggle.line_number():map("ul") 82 | Snacks.toggle.option("conceallevel", { off = 0, on = vim.o.conceallevel > 0 and vim.o.conceallevel or 2 }):map("uc") 83 | Snacks.toggle.treesitter():map("uT") 84 | Snacks.toggle.option("background", { off = "light", on = "dark", name = "Dark Background" }):map("ub") 85 | Snacks.toggle.inlay_hints():map("uh") 86 | Snacks.toggle.indent():map("ug") 87 | Snacks.toggle.dim():map("uD") 88 | end, 89 | }) 90 | end, 91 | } 92 | -------------------------------------------------------------------------------- /doc/snacks-scope.txt: -------------------------------------------------------------------------------- 1 | *snacks-scope.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-scope-table-of-contents* 5 | 6 | 1. Setup |snacks-scope-setup| 7 | 2. Config |snacks-scope-config| 8 | 3. Types |snacks-scope-types| 9 | 4. Module |snacks-scope-module| 10 | - Snacks.scope.attach() |snacks-scope-module-snacks.scope.attach()| 11 | - Snacks.scope.get() |snacks-scope-module-snacks.scope.get()| 12 | Scope detection based on treesitter or indent. 13 | 14 | The indent-based algorithm is similar to what is used in mini.indentscope 15 | . 16 | 17 | 18 | ============================================================================== 19 | 1. Setup *snacks-scope-setup* 20 | 21 | >lua 22 | -- lazy.nvim 23 | { 24 | "folke/snacks.nvim", 25 | opts = { 26 | scope = { 27 | -- your scope configuration comes here 28 | -- or leave it empty to use the default settings 29 | -- refer to the configuration section below 30 | } 31 | } 32 | } 33 | < 34 | 35 | 36 | ============================================================================== 37 | 2. Config *snacks-scope-config* 38 | 39 | >lua 40 | ---@class snacks.scope.Config 41 | ---@field max_size? number 42 | { 43 | -- absolute minimum size of the scope. 44 | -- can be less if the scope is a top-level single line scope 45 | min_size = 2, 46 | -- try to expand the scope to this size 47 | max_size = nil, 48 | siblings = false, -- expand single line scopes with single line siblings 49 | -- what buffers to attach to 50 | filter = function(buf) 51 | return vim.bo[buf].buftype == "" 52 | end, 53 | -- debounce scope detection in ms 54 | debounce = 30, 55 | treesitter = { 56 | -- detect scope based on treesitter. 57 | -- falls back to indent based detection if not available 58 | enabled = true, 59 | ---@type string[]|false 60 | blocks = { 61 | "function_declaration", 62 | "function_definition", 63 | "method_declaration", 64 | "method_definition", 65 | "class_declaration", 66 | "class_definition", 67 | "do_statement", 68 | "while_statement", 69 | "repeat_statement", 70 | "if_statement", 71 | "for_statement", 72 | }, 73 | }, 74 | } 75 | < 76 | 77 | 78 | ============================================================================== 79 | 3. Types *snacks-scope-types* 80 | 81 | >lua 82 | ---@class snacks.scope.Opts: snacks.scope.Config 83 | ---@field buf number 84 | ---@field pos {[1]:number, [2]:number} -- (1,0) indexed 85 | < 86 | 87 | >lua 88 | ---@alias snacks.scope.Attach.cb fun(win: number, buf: number, scope:snacks.scope.Scope?, prev:snacks.scope.Scope?) 89 | < 90 | 91 | >lua 92 | ---@alias snacks.scope.scope {buf: number, from: number, to: number, indent?: number} 93 | < 94 | 95 | 96 | ============================================================================== 97 | 4. Module *snacks-scope-module* 98 | 99 | 100 | `Snacks.scope.attach()` *Snacks.scope.attach()* 101 | 102 | Attach a scope listener 103 | 104 | >lua 105 | ---@param cb snacks.scope.Attach.cb 106 | ---@param opts? snacks.scope.Config 107 | ---@return snacks.scope.Listener 108 | Snacks.scope.attach(cb, opts) 109 | < 110 | 111 | 112 | `Snacks.scope.get()` *Snacks.scope.get()* 113 | 114 | >lua 115 | ---@param opts? snacks.scope.Opts 116 | ---@return snacks.scope.Scope? 117 | Snacks.scope.get(opts) 118 | < 119 | 120 | Generated by panvimdoc 121 | 122 | vim:tw=78:ts=8:noet:ft=help:norl: 123 | -------------------------------------------------------------------------------- /lua/snacks/dim.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.dim 2 | ---@overload fun(opts: snacks.dim.Config) 3 | local M = setmetatable({}, { 4 | __call = function(M, ...) 5 | return M.enable(...) 6 | end, 7 | }) 8 | 9 | M.meta = { 10 | desc = "Focus on the active scope by dimming the rest", 11 | } 12 | 13 | ---@class snacks.dim.Config 14 | local defaults = { 15 | ---@type snacks.scope.Config 16 | scope = { 17 | min_size = 5, 18 | max_size = 20, 19 | siblings = true, 20 | }, 21 | -- animate scopes. Enabled by default for Neovim >= 0.10 22 | -- Works on older versions but has to trigger redraws during animation. 23 | ---@type snacks.animate.Config|{enabled?: boolean} 24 | animate = { 25 | enabled = vim.fn.has("nvim-0.10") == 1, 26 | easing = "outQuad", 27 | duration = { 28 | step = 20, -- ms per step 29 | total = 300, -- maximum duration 30 | }, 31 | }, 32 | -- what buffers to dim 33 | filter = function(buf) 34 | return vim.g.snacks_dim ~= false and vim.b[buf].snacks_dim ~= false and vim.bo[buf].buftype == "" 35 | end, 36 | } 37 | 38 | M.enabled = false 39 | local ns = vim.api.nvim_create_namespace("snacks_dim") 40 | local scopes ---@type snacks.scope.Listener? 41 | local scopes_anim = {} ---@type table 42 | 43 | Snacks.util.set_hl({ 44 | [""] = "DiagnosticUnnecessary", 45 | }, { prefix = "SnacksDim", default = true }) 46 | 47 | --- Called during every redraw cycle, so it should be fast. 48 | --- Everything that can be cached should be cached. 49 | ---@param win number 50 | ---@param buf number 51 | ---@param top number -- 1-indexed 52 | ---@param bottom number -- 1-indexed 53 | ---@private 54 | function M.on_win(win, buf, top, bottom) 55 | local scope = scopes and scopes:get(win) 56 | if not scope then 57 | return 58 | end 59 | local function add(l) 60 | vim.api.nvim_buf_set_extmark(buf, ns, l - 1, 0, { 61 | end_row = l, 62 | end_col = 0, 63 | hl_group = "SnacksDim", 64 | ephemeral = true, 65 | }) 66 | end 67 | local from = M.animating and scopes_anim[win] and scopes_anim[win].from or scope.from 68 | local to = M.animating and scopes_anim[win] and scopes_anim[win].to or scope.to 69 | for l = top, math.min(from - 1, bottom) do 70 | add(l) 71 | end 72 | for l = math.max(to + 1, top), bottom do 73 | add(l) 74 | end 75 | end 76 | 77 | ---@param opts? snacks.dim.Config 78 | function M.enable(opts) 79 | if M.enabled then 80 | return 81 | end 82 | opts = Snacks.config.get("dim", defaults, opts) 83 | 84 | M.enabled = true 85 | 86 | if opts.animate then 87 | M.animating = true 88 | end 89 | 90 | -- setup decoration provider 91 | vim.api.nvim_set_decoration_provider(ns, { 92 | on_win = function(_, win, buf, top, bottom) 93 | if M.enabled and opts.filter(buf) then 94 | M.on_win(win, buf, top + 1, bottom + 1) 95 | end 96 | end, 97 | }) 98 | 99 | scopes = scopes 100 | or Snacks.scope.attach(function(win, buf, scope, prev) 101 | if not M.animating then 102 | Snacks.util.redraw(win) 103 | else 104 | if not (scopes_anim[win] and scopes_anim[win].buf == buf) then 105 | local info = vim.fn.getwininfo(win)[1] 106 | scopes_anim[win] = { 107 | from = info.topline, 108 | to = info.botline, 109 | buf = buf, 110 | } 111 | end 112 | 113 | Snacks.animate(scopes_anim[win].from, scope.from, function(v) 114 | scopes_anim[win].from = v 115 | Snacks.util.redraw(win) 116 | end, vim.tbl_extend("keep", { int = true, id = "snacks_dim_from_" .. win }, opts.animate)) 117 | 118 | Snacks.animate(scopes_anim[win].to, scope.to, function(v) 119 | scopes_anim[win].to = v 120 | Snacks.util.redraw(win) 121 | end, vim.tbl_extend("keep", { int = true, id = "snacks_dim_to_" .. win }, opts.animate)) 122 | end 123 | end, opts.scope) 124 | if not scopes.enabled then 125 | scopes:enable() 126 | end 127 | end 128 | 129 | -- Disable dimming 130 | function M.disable() 131 | if not M.enabled then 132 | return 133 | end 134 | M.enabled = false 135 | if scopes and scopes.enabled then 136 | scopes:disable() 137 | end 138 | scopes_anim = {} 139 | vim.cmd([[redraw!]]) 140 | end 141 | 142 | -- Toggle scope animations 143 | function M.animate() 144 | M.animating = not M.animating 145 | end 146 | 147 | return M 148 | -------------------------------------------------------------------------------- /lua/snacks/words.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.words 2 | local M = {} 3 | 4 | M.meta = { 5 | desc = "Auto-show LSP references and quickly navigate between them", 6 | needs_setup = true, 7 | } 8 | 9 | ---@private 10 | ---@alias LspWord {from:{[1]:number, [2]:number}, to:{[1]:number, [2]:number}} 1-0 indexed 11 | 12 | ---@class snacks.words.Config 13 | ---@field enabled? boolean 14 | local defaults = { 15 | debounce = 200, -- time in ms to wait before updating 16 | notify_jump = false, -- show a notification when jumping 17 | notify_end = true, -- show a notification when reaching the end 18 | foldopen = true, -- open folds after jumping 19 | jumplist = true, -- set jump point before jumping 20 | modes = { "n", "i", "c" }, -- modes to show references 21 | } 22 | 23 | M.enabled = false 24 | 25 | local config = Snacks.config.get("words", defaults) 26 | local ns = vim.api.nvim_create_namespace("vim_lsp_references") 27 | local timer = (vim.uv or vim.loop).new_timer() 28 | 29 | function M.enable() 30 | if M.enabled then 31 | return 32 | end 33 | M.enabled = true 34 | local group = vim.api.nvim_create_augroup("snacks_words", { clear = true }) 35 | 36 | vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "ModeChanged" }, { 37 | group = group, 38 | callback = function() 39 | if not M.is_enabled() then 40 | M.clear() 41 | return 42 | end 43 | if not ({ M.get() })[2] then 44 | M.update() 45 | end 46 | end, 47 | }) 48 | end 49 | 50 | function M.disable() 51 | if not M.enabled then 52 | return 53 | end 54 | M.enabled = false 55 | vim.api.nvim_del_augroup_by_name("snacks_words") 56 | for _, buf in ipairs(vim.api.nvim_list_bufs()) do 57 | vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1) 58 | end 59 | end 60 | 61 | function M.clear() 62 | vim.lsp.buf.clear_references() 63 | end 64 | 65 | ---@private 66 | function M.update() 67 | local buf = vim.api.nvim_get_current_buf() 68 | timer:start(config.debounce, 0, function() 69 | vim.schedule(function() 70 | if vim.api.nvim_buf_is_valid(buf) then 71 | vim.api.nvim_buf_call(buf, function() 72 | if not M.is_enabled() then 73 | return 74 | end 75 | vim.lsp.buf.document_highlight() 76 | M.clear() 77 | end) 78 | end 79 | end) 80 | end) 81 | end 82 | 83 | ---@param buf number? 84 | function M.is_enabled(buf) 85 | buf = buf or vim.api.nvim_get_current_buf() 86 | local mode = vim.api.nvim_get_mode().mode:lower() 87 | mode = mode:gsub("\22", "v"):gsub("\19", "s") 88 | mode = mode:sub(1, 2) == "no" and "o" or mode 89 | mode = mode:sub(1, 1):match("[ncitsvo]") or "n" 90 | local clients = (vim.lsp.get_clients or vim.lsp.get_active_clients)({ bufnr = buf }) 91 | clients = vim.tbl_filter(function(client) 92 | return client.supports_method("textDocument/documentHighlight", { bufnr = buf }) 93 | end, clients) 94 | return M.enabled and vim.tbl_contains(config.modes, mode) and #clients > 0 95 | end 96 | 97 | ---@private 98 | ---@return LspWord[] words, number? current 99 | function M.get() 100 | local cursor = vim.api.nvim_win_get_cursor(0) 101 | local current, ret = nil, {} ---@type number?, LspWord[] 102 | for _, extmark in ipairs(vim.api.nvim_buf_get_extmarks(0, ns, 0, -1, { details = true })) do 103 | local w = { 104 | from = { extmark[2] + 1, extmark[3] }, 105 | to = { extmark[4].end_row + 1, extmark[4].end_col }, 106 | } 107 | ret[#ret + 1] = w 108 | if cursor[1] >= w.from[1] and cursor[1] <= w.to[1] and cursor[2] >= w.from[2] and cursor[2] <= w.to[2] then 109 | current = #ret 110 | end 111 | end 112 | return ret, current 113 | end 114 | 115 | ---@param count number 116 | ---@param cycle? boolean 117 | function M.jump(count, cycle) 118 | local words, idx = M.get() 119 | if not idx then 120 | return 121 | end 122 | idx = idx + count 123 | if cycle then 124 | idx = (idx - 1) % #words + 1 125 | end 126 | local target = words[idx] 127 | if target then 128 | if config.jumplist then 129 | vim.cmd.normal({ "m`", bang = true }) 130 | end 131 | vim.api.nvim_win_set_cursor(0, target.from) 132 | if config.notify_jump then 133 | Snacks.notify.info(("Reference [%d/%d]"):format(idx, #words), { id = "snacks.words.jump", title = "Words" }) 134 | end 135 | if config.foldopen then 136 | vim.cmd.normal({ "zv", bang = true }) 137 | end 138 | elseif config.notify_end then 139 | Snacks.notify.warn("No more references", { id = "snacks.words.jump", title = "Words" }) 140 | end 141 | end 142 | 143 | return M 144 | -------------------------------------------------------------------------------- /docs/scratch.md: -------------------------------------------------------------------------------- 1 | # 🍿 scratch 2 | 3 | Quickly open scratch buffers for testing code, creating notes or 4 | just messing around. Scratch buffers are organized by using context 5 | like your working directory, Git branch and `vim.v.count1`. 6 | 7 | It supports templates, custom keymaps, and auto-saves when you hide the buffer. 8 | 9 | In lua buffers, pressing `` will execute the buffer / selection with 10 | `Snacks.debug.run()` that will show print output inline and show errors as diagnostics. 11 | 12 | ![image](https://github.com/user-attachments/assets/52ac7c1a-908f-4d1d-97a2-ad4642f8dc36) 13 | 14 | ![image](https://github.com/user-attachments/assets/d3e766e9-e64a-4c22-85b4-3d965f645b59) 15 | 16 | ## 🚀 Usage 17 | 18 | Suggested config: 19 | 20 | ```lua 21 | { 22 | "folke/snacks.nvim", 23 | keys = { 24 | { ".", function() Snacks.scratch() end, desc = "Toggle Scratch Buffer" }, 25 | { "S", function() Snacks.scratch.select() end, desc = "Select Scratch Buffer" }, 26 | } 27 | } 28 | ``` 29 | 30 | 31 | 32 | ## 📦 Setup 33 | 34 | ```lua 35 | -- lazy.nvim 36 | { 37 | "folke/snacks.nvim", 38 | opts = { 39 | scratch = { 40 | -- your scratch configuration comes here 41 | -- or leave it empty to use the default settings 42 | -- refer to the configuration section below 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ## ⚙️ Config 49 | 50 | ```lua 51 | ---@class snacks.scratch.Config 52 | ---@field win? snacks.win.Config scratch window 53 | ---@field template? string template for new buffers 54 | ---@field file? string scratch file path. You probably don't need to set this. 55 | ---@field ft? string|fun():string the filetype of the scratch buffer 56 | { 57 | name = "Scratch", 58 | ft = function() 59 | if vim.bo.buftype == "" and vim.bo.filetype ~= "" then 60 | return vim.bo.filetype 61 | end 62 | return "markdown" 63 | end, 64 | ---@type string|string[]? 65 | icon = nil, -- `icon|{icon, icon_hl}`. defaults to the filetype icon 66 | root = vim.fn.stdpath("data") .. "/scratch", 67 | autowrite = true, -- automatically write when the buffer is hidden 68 | -- unique key for the scratch file is based on: 69 | -- * name 70 | -- * ft 71 | -- * vim.v.count1 (useful for keymaps) 72 | -- * cwd (optional) 73 | -- * branch (optional) 74 | filekey = { 75 | cwd = true, -- use current working directory 76 | branch = true, -- use current branch name 77 | count = true, -- use vim.v.count1 78 | }, 79 | win = { style = "scratch" }, 80 | ---@type table 81 | win_by_ft = { 82 | lua = { 83 | keys = { 84 | ["source"] = { 85 | "", 86 | function(self) 87 | local name = "scratch." .. vim.fn.fnamemodify(vim.api.nvim_buf_get_name(self.buf), ":e") 88 | Snacks.debug.run({ buf = self.buf, name = name }) 89 | end, 90 | desc = "Source buffer", 91 | mode = { "n", "x" }, 92 | }, 93 | }, 94 | }, 95 | }, 96 | } 97 | ``` 98 | 99 | ## 🎨 Styles 100 | 101 | ### `scratch` 102 | 103 | ```lua 104 | { 105 | width = 100, 106 | height = 30, 107 | bo = { buftype = "", buflisted = false, bufhidden = "hide", swapfile = false }, 108 | minimal = false, 109 | noautocmd = false, 110 | -- position = "right", 111 | zindex = 20, 112 | wo = { winhighlight = "NormalFloat:Normal" }, 113 | border = "rounded", 114 | title_pos = "center", 115 | footer_pos = "center", 116 | } 117 | ``` 118 | 119 | ## 📚 Types 120 | 121 | ```lua 122 | ---@class snacks.scratch.File 123 | ---@field file string full path to the scratch buffer 124 | ---@field stat uv.fs_stat.result File stat result 125 | ---@field name string name of the scratch buffer 126 | ---@field ft string file type 127 | ---@field icon? string icon for the file type 128 | ---@field cwd? string current working directory 129 | ---@field branch? string Git branch 130 | ---@field count? number vim.v.count1 used to open the buffer 131 | ``` 132 | 133 | ## 📦 Module 134 | 135 | ### `Snacks.scratch()` 136 | 137 | ```lua 138 | ---@type fun(opts?: snacks.scratch.Config): snacks.win 139 | Snacks.scratch() 140 | ``` 141 | 142 | ### `Snacks.scratch.list()` 143 | 144 | Return a list of scratch buffers sorted by mtime. 145 | 146 | ```lua 147 | ---@return snacks.scratch.File[] 148 | Snacks.scratch.list() 149 | ``` 150 | 151 | ### `Snacks.scratch.open()` 152 | 153 | Open a scratch buffer with the given options. 154 | If a window is already open with the same buffer, 155 | it will be closed instead. 156 | 157 | ```lua 158 | ---@param opts? snacks.scratch.Config 159 | Snacks.scratch.open(opts) 160 | ``` 161 | 162 | ### `Snacks.scratch.select()` 163 | 164 | Select a scratch buffer from a list of scratch buffers. 165 | 166 | ```lua 167 | Snacks.scratch.select() 168 | ``` 169 | -------------------------------------------------------------------------------- /lua/snacks/init.lua: -------------------------------------------------------------------------------- 1 | ---@class Snacks: snacks.plugins 2 | local M = {} 3 | 4 | setmetatable(M, { 5 | __index = function(t, k) 6 | ---@diagnostic disable-next-line: no-unknown 7 | t[k] = require("snacks." .. k) 8 | return rawget(t, k) 9 | end, 10 | }) 11 | 12 | _G.Snacks = M 13 | 14 | ---@class snacks.Config.base 15 | ---@field example? string 16 | ---@field config? fun(opts: table, defaults: table) 17 | 18 | ---@class snacks.Config: snacks.plugins.Config 19 | ---@field styles? table 20 | local config = { 21 | bigfile = { enabled = false }, 22 | dashboard = { enabled = false }, 23 | indent = { enabled = false }, 24 | input = { enabled = false }, 25 | notifier = { enabled = false }, 26 | quickfile = { enabled = false }, 27 | scroll = { enabled = false }, 28 | statuscolumn = { enabled = false }, 29 | styles = {}, 30 | words = { enabled = false }, 31 | } 32 | 33 | ---@class snacks.config: snacks.Config 34 | M.config = setmetatable({}, { 35 | __index = function(_, k) 36 | return config[k] 37 | end, 38 | }) 39 | 40 | --- Get an example config from the docs/examples directory. 41 | ---@param snack string 42 | ---@param name string 43 | ---@param opts? table 44 | function M.config.example(snack, name, opts) 45 | local path = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":h:h:h") .. "/docs/examples/" .. snack .. ".lua" 46 | local ok, ret = pcall(function() 47 | return loadfile(path)().examples[name] or error(("`%s` not found"):format(name)) 48 | end) 49 | if not ok then 50 | M.notify.error(("Failed to load `%s.%s`:\n%s"):format(snack, name, ret)) 51 | end 52 | return ok and vim.tbl_deep_extend("force", {}, vim.deepcopy(ret), opts or {}) or {} 53 | end 54 | 55 | ---@generic T: table 56 | ---@param snack string 57 | ---@param defaults T 58 | ---@param ... T[] 59 | ---@return T 60 | function M.config.get(snack, defaults, ...) 61 | local merge, todo = {}, { defaults, config[snack], ... } 62 | for i = 1, select("#", ...) + 2 do 63 | local v = todo[i] --[[@as snacks.Config.base]] 64 | if type(v) == "table" then 65 | if v.example then 66 | table.insert(merge, vim.deepcopy(M.config.example(snack, v.example))) 67 | v.example = nil 68 | end 69 | table.insert(merge, vim.deepcopy(v)) 70 | end 71 | end 72 | local ret = #merge == 1 and merge[1] or vim.tbl_deep_extend("force", unpack(merge)) --[[@as snacks.Config.base]] 73 | if type(ret.config) == "function" then 74 | ret.config(ret, defaults) 75 | end 76 | return ret 77 | end 78 | 79 | --- Register a new window style config. 80 | ---@param name string 81 | ---@param defaults snacks.win.Config 82 | function M.config.style(name, defaults) 83 | config.styles[name] = vim.tbl_deep_extend("force", vim.deepcopy(defaults), config.styles[name] or {}) 84 | end 85 | 86 | M.did_setup = false 87 | M.did_setup_after_vim_enter = false 88 | 89 | ---@param opts snacks.Config? 90 | function M.setup(opts) 91 | if M.did_setup then 92 | return vim.notify("snacks.nvim is already setup", vim.log.levels.ERROR, { title = "snacks.nvim" }) 93 | end 94 | M.did_setup = true 95 | 96 | if vim.fn.has("nvim-0.9.4") ~= 1 then 97 | return vim.notify("snacks.nvim requires Neovim >= 0.9.4", vim.log.levels.ERROR, { title = "snacks.nvim" }) 98 | end 99 | 100 | -- enable all by default when config is passed 101 | opts = opts or {} 102 | for k in pairs(opts) do 103 | opts[k].enabled = opts[k].enabled == nil or opts[k].enabled 104 | end 105 | config = vim.tbl_deep_extend("force", config, opts or {}) 106 | 107 | local events = { 108 | BufReadPre = { "bigfile" }, 109 | BufReadPost = { "quickfile", "indent" }, 110 | LspAttach = { "words" }, 111 | UIEnter = { "dashboard", "scroll", "input" }, 112 | } 113 | 114 | local function load(event) 115 | for _, snack in ipairs(events[event] or {}) do 116 | if M.config[snack] and M.config[snack].enabled then 117 | (M[snack].setup or M[snack].enable)() 118 | end 119 | end 120 | events[event] = nil 121 | end 122 | 123 | if vim.v.vim_did_enter == 1 then 124 | M.did_setup_after_vim_enter = true 125 | load("UIEnter") 126 | end 127 | 128 | vim.api.nvim_create_autocmd(vim.tbl_keys(events), { 129 | group = vim.api.nvim_create_augroup("snacks", { clear = true }), 130 | once = true, 131 | nested = true, 132 | callback = function(ev) 133 | load(ev.event) 134 | end, 135 | }) 136 | 137 | if M.config.statuscolumn.enabled then 138 | vim.o.statuscolumn = [[%!v:lua.require'snacks.statuscolumn'.get()]] 139 | end 140 | 141 | if M.config.notifier.enabled then 142 | vim.notify = function(msg, level, o) 143 | vim.notify = Snacks.notifier.notify 144 | return Snacks.notifier.notify(msg, level, o) 145 | end 146 | end 147 | end 148 | 149 | return M 150 | -------------------------------------------------------------------------------- /docs/terminal.md: -------------------------------------------------------------------------------- 1 | # 🍿 terminal 2 | 3 | Create and toggle terminal windows. 4 | 5 | Based on the provided options, some defaults will be set: 6 | 7 | - if no `cmd` is provided, the window will be opened in a bottom split 8 | - if `cmd` is provided, the window will be opened in a floating window 9 | - for splits, a `winbar` will be added with the terminal title 10 | 11 | ![image](https://github.com/user-attachments/assets/afcc9989-57d7-4518-a390-cc7d6f0cec13) 12 | 13 | ## 🚀 Usage 14 | 15 | ### Edgy Integration 16 | 17 | ```lua 18 | { 19 | "folke/edgy.nvim", 20 | ---@module 'edgy' 21 | ---@param opts Edgy.Config 22 | opts = function(_, opts) 23 | for _, pos in ipairs({ "top", "bottom", "left", "right" }) do 24 | opts[pos] = opts[pos] or {} 25 | table.insert(opts[pos], { 26 | ft = "snacks_terminal", 27 | size = { height = 0.4 }, 28 | title = "%{b:snacks_terminal.id}: %{b:term_title}", 29 | filter = function(_buf, win) 30 | return vim.w[win].snacks_win 31 | and vim.w[win].snacks_win.position == pos 32 | and vim.w[win].snacks_win.relative == "editor" 33 | and not vim.w[win].trouble_preview 34 | end, 35 | }) 36 | end 37 | end, 38 | } 39 | ``` 40 | 41 | 42 | 43 | ## 📦 Setup 44 | 45 | ```lua 46 | -- lazy.nvim 47 | { 48 | "folke/snacks.nvim", 49 | opts = { 50 | terminal = { 51 | -- your terminal configuration comes here 52 | -- or leave it empty to use the default settings 53 | -- refer to the configuration section below 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | ## ⚙️ Config 60 | 61 | ```lua 62 | ---@class snacks.terminal.Config 63 | ---@field win? snacks.win.Config 64 | ---@field override? fun(cmd?: string|string[], opts?: snacks.terminal.Opts) Use this to use a different terminal implementation 65 | { 66 | win = { style = "terminal" }, 67 | } 68 | ``` 69 | 70 | ## 🎨 Styles 71 | 72 | ### `terminal` 73 | 74 | ```lua 75 | { 76 | bo = { 77 | filetype = "snacks_terminal", 78 | }, 79 | wo = {}, 80 | keys = { 81 | q = "hide", 82 | gf = function(self) 83 | local f = vim.fn.findfile(vim.fn.expand(""), "**") 84 | if f == "" then 85 | Snacks.notify.warn("No file under cursor") 86 | else 87 | self:hide() 88 | vim.schedule(function() 89 | vim.cmd("e " .. f) 90 | end) 91 | end 92 | end, 93 | term_normal = { 94 | "", 95 | function(self) 96 | self.esc_timer = self.esc_timer or (vim.uv or vim.loop).new_timer() 97 | if self.esc_timer:is_active() then 98 | self.esc_timer:stop() 99 | vim.cmd("stopinsert") 100 | else 101 | self.esc_timer:start(200, 0, function() end) 102 | return "" 103 | end 104 | end, 105 | mode = "t", 106 | expr = true, 107 | desc = "Double escape to normal mode", 108 | }, 109 | }, 110 | } 111 | ``` 112 | 113 | ## 📚 Types 114 | 115 | ```lua 116 | ---@class snacks.terminal.Opts: snacks.terminal.Config 117 | ---@field cwd? string 118 | ---@field env? table 119 | ---@field interactive? boolean 120 | ``` 121 | 122 | ## 📦 Module 123 | 124 | ```lua 125 | ---@class snacks.terminal: snacks.win 126 | ---@field cmd? string | string[] 127 | ---@field opts snacks.terminal.Opts 128 | Snacks.terminal = {} 129 | ``` 130 | 131 | ### `Snacks.terminal()` 132 | 133 | ```lua 134 | ---@type fun(cmd?: string|string[], opts?: snacks.terminal.Opts): snacks.terminal 135 | Snacks.terminal() 136 | ``` 137 | 138 | ### `Snacks.terminal.colorize()` 139 | 140 | Colorize the current buffer. 141 | Replaces ansii color codes with the actual colors. 142 | 143 | Example: 144 | 145 | ```sh 146 | ls -la --color=always | nvim - -c "lua Snacks.terminal.colorize()" 147 | ``` 148 | 149 | ```lua 150 | Snacks.terminal.colorize() 151 | ``` 152 | 153 | ### `Snacks.terminal.get()` 154 | 155 | Get or create a terminal window. 156 | The terminal id is based on the `cmd`, `cwd`, `env` and `vim.v.count1` options. 157 | `opts.create` defaults to `true`. 158 | 159 | ```lua 160 | ---@param cmd? string | string[] 161 | ---@param opts? snacks.terminal.Opts| {create?: boolean} 162 | ---@return snacks.win? terminal, boolean? created 163 | Snacks.terminal.get(cmd, opts) 164 | ``` 165 | 166 | ### `Snacks.terminal.open()` 167 | 168 | Open a new terminal window. 169 | 170 | ```lua 171 | ---@param cmd? string | string[] 172 | ---@param opts? snacks.terminal.Opts 173 | Snacks.terminal.open(cmd, opts) 174 | ``` 175 | 176 | ### `Snacks.terminal.toggle()` 177 | 178 | Toggle a terminal window. 179 | The terminal id is based on the `cmd`, `cwd`, `env` and `vim.v.count1` options. 180 | 181 | ```lua 182 | ---@param cmd? string | string[] 183 | ---@param opts? snacks.terminal.Opts 184 | Snacks.terminal.toggle(cmd, opts) 185 | ``` 186 | -------------------------------------------------------------------------------- /doc/snacks-animate.txt: -------------------------------------------------------------------------------- 1 | *snacks-animate.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-animate-table-of-contents* 5 | 6 | 1. Setup |snacks-animate-setup| 7 | 2. Config |snacks-animate-config| 8 | 3. Types |snacks-animate-types| 9 | 4. Module |snacks-animate-module| 10 | - Snacks.animate() |snacks-animate-module-snacks.animate()| 11 | - Snacks.animate.add() |snacks-animate-module-snacks.animate.add()| 12 | - Snacks.animate.del() |snacks-animate-module-snacks.animate.del()| 13 | Efficient animation library including over 45 easing functions: 14 | 15 | - Emmanuel Oga’s easing functions 16 | - Easing functions overview 17 | 18 | There’s at any given time at most one timer running, that takes care of all 19 | active animations, controlled by the `fps` setting. 20 | 21 | 22 | ============================================================================== 23 | 1. Setup *snacks-animate-setup* 24 | 25 | >lua 26 | -- lazy.nvim 27 | { 28 | "folke/snacks.nvim", 29 | opts = { 30 | animate = { 31 | -- your animate configuration comes here 32 | -- or leave it empty to use the default settings 33 | -- refer to the configuration section below 34 | } 35 | } 36 | } 37 | < 38 | 39 | 40 | ============================================================================== 41 | 2. Config *snacks-animate-config* 42 | 43 | >lua 44 | ---@class snacks.animate.Config 45 | ---@field easing? snacks.animate.easing|snacks.animate.easing.Fn 46 | { 47 | ---@type snacks.animate.Duration|number 48 | duration = 20, -- ms per step 49 | easing = "linear", 50 | fps = 60, -- frames per second. Global setting for all animations 51 | } 52 | < 53 | 54 | 55 | ============================================================================== 56 | 3. Types *snacks-animate-types* 57 | 58 | All easing functions take these parameters: 59 | 60 | - `t` _(time)_should go from 0 to duration 61 | - `b` _(begin)_value of the property being ease. 62 | - `c` _(change)_ending value of the property - beginning value of the property 63 | - `d` _(duration)_total duration of the animation 64 | 65 | Some functions allow additional modifiers, like the elastic functions which 66 | also can receive an amplitud and a period parameters (defaults are included) 67 | 68 | >lua 69 | ---@alias snacks.animate.easing.Fn fun(t: number, b: number, c: number, d: number): number 70 | < 71 | 72 | Duration can be specified as the total duration or the duration per step. When 73 | both are specified, the minimum of both is used. 74 | 75 | >lua 76 | ---@class snacks.animate.Duration 77 | ---@field step? number duration per step in ms 78 | ---@field total? number total duration in ms 79 | < 80 | 81 | >lua 82 | ---@class snacks.animate.Opts: snacks.animate.Config 83 | ---@field int? boolean interpolate the value to an integer 84 | ---@field id? number|string unique identifier for the animation 85 | < 86 | 87 | >lua 88 | ---@class snacks.animate.ctx 89 | ---@field anim snacks.animate.Animation 90 | ---@field prev number 91 | ---@field done boolean 92 | < 93 | 94 | >lua 95 | ---@alias snacks.animate.cb fun(value:number, ctx: snacks.animate.ctx) 96 | < 97 | 98 | 99 | ============================================================================== 100 | 4. Module *snacks-animate-module* 101 | 102 | 103 | `Snacks.animate()` *Snacks.animate()* 104 | 105 | >lua 106 | ---@type fun(from: number, to: number, cb: snacks.animate.cb, opts?: snacks.animate.Opts): snacks.animate.Animation 107 | Snacks.animate() 108 | < 109 | 110 | 111 | `Snacks.animate.add()` *Snacks.animate.add()* 112 | 113 | Add an animation 114 | 115 | >lua 116 | ---@param from number 117 | ---@param to number 118 | ---@param cb snacks.animate.cb 119 | ---@param opts? snacks.animate.Opts 120 | Snacks.animate.add(from, to, cb, opts) 121 | < 122 | 123 | 124 | `Snacks.animate.del()` *Snacks.animate.del()* 125 | 126 | Delete an animation 127 | 128 | >lua 129 | ---@param id number|string 130 | Snacks.animate.del(id) 131 | < 132 | 133 | Generated by panvimdoc 134 | 135 | vim:tw=78:ts=8:noet:ft=help:norl: 136 | -------------------------------------------------------------------------------- /doc/snacks-util.txt: -------------------------------------------------------------------------------- 1 | *snacks-util.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-util-table-of-contents* 5 | 6 | 1. Types |snacks-util-types| 7 | 2. Module |snacks-util-module| 8 | - Snacks.util.blend() |snacks-util-module-snacks.util.blend()| 9 | - Snacks.util.bo() |snacks-util-module-snacks.util.bo()| 10 | - Snacks.util.color() |snacks-util-module-snacks.util.color()| 11 | - Snacks.util.file_decode() |snacks-util-module-snacks.util.file_decode()| 12 | - Snacks.util.file_encode() |snacks-util-module-snacks.util.file_encode()| 13 | - Snacks.util.icon() |snacks-util-module-snacks.util.icon()| 14 | - Snacks.util.is_transparent()|snacks-util-module-snacks.util.is_transparent()| 15 | - Snacks.util.redraw() |snacks-util-module-snacks.util.redraw()| 16 | - Snacks.util.redraw_range() |snacks-util-module-snacks.util.redraw_range()| 17 | - Snacks.util.set_hl() |snacks-util-module-snacks.util.set_hl()| 18 | - Snacks.util.wo() |snacks-util-module-snacks.util.wo()| 19 | 20 | ============================================================================== 21 | 1. Types *snacks-util-types* 22 | 23 | >lua 24 | ---@alias snacks.util.hl table 25 | < 26 | 27 | 28 | ============================================================================== 29 | 2. Module *snacks-util-module* 30 | 31 | 32 | `Snacks.util.blend()` *Snacks.util.blend()* 33 | 34 | >lua 35 | ---@param fg string foreground color 36 | ---@param bg string background color 37 | ---@param alpha number number between 0 and 1. 0 results in bg, 1 results in fg 38 | Snacks.util.blend(fg, bg, alpha) 39 | < 40 | 41 | 42 | `Snacks.util.bo()` *Snacks.util.bo()* 43 | 44 | Set buffer-local options. 45 | 46 | >lua 47 | ---@param buf number 48 | ---@param bo vim.bo 49 | Snacks.util.bo(buf, bo) 50 | < 51 | 52 | 53 | `Snacks.util.color()` *Snacks.util.color()* 54 | 55 | >lua 56 | ---@param group string hl group to get color from 57 | ---@param prop? string property to get. Defaults to "fg" 58 | Snacks.util.color(group, prop) 59 | < 60 | 61 | 62 | `Snacks.util.file_decode()` *Snacks.util.file_decode()* 63 | 64 | Decodes a file name to a string. 65 | 66 | >lua 67 | ---@param str string 68 | Snacks.util.file_decode(str) 69 | < 70 | 71 | 72 | `Snacks.util.file_encode()` *Snacks.util.file_encode()* 73 | 74 | Encodes a string to be used as a file name. 75 | 76 | >lua 77 | ---@param str string 78 | Snacks.util.file_encode(str) 79 | < 80 | 81 | 82 | `Snacks.util.icon()` *Snacks.util.icon()* 83 | 84 | Get an icon from `mini.icons` or `nvim-web-devicons`. 85 | 86 | >lua 87 | ---@param name string 88 | ---@param cat? string defaults to "file" 89 | ---@return string, string? 90 | Snacks.util.icon(name, cat) 91 | < 92 | 93 | 94 | `Snacks.util.is_transparent()` *Snacks.util.is_transparent()* 95 | 96 | Check if the colorscheme is transparent. 97 | 98 | >lua 99 | Snacks.util.is_transparent() 100 | < 101 | 102 | 103 | `Snacks.util.redraw()` *Snacks.util.redraw()* 104 | 105 | Redraw the window. Optimized for Neovim >= 0.10 106 | 107 | >lua 108 | ---@param win number 109 | Snacks.util.redraw(win) 110 | < 111 | 112 | 113 | `Snacks.util.redraw_range()` *Snacks.util.redraw_range()* 114 | 115 | Redraw the range of lines in the window. Optimized for Neovim >= 0.10 116 | 117 | >lua 118 | ---@param win number 119 | ---@param from number -- 1-indexed, inclusive 120 | ---@param to number -- 1-indexed, inclusive 121 | Snacks.util.redraw_range(win, from, to) 122 | < 123 | 124 | 125 | `Snacks.util.set_hl()` *Snacks.util.set_hl()* 126 | 127 | Ensures the hl groups are always set, even after a colorscheme change. 128 | 129 | >lua 130 | ---@param groups snacks.util.hl 131 | ---@param opts? { prefix?:string, default?:boolean, managed?:boolean } 132 | Snacks.util.set_hl(groups, opts) 133 | < 134 | 135 | 136 | `Snacks.util.wo()` *Snacks.util.wo()* 137 | 138 | Set window-local options. 139 | 140 | >lua 141 | ---@param win number 142 | ---@param wo vim.wo 143 | Snacks.util.wo(win, wo) 144 | < 145 | 146 | Generated by panvimdoc 147 | 148 | vim:tw=78:ts=8:noet:ft=help:norl: 149 | -------------------------------------------------------------------------------- /doc/snacks-input.txt: -------------------------------------------------------------------------------- 1 | *snacks-input.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-input-table-of-contents* 5 | 6 | 1. Setup |snacks-input-setup| 7 | 2. Config |snacks-input-config| 8 | 3. Styles |snacks-input-styles| 9 | - input |snacks-input-styles-input| 10 | 4. Types |snacks-input-types| 11 | 5. Module |snacks-input-module| 12 | - Snacks.input() |snacks-input-module-snacks.input()| 13 | - Snacks.input.disable() |snacks-input-module-snacks.input.disable()| 14 | - Snacks.input.enable() |snacks-input-module-snacks.input.enable()| 15 | - Snacks.input.input() |snacks-input-module-snacks.input.input()| 16 | 6. Links |snacks-input-links| 17 | Better `vim.ui.input`. 18 | 19 | 20 | ============================================================================== 21 | 1. Setup *snacks-input-setup* 22 | 23 | >lua 24 | -- lazy.nvim 25 | { 26 | "folke/snacks.nvim", 27 | opts = { 28 | input = { 29 | -- your input configuration comes here 30 | -- or leave it empty to use the default settings 31 | -- refer to the configuration section below 32 | } 33 | } 34 | } 35 | < 36 | 37 | 38 | ============================================================================== 39 | 2. Config *snacks-input-config* 40 | 41 | >lua 42 | ---@class snacks.input.Config 43 | ---@field enabled? boolean 44 | ---@field win? snacks.win.Config 45 | ---@field icon? string 46 | { 47 | icon = " ", 48 | icon_hl = "SnacksInputIcon", 49 | win = { style = "input" }, 50 | expand = true, 51 | } 52 | < 53 | 54 | 55 | ============================================================================== 56 | 3. Styles *snacks-input-styles* 57 | 58 | 59 | INPUT *snacks-input-styles-input* 60 | 61 | >lua 62 | { 63 | backdrop = false, 64 | position = "float", 65 | border = "rounded", 66 | title_pos = "center", 67 | height = 1, 68 | width = 60, 69 | relative = "editor", 70 | row = 2, 71 | -- relative = "cursor", 72 | -- row = -3, 73 | -- col = 0, 74 | wo = { 75 | winhighlight = "NormalFloat:SnacksInputNormal,FloatBorder:SnacksInputBorder,FloatTitle:SnacksInputTitle", 76 | }, 77 | keys = { 78 | i_esc = { "", { "cmp_close", "cancel" }, mode = "i" }, 79 | -- i_esc = { "", "stopinsert", mode = "i" }, 80 | i_cr = { "", { "cmp_accept", "confirm" }, mode = "i" }, 81 | i_tab = { "", { "cmp_select_next", "cmp" }, mode = "i" }, 82 | q = "cancel", 83 | }, 84 | } 85 | < 86 | 87 | 88 | ============================================================================== 89 | 4. Types *snacks-input-types* 90 | 91 | >lua 92 | ---@class snacks.input.Opts: snacks.input.Config 93 | ---@field prompt? string 94 | ---@field default? string 95 | ---@field completion? string 96 | ---@field highlight? fun() 97 | < 98 | 99 | 100 | ============================================================================== 101 | 5. Module *snacks-input-module* 102 | 103 | 104 | `Snacks.input()` *Snacks.input()* 105 | 106 | >lua 107 | ---@type fun(opts: snacks.input.Opts, on_confirm: fun(value?: string)): snacks.win 108 | Snacks.input() 109 | < 110 | 111 | 112 | `Snacks.input.disable()` *Snacks.input.disable()* 113 | 114 | >lua 115 | Snacks.input.disable() 116 | < 117 | 118 | 119 | `Snacks.input.enable()` *Snacks.input.enable()* 120 | 121 | >lua 122 | Snacks.input.enable() 123 | < 124 | 125 | 126 | `Snacks.input.input()` *Snacks.input.input()* 127 | 128 | >lua 129 | ---@param opts? snacks.input.Opts 130 | ---@param on_confirm fun(value?: string) 131 | Snacks.input.input(opts, on_confirm) 132 | < 133 | 134 | ============================================================================== 135 | 6. Links *snacks-input-links* 136 | 137 | 1. *image*: https://github.com/user-attachments/assets/f7579302-bea1-4f1c-8b3b-723c3f4ca04b 138 | 139 | Generated by panvimdoc 140 | 141 | vim:tw=78:ts=8:noet:ft=help:norl: 142 | -------------------------------------------------------------------------------- /lua/snacks/profiler/loc.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.profiler.loc 2 | ---@field vim_runtime string 3 | ---@field user_runtime string 4 | ---@field user_config string 5 | local M = {} 6 | 7 | local fun_cache = {} ---@type table 8 | local norm_cache = {} ---@type table> 9 | local path_cache = {} ---@type table 10 | local ts_cache = {} ---@type table> 11 | local ts_query ---@type vim.treesitter.Query? 12 | 13 | -- add and normalize locations 14 | function M.load() 15 | local opts = Snacks.profiler.config 16 | M.vim_runtime = M.realpath(vim.env.VIMRUNTIME) 17 | M.user_runtime = M.realpath(opts.runtime or M.vim_runtime) 18 | M.user_config = M.realpath(vim.fn.stdpath("config") .. "") 19 | 20 | Snacks.profiler.tracer.walk(function(entry) 21 | entry.def = M.loc(entry) 22 | entry.ref = entry.ref and M.norm(entry.ref) or nil 23 | end) 24 | end 25 | 26 | -- Get the location at the cursor 27 | function M.current() 28 | local cursor = vim.api.nvim_win_get_cursor(0) 29 | return M.norm({ file = vim.api.nvim_buf_get_name(0), line = cursor[1] }) 30 | end 31 | 32 | --- Get the real path of a file 33 | ---@param path string 34 | function M.realpath(path) 35 | if path_cache[path] then 36 | return path_cache[path] 37 | end 38 | path = vim.fs.normalize(path, { expand_env = false }) 39 | path_cache[path] = vim.fs.normalize(vim.uv.fs_realpath(path) or path, { expand_env = false, _fast = true }) 40 | return path_cache[path] 41 | end 42 | 43 | ---@param loc snacks.profiler.Loc 44 | function M.norm(loc) 45 | local file, line = loc.file, loc.line 46 | local ret = norm_cache[file] and norm_cache[file][line] 47 | if not ret then 48 | ret = M._norm(loc) 49 | norm_cache[file] = norm_cache[file] or {} 50 | norm_cache[file][line] = ret 51 | end 52 | return ret 53 | end 54 | 55 | ---@param loc snacks.profiler.Loc 56 | function M._norm(loc) 57 | if loc.file:sub(1, 4) == "vim/" then 58 | loc.file = M.user_runtime .. "/lua/" .. loc.file 59 | elseif loc.file:find("runtime", 1, true) then 60 | if loc.file:sub(1, #M.vim_runtime) == M.vim_runtime then 61 | loc.file = M.user_runtime .. "/" .. loc.file:sub(#M.vim_runtime + 2) 62 | end 63 | end 64 | loc.file = M.realpath(loc.file) 65 | loc.line = loc.line == 0 and 1 or loc.line 66 | loc.loc = ("%s:%d"):format(loc.file, loc.line) 67 | if loc.file:find(M.user_config, 1, true) == 1 then 68 | local relpath = loc.file:sub(#M.user_config + 2) 69 | local modpath = relpath:match("^lua/(.*)%.lua$") 70 | loc.modname = modpath and modpath:gsub("/", "."):gsub("%.init$", "") or "vimrc" 71 | loc.plugin = "user" 72 | else 73 | local plugin, modpath = loc.file:match("/([^/]+)/lua/(.*)%.lua$") 74 | if plugin and modpath then 75 | plugin = plugin == "runtime" and "nvim" or plugin 76 | loc.plugin = plugin 77 | loc.modname = modpath:gsub("/", "."):gsub("%.init$", "") 78 | end 79 | end 80 | return loc 81 | end 82 | 83 | ---@param entry snacks.profiler.Trace 84 | ---@return snacks.profiler.Loc? 85 | function M.loc(entry) 86 | local ret = fun_cache[entry.fn] 87 | if ret == nil then 88 | local info = debug.getinfo(entry.fn, "S") 89 | if info and info.what ~= "C" then 90 | ret = { file = info.source:sub(2), line = info.linedefined } 91 | if entry.fname and ret.file:sub(1, 4) == "vim/" then 92 | ret.file = M.user_runtime .. "/lua/" .. ret.file 93 | local ts_loc = M.ts_locs(ret.file)[entry.fname] 94 | if ts_loc then 95 | ret.file, ret.line = ts_loc.file, ts_loc.line 96 | end 97 | end 98 | ret = M.norm(ret) 99 | end 100 | fun_cache[entry.fn] = ret or false 101 | end 102 | return ret or nil 103 | end 104 | 105 | ---@param file string 106 | function M.ts_locs(file) 107 | if ts_cache[file] then 108 | return ts_cache[file] 109 | end 110 | ts_query = ts_query 111 | or vim.treesitter.query.parse( 112 | "lua", 113 | [[((function_declaration name: (_) @fun_name) @fun 114 | (#has-parent? @fun chunk)) 115 | ((return_statement (expression_list (identifier) @ret_name)) @ret 116 | (#has-parent? @ret chunk))]] 117 | ) 118 | local source = table.concat(vim.fn.readfile(file), "\n") 119 | local parser = vim.treesitter.get_string_parser(source, "lua") 120 | parser:parse() 121 | local ret, ret_name = {}, nil ---@type table, string? 122 | local funs = {} ---@type table 123 | for id, node in ts_query:iter_captures(parser:trees()[1]:root(), source) do 124 | local name = ts_query.captures[id] 125 | if name == "fun_name" then 126 | funs[vim.treesitter.get_node_text(node, source)] = node:start() + 1 127 | elseif name == "ret_name" then 128 | ret_name = vim.treesitter.get_node_text(node, source) 129 | end 130 | end 131 | for fname, line in pairs(funs) do 132 | fname = ret_name and fname:gsub("^" .. ret_name .. "%.", "") or fname 133 | ret[fname] = { file = file, line = line } 134 | end 135 | ts_cache[file] = ret 136 | return ret 137 | end 138 | 139 | return M 140 | -------------------------------------------------------------------------------- /doc/snacks-zen.txt: -------------------------------------------------------------------------------- 1 | *snacks-zen.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-zen-table-of-contents* 5 | 6 | 1. Setup |snacks-zen-setup| 7 | 2. Config |snacks-zen-config| 8 | 3. Styles |snacks-zen-styles| 9 | - zen |snacks-zen-styles-zen| 10 | - zoom_indicator |snacks-zen-styles-zoom_indicator| 11 | 4. Module |snacks-zen-module| 12 | - Snacks.zen() |snacks-zen-module-snacks.zen()| 13 | - Snacks.zen.zen() |snacks-zen-module-snacks.zen.zen()| 14 | - Snacks.zen.zoom() |snacks-zen-module-snacks.zen.zoom()| 15 | 5. Links |snacks-zen-links| 16 | Zen mode • distraction-free coding. Integrates with `Snacks.toggle` to toggle 17 | various UI elements and with `Snacks.dim` to dim code out of scope. 18 | 19 | Similar plugins: 20 | 21 | - zen-mode.nvim 22 | - true-zen.nvim 23 | 24 | 25 | ============================================================================== 26 | 1. Setup *snacks-zen-setup* 27 | 28 | >lua 29 | -- lazy.nvim 30 | { 31 | "folke/snacks.nvim", 32 | opts = { 33 | zen = { 34 | -- your zen configuration comes here 35 | -- or leave it empty to use the default settings 36 | -- refer to the configuration section below 37 | } 38 | } 39 | } 40 | < 41 | 42 | 43 | ============================================================================== 44 | 2. Config *snacks-zen-config* 45 | 46 | >lua 47 | ---@class snacks.zen.Config 48 | { 49 | -- You can add any `Snacks.toggle` id here. 50 | -- Toggle state is restored when the window is closed. 51 | -- Toggle config options are NOT merged. 52 | ---@type table 53 | toggles = { 54 | dim = true, 55 | git_signs = false, 56 | mini_diff_signs = false, 57 | -- diagnostics = false, 58 | -- inlay_hints = false, 59 | }, 60 | show = { 61 | statusline = false, -- can only be shown when using the global statusline 62 | tabline = false, 63 | }, 64 | ---@type snacks.win.Config 65 | win = { style = "zen" }, 66 | 67 | --- Options for the `Snacks.zen.zoom()` 68 | ---@type snacks.zen.Config 69 | zoom = { 70 | toggles = {}, 71 | show = { statusline = true, tabline = true }, 72 | win = { 73 | backdrop = false, 74 | width = 0, -- full width 75 | }, 76 | }, 77 | } 78 | < 79 | 80 | 81 | ============================================================================== 82 | 3. Styles *snacks-zen-styles* 83 | 84 | 85 | ZEN *snacks-zen-styles-zen* 86 | 87 | >lua 88 | { 89 | enter = true, 90 | fixbuf = false, 91 | minimal = false, 92 | width = 120, 93 | height = 0, 94 | backdrop = { transparent = true, blend = 40 }, 95 | keys = { q = false }, 96 | wo = { 97 | winhighlight = "NormalFloat:Normal", 98 | }, 99 | } 100 | < 101 | 102 | 103 | ZOOM_INDICATOR *snacks-zen-styles-zoom_indicator* 104 | 105 | fullscreen indicator only shown when the window is maximized 106 | 107 | >lua 108 | { 109 | text = "▍ zoom 󰊓 ", 110 | minimal = true, 111 | enter = false, 112 | focusable = false, 113 | height = 1, 114 | row = 0, 115 | col = -1, 116 | backdrop = false, 117 | } 118 | < 119 | 120 | 121 | ============================================================================== 122 | 4. Module *snacks-zen-module* 123 | 124 | 125 | `Snacks.zen()` *Snacks.zen()* 126 | 127 | >lua 128 | ---@type fun(opts: snacks.zen.Config): snacks.win 129 | Snacks.zen() 130 | < 131 | 132 | 133 | `Snacks.zen.zen()` *Snacks.zen.zen()* 134 | 135 | >lua 136 | ---@param opts? snacks.zen.Config 137 | Snacks.zen.zen(opts) 138 | < 139 | 140 | 141 | `Snacks.zen.zoom()` *Snacks.zen.zoom()* 142 | 143 | >lua 144 | ---@param opts? snacks.zen.Config 145 | Snacks.zen.zoom(opts) 146 | < 147 | 148 | ============================================================================== 149 | 5. Links *snacks-zen-links* 150 | 151 | 1. *image*: https://github.com/user-attachments/assets/77c607ec-c354-4e17-bcd1-fdcd4b4c0057 152 | 153 | Generated by panvimdoc 154 | 155 | vim:tw=78:ts=8:noet:ft=help:norl: 156 | -------------------------------------------------------------------------------- /doc/snacks-init.txt: -------------------------------------------------------------------------------- 1 | *snacks-init.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-init-table-of-contents* 5 | 6 | 1. Config |snacks-init-config| 7 | 2. Types |snacks-init-types| 8 | 3. Module |snacks-init-module| 9 | - Snacks.config.example() |snacks-init-module-snacks.config.example()| 10 | - Snacks.config.get() |snacks-init-module-snacks.config.get()| 11 | - Snacks.config.style() |snacks-init-module-snacks.config.style()| 12 | - Snacks.setup() |snacks-init-module-snacks.setup()| 13 | 14 | ============================================================================== 15 | 1. Config *snacks-init-config* 16 | 17 | >lua 18 | ---@class snacks.Config 19 | ---@field animate? snacks.animate.Config 20 | ---@field bigfile? snacks.bigfile.Config 21 | ---@field dashboard? snacks.dashboard.Config 22 | ---@field dim? snacks.dim.Config 23 | ---@field gitbrowse? snacks.gitbrowse.Config 24 | ---@field indent? snacks.indent.Config 25 | ---@field input? snacks.input.Config 26 | ---@field lazygit? snacks.lazygit.Config 27 | ---@field notifier? snacks.notifier.Config 28 | ---@field profiler? snacks.profiler.Config 29 | ---@field quickfile? snacks.quickfile.Config 30 | ---@field scope? snacks.scope.Config 31 | ---@field scratch? snacks.scratch.Config 32 | ---@field scroll? snacks.scroll.Config 33 | ---@field statuscolumn? snacks.statuscolumn.Config 34 | ---@field terminal? snacks.terminal.Config 35 | ---@field toggle? snacks.toggle.Config 36 | ---@field win? snacks.win.Config 37 | ---@field words? snacks.words.Config 38 | ---@field zen? snacks.zen.Config 39 | ---@field styles? table 40 | { 41 | bigfile = { enabled = false }, 42 | dashboard = { enabled = false }, 43 | indent = { enabled = false }, 44 | input = { enabled = false }, 45 | notifier = { enabled = false }, 46 | quickfile = { enabled = false }, 47 | scroll = { enabled = false }, 48 | statuscolumn = { enabled = false }, 49 | styles = {}, 50 | words = { enabled = false }, 51 | } 52 | < 53 | 54 | 55 | ============================================================================== 56 | 2. Types *snacks-init-types* 57 | 58 | >lua 59 | ---@class snacks.Config.base 60 | ---@field example? string 61 | ---@field config? fun(opts: table, defaults: table) 62 | < 63 | 64 | 65 | ============================================================================== 66 | 3. Module *snacks-init-module* 67 | 68 | >lua 69 | ---@class Snacks 70 | ---@field animate snacks.animate 71 | ---@field bigfile snacks.bigfile 72 | ---@field bufdelete snacks.bufdelete 73 | ---@field dashboard snacks.dashboard 74 | ---@field debug snacks.debug 75 | ---@field dim snacks.dim 76 | ---@field git snacks.git 77 | ---@field gitbrowse snacks.gitbrowse 78 | ---@field health snacks.health 79 | ---@field indent snacks.indent 80 | ---@field input snacks.input 81 | ---@field lazygit snacks.lazygit 82 | ---@field meta snacks.meta 83 | ---@field notifier snacks.notifier 84 | ---@field notify snacks.notify 85 | ---@field profiler snacks.profiler 86 | ---@field quickfile snacks.quickfile 87 | ---@field rename snacks.rename 88 | ---@field scope snacks.scope 89 | ---@field scratch snacks.scratch 90 | ---@field scroll snacks.scroll 91 | ---@field statuscolumn snacks.statuscolumn 92 | ---@field terminal snacks.terminal 93 | ---@field toggle snacks.toggle 94 | ---@field util snacks.util 95 | ---@field win snacks.win 96 | ---@field words snacks.words 97 | ---@field zen snacks.zen 98 | Snacks = {} 99 | < 100 | 101 | 102 | `Snacks.config.example()` *Snacks.config.example()* 103 | 104 | Get an example config from the docs/examples directory. 105 | 106 | >lua 107 | ---@param snack string 108 | ---@param name string 109 | ---@param opts? table 110 | Snacks.config.example(snack, name, opts) 111 | < 112 | 113 | 114 | `Snacks.config.get()` *Snacks.config.get()* 115 | 116 | >lua 117 | ---@generic T: table 118 | ---@param snack string 119 | ---@param defaults T 120 | ---@param ... T[] 121 | ---@return T 122 | Snacks.config.get(snack, defaults, ...) 123 | < 124 | 125 | 126 | `Snacks.config.style()` *Snacks.config.style()* 127 | 128 | Register a new window style config. 129 | 130 | >lua 131 | ---@param name string 132 | ---@param defaults snacks.win.Config 133 | Snacks.config.style(name, defaults) 134 | < 135 | 136 | 137 | `Snacks.setup()` *Snacks.setup()* 138 | 139 | >lua 140 | ---@param opts snacks.Config? 141 | Snacks.setup(opts) 142 | < 143 | 144 | Generated by panvimdoc 145 | 146 | vim:tw=78:ts=8:noet:ft=help:norl: 147 | -------------------------------------------------------------------------------- /doc/snacks-indent.txt: -------------------------------------------------------------------------------- 1 | *snacks-indent.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-indent-table-of-contents* 5 | 6 | 1. Setup |snacks-indent-setup| 7 | 2. Config |snacks-indent-config| 8 | 3. Types |snacks-indent-types| 9 | 4. Module |snacks-indent-module| 10 | - Snacks.indent.animate() |snacks-indent-module-snacks.indent.animate()| 11 | - Snacks.indent.disable() |snacks-indent-module-snacks.indent.disable()| 12 | - Snacks.indent.enable() |snacks-indent-module-snacks.indent.enable()| 13 | 5. Links |snacks-indent-links| 14 | Visualize indent guides and scopes based on treesitter or indent. 15 | 16 | Similar plugins: 17 | 18 | - indent-blankline.nvim 19 | - mini.indentscope 20 | 21 | 22 | ============================================================================== 23 | 1. Setup *snacks-indent-setup* 24 | 25 | >lua 26 | -- lazy.nvim 27 | { 28 | "folke/snacks.nvim", 29 | opts = { 30 | indent = { 31 | -- your indent configuration comes here 32 | -- or leave it empty to use the default settings 33 | -- refer to the configuration section below 34 | } 35 | } 36 | } 37 | < 38 | 39 | 40 | ============================================================================== 41 | 2. Config *snacks-indent-config* 42 | 43 | >lua 44 | ---@class snacks.indent.Config 45 | ---@field enabled? boolean 46 | { 47 | indent = { 48 | char = "│", 49 | blank = " ", 50 | -- blank = "∙", 51 | only_scope = false, -- only show indent guides of the scope 52 | only_current = false, -- only show indent guides in the current window 53 | hl = "SnacksIndent", ---@type string|string[] hl groups for indent guides 54 | -- can be a list of hl groups to cycle through 55 | -- hl = { 56 | -- "SnacksIndent1", 57 | -- "SnacksIndent2", 58 | -- "SnacksIndent3", 59 | -- "SnacksIndent4", 60 | -- "SnacksIndent5", 61 | -- "SnacksIndent6", 62 | -- "SnacksIndent7", 63 | -- "SnacksIndent8", 64 | -- }, 65 | }, 66 | ---@class snacks.indent.Scope.Config: snacks.scope.Config 67 | scope = { 68 | -- animate scopes. Enabled by default for Neovim >= 0.10 69 | -- Works on older versions but has to trigger redraws during animation. 70 | ---@type snacks.animate.Config|{enabled?: boolean} 71 | animate = { 72 | enabled = vim.fn.has("nvim-0.10") == 1, 73 | easing = "linear", 74 | duration = { 75 | step = 20, -- ms per step 76 | total = 500, -- maximum duration 77 | }, 78 | }, 79 | char = "│", 80 | underline = false, -- underline the start of the scope 81 | only_current = false, -- only show scope in the current window 82 | hl = "SnacksIndentScope", ---@type string|string[] hl group for scopes 83 | }, 84 | blank = { 85 | char = " ", 86 | -- char = "·", 87 | hl = "SnacksIndentBlank", ---@type string|string[] hl group for blank spaces 88 | }, 89 | -- filter for buffers to enable indent guides 90 | filter = function(buf) 91 | return vim.g.snacks_indent ~= false and vim.b[buf].snacks_indent ~= false and vim.bo[buf].buftype == "" 92 | end, 93 | priority = 200, 94 | } 95 | < 96 | 97 | 98 | ============================================================================== 99 | 3. Types *snacks-indent-types* 100 | 101 | >lua 102 | ---@class snacks.indent.Scope: snacks.scope.Scope 103 | ---@field win number 104 | ---@field step? number 105 | < 106 | 107 | 108 | ============================================================================== 109 | 4. Module *snacks-indent-module* 110 | 111 | 112 | `Snacks.indent.animate()` *Snacks.indent.animate()* 113 | 114 | Toggle scope animations 115 | 116 | >lua 117 | Snacks.indent.animate() 118 | < 119 | 120 | 121 | `Snacks.indent.disable()` *Snacks.indent.disable()* 122 | 123 | Disable indent guides 124 | 125 | >lua 126 | Snacks.indent.disable() 127 | < 128 | 129 | 130 | `Snacks.indent.enable()` *Snacks.indent.enable()* 131 | 132 | Enable indent guides 133 | 134 | >lua 135 | Snacks.indent.enable() 136 | < 137 | 138 | ============================================================================== 139 | 5. Links *snacks-indent-links* 140 | 141 | 1. *image*: https://github.com/user-attachments/assets/56a99495-05ab-488e-9619-574cb7ff2b7d 142 | 143 | Generated by panvimdoc 144 | 145 | vim:tw=78:ts=8:noet:ft=help:norl: 146 | -------------------------------------------------------------------------------- /doc/snacks-gitbrowse.txt: -------------------------------------------------------------------------------- 1 | *snacks-gitbrowse.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-gitbrowse-table-of-contents* 5 | 6 | 1. Setup |snacks-gitbrowse-setup| 7 | 2. Config |snacks-gitbrowse-config| 8 | 3. Types |snacks-gitbrowse-types| 9 | 4. Module |snacks-gitbrowse-module| 10 | - Snacks.gitbrowse() |snacks-gitbrowse-module-snacks.gitbrowse()| 11 | - Snacks.gitbrowse.get_url()|snacks-gitbrowse-module-snacks.gitbrowse.get_url()| 12 | - Snacks.gitbrowse.open() |snacks-gitbrowse-module-snacks.gitbrowse.open()| 13 | Open the repo of the active file in the browser (e.g., GitHub) 14 | 15 | 16 | ============================================================================== 17 | 1. Setup *snacks-gitbrowse-setup* 18 | 19 | >lua 20 | -- lazy.nvim 21 | { 22 | "folke/snacks.nvim", 23 | opts = { 24 | gitbrowse = { 25 | -- your gitbrowse configuration comes here 26 | -- or leave it empty to use the default settings 27 | -- refer to the configuration section below 28 | } 29 | } 30 | } 31 | < 32 | 33 | 34 | ============================================================================== 35 | 2. Config *snacks-gitbrowse-config* 36 | 37 | >lua 38 | ---@class snacks.gitbrowse.Config 39 | ---@field url_patterns? table> 40 | { 41 | notify = true, -- show notification on open 42 | -- Handler to open the url in a browser 43 | ---@param url string 44 | open = function(url) 45 | if vim.fn.has("nvim-0.10") == 0 then 46 | require("lazy.util").open(url, { system = true }) 47 | return 48 | end 49 | vim.ui.open(url) 50 | end, 51 | ---@type "repo" | "branch" | "file" | "commit" 52 | what = "file", -- what to open. not all remotes support all types 53 | branch = nil, ---@type string? 54 | line_start = nil, ---@type number? 55 | line_end = nil, ---@type number? 56 | -- patterns to transform remotes to an actual URL 57 | remote_patterns = { 58 | { "^(https?://.*)%.git$" , "%1" }, 59 | { "^git@(.+):(.+)%.git$" , "https://%1/%2" }, 60 | { "^git@(.+):(.+)$" , "https://%1/%2" }, 61 | { "^git@(.+)/(.+)$" , "https://%1/%2" }, 62 | { "^ssh://git@(.*)$" , "https://%1" }, 63 | { "^ssh://([^:/]+)(:%d+)/(.*)$" , "https://%1/%3" }, 64 | { "^ssh://([^/]+)/(.*)$" , "https://%1/%2" }, 65 | { "ssh%.dev%.azure%.com/v3/(.*)/(.*)$", "dev.azure.com/%1/_git/%2" }, 66 | { "^https://%w*@(.*)" , "https://%1" }, 67 | { "^git@(.*)" , "https://%1" }, 68 | { ":%d+" , "" }, 69 | { "%.git$" , "" }, 70 | }, 71 | url_patterns = { 72 | ["github%.com"] = { 73 | branch = "/tree/{branch}", 74 | file = "/blob/{branch}/{file}#L{line_start}-L{line_end}", 75 | commit = "/commit/{commit}", 76 | }, 77 | ["gitlab%.com"] = { 78 | branch = "/-/tree/{branch}", 79 | file = "/-/blob/{branch}/{file}#L{line_start}-L{line_end}", 80 | commit = "/-/commit/{commit}", 81 | }, 82 | ["bitbucket%.org"] = { 83 | branch = "/src/{branch}", 84 | file = "/src/{branch}/{file}#lines-{line_start}-L{line_end}", 85 | commit = "/commits/{commit}", 86 | }, 87 | }, 88 | } 89 | < 90 | 91 | 92 | ============================================================================== 93 | 3. Types *snacks-gitbrowse-types* 94 | 95 | >lua 96 | ---@class snacks.gitbrowse.Fields 97 | ---@field branch? string 98 | ---@field file? string 99 | ---@field line_start? number 100 | ---@field line_end? number 101 | ---@field commit? string 102 | ---@field line_count? number 103 | < 104 | 105 | 106 | ============================================================================== 107 | 4. Module *snacks-gitbrowse-module* 108 | 109 | 110 | `Snacks.gitbrowse()` *Snacks.gitbrowse()* 111 | 112 | >lua 113 | ---@type fun(opts?: snacks.gitbrowse.Config) 114 | Snacks.gitbrowse() 115 | < 116 | 117 | 118 | `Snacks.gitbrowse.get_url()` *Snacks.gitbrowse.get_url()* 119 | 120 | >lua 121 | ---@param repo string 122 | ---@param fields snacks.gitbrowse.Fields 123 | ---@param opts? snacks.gitbrowse.Config 124 | Snacks.gitbrowse.get_url(repo, fields, opts) 125 | < 126 | 127 | 128 | `Snacks.gitbrowse.open()` *Snacks.gitbrowse.open()* 129 | 130 | >lua 131 | ---@param opts? snacks.gitbrowse.Config 132 | Snacks.gitbrowse.open(opts) 133 | < 134 | 135 | Generated by panvimdoc 136 | 137 | vim:tw=78:ts=8:noet:ft=help:norl: 138 | -------------------------------------------------------------------------------- /lua/snacks/util.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.util 2 | local M = {} 3 | 4 | M.meta = { 5 | desc = "Utility functions for Snacks _(library)_", 6 | } 7 | 8 | ---@alias snacks.util.hl table 9 | 10 | local hl_groups = {} ---@type table 11 | vim.api.nvim_create_autocmd("ColorScheme", { 12 | group = vim.api.nvim_create_augroup("snacks_util_hl", { clear = true }), 13 | callback = function() 14 | for hl_group, hl in pairs(hl_groups) do 15 | vim.api.nvim_set_hl(0, hl_group, hl) 16 | end 17 | end, 18 | }) 19 | 20 | --- Ensures the hl groups are always set, even after a colorscheme change. 21 | ---@param groups snacks.util.hl 22 | ---@param opts? { prefix?:string, default?:boolean, managed?:boolean } 23 | function M.set_hl(groups, opts) 24 | opts = opts or {} 25 | for hl_group, hl in pairs(groups) do 26 | hl_group = opts.prefix and opts.prefix .. hl_group or hl_group 27 | hl = type(hl) == "string" and { link = hl } or hl --[[@as vim.api.keyset.highlight]] 28 | hl.default = opts.default 29 | if opts.managed ~= false then 30 | hl_groups[hl_group] = hl 31 | end 32 | vim.api.nvim_set_hl(0, hl_group, hl) 33 | end 34 | end 35 | 36 | ---@param group string hl group to get color from 37 | ---@param prop? string property to get. Defaults to "fg" 38 | function M.color(group, prop) 39 | prop = prop or "fg" 40 | local hl = vim.api.nvim_get_hl(0, { name = group, link = false }) 41 | return hl[prop] and string.format("#%06x", hl[prop]) 42 | end 43 | 44 | --- Set window-local options. 45 | ---@param win number 46 | ---@param wo vim.wo 47 | function M.wo(win, wo) 48 | for k, v in pairs(wo or {}) do 49 | vim.api.nvim_set_option_value(k, v, { scope = "local", win = win }) 50 | end 51 | end 52 | 53 | --- Set buffer-local options. 54 | ---@param buf number 55 | ---@param bo vim.bo 56 | function M.bo(buf, bo) 57 | for k, v in pairs(bo or {}) do 58 | vim.api.nvim_set_option_value(k, v, { buf = buf }) 59 | end 60 | end 61 | 62 | --- Get an icon from `mini.icons` or `nvim-web-devicons`. 63 | ---@param name string 64 | ---@param cat? string defaults to "file" 65 | ---@return string, string? 66 | function M.icon(name, cat) 67 | local try = { 68 | function() 69 | return require("mini.icons").get(cat or "file", name) 70 | end, 71 | function() 72 | local Icons = require("nvim-web-devicons") 73 | if cat == "filetype" then 74 | return Icons.get_icon_by_filetype(name, { default = false }) 75 | elseif cat == "file" then 76 | local ext = name:match("%.(%w+)$") 77 | return Icons.get_icon(name, ext, { default = false }) --[[@as string, string]] 78 | elseif cat == "extension" then 79 | return Icons.get_icon(nil, name, { default = false }) --[[@as string, string]] 80 | end 81 | end, 82 | } 83 | for _, fn in ipairs(try) do 84 | local ret = { pcall(fn) } 85 | if ret[1] and ret[2] then 86 | return ret[2], ret[3] 87 | end 88 | end 89 | return " " 90 | end 91 | 92 | -- Encodes a string to be used as a file name. 93 | ---@param str string 94 | function M.file_encode(str) 95 | return str:gsub("([^%w%-_%.\t ])", function(c) 96 | return string.format("_%%%02X", string.byte(c)) 97 | end) 98 | end 99 | 100 | -- Decodes a file name to a string. 101 | ---@param str string 102 | function M.file_decode(str) 103 | return str:gsub("_%%(%x%x)", function(hex) 104 | return string.char(tonumber(hex, 16)) 105 | end) 106 | end 107 | 108 | ---@param fg string foreground color 109 | ---@param bg string background color 110 | ---@param alpha number number between 0 and 1. 0 results in bg, 1 results in fg 111 | function M.blend(fg, bg, alpha) 112 | local bg_rgb = { tonumber(bg:sub(2, 3), 16), tonumber(bg:sub(4, 5), 16), tonumber(bg:sub(6, 7), 16) } 113 | local fg_rgb = { tonumber(fg:sub(2, 3), 16), tonumber(fg:sub(4, 5), 16), tonumber(fg:sub(6, 7), 16) } 114 | local blend = function(i) 115 | local ret = (alpha * fg_rgb[i] + ((1 - alpha) * bg_rgb[i])) 116 | return math.floor(math.min(math.max(0, ret), 255) + 0.5) 117 | end 118 | return string.format("#%02x%02x%02x", blend(1), blend(2), blend(3)) 119 | end 120 | 121 | local transparent ---@type boolean? 122 | 123 | --- Check if the colorscheme is transparent. 124 | function M.is_transparent() 125 | if transparent == nil then 126 | transparent = M.color("Normal", "bg") == nil 127 | vim.api.nvim_create_autocmd("ColorScheme", { 128 | group = vim.api.nvim_create_augroup("snacks_util_transparent", { clear = true }), 129 | callback = function() 130 | transparent = nil 131 | end, 132 | }) 133 | end 134 | return transparent 135 | end 136 | 137 | --- Redraw the range of lines in the window. 138 | --- Optimized for Neovim >= 0.10 139 | ---@param win number 140 | ---@param from number -- 1-indexed, inclusive 141 | ---@param to number -- 1-indexed, inclusive 142 | function M.redraw_range(win, from, to) 143 | if vim.api.nvim__redraw then 144 | vim.api.nvim__redraw({ win = win, range = { math.floor(from - 1), math.floor(to) }, valid = true, flush = false }) 145 | else 146 | vim.cmd([[redraw!]]) 147 | end 148 | end 149 | 150 | --- Redraw the window. 151 | --- Optimized for Neovim >= 0.10 152 | ---@param win number 153 | function M.redraw(win) 154 | if vim.api.nvim__redraw then 155 | vim.api.nvim__redraw({ win = win, valid = false, flush = false }) 156 | else 157 | vim.cmd([[redraw!]]) 158 | end 159 | end 160 | 161 | return M 162 | -------------------------------------------------------------------------------- /lua/snacks/input.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.input 2 | ---@overload fun(opts: snacks.input.Opts, on_confirm: fun(value?: string)): snacks.win 3 | local M = setmetatable({}, { 4 | __call = function(M, ...) 5 | return M.input(...) 6 | end, 7 | }) 8 | 9 | M.meta = { 10 | desc = "Better `vim.ui.input`", 11 | needs_setup = true, 12 | } 13 | 14 | ---@class snacks.input.Config 15 | ---@field enabled? boolean 16 | ---@field win? snacks.win.Config 17 | ---@field icon? string 18 | local defaults = { 19 | icon = " ", 20 | icon_hl = "SnacksInputIcon", 21 | win = { style = "input" }, 22 | expand = true, 23 | } 24 | 25 | Snacks.util.set_hl({ 26 | Icon = "DiagnosticHint", 27 | Normal = "Normal", 28 | Border = "DiagnosticInfo", 29 | Title = "DiagnosticInfo", 30 | }, { prefix = "SnacksInput", default = true }) 31 | 32 | Snacks.config.style("input", { 33 | backdrop = false, 34 | position = "float", 35 | border = "rounded", 36 | title_pos = "center", 37 | height = 1, 38 | width = 60, 39 | relative = "editor", 40 | row = 2, 41 | -- relative = "cursor", 42 | -- row = -3, 43 | -- col = 0, 44 | wo = { 45 | winhighlight = "NormalFloat:SnacksInputNormal,FloatBorder:SnacksInputBorder,FloatTitle:SnacksInputTitle", 46 | }, 47 | keys = { 48 | i_esc = { "", { "cmp_close", "cancel" }, mode = "i" }, 49 | -- i_esc = { "", "stopinsert", mode = "i" }, 50 | i_cr = { "", { "cmp_accept", "confirm" }, mode = "i" }, 51 | i_tab = { "", { "cmp_select_next", "cmp" }, mode = "i" }, 52 | q = "cancel", 53 | }, 54 | }) 55 | 56 | local ui_input = vim.ui.input 57 | 58 | ---@class snacks.input.Opts: snacks.input.Config 59 | ---@field prompt? string 60 | ---@field default? string 61 | ---@field completion? string 62 | ---@field highlight? fun() 63 | 64 | ---@class snacks.input.ctx 65 | ---@field opts? snacks.input.Opts 66 | ---@field win? snacks.win 67 | local ctx = {} 68 | 69 | ---@param opts? snacks.input.Opts 70 | ---@param on_confirm fun(value?: string) 71 | function M.input(opts, on_confirm) 72 | assert(type(on_confirm) == "function", "`on_confirm` must be a function") 73 | local function confirm(value) 74 | ctx.win = nil 75 | ctx.opts = nil 76 | vim.cmd.stopinsert() 77 | vim.schedule_wrap(on_confirm)(value) 78 | end 79 | 80 | opts = Snacks.config.get("input", defaults, opts) --[[@as snacks.input.Opts]] 81 | 82 | opts.win = Snacks.win.resolve("input", opts.win, { 83 | enter = true, 84 | title = (" %s "):format(vim.trim(opts.prompt or "Input")), 85 | bo = { 86 | modifiable = true, 87 | completefunc = "v:lua.Snacks.input.complete", 88 | omnifunc = "v:lua.Snacks.input.complete", 89 | }, 90 | wo = { statuscolumn = " %#" .. opts.icon_hl .. "#" .. opts.icon .. " " }, 91 | actions = { 92 | cancel = function(self) 93 | confirm() 94 | self:close() 95 | end, 96 | stopinsert = function() 97 | vim.cmd("stopinsert") 98 | end, 99 | confirm = function(self) 100 | confirm(self:text()) 101 | self:close() 102 | end, 103 | cmp = function() 104 | return vim.fn.pumvisible() == 0 and "" 105 | end, 106 | cmp_close = function() 107 | return vim.fn.pumvisible() == 1 and "" 108 | end, 109 | cmp_accept = function() 110 | return vim.fn.pumvisible() == 1 and "" 111 | end, 112 | cmp_select_next = function() 113 | return vim.fn.pumvisible() == 1 and "" 114 | end, 115 | cmp_select_prev = function() 116 | return vim.fn.pumvisible() == 1 and "" 117 | end, 118 | }, 119 | }) 120 | 121 | local min_width = opts.win.width or 60 122 | if opts.expand then 123 | ---@param self snacks.win 124 | opts.win.width = function(self) 125 | local w = type(min_width) == "function" and min_width(self) or min_width --[[@as number]] 126 | return math.max(w, vim.api.nvim_strwidth(self:text()) + 5) 127 | end 128 | end 129 | 130 | local win = Snacks.win(opts.win) 131 | ctx = { opts = opts, win = win } 132 | vim.cmd.startinsert() 133 | if opts.default then 134 | vim.api.nvim_buf_set_lines(win.buf, 0, -1, false, { opts.default }) 135 | vim.api.nvim_win_set_cursor(win.win, { 1, #opts.default + 1 }) 136 | end 137 | 138 | if opts.expand then 139 | vim.api.nvim_create_autocmd("TextChangedI", { 140 | buffer = win.buf, 141 | callback = function() 142 | win:update() 143 | vim.api.nvim_win_call(win.win, function() 144 | vim.fn.winrestview({ leftcol = 0 }) 145 | end) 146 | end, 147 | }) 148 | end 149 | 150 | return win 151 | end 152 | 153 | ---@param findstart number 154 | ---@param base string 155 | ---@private 156 | function M.complete(findstart, base) 157 | local completion = ctx.opts.completion 158 | if findstart == 1 then 159 | return 0 160 | end 161 | if not completion then 162 | return {} 163 | end 164 | local ok, results = pcall(vim.fn.getcompletion, base, completion) 165 | return ok and results or {} 166 | end 167 | 168 | function M.enable() 169 | vim.ui.input = M.input 170 | end 171 | 172 | function M.disable() 173 | vim.ui.input = ui_input 174 | end 175 | 176 | ---@private 177 | function M.health() 178 | if Snacks.config.get("input", defaults).enabled then 179 | if vim.ui.input == M.input then 180 | Snacks.health.ok("`vim.ui.input` is set to `Snacks.input`") 181 | else 182 | Snacks.health.error("`vim.ui.input` is not set to `Snacks.input`") 183 | end 184 | end 185 | end 186 | 187 | return M 188 | -------------------------------------------------------------------------------- /doc/snacks-debug.txt: -------------------------------------------------------------------------------- 1 | *snacks-debug.txt* snacks.nvim 2 | 3 | ============================================================================== 4 | Table of Contents *snacks-debug-table-of-contents* 5 | 6 | 1. Types |snacks-debug-types| 7 | 2. Module |snacks-debug-module| 8 | - Snacks.debug() |snacks-debug-module-snacks.debug()| 9 | - Snacks.debug.backtrace() |snacks-debug-module-snacks.debug.backtrace()| 10 | - Snacks.debug.inspect() |snacks-debug-module-snacks.debug.inspect()| 11 | - Snacks.debug.log() |snacks-debug-module-snacks.debug.log()| 12 | - Snacks.debug.profile() |snacks-debug-module-snacks.debug.profile()| 13 | - Snacks.debug.run() |snacks-debug-module-snacks.debug.run()| 14 | - Snacks.debug.stats() |snacks-debug-module-snacks.debug.stats()| 15 | - Snacks.debug.trace() |snacks-debug-module-snacks.debug.trace()| 16 | - Snacks.debug.tracemod() |snacks-debug-module-snacks.debug.tracemod()| 17 | 3. Links |snacks-debug-links| 18 | Utility functions you can use in your code. 19 | 20 | Personally, I have the code below at the top of my `init.lua` 21 | 22 | >lua 23 | _G.dd = function(...) 24 | Snacks.debug.inspect(...) 25 | end 26 | _G.bt = function() 27 | Snacks.debug.backtrace() 28 | end 29 | vim.print = _G.dd 30 | < 31 | 32 | Whatthis does: 33 | 34 | - Add a global `dd(...)` you can use anywhere to quickly show a 35 | notification with a pretty printed dump of the object(s) 36 | with lua treesitter highlighting 37 | - Add a global `bt()` to show a notification with a pretty 38 | backtrace. 39 | - Override Neovim’s `vim.print`, which is also used by `:= {something = 123}` 40 | 41 | 42 | ============================================================================== 43 | 1. Types *snacks-debug-types* 44 | 45 | >lua 46 | ---@alias snacks.debug.Trace {name: string, time: number, [number]:snacks.debug.Trace} 47 | ---@alias snacks.debug.Stat {name:string, time:number, count?:number, depth?:number} 48 | < 49 | 50 | 51 | ============================================================================== 52 | 2. Module *snacks-debug-module* 53 | 54 | 55 | `Snacks.debug()` *Snacks.debug()* 56 | 57 | >lua 58 | ---@type fun(...) 59 | Snacks.debug() 60 | < 61 | 62 | 63 | `Snacks.debug.backtrace()` *Snacks.debug.backtrace()* 64 | 65 | Show a notification with a pretty backtrace 66 | 67 | >lua 68 | ---@param msg? string|string[] 69 | ---@param opts? snacks.notify.Opts 70 | Snacks.debug.backtrace(msg, opts) 71 | < 72 | 73 | 74 | `Snacks.debug.inspect()` *Snacks.debug.inspect()* 75 | 76 | Show a notification with a pretty printed dump of the object(s) with lua 77 | treesitter highlighting and the location of the caller 78 | 79 | >lua 80 | Snacks.debug.inspect(...) 81 | < 82 | 83 | 84 | `Snacks.debug.log()` *Snacks.debug.log()* 85 | 86 | Log a message to the file `./debug.log`. - a timestamp will be added to every 87 | message. - accepts multiple arguments and pretty prints them. - if the argument 88 | is not a string, it will be printed using `vim.inspect`. - if the message is 89 | smaller than 120 characters, it will be printed on a single line. 90 | 91 | >lua 92 | Snacks.debug.log("Hello", { foo = "bar" }, 42) 93 | -- 2024-11-08 08:56:52 Hello { foo = "bar" } 42 94 | < 95 | 96 | >lua 97 | Snacks.debug.log(...) 98 | < 99 | 100 | 101 | `Snacks.debug.profile()` *Snacks.debug.profile()* 102 | 103 | Very simple function to profile a lua function. **flush**set to `true` to use 104 | `jit.flush` in every iteration. **count**defaults to 100 105 | 106 | >lua 107 | ---@param fn fun() 108 | ---@param opts? {count?: number, flush?: boolean, title?: string} 109 | Snacks.debug.profile(fn, opts) 110 | < 111 | 112 | 113 | `Snacks.debug.run()` *Snacks.debug.run()* 114 | 115 | Run the current buffer or a range of lines. Shows the output of `print` inlined 116 | with the code. Any error will be shown as a diagnostic. 117 | 118 | >lua 119 | ---@param opts? {name?:string, buf?:number, print?:boolean} 120 | Snacks.debug.run(opts) 121 | < 122 | 123 | 124 | `Snacks.debug.stats()` *Snacks.debug.stats()* 125 | 126 | >lua 127 | ---@param opts? {min?: number, show?:boolean} 128 | ---@return {summary:table, trace:snacks.debug.Stat[], traces:snacks.debug.Trace[]} 129 | Snacks.debug.stats(opts) 130 | < 131 | 132 | 133 | `Snacks.debug.trace()` *Snacks.debug.trace()* 134 | 135 | >lua 136 | ---@param name string? 137 | Snacks.debug.trace(name) 138 | < 139 | 140 | 141 | `Snacks.debug.tracemod()` *Snacks.debug.tracemod()* 142 | 143 | >lua 144 | ---@param modname string 145 | ---@param mod? table 146 | ---@param suffix? string 147 | Snacks.debug.tracemod(modname, mod, suffix) 148 | < 149 | 150 | ============================================================================== 151 | 3. Links *snacks-debug-links* 152 | 153 | 1. *image*: https://github.com/user-attachments/assets/0517aed7-fbd0-42ee-8058-c213410d80a7 154 | 155 | Generated by panvimdoc 156 | 157 | vim:tw=78:ts=8:noet:ft=help:norl: 158 | -------------------------------------------------------------------------------- /lua/snacks/animate/init.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.animate 2 | ---@overload fun(from: number, to: number, cb: snacks.animate.cb, opts?: snacks.animate.Opts): snacks.animate.Animation 3 | local M = setmetatable({}, { 4 | __call = function(M, ...) 5 | return M.add(...) 6 | end, 7 | }) 8 | 9 | M.meta = { 10 | desc = "Efficient animations including over 45 easing functions _(library)_", 11 | } 12 | 13 | -- All easing functions take these parameters: 14 | -- 15 | -- * `t` _(time)_: should go from 0 to duration 16 | -- * `b` _(begin)_: value of the property being ease. 17 | -- * `c` _(change)_: ending value of the property - beginning value of the property 18 | -- * `d` _(duration)_: total duration of the animation 19 | -- 20 | -- Some functions allow additional modifiers, like the elastic functions 21 | -- which also can receive an amplitud and a period parameters (defaults 22 | -- are included) 23 | ---@alias snacks.animate.easing.Fn fun(t: number, b: number, c: number, d: number): number 24 | 25 | --- Duration can be specified as the total duration or the duration per step. 26 | --- When both are specified, the minimum of both is used. 27 | ---@class snacks.animate.Duration 28 | ---@field step? number duration per step in ms 29 | ---@field total? number total duration in ms 30 | 31 | ---@class snacks.animate.Config 32 | ---@field easing? snacks.animate.easing|snacks.animate.easing.Fn 33 | local defaults = { 34 | ---@type snacks.animate.Duration|number 35 | duration = 20, -- ms per step 36 | easing = "linear", 37 | fps = 60, -- frames per second. Global setting for all animations 38 | } 39 | 40 | ---@class snacks.animate.Opts: snacks.animate.Config 41 | ---@field int? boolean interpolate the value to an integer 42 | ---@field id? number|string unique identifier for the animation 43 | 44 | ---@class snacks.animate.ctx 45 | ---@field anim snacks.animate.Animation 46 | ---@field prev number 47 | ---@field done boolean 48 | 49 | ---@alias snacks.animate.cb fun(value:number, ctx: snacks.animate.ctx) 50 | 51 | local uv = vim.uv or vim.loop 52 | local _id = 0 53 | local active = {} ---@type table 54 | local timer = assert(uv.new_timer()) 55 | local scheduled = false 56 | 57 | ---@class snacks.animate.Animation 58 | ---@field id number|string unique identifier 59 | ---@field opts snacks.animate.Opts 60 | ---@field from number start value 61 | ---@field to number end value 62 | ---@field duration number total duration in ms 63 | ---@field easing snacks.animate.easing.Fn 64 | ---@field value number current value 65 | ---@field start number start time in ms 66 | ---@field cb snacks.animate.cb 67 | local Animation = {} 68 | Animation.__index = Animation 69 | 70 | ---@return number value, boolean done 71 | function Animation:next() 72 | self.start = self.start == 0 and uv.hrtime() or self.start 73 | local elapsed = (uv.hrtime() - self.start) / 1e6 -- ms 74 | local b, c, d = self.from, self.to - self.from, self.duration 75 | local t, done = math.min(elapsed, d), elapsed >= d 76 | local value = done and b + c or self.easing(t, b, c, d) 77 | value = self.opts.int and (value + (2 ^ 52 + 2 ^ 51) - (2 ^ 52 + 2 ^ 51)) or value 78 | return value, done 79 | end 80 | 81 | ---@return boolean done 82 | function Animation:update() 83 | local value, done = self:next() 84 | local prev = self.value 85 | if prev ~= value or done then 86 | self.cb(value, { anim = self, prev = prev, done = done }) 87 | self.value = value 88 | end 89 | return done 90 | end 91 | 92 | function Animation:dirty() 93 | local value, done = self:next() 94 | return self.value ~= value or done 95 | end 96 | 97 | function Animation:stop() 98 | active[self.id] = nil 99 | end 100 | 101 | --- Add an animation 102 | ---@param from number 103 | ---@param to number 104 | ---@param cb snacks.animate.cb 105 | ---@param opts? snacks.animate.Opts 106 | function M.add(from, to, cb, opts) 107 | opts = Snacks.config.get("animate", defaults, opts) --[[@as snacks.animate.Opts]] 108 | 109 | -- calculate duration 110 | local d = type(opts.duration) == "table" and opts.duration or { step = opts.duration } 111 | ---@cast d snacks.animate.Duration 112 | local duration = 0 113 | if d.step then 114 | duration = d.step * math.abs(to - from) 115 | duration = math.min(duration, d.total or duration) 116 | elseif d.total then 117 | duration = d.total 118 | end 119 | 120 | -- resolve easing function 121 | local easing = opts.easing or "linear" 122 | easing = type(easing) == "string" and require("snacks.animate.easing")[easing] or easing 123 | ---@cast easing snacks.animate.easing.Fn 124 | 125 | _id = _id + 1 126 | ---@type snacks.animate.Animation 127 | local ret = setmetatable({ 128 | id = opts.id or _id, 129 | opts = opts, 130 | from = from, 131 | to = to, 132 | value = from, 133 | duration = duration --[[@as number]], 134 | easing = easing, 135 | start = 0, 136 | cb = cb, 137 | }, Animation) 138 | active[ret.id] = ret 139 | M.start() 140 | return ret 141 | end 142 | 143 | --- Delete an animation 144 | ---@param id number|string 145 | function M.del(id) 146 | active[id] = nil 147 | end 148 | 149 | --- Step the animations and stop loop if no animations are active 150 | ---@private 151 | function M.step() 152 | if scheduled then -- no need to check this step 153 | return 154 | elseif vim.tbl_isempty(active) then 155 | return timer:stop() 156 | end 157 | 158 | -- check if any animation needs to be updated 159 | local update = false 160 | for _, anim in pairs(active) do 161 | if anim:dirty() then 162 | update = true 163 | break 164 | end 165 | end 166 | 167 | if update then 168 | -- schedule an update 169 | scheduled = true 170 | vim.schedule(function() 171 | scheduled = false 172 | for a, anim in pairs(active) do 173 | if anim:update() then 174 | active[a] = nil 175 | end 176 | end 177 | end) 178 | end 179 | end 180 | 181 | --- Start the animation loop 182 | ---@private 183 | function M.start() 184 | if timer:is_active() then 185 | return 186 | end 187 | local opts = Snacks.config.get("animate", defaults) 188 | local ms = 1000 / (opts and opts.fps or 30) 189 | timer:start(0, ms, M.step) 190 | end 191 | 192 | return M 193 | -------------------------------------------------------------------------------- /lua/snacks/zen.lua: -------------------------------------------------------------------------------- 1 | ---@class snacks.zen 2 | ---@overload fun(opts: snacks.zen.Config): snacks.win 3 | local M = setmetatable({}, { 4 | __call = function(M, ...) 5 | return M.zen(...) 6 | end, 7 | }) 8 | 9 | M.meta = { 10 | desc = "Zen mode • distraction-free coding", 11 | } 12 | 13 | ---@class snacks.zen.Config 14 | local defaults = { 15 | -- You can add any `Snacks.toggle` id here. 16 | -- Toggle state is restored when the window is closed. 17 | -- Toggle config options are NOT merged. 18 | ---@type table 19 | toggles = { 20 | dim = true, 21 | git_signs = false, 22 | mini_diff_signs = false, 23 | -- diagnostics = false, 24 | -- inlay_hints = false, 25 | }, 26 | show = { 27 | statusline = false, -- can only be shown when using the global statusline 28 | tabline = false, 29 | }, 30 | ---@type snacks.win.Config 31 | win = { style = "zen" }, 32 | 33 | --- Options for the `Snacks.zen.zoom()` 34 | ---@type snacks.zen.Config 35 | zoom = { 36 | toggles = {}, 37 | show = { statusline = true, tabline = true }, 38 | win = { 39 | backdrop = false, 40 | width = 0, -- full width 41 | }, 42 | }, 43 | } 44 | 45 | Snacks.config.style("zen", { 46 | enter = true, 47 | fixbuf = false, 48 | minimal = false, 49 | width = 120, 50 | height = 0, 51 | backdrop = { transparent = true, blend = 40 }, 52 | keys = { q = false }, 53 | wo = { 54 | winhighlight = "NormalFloat:Normal", 55 | }, 56 | }) 57 | 58 | -- fullscreen indicator 59 | -- only shown when the window is maximized 60 | Snacks.config.style("zoom_indicator", { 61 | text = "▍ zoom 󰊓 ", 62 | minimal = true, 63 | enter = false, 64 | focusable = false, 65 | height = 1, 66 | row = 0, 67 | col = -1, 68 | backdrop = false, 69 | }) 70 | 71 | Snacks.util.set_hl({ 72 | Icon = "DiagnosticWarn", 73 | }, { prefix = "SnacksZen", default = true }) 74 | 75 | ---@param opts? {statusline: boolean, tabline: boolean} 76 | local function get_main(opts) 77 | opts = opts or {} 78 | local bottom = opts.statusline and (vim.o.cmdheight + (vim.o.laststatus == 3 and 1 or 0)) or 0 79 | local top = opts.tabline 80 | and ((vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)) and 1 or 0) 81 | or 0 82 | ---@class snacks.zen.Main values are 0-indexed 83 | local ret = { 84 | width = vim.o.columns, 85 | row = top, 86 | height = vim.o.lines - top - bottom, 87 | } 88 | return ret 89 | end 90 | 91 | local zen_win ---@type snacks.win? 92 | 93 | ---@param opts? snacks.zen.Config 94 | function M.zen(opts) 95 | local toggles = opts and opts.toggles 96 | opts = Snacks.config.get("zen", defaults, opts) 97 | opts.toggles = toggles or opts.toggles 98 | 99 | -- close if already open 100 | if zen_win and zen_win:valid() then 101 | zen_win:close() 102 | zen_win = nil 103 | return 104 | end 105 | 106 | local parent_win = vim.api.nvim_get_current_win() 107 | local buf = vim.api.nvim_get_current_buf() 108 | local win_opts = Snacks.win.resolve({ style = "zen" }, opts.win, { buf = buf }) 109 | if Snacks.util.is_transparent() and type(win_opts.backdrop) == "table" then 110 | win_opts.backdrop.transparent = false 111 | end 112 | 113 | local zoom_indicator ---@type snacks.win? 114 | local show_indicator = false 115 | 116 | -- calculate window size 117 | if win_opts.height == 0 and (opts.show.statusline or opts.show.tabline) then 118 | local main = get_main(opts.show) 119 | win_opts.row = main.row 120 | win_opts.height = function() 121 | return get_main(opts.show).height 122 | end 123 | if type(win_opts.backdrop) == "table" then 124 | win_opts.backdrop.win = win_opts.backdrop.win or {} 125 | win_opts.backdrop.win.row = win_opts.row 126 | win_opts.backdrop.win.height = win_opts.height 127 | end 128 | if win_opts.width == 0 then 129 | show_indicator = true 130 | end 131 | end 132 | 133 | -- create window 134 | local win = Snacks.win(win_opts) 135 | vim.cmd([[norm! zz]]) 136 | zen_win = win 137 | 138 | if show_indicator then 139 | zoom_indicator = Snacks.win({ 140 | show = false, 141 | style = "zoom_indicator", 142 | zindex = win.opts.zindex + 1, 143 | wo = { winhighlight = "NormalFloat:SnacksZenIcon" }, 144 | }) 145 | zoom_indicator:open_buf() 146 | local lines = vim.api.nvim_buf_get_lines(zoom_indicator.buf, 0, -1, false) 147 | zoom_indicator.opts.width = vim.api.nvim_strwidth(lines[1] or "") 148 | zoom_indicator:show() 149 | end 150 | 151 | -- set toggle states 152 | ---@type {toggle: snacks.toggle.Class, state: unknown}[] 153 | local states = {} 154 | for id, state in pairs(opts.toggles) do 155 | local toggle = Snacks.toggle.get(id) 156 | if toggle then 157 | table.insert(states, { toggle = toggle, state = toggle:get() }) 158 | toggle:set(state) 159 | end 160 | end 161 | 162 | -- restore toggle states when window is closed 163 | vim.api.nvim_create_autocmd("WinClosed", { 164 | group = win.augroup, 165 | pattern = tostring(win.win), 166 | callback = vim.schedule_wrap(function() 167 | if zoom_indicator then 168 | zoom_indicator:close() 169 | end 170 | for _, state in ipairs(states) do 171 | state.toggle:set(state.state) 172 | end 173 | end), 174 | }) 175 | 176 | -- update the buffer of the parent window 177 | -- when the zen buffer changes 178 | vim.api.nvim_create_autocmd("BufWinEnter", { 179 | group = win.augroup, 180 | callback = function() 181 | vim.api.nvim_win_set_buf(parent_win, win.buf) 182 | end, 183 | }) 184 | 185 | -- close when entering another window 186 | vim.api.nvim_create_autocmd("WinEnter", { 187 | group = win.augroup, 188 | callback = function() 189 | local w = vim.api.nvim_get_current_win() 190 | if w == win.win then 191 | return 192 | end 193 | -- exit if other window is not a floating window 194 | if vim.api.nvim_win_get_config(w).relative == "" then 195 | win:close() 196 | end 197 | end, 198 | }) 199 | return win 200 | end 201 | 202 | ---@param opts? snacks.zen.Config 203 | function M.zoom(opts) 204 | return M.zen(Snacks.config.get("zen", defaults.zoom, opts)) 205 | end 206 | 207 | return M 208 | --------------------------------------------------------------------------------