pax_global_header00006660000000000000000000000064147415052370014522gustar00rootroot0000000000000052 comment=655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3 akinsho-bufferline.nvim-655133c/000077500000000000000000000000001474150523700165205ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/.github/000077500000000000000000000000001474150523700200605ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/.github/FUNDING.yml000066400000000000000000000000201474150523700216650ustar00rootroot00000000000000github: akinsho akinsho-bufferline.nvim-655133c/.github/ISSUE_TEMPLATE/000077500000000000000000000000001474150523700222435ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/.github/ISSUE_TEMPLATE/bug-report.yml000066400000000000000000000031531474150523700250560ustar00rootroot00000000000000name: Bug Report description: File a bug report title: "[Bug]: " labels: [bug, triage] body: - type: checkboxes attributes: label: Is there an existing issue for this? description: Please search to see if an issue already exists for the bug you encountered. options: - label: I have searched the existing issues required: true - type: markdown attributes: value: | Please make sure you have read *both* the README *and* help text before opening an issue. Highlight groups are documented in the help docs. - type: textarea id: what-happened attributes: label: What happened? placeholder: Please describe the bug you are experiencing? value: "The issue is that..." validations: required: true - type: textarea id: what-expected attributes: label: What did you expect to happen? description: Expected behaviour value: "Expected behaviour" validations: required: true - type: textarea id: config attributes: label: Config description: The configuration you use with bufferline that causes this issue placeholder: Please add your bufferline configuration validations: required: true - type: textarea id: additional-information attributes: label: Additional Information placeholder: Add in any relevant additional information value: "..." validations: required: false - type: input id: commit attributes: label: commit description: what commit triggered this issue? validations: required: false akinsho-bufferline.nvim-655133c/.github/ISSUE_TEMPLATE/feature-request.yml000066400000000000000000000016011474150523700261050ustar00rootroot00000000000000name: Feature Request description: Request for a new feature title: "[Feature Request]: " labels: ["enhancement"] body: - type: textarea id: "what-feature" attributes: label: "What?" description: "Describe the feature you want to see." placeholder: "Please describe the feature you want to see and most importantly how it will work." - type: textarea id: "why-feature" attributes: label: "Why?" description: "Describe the reason why you want to see this feature." placeholder: "Please describe the reason why you want this feature, it should be applicable to more people than just you." - type: markdown attributes: value: | Please note that I won't just add features simply because you request them, my time is limited so if you would like to see a feature you should consider if and how you can contribute it yourself. akinsho-bufferline.nvim-655133c/.github/release.yaml000066400000000000000000000006141474150523700223650ustar00rootroot00000000000000changelog: exclude: labels: - ignore-for-release categories: - title: Breaking Changes 🛠 labels: - breaking - breaking-change - title: New Features 🎉 labels: - feature - enhancement - title: Bug Fixes 🐛 labels: - fix - bug - bugfix - title: Other Changes labels: - "*" akinsho-bufferline.nvim-655133c/.github/workflows/000077500000000000000000000000001474150523700221155ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/.github/workflows/ci.yaml000066400000000000000000000043371474150523700234030ustar00rootroot00000000000000name: CI on: push: # paths-ignore: # - ".github/**" # - "*.md" jobs: format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: JohnnyMorganz/stylua-action@v2 with: version: latest token: ${{ secrets.GITHUB_TOKEN }} args: --config-path=stylua.toml lua/ - name: Commit files run: | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" if ! [[ -z $(git status -s) ]]; then git commit -m "chore: formatting" lua/* fi - name: Push changes uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: ${{ github.ref }} tests: name: Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Neovim shell: bash run: | mkdir -p /tmp/nvim wget -q https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage -O /tmp/nvim/nvim.appimage cd /tmp/nvim chmod a+x ./nvim.appimage ./nvim.appimage --appimage-extract echo "/tmp/nvim/squashfs-root/usr/bin/" >> $GITHUB_PATH - name: Run tests run: | nvim --version [ ! -d tests ] && exit 0 nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal_init.lua', sequential = true}" release: name: release if: ${{ github.ref == 'refs/heads/main' }} needs: - tests runs-on: ubuntu-latest steps: - uses: google-github-actions/release-please-action@v3 id: release with: release-type: simple package-name: bufferline.nvim - uses: actions/checkout@v3 - name: tag stable versions if: ${{ steps.release.outputs.release_created }} run: | git config user.name github-actions[bot] git config user.email github-actions[bot]@users.noreply.github.com git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git" akinsho-bufferline.nvim-655133c/.github/workflows/contributing.yaml000066400000000000000000000005031474150523700255060ustar00rootroot00000000000000name: Semantic Pull Request Subject on: pull_request: types: - opened - reopened - edited - synchronize jobs: semantic-pr-subject: runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@v4.5.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} akinsho-bufferline.nvim-655133c/.gitignore000066400000000000000000000000441474150523700205060ustar00rootroot00000000000000.luacheckcache screenshots/ .tests/ akinsho-bufferline.nvim-655133c/.luarc.json000066400000000000000000000006601474150523700206010ustar00rootroot00000000000000{ "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", "Lua.diagnostics.disable": [ "assign-type-mismatch", "cast-local-type", "missing-parameter" ], "Lua.diagnostics.globals": [ "vim", "P", "describe", "it", "before_each", "after_each", "packer_plugins", "___bufferline_private" ] }akinsho-bufferline.nvim-655133c/CHANGELOG.md000066400000000000000000000271541474150523700203420ustar00rootroot00000000000000# Changelog ## [4.9.1](https://github.com/akinsho/bufferline.nvim/compare/v4.9.0...v4.9.1) (2025-01-14) ### Bug Fixes * **utils:** correctly check for showing buffer icons ([1363a05](https://github.com/akinsho/bufferline.nvim/commit/1363a05043f1bca8012b979474fd35936f264d7c)) ## [4.9.0](https://github.com/akinsho/bufferline.nvim/compare/v4.8.0...v4.9.0) (2024-10-24) ### Features * **pick:** add config option for pick alphabet ([#972](https://github.com/akinsho/bufferline.nvim/issues/972)) ([5cc447c](https://github.com/akinsho/bufferline.nvim/commit/5cc447cb2b463cb499c82eaeabbed4f5fa6a0a44)) ## [4.8.0](https://github.com/akinsho/bufferline.nvim/compare/v4.7.0...v4.8.0) (2024-10-22) ### Features * **tabpages:** pass the bufnr to the `name_formatter` ([#941](https://github.com/akinsho/bufferline.nvim/issues/941)) ([28e347d](https://github.com/akinsho/bufferline.nvim/commit/28e347dbc6d0e8367ea56fb045fb9d135579ff79)) ## [4.7.0](https://github.com/akinsho/bufferline.nvim/compare/v4.6.1...v4.7.0) (2024-07-10) ### Features * **diag:** add `diagnostics_update_on_event` option ([#932](https://github.com/akinsho/bufferline.nvim/issues/932)) ([aa16daf](https://github.com/akinsho/bufferline.nvim/commit/aa16dafdc642594c7ade7e88d31a6119feb189d6)) ### Bug Fixes * **tabs:** use custom separator_style in tabpages ([#852](https://github.com/akinsho/bufferline.nvim/issues/852)) ([81820ca](https://github.com/akinsho/bufferline.nvim/commit/81820cac7c85e51e4cf179f8a66d13dbf7b032d9)) * UNKNOWN PLUGIN error resulting from unloaded buffers ([#928](https://github.com/akinsho/bufferline.nvim/issues/928)) ([6ac7e4f](https://github.com/akinsho/bufferline.nvim/commit/6ac7e4f1eead72507cfdbc94dcd0c26b98b2f86e)) * UNKNOWN PLUGIN error resulting from unloaded buffers ([#931](https://github.com/akinsho/bufferline.nvim/issues/931)) ([1662fed](https://github.com/akinsho/bufferline.nvim/commit/1662fed6ecd512d1f381fc2a4e77532c379d25c6)) ### Reverts * remove fix for referencing unloaded buffers ([#930](https://github.com/akinsho/bufferline.nvim/issues/930)) ([46192e7](https://github.com/akinsho/bufferline.nvim/commit/46192e794b73f92136326c10ecdbdbf15e35705f)) ## [4.6.1](https://github.com/akinsho/bufferline.nvim/compare/v4.6.0...v4.6.1) (2024-05-21) ### Bug Fixes * replace tbl_flatten to flatten():totable() ([#912](https://github.com/akinsho/bufferline.nvim/issues/912)) ([b2dc003](https://github.com/akinsho/bufferline.nvim/commit/b2dc003aca1dc638ccc3e7752ab3969b4184a690)) ## [4.6.0](https://github.com/akinsho/bufferline.nvim/compare/v4.5.3...v4.6.0) (2024-05-20) ### Features * add `auto_toggle_bufferline` option ([#876](https://github.com/akinsho/bufferline.nvim/issues/876)) ([f6f00d9](https://github.com/akinsho/bufferline.nvim/commit/f6f00d9ac1a51483ac78418f9e63126119a70709)) ### Bug Fixes * maintain backwards compatibility ([#909](https://github.com/akinsho/bufferline.nvim/issues/909)) ([155b257](https://github.com/akinsho/bufferline.nvim/commit/155b257b0c1d7999b0ffc837e1dd3a110cdc33d0)) * reimplement the deprecated function tbl_add_reverse_lookup ([#904](https://github.com/akinsho/bufferline.nvim/issues/904)) ([9ae49d7](https://github.com/akinsho/bufferline.nvim/commit/9ae49d71c84b42b91795f7b7cead223c6346e774)) * **utils:** update is_list to handle breaking change ([#892](https://github.com/akinsho/bufferline.nvim/issues/892)) ([a6ad228](https://github.com/akinsho/bufferline.nvim/commit/a6ad228f77c276a4324924a6899cbfad70541547)) * vim.diagnostic.is_disabled() deprecation warning ([#907](https://github.com/akinsho/bufferline.nvim/issues/907)) ([2cd3984](https://github.com/akinsho/bufferline.nvim/commit/2cd39842c6426fb6c9a79fa57420121cc81c9804)) ## [4.5.3](https://github.com/akinsho/bufferline.nvim/compare/v4.5.2...v4.5.3) (2024-04-19) ### Bug Fixes * **utils:** improve path separator detection on Windows ([#888](https://github.com/akinsho/bufferline.nvim/issues/888)) ([d7ebc0d](https://github.com/akinsho/bufferline.nvim/commit/d7ebc0de62a2f752dcd3cadf6f3235a0702f15a3)) ## [4.5.2](https://github.com/akinsho/bufferline.nvim/compare/v4.5.1...v4.5.2) (2024-03-07) ### Bug Fixes * **tabpages:** renaming bug on reopened tab ([#877](https://github.com/akinsho/bufferline.nvim/issues/877)) ([1064399](https://github.com/akinsho/bufferline.nvim/commit/10643990c33ca295bfe970d775c6e7697354aa0f)) ## [4.5.1](https://github.com/akinsho/bufferline.nvim/compare/v4.5.0...v4.5.1) (2024-03-05) ### Bug Fixes * **tabpages:** typo in rename_tab ([#873](https://github.com/akinsho/bufferline.nvim/issues/873)) ([5bf13d1](https://github.com/akinsho/bufferline.nvim/commit/5bf13d17a8c8abbce8d3ef83c8658b32e08ce913)) ## [4.5.0](https://github.com/akinsho/bufferline.nvim/compare/v4.4.1...v4.5.0) (2024-01-22) ### Features * **ui:** tab renaming ([#848](https://github.com/akinsho/bufferline.nvim/issues/848)) ([f2e6c86](https://github.com/akinsho/bufferline.nvim/commit/f2e6c86975deb0f4594d671b7f31c379802491d3)) ### Bug Fixes * skip invalid regex in truncate_name ([#841](https://github.com/akinsho/bufferline.nvim/issues/841)) ([ac788fb](https://github.com/akinsho/bufferline.nvim/commit/ac788fbc493839c1e76daa8d119934b715fdb90e)) ## [4.4.1](https://github.com/akinsho/bufferline.nvim/compare/v4.4.0...v4.4.1) (2023-12-06) ### Bug Fixes * **commands:** potential nil access ([#821](https://github.com/akinsho/bufferline.nvim/issues/821)) ([6e96fa2](https://github.com/akinsho/bufferline.nvim/commit/6e96fa27a0d4dd6c00a252b51c0b43b9b95cd302)) * remove `missing required fields` diagnostic from config ([#812](https://github.com/akinsho/bufferline.nvim/issues/812)) ([1a33975](https://github.com/akinsho/bufferline.nvim/commit/1a3397556d194bb1f2cc530b07124ccc512c5501)) * use link if specified in custom areas ([#839](https://github.com/akinsho/bufferline.nvim/issues/839)) ([9ca364d](https://github.com/akinsho/bufferline.nvim/commit/9ca364d488b98894ca780c40aae9ea63967c8fcf)) ## [4.4.0](https://github.com/akinsho/bufferline.nvim/compare/v4.3.0...v4.4.0) (2023-09-20) ### Features * Support `name_formatter` for unnamed buffers ([#806](https://github.com/akinsho/bufferline.nvim/issues/806)) ([9961d87](https://github.com/akinsho/bufferline.nvim/commit/9961d87bb3ec008213c46ba14b3f384a5f520eb5)) ### Bug Fixes * **diagnostics:** ignore disabled diagnostics ([#816](https://github.com/akinsho/bufferline.nvim/issues/816)) ([8a51c4b](https://github.com/akinsho/bufferline.nvim/commit/8a51c4b5d105d93fd2bc435bf93d4d5556fb2a60)) * **icons:** display overriden devicons ([#817](https://github.com/akinsho/bufferline.nvim/issues/817)) ([81cd04f](https://github.com/akinsho/bufferline.nvim/commit/81cd04fe7c914d020d331cea1e707da5f14c2665)) * **readme:** Typo ([#793](https://github.com/akinsho/bufferline.nvim/issues/793)) ([99f0932](https://github.com/akinsho/bufferline.nvim/commit/99f0932365b34e22549ff58e1bea388465d15e99)) ## [4.3.0](https://github.com/akinsho/bufferline.nvim/compare/v4.2.0...v4.3.0) (2023-07-17) ### Features * **command:** add BufferLineCloseOthers command ([#774](https://github.com/akinsho/bufferline.nvim/issues/774)) ([9d6ab3a](https://github.com/akinsho/bufferline.nvim/commit/9d6ab3a56ad71bed9929c7acd7620e827a073d25)) * **ui:** trunc marker highlights ([#781](https://github.com/akinsho/bufferline.nvim/issues/781)) ([77779e3](https://github.com/akinsho/bufferline.nvim/commit/77779e34d673dd41244b710c22fb18bbfa4c455f)), closes [#792](https://github.com/akinsho/bufferline.nvim/issues/792) ### Bug Fixes * **config:** highlighting for tab separators ([#784](https://github.com/akinsho/bufferline.nvim/issues/784)) ([cd27a52](https://github.com/akinsho/bufferline.nvim/commit/cd27a52ecdfed7f14a41b61b7976f155e3d593c7)) * store paths in g:BufferlinePositions ([#780](https://github.com/akinsho/bufferline.nvim/issues/780)) ([2f391fd](https://github.com/akinsho/bufferline.nvim/commit/2f391fde91b9c3876eee359ee24cc352050e5e48)) * **ui:** always schedule refreshing ([fe77474](https://github.com/akinsho/bufferline.nvim/commit/fe774743cc7434d8f5539093108bf7d6d950f416)) ## [4.2.0](https://github.com/akinsho/bufferline.nvim/compare/v4.1.0...v4.2.0) (2023-06-26) ### Features * **commands/go_to:** go to the last element if index out of bounds ([#758](https://github.com/akinsho/bufferline.nvim/issues/758)) ([6073426](https://github.com/akinsho/bufferline.nvim/commit/60734264a8655a7db3595159fb50076dc24c2f2c)) * **commands:** add option to wrap when moving buffers at ends ([#759](https://github.com/akinsho/bufferline.nvim/issues/759)) ([da1875c](https://github.com/akinsho/bufferline.nvim/commit/da1875c1eee9aa9b7e19cda5c70ed7d7702d5f06)) ### Performance Improvements * **ui:** avoid (some) expensive functions ([#754](https://github.com/akinsho/bufferline.nvim/issues/754)) ([018bdf6](https://github.com/akinsho/bufferline.nvim/commit/018bdf61a97e00caeff05d16977437c63018762e)) ## [4.1.0](https://github.com/akinsho/bufferline.nvim/compare/v4.0.0...v4.1.0) (2023-05-03) ### Features * **ui:** add `padded_slope` style ([#739](https://github.com/akinsho/bufferline.nvim/issues/739)) ([f336811](https://github.com/akinsho/bufferline.nvim/commit/f336811168e04362dfceb51b7e992dfd6ae4e78e)) ### Bug Fixes * **docs:** use correct value for style presets ([#747](https://github.com/akinsho/bufferline.nvim/issues/747)) ([9eed863](https://github.com/akinsho/bufferline.nvim/commit/9eed86350dcb4a5cca13056d0d16ba85e20e5024)) * **groups:** use correct cmdline completion function ([a4bd445](https://github.com/akinsho/bufferline.nvim/commit/a4bd44523316928a7c4a5c09a3407d02c30b6027)) ## [4.0.0](https://github.com/akinsho/bufferline.nvim/compare/v3.7.0...v4.0.0) (2023-04-23) ### ⚠ BREAKING CHANGES * **groups:** change argument to group matcher * **config:** deprecate show_buffer_default_icon ### Features * **colors:** add diagnostic underline fallback ([bd9915f](https://github.com/akinsho/bufferline.nvim/commit/bd9915fa13f53176fe3a4a943e3f95c7e4312e50)) * **config:** allow specifying style presets ([13cb114](https://github.com/akinsho/bufferline.nvim/commit/13cb114e91c17238aaa271746aaeb8e967f350a2)) * **diag:** sane fallback to underline color ([0cd505b](https://github.com/akinsho/bufferline.nvim/commit/0cd505b333151e883cdd854539e5eae0e4f3e339)) ### Bug Fixes * **color:** follow linked hl groups ([e6e7cc4](https://github.com/akinsho/bufferline.nvim/commit/e6e7cc454fa28304246e97a9acfe7c6cf2adc5d6)) * **highlights:** if color_icons is false set to NONE ([8b32447](https://github.com/akinsho/bufferline.nvim/commit/8b32447f1ba00f71ec2ebb413249d1d84228d9fb)), closes [#702](https://github.com/akinsho/bufferline.nvim/issues/702) * **sorters:** insert_after_current strategy ([1620cfe](https://github.com/akinsho/bufferline.nvim/commit/1620cfe8f226b49bfc4886a092449f565b4d84ab)) ### Code Refactoring * **config:** deprecate show_buffer_default_icon ([6ccdee8](https://github.com/akinsho/bufferline.nvim/commit/6ccdee8e931503699eb8f92c7faafd0ad1a8cf69)) * **groups:** change argument to group matcher ([38d62b8](https://github.com/akinsho/bufferline.nvim/commit/38d62b8bae62c681d6e259de54421d4155976897)) ## [3.7.0](https://github.com/akinsho/bufferline.nvim/compare/v3.6.0...v3.7.0) (2023-04-15) ### Features * **groups:** close and unpin ([#698](https://github.com/akinsho/bufferline.nvim/issues/698)) ([52241b5](https://github.com/akinsho/bufferline.nvim/commit/52241b57ed41c2283020c6c79ef48fc7cd808bea)) ### Bug Fixes * **ui:** Use correct function to check for list ([#726](https://github.com/akinsho/bufferline.nvim/issues/726)) ([dd86c31](https://github.com/akinsho/bufferline.nvim/commit/dd86c312fd225549ac02567d47570c04ba456402)) * **utils:** fix utils.is_list ([#728](https://github.com/akinsho/bufferline.nvim/issues/728)) ([2c8d615](https://github.com/akinsho/bufferline.nvim/commit/2c8d615c47a5013b24b3b4bdebec2fda1b38cdd9)) akinsho-bufferline.nvim-655133c/LICENSE000066400000000000000000001045151474150523700175330ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . akinsho-bufferline.nvim-655133c/README.md000066400000000000000000000305411474150523700200020ustar00rootroot00000000000000[![CI](https://github.com/akinsho/bufferline.nvim/actions/workflows/ci.yaml/badge.svg)](https://github.com/akinsho/bufferline.nvim/actions/workflows/ci.yaml)

bufferline.nvim

A snazzy 💅 buffer line (with tabpage integration) for Neovim built using lua.

![Demo GIF](https://user-images.githubusercontent.com/22454918/111992693-9c6a9b00-8b0d-11eb-8c39-19db58583061.gif) - [Requirements](#requirements) - [Installation](#installation) - [Usage](#usage) - [Configuration](#configuration) - [Features](#features) - [Alternate styling](#alternate-styling) - [Hover events](#hover-events) - [Underline indicator](#underline-indicator) - [Tabpages](#tabpages) - [LSP indicators](#lsp-indicators) - [Groups](#groups) - [Sidebar offsets](#sidebar-offsets) - [Numbers](#numbers) - [Picking](#picking) - [Pinning](#pinning) - [Unique names](#unique-names) - [Close icons](#close-icons) - [Re-ordering](#re-ordering) - [Custom areas](#custom-areas) - [How do I see only buffers per tab?](#how-do-i-see-only-buffers-per-tab) - [Caveats](#caveats) - [FAQ](#faq) This plugin shamelessly attempts to emulate the aesthetics of GUI text editors/Doom Emacs. It was inspired by a screenshot of DOOM Emacs using [centaur tabs](https://github.com/ema2159/centaur-tabs). ## Requirements - Neovim 0.8+ - A patched font (see [nerd fonts](https://github.com/ryanoasis/nerd-fonts)) - A colorscheme (either your custom highlight or a maintained one somewhere) ## Installation It is advised that you specify either the latest tag or a specific tag and bump them manually if you'd prefer to inspect changes before updating. If you'd like to use an older version of the plugin compatible with nvim-0.6.1 and below please change your tag to `tag = "v1.*"` **Lua** ```lua -- using packer.nvim use {'akinsho/bufferline.nvim', tag = "*", requires = 'nvim-tree/nvim-web-devicons'} -- using lazy.nvim {'akinsho/bufferline.nvim', version = "*", dependencies = 'nvim-tree/nvim-web-devicons'} ``` **Vimscript** ```vim Plug 'nvim-tree/nvim-web-devicons' " Recommended (for coloured icons) " Plug 'ryanoasis/vim-devicons' Icons without colours Plug 'akinsho/bufferline.nvim', { 'tag': '*' } ``` ## Usage See the docs for details `:h bufferline.nvim` You need to be using `termguicolors` for this plugin to work, as it reads the hex `gui` color values of various highlight groups. **Vimscript** ```vim " In your init.lua or init.vim set termguicolors lua << EOF require("bufferline").setup{} EOF ``` **Lua** ```lua vim.opt.termguicolors = true require("bufferline").setup{} ``` You can close buffers by clicking the close icon or by _right clicking_ the tab anywhere ## Configuration for more details on how to configure this plugin in details please see `:h bufferline-configuration` ## Features - Colours derived from colorscheme where possible. - Sort buffers by `extension`, `directory` or pass in a custom compare function - Configuration via lua functions for greater customization. #### Alternate styling ##### Slanted tabs ![slanted tabs](https://user-images.githubusercontent.com/22454918/111992989-fec39b80-8b0d-11eb-851b-010641196a04.png) **NOTE**: some terminals require special characters to be padded so set the style to `padded_slant` if the appearance isn't right in your terminal emulator. Please keep in mind though that results may vary depending on your terminal emulator of choice and this style might not work for all terminals ##### Sloped tabs ![sloped tabs](https://user-images.githubusercontent.com/22454918/220115787-0ba2264f-1cf5-4f18-a322-7c7cfa3d8f42.png) see: `:h bufferline-styling` --- #### Hover events **NOTE**: this is _only_ available for >= neovim 0.8+ ![hover-event-preview](https://user-images.githubusercontent.com/22454918/189106657-163b0550-897c-42c8-a571-d899bdd69998.gif) see `:help bufferline-hover-events` for more information on configuration --- #### Underline indicator Screen Shot 2022-08-22 at 09 14 24 **NOTE**: as with the above your mileage will vary based on your terminal emulator. The screenshot above was achieved using kitty nightly (as of August 2022), with increased underline thickness and an increased underline position so it sits further from the text --- #### Tabpages Screen Shot 2022-03-08 at 17 39 57 This plugin can also be set to show only tabpages. This can be done by setting the `mode` option to `tabs`. This will change the bufferline to a tabline it has a lot of the same features/styling but not all. A few things to note are - Sorting doesn't work yet as that needs to be thought through. - Grouping doesn't work yet as that also needs to be thought through. --- #### LSP indicators ![LSP Indicator](https://user-images.githubusercontent.com/22454918/113215394-b1180300-9272-11eb-9632-8a9f9aae99fa.png) By setting `diagnostics = "nvim_lsp" | "coc"` you will get an indicator in the bufferline for a given tab if it has any errors This will allow you to tell at a glance if a particular buffer has errors. In order to customise the appearance of the diagnostic count you can pass a custom function in your setup.
snippet ```lua -- rest of config ... --- count is an integer representing total count of errors --- level is a string "error" | "warning" --- diagnostics_dict is a dictionary from error level ("error", "warning" or "info")to number of errors for each level. --- this should return a string --- Don't get too fancy as this function will be executed a lot diagnostics_indicator = function(count, level, diagnostics_dict, context) local icon = level:match("error") and " " or " " return " " .. icon .. count end ```
![diagnostics_indicator](https://user-images.githubusercontent.com/4028913/112573484-9ee92100-8da9-11eb-9ffd-da9cb9cae3a6.png)
snippet ```lua diagnostics_indicator = function(count, level, diagnostics_dict, context) local s = " " for e, n in pairs(diagnostics_dict) do local sym = e == "error" and " " or (e == "warning" and " " or " ") s = s .. n .. sym end return s end ```
The highlighting for the file name if there is an error can be changed by replacing the highlights for see: `:h bufferline-highlights` LSP indicators can additionally be reported conditionally, based on buffer context. For instance, you could disable reporting LSP indicators for the current buffer and only have them appear for other buffers. ```lua diagnostics_indicator = function(count, level, diagnostics_dict, context) if context.buffer:current() then return '' end return '' end ``` ![current](https://user-images.githubusercontent.com/58056722/119390133-e5d19500-bccc-11eb-915d-f5d11f8e652c.jpeg) ![visible](https://user-images.githubusercontent.com/58056722/119390136-e66a2b80-bccc-11eb-9a87-e622e3e20563.jpeg) The first bufferline shows `diagnostic.lua` as the currently opened `current` buffer. It has LSP reported errors, but they don't show up in the bufferline. The second bufferline shows `500-nvim-bufferline.lua` as the currently opened `current` buffer. Because the 'faulty' `diagnostic.lua` buffer has now transitioned from `current` to `visible`, the LSP indicator does show up. --- #### Groups ![bufferline_group_toggle](https://user-images.githubusercontent.com/22454918/132410772-0a4c0b95-63bb-4281-8a4e-a652458c3f0f.gif) The buffers this plugin shows can be grouped based on a users configuration. Groups are a way of allowing a user to visualize related buffers in clusters as well as operating on them together e.g. by clicking the group indicator all grouped buffers can be hidden. They are partially inspired by google chrome's tabs as well as centaur tab's groups. see `:help bufferline-groups` for more information on how to set these up --- #### Sidebar offsets ![explorer header](https://user-images.githubusercontent.com/22454918/117363338-5fd3e280-aeb4-11eb-99f2-5ec33dff6f31.png) --- #### Numbers ![bufferline with numbers](https://user-images.githubusercontent.com/22454918/119562833-b5f2c200-bd9e-11eb-81d3-06876024bf30.png) You can prefix buffer names with either the `ordinal` or `buffer id`, using the `numbers` option. Currently this can be specified as either a string of `buffer_id` | `ordinal` or a function ![numbers](https://user-images.githubusercontent.com/22454918/130784872-936d4c55-b9dd-413b-871d-7bc66caf8f17.png) see `:help bufferline-numbers` for more details --- #### Unique names ![duplicate names](https://user-images.githubusercontent.com/22454918/111993343-6da0f480-8b0e-11eb-8d93-44019458d2c9.png) --- #### Close icons ![close button](https://user-images.githubusercontent.com/22454918/111993390-7a254d00-8b0e-11eb-9951-43b4350f6a29.gif) --- #### Re-ordering ![re-order buffers](https://user-images.githubusercontent.com/22454918/111993463-91643a80-8b0e-11eb-87f0-26acfe92c021.gif) This order can be persisted between sessions (enabled by default). --- #### Picking ![bufferline pick](https://user-images.githubusercontent.com/22454918/111993296-5bbf5180-8b0e-11eb-9ad9-fcf9619436fd.gif) --- #### Pinning Screen Shot 2022-03-31 at 18 13 50 --- #### Custom areas ![custom area](https://user-images.githubusercontent.com/22454918/118527523-4d219f00-b739-11eb-889f-60fb06fd71bc.png) see `:help bufferline-custom-areas` ## How do I see only buffers per tab? This behaviour is _not native in neovim_ there is no internal concept of localised buffers to tabs as that is not how tabs were designed to work. They were designed to show an arbitrary layout of windows per tab. You can get this behaviour using [scope.nvim](https://github.com/tiagovla/scope.nvim) with this plugin. Although I believe a better long-term solution for users who want this functionality is to ask for real native support for this upstream. ## Caveats - This won't appeal to everyone's tastes. This plugin is opinionated about how the tabline looks, it's unlikely to please everyone. - I want to prevent this becoming a pain to maintain so I'll be conservative about what I add. - This plugin relies on some basic highlights being set by your colour scheme i.e. `Normal`, `String`, `TabLineSel` (`WildMenu` as fallback), `Comment`. It's unlikely to work with all colour schemes. You can either try manually overriding the colours or manually creating these highlight groups before loading this plugin. - If the contrast in your colour scheme isn't very high, think an all black colour scheme, some of the highlights of this plugin won't really work as intended since it depends on darkening things. ## FAQ - **Why isn't the bufferline appearing?** The most common reason for this that has come up in various issues is it clashes with another plugin. Please make sure that you do not have another bufferline plugin installed. If you are using `airline` make sure you set `let g:airline#extensions#tabline#enabled = 0`. If you are using `lightline` this also takes over the tabline by default and needs to be deactivated. If you are on Windows and use the GUI version of nvim (nvim-qt.exe) then also ensure, that `GuiTabline` is disabled. For this create a file called `ginit.vim` in your nvim config directory and put the line `GuiTabline 0` in it. Otherwise the QT tabline will overlay any terminal tablines. - **Doesn't this plugin go against the "vim way"?** This is much better explained by [buftablines's author](https://github.com/ap/vim-buftabline#why-this-and-not-vim-tabs). Please read this for a more comprehensive answer to this question. The short answer to this is buffers represent files in nvim and tabs, a collection of windows (or just one). Vim natively allows visualising tabs i.e. collections of window, but not just the files that are open. There are _endless_ debates on this topic, but allowing a user to see what files they have open doesn't go against any clearly stated vim philosophy. It's a text editor and not a religion 🙏. Obviously this won't appeal to everyone, which isn't really a feasible objective anyway. akinsho-bufferline.nvim-655133c/doc/000077500000000000000000000000001474150523700172655ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/doc/bufferline.txt000066400000000000000000001406121474150523700221530ustar00rootroot00000000000000*bufferline.nvim* For Neovim version 0.5+ Last change: 2021 August 20 A snazzy bufferline for neovim written in lua Author: Akin Sowemimo ============================================================================== CONTENTS *bufferline* *bufferline-contents* Introduction...........................: |bufferline-introduction| Usage..................................: |bufferline-usage| Configuration..........................: |bufferline-configuration| Hover Events...........................: |bufferline-hover-events| Styling................................: |bufferline-styling| Style Presets..........................: |bufferline-style-presets| Tabpages...............................: |bufferline-tabpages| Numbers................................: |bufferline-numbers| LSP Diagnostics........................: |bufferline-diagnostics| Groups.................................: |bufferline-groups| Sorting................................: |bufferline-sorting| Filtering..............................: |bufferline-filtering| Commands...............................: |bufferline-commands| Custom functions.......................: |bufferline-functions| Pick...................................: |bufferline-pick| Mappings...............................: |bufferline-mappings| Highlights.............................: |bufferline-highlights| Mouse actions..........................: |bufferline-mouse-actions| Custom areas...........................: |bufferline-custom-areas| Working with Elements..................: |bufferline-working-with-elements| Issues.................................: |bufferline-issues| ============================================================================== INTRODUCTION *bufferline-introduction* A _snazzy_ 💅 buffer line (tab integration) for Neovim built using `lua`. This plugin shamelessly attempts to emulate the aesthetics of GUI text editors/Doom Emacs. It was inspired by a screenshot of DOOM Emacs using centaur tabs (https://github.com/ema2159/centaur-tabs). I don't intend to copy all of it's functionality though. ============================================================================== USAGE *bufferline-usage* You are *strongly* advised to use `termguicolors` for this plugin, especially if you are using a colorscheme that uses gui colour values. However this plugin also works with cterm terminal colours. When `termguicolors` is enabled, it reads the hex `gui` colour values of various highlight groups. Otherwise it expects the user to explicitly specify colour numbers (most modern terminals allow 256 colour values). Please be aware that if you choose to use this option it will be *your* responsibility to make the colours work to your liking. Please do not open an issue if you are unable to do so, as you use this option entirely at your discretion. >vim set termguicolors " In your init.vim or init.lua lua require"bufferline".setup() < ============================================================================== CONFIGURATION *bufferline-configuration* The available configuration are: >lua local bufferline = require('bufferline') bufferline.setup { options = { mode = "buffers", -- set to "tabs" to only show tabpages instead style_preset = bufferline.style_preset.default, -- or bufferline.style_preset.minimal, themable = true | false, -- allows highlight groups to be overriden i.e. sets highlights as default numbers = "none" | "ordinal" | "buffer_id" | "both" | function({ ordinal, id, lower, raise }): string, close_command = "bdelete! %d", -- can be a string | function, | false see "Mouse actions" right_mouse_command = "bdelete! %d", -- can be a string | function | false, see "Mouse actions" left_mouse_command = "buffer %d", -- can be a string | function, | false see "Mouse actions" middle_mouse_command = nil, -- can be a string | function, | false see "Mouse actions" indicator = { icon = '▎', -- this should be omitted if indicator style is not 'icon' style = 'icon' | 'underline' | 'none', }, buffer_close_icon = '󰅖', modified_icon = '● ', close_icon = ' ', left_trunc_marker = ' ', right_trunc_marker = ' ', --- name_formatter can be used to change the buffer's label in the bufferline. --- Please note some names can/will break the --- bufferline so use this at your discretion knowing that it has --- some limitations that will *NOT* be fixed. name_formatter = function(buf) -- buf contains: -- name | str | the basename of the active file -- path | str | the full path of the active file -- bufnr | int | the number of the active buffer -- buffers (tabs only) | table(int) | the numbers of the buffers in the tab -- tabnr (tabs only) | int | the "handle" of the tab, can be converted to its ordinal number using: `vim.api.nvim_tabpage_get_number(buf.tabnr)` end, max_name_length = 18, max_prefix_length = 15, -- prefix used when a buffer is de-duplicated truncate_names = true, -- whether or not tab names should be truncated tab_size = 18, diagnostics = false | "nvim_lsp" | "coc", diagnostics_update_in_insert = false, -- only applies to coc diagnostics_update_on_event = true, -- use nvim's diagnostic handler -- The diagnostics indicator can be set to nil to keep the buffer name highlight but delete the highlighting diagnostics_indicator = function(count, level, diagnostics_dict, context) return "("..count..")" end, -- NOTE: this will be called a lot so don't do any heavy processing here custom_filter = function(buf_number, buf_numbers) -- filter out filetypes you don't want to see if vim.bo[buf_number].filetype ~= "" then return true end -- filter out by buffer name if vim.fn.bufname(buf_number) ~= "" then return true end -- filter out based on arbitrary rules -- e.g. filter out vim wiki buffer from tabline in your work repo if vim.fn.getcwd() == "" and vim.bo[buf_number].filetype ~= "wiki" then return true end -- filter out by it's index number in list (don't show first buffer) if buf_numbers[1] ~= buf_number then return true end end, offsets = { { filetype = "NvimTree", text = "File Explorer" | function , text_align = "left" | "center" | "right" separator = true } }, color_icons = true | false, -- whether or not to add the filetype icon highlights get_element_icon = function(element) -- element consists of {filetype: string, path: string, extension: string, directory: string} -- This can be used to change how bufferline fetches the icon -- for an element e.g. a buffer or a tab. -- e.g. local icon, hl = require('nvim-web-devicons').get_icon_by_filetype(element.filetype, { default = false }) return icon, hl -- or local custom_map = {my_thing_ft: {icon = "my_thing_icon", hl}} return custom_map[element.filetype] end, show_buffer_icons = true | false, -- disable filetype icons for buffers show_buffer_close_icons = true | false, show_close_icon = true | false, show_tab_indicators = true | false, show_duplicate_prefix = true | false, -- whether to show duplicate buffer prefix duplicates_across_groups = true, -- whether to consider duplicate paths in different groups as duplicates persist_buffer_sort = true, -- whether or not custom sorted buffers should persist move_wraps_at_ends = false, -- whether or not the move command "wraps" at the first or last position -- can also be a table containing 2 custom separators -- [focused and unfocused]. eg: { '|', '|' } separator_style = "slant" | "slope" | "thick" | "thin" | { 'any', 'any' }, enforce_regular_tabs = false | true, always_show_bufferline = true | false, auto_toggle_bufferline = true | false, hover = { enabled = true, delay = 200, reveal = {'close'} }, sort_by = 'insert_after_current' |'insert_at_end' | 'id' | 'extension' | 'relative_directory' | 'directory' | 'tabs' | function(buffer_a, buffer_b) -- add custom logic local modified_a = vim.fn.getftime(buffer_a.path) local modified_b = vim.fn.getftime(buffer_b.path) return modified_a > modified_b end, pick = { alphabet = "abcdefghijklmopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ1234567890", }, } } < ============================================================================== HOVER EVENTS *bufferline-hover-events* You can configure bufferline to respond to hover events if you are using version 0.8 of neovim or higher. In order to enable this feature. You must first enable the |'mousemoveevent'| vim option and then add the item that you would like to hide/reveal on hover to your config and set `enable = true` i.e. NOTE: currently only hiding/revealing the buffer close icon is possible. >lua options = { hover = { enabled = true, delay = 200, reveal = {'close'} } } < With the above configuration, the hover events will be emitted after hovering for `200ms` and the close button will be hidden until you hover on it. ============================================================================== STYLING *bufferline-styling* You can change the appearance of the bufferline separators by setting the `separator_style`. The available options are: * `slant` - Use slanted/triangular separators * `padded_slant` - Same as `slant` but with extra padding which some terminals require. If `slant` does not render correctly for you try padded this instead. * `slope` - Use slanted/triangular separators but slopped to the right * `padded_slope` - Same as `slope` but with extra padding which some terminals require. If `slope` does not render correctly for you try padded this instead. * `thick` - Increase the thickness of the separator characters * `thin` - (default) Use thin separator characters * finally you can pass in a custom list containing 2 characters which will be used as the separators e.g. `{"|", "|"}`, the first is the left and the second is the right separator ============================================================================== STYLE PRESETS *bufferline-style-presets* You also use one of the pre-defined rulesets i.e. preset for the bufferline e.g. you can remove all bold text by setting the `options.style_preset` to `require('bufferline').style_preset.no_bold` or `no_italic`. There are also more stylistic presets such as `minimal` which makes the foreground match the background so that only the text of the buffers is visible and the bufferline is more unobtrusive. e.g. >lua local bufferline = require('bufferline') bufferline.setup({ options = { style_preset = bufferline.style_preset.no_italic, -- or you can combine these e.g. style_preset = { bufferline.style_preset.no_italic, bufferline.style_preset.no_bold }, } }) < The available options are - `no_italic` - `no_bold` - `minimal` ============================================================================== TABPAGES *bufferline-tabpages* This plugin can also be set to show only tabpages. This can be done by setting the `mode` option to `tabs`. This will change the bufferline to a tabline it has a lot of the same features/styling but not all. A few things to note are * Sorting doesn't work yet as that needs to be thought through. * Grouping doesn't work yet as that also needs to be thought through. `BufferLineTabRename` Tabs can be renamed using the `BufferLineTabRename` command: e.g. - Rename the current tab: `BufferLineTabRename Code` - Rename a tab based on it's `tabnr`: `BufferLineTabRename 1 Code` ============================================================================== NUMBERS *bufferline-numbers* You can prefix buffer names with either the `ordinal` or `buffer id`, using the `numbers` option. Currently this can be specified as either a string of `buffer_id` | `ordinal` or a function This function allows maximum flexibility in determining the appearance of this section. It is passed a table with the following keys: * `raise` - a helper function to convert the passed number to superscript e.g. `raise(id)`. * `lower` - a helper function to convert the passed number to subscript e.g. `lower(id)`. * `ordinal` - The buffer ordinal number. * `id` - The buffer ID. > -- For ⁸·₂ numbers = function(opts) return string.format('%s·%s', opts.raise(opts.id), opts.lower(opts.ordinal)) end, -- For ₈.₂ numbers = function(opts) return string.format('%s.%s', opts.lower(opts.id), opts.lower(opts.ordinal)) end, -- For 2.)8.) - change the order of arguments to change the order in the string numbers = function(opts) return string.format('%s.)%s.)', opts.ordinal, opts.id) end, -- For 8|² - numbers = function(opts) return string.format('%s|%s', opts.id, opts.raise(opts.ordinal)) end, < ============================================================================== LSP DIAGNOSTICS *bufferline-diagnostics* By setting `diagnostics = "nvim_lsp" | "coc"` you will get an indicator in the bufferline for a given tab if it has any errors This will allow you to tell at a glance if a particular buffer has errors. Currently only the native neovim lsp is supported, mainly because it has the easiest API for fetching all errors for all buffers (with an attached lsp client) This feature is _WIP_ so beware and report any issues if you find any. In order to customise the appearance of the diagnostic count you can pass a custom function in your setup. >lua -- rest of config ... --- count is an integer representing total count of errors --- level is a string "error" | "warning" --- this should return a string --- Don't get too fancy as this function will be executed a lot diagnostics_indicator = function(count, level) local icon = level:match("error") and " " or " " return " " .. icon .. count end < The highlighting for the filename if there is an error can be changed by replacing the highlights for `error`, `error_visible`, `error_selected`, `warning`, `warning_visible`, `warning_selected`. The diagnostics indicator can be set to `false` to remove the indicators completely whilst still keeping the highlight of the buffer name. LSP indicators can additionally be reported conditionally, based on buffer context. For instance, you could disable reporting LSP indicators for the current buffer and only have them appear for other buffers. >lua diagnostics_indicator = function(count, level, diagnostics_dict, context) if context.buffer:current() then return '' end return ' ' end < ============================================================================== GROUPS *bufferline-groups* The buffers this plugin shows can be grouped based on a users configuration. Groups are a way of allowing a user to visualize related buffers in clusters as well as operating on them together e.g. by clicking the group indicator all grouped buffers can be hidden. They are partially inspired by google chrome's, tabs as well as centaur tab's groups. In order to group buffers specify a list of groups in your config e.g. >lua groups = { options = { toggle_hidden_on_enter = true -- when you re-enter a hidden group this options re-opens that group so the buffer is visible }, items = { { name = "Tests", -- Mandatory highlight = {underline = true, sp = "blue"}, -- Optional priority = 2, -- determines where it will appear relative to other groups (Optional) icon = " ", -- Optional matcher = function(buf) -- Mandatory return buf.filename:match('%_test') or buf.filename:match('%_spec') end, }, { name = "Docs", highlight = {undercurl = true, sp = "green"}, auto_close = false, -- whether or not close this group if it doesn't contain the current buffer matcher = function(buf) return buf.filename:match('%.md') or buf.filename:match('%.txt') end, separator = { -- Optional style = require('bufferline.groups').separator.tab }, } } } < ============================================================================== ORDERING GROUPS *bufferline-ordering-groups* Groups are ordered by their position in the `items` list, the first group shows at the start of the bufferline and so on. You might want to order groups around the un-grouped buffers e.g. > | group 1 | buf 1 (ungrouped) | buf 2 (ungrouped) | group 2 | < In this case built-in groups are provided (for now just the `ungrouped`) built-in so you can achieve the order above using >lua local groups = require('bufferline.groups') groups = { items = { {name = "group 1", ... }, groups.builtin.ungrouped, -- the ungrouped buffers will be in the middle of the grouped ones {name = "group 2", ...}, } } < ============================================================================== GROUP COMMANDS *bufferline-group-commands* Grouped buffers can also be interacted with using a few commands namely * `:BufferLineGroupClose` - which will close all buffers in this group * `:BufferLineGroupToggle` - which will hide or show a group Other group related functionality can be implemented using the `require('bufferline').group_action` API. e.g. >lua function _G.__group_open() require('bufferline').group_action(, function(buf) vim.cmd('vsplit '..buf.path) end) end < ============================================================================== PINNING BUFFERS *bufferline-pinning* Buffers can be pinned to the start of the bufferline by using the `:BufferLineTogglePin` command, this will override other groupings or sorting order for the buffer and position it left of all other buffers. Pinned buffers are essentially a builtin group that positions the assigned elements. The icons and highlights for pinned buffers can be changed similarly to other groups e.g. >lua config = { options = { groups = { items = { require('bufferline.groups').builtin.pinned:with({ icon = "󰐃 " }) ... -- other items } } } } < ============================================================================== REGULAR TAB SIZES *bufferline-regular-tabs* Generally this plugin enforces a minimum tab size so that the buffer line appears consistent. Where a tab is smaller than the tab size it is padded. If it is larger than the tab size it is allowed to grow up to the max name length specified (+ the other indicators). If you set `enforce_regular_tabs = true` tabs will be prevented from extending beyond the tab size and all tabs will be the same length NOTE: when this option is set to `true`. It will disable the ability to deduplicate buffers. ============================================================================== SORTING *bufferline-sorting* Bufferline allows you to sort the visible buffers by `extension` or `directory`: >vim " Using vim commands :BufferLineSortByExtension :BufferLineSortByDirectory :BufferLineSortByTabs -- Or using lua functions :lua require'bufferline'.sort_by('extension')` :lua require'bufferline'.sort_by('directory')` :lua require'bufferline'.sort_by('tabs')` By default bufferline will sort by buffer number which is an integer value provided by vim to identify a buffer that increases as new buffers are opened this means that new buffers are placed at the end of the bufferline. For more advanced usage you can provide a custom compare function which will receive two buffers to compare. You can see what fields are available to use using >lua sort_by = function(buffer_a, buffer_b) print(vim.inspect(buffer_a)) -- add custom logic local modified_a = vim.fn.getftime(buffer_a.path) local modified_b = vim.fn.getftime(buffer_b.path) return modified_a > modified_b end < When using a sorted bufferline it's advisable that you use the `BufferLineCycleNext` and `BufferLineCyclePrev` commands since these will traverse the bufferline bufferlist in order whereas `bnext` and `bprev` will cycle buffers according to the buffer numbers given by vim. ============================================================================== FILTERING *bufferline-filtering* Bufferline can be configured to take a custom filtering function via the `custom_filter` option. This value must be a lua function that will receive each buffer number that is going to be used for the bufferline, as well as all the others. A user can then check whatever they would like and return `true` if they would like it to appear and `false` if not. For example: >lua custom_filter = function(buf, buf_nums) -- dont show help buffers in the bufferline return not vim.bo[buf].filetype == "help" then -- you can use more custom logic for example -- don't show files matching a pattern return not vim.fn.bufname(buf):match('test') -- show only certain filetypes in certain tabs e.g. js in one, css in another etc. local tab_num = vim.fn.tabpagenr() if tab_num == 1 and vim.bo[buf].filetype == "javascript" then return true elseif tab_num == 2 and vim.bo[buf].filetype == "css" then return true else return false end -- My personal example: -- Don't show output log buffers in the same tab as my other code. -- 1. Check if there are any log buffers in the full list of buffers -- if not don't do any filtering local logs = vim.tbl_filter( function(b) return vim.bo[b].filetype == "log" end, buf_nums ) if vim.tbl_isempty(logs) then return true end -- 2. if there are log buffers then only show the log buffer local tab_num = vim.fn.tabpagenr() local is_log = vim.bo[buf].filetype == "log" -- only show log buffers in secondary tabs return (tab_num == 2 and is_log) or (tab_num ~= 2 and not is_log) end < ============================================================================== COMMANDS *bufferline-commands* Bufferline includes a few commands to allow deleting buffers. These commands are: * `BufferLineCloseRight` - close all visible buffers to the right of the current buffer * `BufferLineCloseLeft` - close all visible buffers to the left of the current buffer * `BufferLineCloseOthers` - close all other visible buffers These commands apply the configured `close_command` to each of the corresponding buffers. ============================================================================== CUSTOM-FUNCTIONS *bufferline-functions* A user can also execute arbitrary functions against a buffer using the `exec` function. For example >lua require('bufferline').exec( 4, -- the forth visible buffer from the left user_function -- an arbitrary user function which gets passed the buffer ) -- e.g. function _G.bdel(num) require('bufferline').exec(num, function(buf, visible_buffers) vim.cmd('bdelete '..buf.id) end) end vim.cmd [[ command -count Bdel lua _G.bdel() ]] ============================================================================== SIDEBAR OFFSET *bufferline-offset* You can prevent the bufferline drawing above a *vertical* sidebar split such as a file explorer. To do this you must set the `offsets` configuration option to a list of tables containing the details of the window to avoid. *NOTE:* this is only relevant for left or right aligned sidebar windows such as `NvimTree`, `NERDTree` or `Vista` >lua offsets = { { filetype = "NvimTree", text = "File Explorer", highlight = "Directory", separator = true -- use a "true" to enable the default, or set your own character } } < The `filetype` is used to check whether a particular window is a match, the `text` is *optional* and will show above the window if specified. `text` can be either a string or a function which should also return a string. See the example below. >lua offsets = { { filetype = "NvimTree", text = function() return vim.fn.getcwd() end, highlight = "Directory", text_align = "left" } } If it is too long it will be truncated. The highlight controls what highlight is shown above the window. You can also change the alignment of the text in the offset section using `text_align` which can be set to `left`, `right` or `center`. An offset can also have a separator which mimics the appearance of the win separator, and the intent is give a vertical split the appearance of a consistent separator. The highlight can be changed using the `offset_separator` highlight. The value can be set to a `boolean` (uses the default separator if true) or to a string which will be the character that will be used. Lastly you can specify a `padding` option as well which will increase the amount the bufferline is offset beyond just the window width, this isn't something that is generally required though. ============================================================================== BUFFERLINE PICK *bufferline-pick* Using the `BufferLinePick` command will allow for easy selection of a buffer in view. Trigger the command, using `:BufferLinePick` or better still map this to a key, e.g. >vim nnoremap gb :BufferLinePick < then pick a buffer by typing the character for that specific buffer that appears This functionality can also be used to close a buffer using `BufferLinePickClose` by triggering this command the same selection UI will appear but on selecting a buffer it will be closed. this can also be mapped to something like >vim nnoremap gD :BufferLinePickClose > You can configure the characters used for selecting buffers using the `pick.alphabet` option. By default, both uppercase and lowercase alphanumeric characters are used. >lua options = { pick = { alphabet = "abcdefghijklmopqrstuvwxyz" } } < With the configuration above, bufferline will only use lowercase letters. ============================================================================== MOUSE ACTIONS *bufferline-mouse-actions* You can configure different type of mouse clicks to behave differently. The current mouse click types are * Left - `left_mouse_command` * Right - `right_mouse_command` * Middle - `middle_mouse_command` * Close - `close_command` Currently left mouse opens the selected buffer but the command can be tweaked using `left_mouse_command` which can be specified as either a lua function or string which uses lua's printf style string formatting (https://www.lua.org/pil/20.html) e.g. `buffer %d` You can do things like open a vertical split on right clicking the buffer name for example using. >lua right_mouse_command = "vertical sbuffer %d" < Or you can set the value to a function and handle the click action however you please for example you can use another plugin such as `bufdelete.nvim` (https://github.com/famiu/bufdelete.nvim) to handle closing the buffer using the `close_command`. >lua left_mouse_command = function(bufnum) require('bufdelete').bufdelete(bufnum, true) end < You can also set this value to an empty string or to `false` to disable the action. ============================================================================== MAPPINGS *bufferline-mappings* `BufferLineGoToBuffer` You can select a buffer by it's visible position in the bufferline using the `BufferLineGoToBuffer` command. This means that if you have 60 buffers open but only 7 visible in the bufferline using `BufferLineGoToBuffer 4` will go to the 4th visible buffer but not necessarily the 5th in the absolute list of open buffers. To select the last visible buffer, you can also use `BufferLineGoToBuffer -1`. > <- (30) | buf31 | buf32 | buf33 | buf34 | buf35 | buf36 | buf37 (24) -> < Using `BufferLineGoToBuffer 4` will open `buf34` as it is the 4th visible buffer. This can then be mapped using >vim nnoremap 1 BufferLineGoToBuffer 1 nnoremap 2 BufferLineGoToBuffer 2 nnoremap 3 BufferLineGoToBuffer 3 nnoremap 4 BufferLineGoToBuffer 4 nnoremap 5 BufferLineGoToBuffer 5 nnoremap 6 BufferLineGoToBuffer 6 nnoremap 7 BufferLineGoToBuffer 7 nnoremap 8 BufferLineGoToBuffer 8 nnoremap 9 BufferLineGoToBuffer 9 nnoremap $ BufferLineGoToBuffer -1 < If you'd rather map these yourself, use: >vim nnoremap mymap :lua require"bufferline".go_to(num) Alternatively, if you want to instead jump to the absolute position of the buffer in the bufferline (as displayed by the ordinal buffer numbers), you can use the `lua` API to set it up using `require'bufferline'.go_to(number, absolute)`, where absolute is a boolean that determines whether to use the absolute buffer position or the visible/relative one. >vim nnoremap 1 lua require("bufferline").go_to(1, true) nnoremap 2 lua require("bufferline").go_to(2, true) nnoremap 3 lua require("bufferline").go_to(3, true) nnoremap 4 lua require("bufferline").go_to(4, true) nnoremap 5 lua require("bufferline").go_to(5, true) nnoremap 6 lua require("bufferline").go_to(6, true) nnoremap 7 lua require("bufferline").go_to(7, true) nnoremap 8 lua require("bufferline").go_to(8, true) nnoremap 9 lua require("bufferline").go_to(9, true) nnoremap $ lua require("bufferline").go_to(-1, true) < You can close buffers by clicking the close icon or by right clicking the tab anywhere A few of this plugins commands can be mapped for ease of use. >vim " These commands will navigate through buffers in order " regardless of which mode you are using e.g. if you change " the order of buffers :bnext and :bprevious will not respect the custom ordering nnoremap [b :BufferLineCycleNext nnoremap b] :BufferLineCyclePrev " These commands will move the current buffer backwards or forwards in the bufferline nnoremap :BufferLineMoveNext nnoremap :BufferLineMovePrev " These commands will move the current buffer to the first or the last position in the bufferline nnoremap :lua require'bufferline'.move_to(1) nnoremap :lua require'bufferline'.move_to(-1) " These commands will sort buffers by directory, language, or a custom criteria nnoremap be :BufferLineSortByExtension nnoremap bd :BufferLineSortByDirectory nnoremap :lua require'bufferline'.sort_by(function (buf_a, buf_b) return buf_a.id < buf_b.id end) If you manually arrange your buffers using `:BufferLineMove{Prev|Next}` during an nvim session this can be persisted for the session. This is enabled by default but you need to ensure that your `sessionoptions+=globals` otherwise the session file will not track global variables which is the mechanism used to store your sort order. ============================================================================== HIGHLIGHTS *bufferline-highlights* When `termguicolors` is enabled, this plugin is designed to work automatically, deriving colours from the user's theme, you can change highlight groups by overriding the section you'd like to change. Keep in mind that despite my best efforts not to change these they might require the occasional tweak (if you don't customise these too much you should be fine 🤞). Highlight values can also be specified as tables with a key of the highlight name e.g. `Normal` and the attribute which is one of `fg`, `bg`. See the `{what}` argument of `:h synIDAttr` for details, but only these 2 have been tested for example: >lua highlights = { fill = { bg = { attribute = "fg", highlight = "Pmenu" } } } < This will automatically pull the value of `Pmenu` fg colour and use it Any improperly specified tables will be set to `nil` and overriden with the default value for that key. NOTE: you can specify colors the same way you specify colors for `nvim_set_hl`. See `:h vim.api.nvim_set_hl` . >lua require('bufferline').setup({ highlights = { fill = { fg = '', bg = '', }, background = { fg = '', bg = '', }, tab = { fg = '', bg = '', }, tab_selected = { fg = '', bg = '', }, tab_separator = { fg = '', bg = '', }, tab_separator_selected = { fg = '', bg = '', sp = '', underline = '', }, tab_close = { fg = '', bg = '', }, close_button = { fg = '', bg = '', }, close_button_visible = { fg = '', bg = '', }, close_button_selected = { fg = '', bg = '', }, buffer_visible = { fg = '', bg = '', }, buffer_selected = { fg = '', bg = '', bold = true, italic = true, }, numbers = { fg = '', bg = '', }, numbers_visible = { fg = '', bg = '', }, numbers_selected = { fg = '', bg = '', bold = true, italic = true, }, diagnostic = { fg = '', bg = '', }, diagnostic_visible = { fg = '', bg = '', }, diagnostic_selected = { fg = '', bg = '', bold = true, italic = true, }, hint = { fg = '', sp = '', bg = '', }, hint_visible = { fg = '', bg = '', }, hint_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, hint_diagnostic = { fg = '', sp = '', bg = '', }, hint_diagnostic_visible = { fg = '', bg = '', }, hint_diagnostic_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, info = { fg = '', sp = '', bg = '', }, info_visible = { fg = '', bg = '', }, info_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, info_diagnostic = { fg = '', sp = '', bg = '', }, info_diagnostic_visible = { fg = '', bg = '', }, info_diagnostic_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, warning = { fg = '', sp = '', bg = '', }, warning_visible = { fg = '', bg = '', }, warning_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, warning_diagnostic = { fg = '', sp = '', bg = '', }, warning_diagnostic_visible = { fg = '', bg = '', }, warning_diagnostic_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, error = { fg = '', bg = '', sp = '', }, error_visible = { fg = '', bg = '', }, error_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, error_diagnostic = { fg = '', bg = '', sp = '', }, error_diagnostic_visible = { fg = '', bg = '', }, error_diagnostic_selected = { fg = '', bg = '', sp = '', bold = true, italic = true, }, modified = { fg = '', bg = '', }, modified_visible = { fg = '', bg = '', }, modified_selected = { fg = '', bg = '', }, duplicate_selected = { fg = '', bg = '', italic = true, }, duplicate_visible = { fg = '', bg = '', italic = true, }, duplicate = { fg = '', bg = '', italic = true, }, separator_selected = { fg = '', bg = '', }, separator_visible = { fg = '', bg = '', }, separator = { fg = '', bg = '', }, indicator_visible = { fg = '', bg = '', }, indicator_selected = { fg = '', bg = '', }, pick_selected = { fg = '', bg = '', bold = true, italic = true, }, pick_visible = { fg = '', bg = '', bold = true, italic = true, }, pick = { fg = '', bg = '', bold = true, italic = true, }, offset_separator = { fg = '', bg = '', }, trunc_marker = { fg = '', bg = '', } }; }) < When `termguicolors` is not enabled, a user is expected to specify colour values similar to the format above (use `ctermbg` and `ctermfg` instead). A user can also directly specify [colour numbers](https://www.ditig.com/256-colors-cheat-sheet). for example: >lua highlights = { fill = { ctermbg = 7, ctermfg = 0, } } This will set the background of the highlight group to gray and foreground to black. Here the colours are system determined, and the user may have changed the rgb values through customizing the terminal emulator itself. This is possible for colour numbers 0-15. ============================================================================== CUSTOM AREAS *bufferline-custom-areas* You can also add custom content at the start or end of the bufferline using `custom_areas` this option allows a user to specify a function which should return the text and highlight for that text to be shown in a list of tables. For example: >lua custom_areas = { right = function() local result = {} local seve = vim.diagnostic.severity local error = #vim.diagnostic.get(0, {severity = seve.ERROR}) local warning = #vim.diagnostic.get(0, {severity = seve.WARN}) local info = #vim.diagnostic.get(0, {severity = seve.INFO}) local hint = #vim.diagnostic.get(0, {severity = seve.HINT}) if error ~= 0 then table.insert(result, {text = "  " .. error, link = "DiagnosticError"}) end if warning ~= 0 then table.insert(result, {text = "  " .. warning, link = "DiagnosticWarn"}) end if hint ~= 0 then table.insert(result, {text = "  " .. hint, link = "DiagnosticHint"}) end if info ~= 0 then table.insert(result, {text = "  " .. info, link = "DiagnosticInfo"}) end return result end, } < Please note that this function will be called a lot and should be as inexpensive as possible so it does not block rendering the tabline. ============================================================================== WORKING WITH ELEMENTS *bufferline-working-with-elements* Bufferline exposes some information about the buffers it shows will allow you to implement your own custom functionality. Note that this will not include any buffers that are filtered out of bufferline, making it handy for writing functions that need to ignore special buffers. The output has the following structure: >lua { mode = "tabs" -- depends on your config setting for mode elements = { {id = 1, name = "hi.txt", path = "/tmp/folder/hi.txt"}, -- and so on for all open buffers } } < Here's an example that will let you close all open buffers. >lua function close_all_buffers () for _, e in ipairs(bufferline.get_elements().elements) do vim.schedule(function() vim.cmd("bd ".. e.id) end) end end < ============================================================================== COLORSCHEME DEVELOPMENT *bufferline-colorscheme-development* Bufferline was initially developed to work without external highlight configuration by colorschemes. It takes default highlights from neovim and adapts these for it's usage. The highlight groups it uses are The `->` represents the fallback chain for a highlight. `Normal` `String` `DiagnosticError` -> `LspDiagnosticsDefaultError` `DiagnosticWarn` -> `LspDiagnosticsDefaultWarn` -> `WarningMsg` `DiagnosticHint` -> `LspDiagnosticsDefaultHint` -> `Directory` `DiagnosticInfo` -> `LspDiagnosticsDefaultInfo` -> `Normal` `TabLineSel` -> `WildMenu` If the above groups are correctly highlighted then bufferline should appear as intended where it can be i.e. sufficient contrast etc. If however a colorscheme intends to override bufferline highlight groups a user can set the `themable` option in their bufferline config to `true`. This will change bufferline's highlights to use the `default` keyword so they can be more easily overriden if that is the user's preference. ============================================================================== ISSUES *bufferline-issues* Please raise any issues you encounter whilst using this plugin at: https://github.com/akinsho/bufferline.lua/issues vim:tw=78:sw=4:ft=help:norl: akinsho-bufferline.nvim-655133c/lua/000077500000000000000000000000001474150523700173015ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/lua/bufferline.lua000066400000000000000000000176711474150523700221410ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local ui = lazy.require("bufferline.ui") ---@module "bufferline.ui" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local state = lazy.require("bufferline.state") ---@module "bufferline.state" local groups = lazy.require("bufferline.groups") ---@module "bufferline.groups" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local sorters = lazy.require("bufferline.sorters") ---@module "bufferline.sorters" local buffers = lazy.require("bufferline.buffers") ---@module "bufferline.buffers" local commands = lazy.require("bufferline.commands") ---@module "bufferline.commands" local tabpages = lazy.require("bufferline.tabpages") ---@module "bufferline.tabpages" local highlights = lazy.require("bufferline.highlights") ---@module "bufferline.highlights" local hover = lazy.require("bufferline.hover") ---@module "bufferline.hover" -- @v:lua@ in the tabline only supports global functions, so this is -- the only way to add click handlers without autoloaded vimscript functions _G.___bufferline_private = _G.___bufferline_private or {} -- to guard against reloads local api = vim.api -----------------------------------------------------------------------------// --- API values -----------------------------------------------------------------------------// local M = { move = commands.move, move_to = commands.move_to, exec = commands.exec, go_to = commands.go_to, cycle = commands.cycle, sort_by = commands.sort_by, pick = commands.pick, get_elements = commands.get_elements, close_with_pick = commands.close_with_pick, close_in_direction = commands.close_in_direction, rename_tab = commands.rename_tab, close_others = commands.close_others, unpin_and_close = commands.unpin_and_close, ---@deprecated pick_buffer = commands.pick, ---@deprecated go_to_buffer = commands.go_to, ---@deprecated sort_buffers_by = commands.sort_by, ---@deprecated close_buffer_with_pick = commands.close_with_pick, style_preset = config.STYLE_PRESETS, groups = groups, } -----------------------------------------------------------------------------// --- @return string, bufferline.Segment[][] local function bufferline() local is_tabline = config:is_tabline() local components = is_tabline and tabpages.get_components(state) or buffers.get_components(state) -- NOTE: keep track of the previous state so it can be used for sorting -- specifically to position newly opened buffers next to the buffer that was previously open local prev_idx, prev_components = state.current_element_index, state.components local function sorter(list) return sorters.sort(list, { current_index = prev_idx, prev_components = prev_components, custom_sort = state.custom_sort, }) end local _, current_idx = utils.find(function(component) return component:current() end, components) state.set({ current_element_index = current_idx }) components = not is_tabline and groups.render(components, sorter) or sorter(components) local tabline = ui.tabline(components, tabpages.get()) state.set({ --- store the full unfiltered lists __components = components, --- Store copies without focusable/hidden elements components = components, visible_components = tabline.visible_components, --- size data stored for use elsewhere e.g. hover positioning left_offset_size = tabline.left_offset_size, right_offset_size = tabline.right_offset_size, }) return tabline.str, tabline.segments end --- If the item count has changed and the next tabline status is different then update it local function toggle_bufferline() if not config.options.auto_toggle_bufferline then return end local item_count = config:is_tabline() and utils.get_tab_count() or utils.get_buf_count() local status = (config.options.always_show_bufferline or item_count > 1) and 2 or 0 if vim.o.showtabline ~= status then vim.o.showtabline = status end end ---@private function _G.nvim_bufferline() toggle_bufferline() -- Always populate state regardless of if tabline status is less than 2 #352 return bufferline() end ---@param conf bufferline.Config local function setup_autocommands(conf) local BUFFERLINE_GROUP = "BufferlineCmds" local options = conf.options api.nvim_create_augroup(BUFFERLINE_GROUP, { clear = true }) api.nvim_create_autocmd("ColorScheme", { pattern = "*", group = BUFFERLINE_GROUP, callback = function() highlights.reset_icon_hl_cache() highlights.set_all(config.update_highlights()) end, }) if not options or vim.tbl_isempty(options) then return end if options.persist_buffer_sort then api.nvim_create_autocmd("SessionLoadPost", { pattern = "*", group = BUFFERLINE_GROUP, callback = function() state.custom_sort = utils.restore_positions() end, }) end if not options.always_show_bufferline then -- toggle tabline api.nvim_create_autocmd({ "BufAdd", "TabEnter" }, { pattern = "*", group = BUFFERLINE_GROUP, callback = function() toggle_bufferline() end, }) end api.nvim_create_autocmd("BufRead", { pattern = "*", once = true, callback = function() vim.schedule(groups.handle_group_enter) end, }) api.nvim_create_autocmd("BufEnter", { pattern = "*", callback = function() groups.handle_group_enter() end, }) api.nvim_create_autocmd("User", { pattern = "BufferLineHoverOver", callback = function(args) ui.on_hover_over(args.buf, args.data) end, }) api.nvim_create_autocmd("User", { pattern = "BufferLineHoverOut", callback = ui.on_hover_out, }) end local function command(name, cmd, opts) api.nvim_create_user_command(name, cmd, opts or {}) end local function setup_commands() command("BufferLinePick", function() M.pick() end) command("BufferLinePickClose", function() M.close_with_pick() end) command("BufferLineCycleNext", function() M.cycle(1) end) command("BufferLineCyclePrev", function() M.cycle(-1) end) command("BufferLineCloseRight", function() M.close_in_direction("right") end) command("BufferLineCloseLeft", function() M.close_in_direction("left") end) command("BufferLineCloseOthers", function() M.close_others() end) command("BufferLineMoveNext", function() M.move(1) end) command("BufferLineMovePrev", function() M.move(-1) end) command("BufferLineSortByExtension", function() M.sort_by("extension") end) command("BufferLineSortByDirectory", function() M.sort_by("directory") end) command("BufferLineSortByRelativeDirectory", function() M.sort_by("relative_directory") end) command("BufferLineSortByTabs", function() M.sort_by("tabs") end) command("BufferLineGoToBuffer", function(opts) M.go_to(opts.args) end, { nargs = 1 }) command("BufferLineTogglePin", function() groups.toggle_pin() end, { nargs = 0 }) command("BufferLineTabRename", function(opts) M.rename_tab(opts.fargs) end, { nargs = "*" }) command("BufferLineGroupClose", function(opts) groups.action(opts.args, "close") end, { nargs = 1, complete = groups.complete, }) command("BufferLineGroupToggle", function(opts) groups.action(opts.args, "toggle") end, { nargs = 1, complete = groups.complete, }) end local function setup_diagnostic_handler(preferences) if preferences.options.diagnostics == "nvim_lsp" and preferences.options.diagnostics_update_on_event then vim.diagnostic.handlers["bufferline"] = { show = function() ui.refresh() end, hide = function() ui.refresh() end, } end end ---@param conf bufferline.UserConfig? function M.setup(conf) conf = conf or {} config.setup(conf) groups.setup(conf) -- Groups must be set up before the config is applied local preferences = config.apply() -- on loading (and reloading) the plugin's config reset all the highlights highlights.set_all(preferences) hover.setup(preferences) setup_commands() setup_autocommands(preferences) setup_diagnostic_handler(preferences) vim.o.tabline = "%!v:lua.nvim_bufferline()" toggle_bufferline() end return M akinsho-bufferline.nvim-655133c/lua/bufferline/000077500000000000000000000000001474150523700214225ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/lua/bufferline/buffers.lua000066400000000000000000000055761474150523700235760ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local ui = lazy.require("bufferline.ui") ---@module "bufferline.ui" local groups = lazy.require("bufferline.groups") ---@module "bufferline.groups" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local pick = require("bufferline.pick") ---@module "bufferline.pick" local duplicates = require("bufferline.duplicates") ---@module "bufferline.duplicates" local diagnostics = require("bufferline.diagnostics") ---@module "bufferline.diagnostics" local models = require("bufferline.models") ---@module "bufferline.models" local M = {} local api = vim.api --- sorts buf_names in place, but doesn't add/remove any values --- @param buf_nums number[] --- @param sorted number[] --- @return number[] local function get_updated_buffers(buf_nums, sorted) if not sorted then return buf_nums end local nums = { unpack(buf_nums) } local reverse_lookup_sorted = utils.tbl_reverse_lookup(sorted) --- a comparator that sorts buffers by their position in sorted local sort_by_sorted = function(buf_id_1, buf_id_2) local buf_1_rank = reverse_lookup_sorted[buf_id_1] local buf_2_rank = reverse_lookup_sorted[buf_id_2] if not buf_1_rank then return false end if not buf_2_rank then return true end return buf_1_rank < buf_2_rank end table.sort(nums, sort_by_sorted) return nums end ---Filter the buffers to show based on the user callback passed in ---@param buf_nums integer[] ---@param callback fun(buf: integer, bufs: integer[]): boolean ---@return integer[] local function apply_buffer_filter(buf_nums, callback) if type(callback) ~= "function" then return buf_nums end local filtered = {} for _, buf in ipairs(buf_nums) do if callback(buf, buf_nums) then table.insert(filtered, buf) end end return filtered end ---Return a list of the buffers open in nvim as Components ---@param state bufferline.State ---@return bufferline.Buffer[] function M.get_components(state) local options = config.options local buf_nums = utils.get_valid_buffers() local filter = options.custom_filter buf_nums = filter and apply_buffer_filter(buf_nums, filter) or buf_nums buf_nums = get_updated_buffers(buf_nums, state.custom_sort) pick.reset() duplicates.reset() ---@type bufferline.Buffer[] local components = {} local all_diagnostics = diagnostics.get(options) local Buffer = models.Buffer for i, buf_id in ipairs(buf_nums) do local buf = Buffer:new({ path = api.nvim_buf_get_name(buf_id), id = buf_id, ordinal = i, diagnostics = all_diagnostics[buf_id], name_formatter = options.name_formatter, }) buf.letter = pick.get(buf) buf.group = groups.set_id(buf) components[i] = buf end return vim.tbl_map(function(buf) return ui.element(state, buf) end, duplicates.mark(components)) end return M akinsho-bufferline.nvim-655133c/lua/bufferline/colors.lua000066400000000000000000000072551474150523700234370ustar00rootroot00000000000000local M = {} local api = vim.api ---Convert a hex color to rgb ---@param color string ---@return number ---@return number ---@return number local function hex_to_rgb(color) local hex = color:gsub("#", "") return tonumber(hex:sub(1, 2), 16), tonumber(hex:sub(3, 4), 16), tonumber(hex:sub(5), 16) end local function alter(attr, percent) return math.floor(attr * (100 + percent) / 100) end ---@source https://stackoverflow.com/q/5560248 ---see: https://stackoverflow.com/a/37797380 ---Darken a specified hex color ---@param color string? ---@param percent number ---@return string function M.shade_color(color, percent) if not color then return "NONE" end local r, g, b = hex_to_rgb(color) if not r or not g or not b then return "NONE" end r, g, b = alter(r, percent), alter(g, percent), alter(b, percent) r, g, b = math.min(r, 255), math.min(g, 255), math.min(b, 255) return ("#%02x%02x%02x"):format(r, g, b) end --- Determine whether to use black or white text --- References: --- 1. https://stackoverflow.com/a/1855903/837964 --- 2. https://stackoverflow.com/a/596243 function M.color_is_bright(hex) if not hex then return false end local r, g, b = hex_to_rgb(hex) -- If any of the colors are missing return false if not r or not g or not b then return false end -- Counting the perceptive luminance - human eye favors green color local luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255 return luminance > 0.5 -- if > 0.5 Bright colors, black font, otherwise Dark colors, white font end -- TODO: remove when 0.9 is stable local new_hl_api = api.nvim_get_hl ~= nil local get_hl = function(name, use_cterm) if new_hl_api then local hl = api.nvim_get_hl(0, { name = name, link = false }) if use_cterm then hl.fg, hl.bg = hl.ctermfg, hl.ctermbg end return hl end ---@diagnostic disable-next-line: undefined-field return api.nvim_get_hl_by_name(name, not use_cterm) end -- Map of nvim_get_hl() highlight attributes (new API) to -- nvim_get_hl_by_name() highlight attributes (old API). local hl_color_attrs = { fg = "foreground", bg = "background", sp = "special", } ---@alias GetColorOpts { name: string, attribute: "fg" | "bg" | "sp", fallback: GetColorOpts?, not_match: string?, cterm: boolean? } --- parses the GUI hex color code (or cterm color number) from the given hl_name --- color number (0-255) is returned if cterm is set to true in opts --- if unable to parse, uses the fallback value ---@param opts GetColorOpts ---@return string? | number? function M.get_color(opts) local name, attribute, fallback, not_match, cterm = opts.name, opts.attribute, opts.fallback, opts.not_match, opts.cterm -- TODO: remove when 0.9 is stable if not new_hl_api then attribute = hl_color_attrs[attribute] end -- try and get hl from name local success, hl = pcall(get_hl, name, cterm) if success and hl and hl[attribute] then if cterm then return hl[attribute] end -- convert from decimal color value to hex (e.g. 14257292 => "#D98C8C") local hex = ("#%06x"):format(hl[attribute]) if not not_match or not_match ~= hex then return hex end end --- NOTE: in case of cterm, nvim_get_hl_by_name may return incorrect color --- numbers (but still < 256) for some highlight groups like TabLine, --- but return correct numbers for groups like DevIconPl. this problem --- does not happen for gui colors. if cterm then return end -- no fallback for cterm colors if fallback and type(fallback) == "string" then return fallback end -- basic fallback if fallback and type(fallback) == "table" then return M.get_color(fallback) end -- bit of recursive fallback logic, which allows chaining return "NONE" -- we couldn't resolve the color end return M akinsho-bufferline.nvim-655133c/lua/bufferline/commands.lua000066400000000000000000000222141474150523700237270ustar00rootroot00000000000000---------------------------------------------------------------------------// -- User commands ---------------------------------------------------------------------------// local lazy = require("bufferline.lazy") local ui = lazy.require("bufferline.ui") ---@module "bufferline.ui" local state = lazy.require("bufferline.state") ---@module "bufferline.state" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local groups = lazy.require("bufferline.groups") ---@module "bufferline.groups" local sorters = lazy.require("bufferline.sorters") ---@module "bufferline.sorters" local pick = lazy.require("bufferline.pick") ---@module "bufferline.pick" local tabpage = lazy.require("bufferline.tabpages") ---@module "bufferline.tabpages" local M = {} local fmt = string.format local api = vim.api --- open the current element ---@param id number local function open_element(id) if config:is_tabline() and api.nvim_tabpage_is_valid(id) then api.nvim_set_current_tabpage(id) elseif api.nvim_buf_is_valid(id) then api.nvim_set_current_buf(id) end end ---Get the current element i.e. tab or buffer ---@return number local function get_current_element() if config:is_tabline() then return api.nvim_get_current_tabpage() end return api.nvim_get_current_buf() end ---Handle a user "command" which can be a string or a function ---@param command string|function ---@param id number local function handle_user_command(command, id) if not command then return end if type(command) == "function" then command(id) elseif type(command) == "string" then -- Fix #574 without the scheduling the command the tabline does not refresh correctly vim.schedule(function() vim.cmd(fmt(command, id)) ui.refresh() end) end end ---@param position number local function handle_group_click(position) groups.toggle_hidden(position) ui.refresh() end ---@param id number local function handle_close(id) local options = config.options local close = options.close_command handle_user_command(close, id) end ---@param id number local function delete_element(id) if config:is_tabline() then vim.cmd("tabclose " .. id) else handle_close(id) end end ---@param id number function M.handle_win_click(id) local win_id = vim.fn.bufwinid(id) vim.fn.win_gotoid(win_id) end local cmds = { r = "right_mouse_command", l = "left_mouse_command", m = "middle_mouse_command", } ---Handler for each type of mouse click ---@param id number ---@param button string local function handle_click(id, _, button) local options = config.options if id then handle_user_command(options[cmds[button]], id) end end ---Execute an arbitrary user function on a visible by it's position buffer ---@param index number ---@param func fun(num: number, table?) function M.exec(index, func) local target = state.visible_components[index] if target and type(func) == "function" then func(target, state.visible_components) end end function M.pick() pick.choose_then(open_element) end function M.close_with_pick() pick.choose_then(function(id) handle_close(id) end) end function M.unpin_and_close(id) local win_id = id or vim.api.nvim_get_current_buf() handle_close(win_id) groups.remove_id_from_manual_groupings(win_id) end --- Open a element based on it's visible position in the list --- unless absolute is specified in which case this will open it based on it place in the full list --- this is significantly less helpful if you have a lot of elements open ---@param num number | string ---@param absolute boolean? whether or not to use the elements absolute position or visible positions function M.go_to(num, absolute) num = type(num) == "string" and tonumber(num) or num local list = absolute and state.components or state.visible_components local element = list[num] if num == -1 or not element then element = list[#list] end if element then open_element(element.id) end end ---@param current_state bufferline.State ---@param opts table? ---@return number? ---@return bufferline.TabElement? function M.get_current_element_index(current_state, opts) opts = opts or { include_hidden = false } local list = opts.include_hidden and current_state.__components or current_state.components for index, item in ipairs(list) do local element = item:as_element() if element and element.id == get_current_element() then return index, element end end end ---@param current_state bufferline.State ---@return number local get_last_pinned_index = function(current_state) for index, item in ipairs(current_state.components) do local element = item:as_element() if element and not groups._is_pinned(element) then return index - 1 end end return 0 end --- Move the buffer at index `from_index` (or current index if not specified) to position `to_index` --- @param to_index number negative indices are accepted (counting from the right instead of the left, e.g. -1 for the last position, -2 for the second-last, etc.) --- @param from_index number? function M.move_to(to_index, from_index) local index = from_index or M.get_current_element_index(state) if not index then return utils.notify("Unable to find buffer to move, sorry", "warn") end -- Calculate next index depending on the sign of `to_index` local next_index = to_index > 0 and to_index or #state.components + 1 + to_index if next_index >= 1 and next_index <= #state.components then local item = state.components[index] local destination_buf = state.components[next_index] state.components[next_index] = item state.components[index] = destination_buf state.custom_sort = utils.get_ids(state.components) local opts = config.options if opts.persist_buffer_sort then utils.save_positions(state.custom_sort) end ui.refresh() end end --- @param direction number function M.move(direction) local index, element = M.get_current_element_index(state) if not element then return end local next_index = index + direction if not config.options.move_wraps_at_ends or not index then return M.move_to(next_index, index) end local last_pinned_index = get_last_pinned_index(state) if groups._is_pinned(element) then if next_index <= 0 then next_index = last_pinned_index elseif next_index > last_pinned_index then next_index = 1 end else if next_index <= last_pinned_index then next_index = #state.components elseif next_index > #state.components then next_index = last_pinned_index + 1 end end M.move_to(next_index, index) end --- @param direction number function M.cycle(direction) if vim.opt.showtabline == 0 then if direction > 0 then vim.cmd("bnext") end if direction < 0 then vim.cmd("bprev") end end local index = M.get_current_element_index(state) if not index then return end local length = #state.components local next_index = index + direction if next_index <= length and next_index >= 1 then next_index = index + direction elseif index + direction <= 0 then next_index = length else next_index = 1 end local item = state.components[next_index] if not item then return utils.notify(fmt("This %s does not exist", item.type), "error") end open_element(item.id) end function M.get_elements() return { mode = config.options.mode, elements = vim.tbl_map( function(elem) return { id = elem.id, name = elem.name, path = elem.path } end, state.components ), } end ---@alias Direction "'left'" | "'right'" ---Close all elements to the left or right of the current buffer ---@param direction Direction function M.close_in_direction(direction) local index = M.get_current_element_index(state) if not index then return end local length = #state.components if not (index == length and direction == "right") and not (index == 1 and direction == "left") then local start = direction == "left" and 1 or index + 1 local _end = direction == "left" and index - 1 or length for _, item in ipairs(vim.list_slice(state.components, start, _end)) do delete_element(item.id) end end ui.refresh() end --Close other buffers function M.close_others() local index = M.get_current_element_index(state) if not index then return end for i, item in ipairs(state.components) do if i ~= index then delete_element(item.id) end end ui.refresh() end --- sorts all elements --- @param sort_by (string|function)? function M.sort_by(sort_by) if next(state.components) == nil then return utils.notify("Unable to find elements to sort, sorry", "warn") end sorters.sort(state.components, { sort_by = sort_by }) state.custom_sort = utils.get_ids(state.components) local opts = config.options if opts.persist_buffer_sort then utils.save_positions(state.custom_sort) end ui.refresh() end function M.rename_tab(args) if #args == 0 then return end local tabnr = tonumber(args[1]) local name = table.concat(args, " ", 2) if not tabnr then name = table.concat(args, " ") tabnr = 0 end tabpage.rename_tab(tabnr, name) end _G.___bufferline_private.handle_close = handle_close _G.___bufferline_private.handle_click = handle_click _G.___bufferline_private.handle_group_click = handle_group_click return M akinsho-bufferline.nvim-655133c/lua/bufferline/config.lua000066400000000000000000000572211474150523700234010ustar00rootroot00000000000000local M = {} local api = vim.api local fmt = string.format local lazy = require("bufferline.lazy") local groups = lazy.require("bufferline.groups") ---@module "bufferline.groups" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local highlights = lazy.require("bufferline.highlights") ---@module "bufferline.highlights" local colors = lazy.require("bufferline.colors") ---@module "bufferline.colors" local constants = lazy.require("bufferline.constants") ---@module "bufferline.colors" ---@enum bufferline.StylePreset local PRESETS = { default = 1, minimal = 2, no_bold = 3, no_italic = 4, } ---The local class instance of the merged user's configuration ---this includes all default values and highlights filled out ---@type bufferline.Config local config = {} ---The class definition for the user configuration ---@type bufferline.Config local Config = {} function Config:new(o) assert(o, "User options must be passed in") self.__index = self -- save a copy of the user's preferences so we can reference exactly what they -- wanted after the config and defaults have been merged. Do this using a copy -- so that reference isn't unintentionally mutated self.user = vim.deepcopy(o) setmetatable(o, self) return o end ---Combine user preferences with defaults preferring the user's own settings ---@param defaults bufferline.Config ---@return bufferline.Config function Config:merge(defaults) assert(defaults and type(defaults) == "table", "A valid config table must be passed to merge") self.options = vim.tbl_deep_extend("force", defaults.options, self.options or {}) self.highlights = vim.tbl_deep_extend("force", defaults.highlights, self.highlights or {}) return self end local deprecations = { show_buffer_default_icon = { name = "show_buffer_default_icon", alternative = "get_element_icon = function(buf) return require('nvim-web-devicons').get_icon(..., {default = false})", version = "4.0.0", }, } ---@param options bufferline.Options local function validate_user_options(options) if not options then return end for key, _ in pairs(options) do local item = deprecations[key] if item then vim.schedule(function() vim.deprecate(item.name, item.alternative, item.version, "bufferline") end) end end if options.diagnostics == "nvim_lsp" and options.diagnostics_update_in_insert then vim.schedule( function() vim.deprecate( "diagnostics_update_in_insert", "vim.diagnostic.config { update_in_insert = true }", "4.6.3", "bufferline" ) end ) end end ---@param options bufferline.Options ---@return {[string]: table}[] local function get_offset_highlights(options) if not options or not options.offsets then return {} end return utils.fold(function(accum, offset, i) if offset.highlight and type(offset.highlight) == "table" then accum[fmt("offset_%d", i)] = offset.highlight end return accum end, options.offsets) end ---@param options bufferline.Options ---@return table[] local function get_group_highlights(options) if not options or not options.groups then return {} end return utils.fold(function(accum, group) if group.highlight then accum[group.name] = group.highlight end return accum end, options.groups.items) end local function validate_user_highlights(opts, defaults, hls) if not hls then return end local incorrect = { invalid_hl = {} } local offset_highlights = get_offset_highlights(opts) local group_highlights = get_group_highlights(opts) local all_hls = vim.tbl_extend("force", {}, hls, offset_highlights, group_highlights) for k, _ in pairs(all_hls) do if hls[k] then if not defaults.highlights[k] then table.insert(incorrect.invalid_hl, k) end end end -- Don't continue if there are no incorrect highlights if next(incorrect.invalid_hl) then local is_plural = #incorrect > 1 local msg = table.concat({ table.concat(incorrect.invalid_hl, ", "), is_plural and " are " or " is ", "not", is_plural and " " or " a ", "valid highlight", is_plural and " groups. " or " group. ", "Please check :help bufferline-highlights for all valid highlights", }) utils.notify(msg, "error") end end --- Check that the user has not placed setting in the wrong tables ---@param conf bufferline.UserConfig local function validate_config_structure(conf) local invalid = {} for key, _ in pairs(conf) do if key ~= "options" and key ~= "highlights" then table.insert(invalid, " - " .. key) end end if next(invalid) then utils.notify({ "All configuration should be inside of the options or highlights table", "the following keys are in the wrong place", unpack(invalid), }, "warn") end end ---Ensure the user has only specified highlight groups that exist ---@param defaults bufferline.Config ---@param resolved bufferline.Highlights function Config:validate(defaults, resolved) validate_config_structure(self.user) validate_user_options(self.user.options) validate_user_highlights(self.user.options, defaults, resolved) end function Config:mode() if not self.options then return "buffers" end return self.options.mode end function Config:is_bufferline() return self:mode() == "buffers" end function Config:is_tabline() return self:mode() == "tabs" end ---Derive the colors for the bufferline ---@param preset bufferline.StylePreset | bufferline.StylePreset[] ---@return bufferline.Highlights local function derive_colors(preset) local hex = colors.get_color local tint = colors.shade_color if type(preset) ~= "table" then preset = { preset } end local is_minimal = vim.tbl_contains(preset, PRESETS.minimal) local italic = not vim.tbl_contains(preset, PRESETS.no_italic) local bold = not vim.tbl_contains(preset, PRESETS.no_bold) local comment_fg = hex({ name = "Comment", attribute = "fg", fallback = { name = "Normal", attribute = "fg" }, }) local normal_fg = hex({ name = "Normal", attribute = "fg" }) local normal_bg = hex({ name = "Normal", attribute = "bg" }) local string_fg = hex({ name = "String", attribute = "fg" }) local error_fg = hex({ name = "DiagnosticError", -- diagnostic with text highlight attribute = "fg", fallback = { name = "DiagnosticError", -- diagnostic with underline highlight attribute = "sp", fallback = { name = "Error", attribute = "fg", }, }, }) local warning_fg = hex({ name = "DiagnosticWarn", attribute = "fg", fallback = { name = "DiagnosticWarn", attribute = "sp", fallback = { name = "WarningMsg", attribute = "fg", }, }, }) local info_fg = hex({ name = "DiagnosticInfo", attribute = "fg", fallback = { name = "DiagnosticInfo", attribute = "sp", fallback = { name = "Normal", attribute = "fg", }, }, }) local hint_fg = hex({ name = "DiagnosticHint", attribute = "fg", fallback = { name = "DiagnosticHint", attribute = "sp", fallback = { name = "Directory", attribute = "fg", }, }, }) local tabline_sel_bg = hex({ name = "TabLineSel", attribute = "bg", not_match = normal_bg, fallback = { name = "TabLineSel", attribute = "fg", not_match = normal_bg, fallback = { name = "WildMenu", attribute = "fg" }, }, }) local win_separator_fg = hex({ name = "WinSeparator", attribute = "fg", fallback = { name = "VertSplit", attribute = "fg", }, }) -- If the colorscheme is bright we shouldn't do as much shading -- as this makes light color schemes harder to read local is_bright_background = colors.color_is_bright(normal_bg) local separator_shading = is_bright_background and -20 or -45 local background_shading = is_bright_background and -12 or -25 local diagnostic_shading = is_bright_background and -12 or -25 local duplicate_color = tint(comment_fg, -5) local visible_bg = is_minimal and normal_bg or tint(normal_bg, -8) local visible_fg = is_minimal and tint(normal_fg, -30) or comment_fg local separator_background_color = is_minimal and normal_bg or tint(normal_bg, separator_shading) local background_color = is_minimal and normal_bg or tint(normal_bg, background_shading) -- diagnostic colors by default are a few shades darker local normal_diagnostic_fg = tint(normal_fg, diagnostic_shading) local comment_diagnostic_fg = tint(comment_fg, diagnostic_shading) local hint_diagnostic_fg = tint(hint_fg, diagnostic_shading) local info_diagnostic_fg = tint(info_fg, diagnostic_shading) local warning_diagnostic_fg = tint(warning_fg, diagnostic_shading) local error_diagnostic_fg = tint(error_fg, diagnostic_shading) local indicator_style = vim.tbl_get(config, "user", "options", "indicator", "style") local has_underline_indicator = indicator_style == "underline" local underline_sp = has_underline_indicator and tabline_sel_bg or nil local trunc_marker_fg = comment_fg local trunc_marker_bg = separator_background_color return { trunc_marker = { fg = trunc_marker_fg, bg = trunc_marker_bg, }, fill = { fg = comment_fg, bg = separator_background_color, }, group_separator = { fg = comment_fg, bg = separator_background_color, }, group_label = { bg = comment_fg, fg = separator_background_color, }, tab = { fg = comment_fg, bg = background_color, }, tab_selected = { fg = tabline_sel_bg, bg = normal_bg, sp = underline_sp, bold = is_minimal and bold, underline = has_underline_indicator, }, tab_close = { fg = comment_fg, bg = background_color, }, close_button = { fg = comment_fg, bg = background_color, }, close_button_visible = { fg = visible_fg, bg = visible_bg, }, close_button_selected = { fg = normal_fg, bg = normal_bg, sp = underline_sp, underline = has_underline_indicator, }, background = { fg = comment_fg, bg = background_color, }, buffer = { fg = comment_fg, bg = background_color, }, buffer_visible = { fg = visible_fg, bg = visible_bg, italic = is_minimal and italic, bold = is_minimal and bold, }, buffer_selected = { fg = normal_fg, bg = normal_bg, bold = bold, italic = italic, sp = underline_sp, underline = has_underline_indicator, }, numbers = { fg = comment_fg, bg = background_color, }, numbers_selected = { fg = normal_fg, bg = normal_bg, bold = bold, italic = italic, sp = underline_sp, underline = has_underline_indicator, }, numbers_visible = { fg = visible_fg, bg = visible_bg, }, diagnostic = { fg = comment_diagnostic_fg, bg = background_color, }, diagnostic_visible = { fg = comment_diagnostic_fg, bg = visible_bg, }, diagnostic_selected = { fg = normal_diagnostic_fg, bg = normal_bg, bold = bold, italic = italic, sp = underline_sp, underline = has_underline_indicator, }, hint = { fg = comment_fg, sp = hint_fg, bg = background_color, }, hint_visible = { fg = visible_fg, bg = visible_bg, italic = is_minimal and italic, bold = is_minimal and bold, }, hint_selected = { fg = hint_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or hint_fg, }, hint_diagnostic = { fg = comment_diagnostic_fg, sp = hint_diagnostic_fg, bg = background_color, }, hint_diagnostic_visible = { fg = comment_diagnostic_fg, bg = visible_bg, }, hint_diagnostic_selected = { fg = hint_diagnostic_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or hint_diagnostic_fg, }, info = { fg = comment_fg, sp = info_fg, bg = background_color, }, info_visible = { fg = visible_fg, bg = visible_bg, italic = is_minimal and italic, bold = is_minimal and bold, }, info_selected = { fg = info_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or info_fg, }, info_diagnostic = { fg = comment_diagnostic_fg, sp = info_diagnostic_fg, bg = background_color, }, info_diagnostic_visible = { fg = comment_diagnostic_fg, bg = visible_bg, }, info_diagnostic_selected = { fg = info_diagnostic_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or info_diagnostic_fg, }, warning = { fg = comment_fg, sp = warning_fg, bg = background_color, }, warning_visible = { fg = visible_fg, bg = visible_bg, italic = is_minimal and italic, bold = is_minimal and bold, }, warning_selected = { fg = warning_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or warning_fg, }, warning_diagnostic = { fg = comment_diagnostic_fg, sp = warning_diagnostic_fg, bg = background_color, }, warning_diagnostic_visible = { fg = comment_diagnostic_fg, bg = visible_bg, }, warning_diagnostic_selected = { fg = warning_diagnostic_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or warning_diagnostic_fg, }, error = { fg = comment_fg, bg = background_color, sp = error_fg, }, error_visible = { fg = visible_fg, bg = visible_bg, italic = is_minimal and italic, bold = is_minimal and bold, }, error_selected = { fg = error_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or error_fg, }, error_diagnostic = { fg = comment_diagnostic_fg, bg = background_color, sp = error_diagnostic_fg, }, error_diagnostic_visible = { fg = comment_diagnostic_fg, bg = visible_bg, }, error_diagnostic_selected = { fg = error_diagnostic_fg, bg = normal_bg, bold = bold, italic = italic, underline = has_underline_indicator, sp = underline_sp or error_diagnostic_fg, }, modified = { fg = string_fg, bg = background_color, }, modified_visible = { fg = string_fg, bg = visible_bg, }, modified_selected = { fg = string_fg, bg = normal_bg, sp = underline_sp, underline = has_underline_indicator, }, duplicate_selected = { fg = duplicate_color, italic = italic, bg = normal_bg, sp = underline_sp, underline = has_underline_indicator, }, duplicate_visible = { fg = duplicate_color, italic = italic, bg = visible_bg, }, duplicate = { fg = duplicate_color, italic = italic, bg = background_color, }, separator_selected = { fg = separator_background_color, bg = normal_bg, sp = underline_sp, underline = has_underline_indicator, }, separator_visible = { fg = separator_background_color, bg = visible_bg, }, separator = { fg = separator_background_color, bg = background_color, }, tab_separator = { fg = separator_background_color, bg = background_color, }, tab_separator_selected = { fg = separator_background_color, bg = normal_bg, sp = underline_sp, underline = has_underline_indicator, }, indicator_selected = { fg = tabline_sel_bg, bg = normal_bg, sp = underline_sp, underline = has_underline_indicator, }, indicator_visible = { fg = visible_bg, bg = visible_bg, }, pick_selected = { fg = error_fg, bg = normal_bg, bold = bold, italic = italic, sp = underline_sp, underline = has_underline_indicator, }, pick_visible = { fg = error_fg, bg = visible_bg, bold = bold, italic = italic, }, pick = { fg = error_fg, bg = background_color, bold = bold, italic = italic, }, offset_separator = { fg = win_separator_fg, bg = separator_background_color, }, } end -- Ideally this plugin should generate a beautiful tabline a little similar -- to what you would get on other editors. The aim is that the default should -- be so nice it's what anyone using this plugin sticks with. It should ideally -- work across any well designed colorscheme deriving colors automagically. -- Icons from https://fontawesome.com/cheatsheet ---@return bufferline.Config local function get_defaults() local preset = vim.tbl_get(config, "user", "options", "style_preset") --[[@as bufferline.StylePreset]] ---@type bufferline.Options local opts = { mode = "buffers", themable = true, -- whether or not bufferline highlights can be overridden externally style_preset = preset, numbers = "none", buffer_close_icon = "", modified_icon = "●", close_icon = "", close_command = "bdelete! %d", left_mouse_command = "buffer %d", right_mouse_command = "bdelete! %d", middle_mouse_command = nil, -- U+2590 ▐ Right half block, this character is right aligned so the -- background highlight doesn't appear in the middle -- alternatives: right aligned => ▕ ▐ , left aligned => ▍ indicator = { icon = constants.indicator, style = "icon" }, left_trunc_marker = "", right_trunc_marker = "", separator_style = "thin", name_formatter = nil, truncate_names = true, tab_size = 18, max_name_length = 18, color_icons = true, show_buffer_icons = true, show_buffer_close_icons = true, get_element_icon = nil, show_close_icon = true, show_tab_indicators = true, show_duplicate_prefix = true, duplicates_across_groups = true, enforce_regular_tabs = false, always_show_bufferline = true, auto_toggle_bufferline = true, persist_buffer_sort = true, move_wraps_at_ends = false, max_prefix_length = 15, sort_by = "id", diagnostics = false, diagnostics_indicator = nil, diagnostics_update_in_insert = true, diagnostics_update_on_event = true, offsets = {}, groups = { items = {}, options = { toggle_hidden_on_enter = true } }, hover = { enabled = false, reveal = {}, delay = 200 }, debug = { logging = false }, pick = { alphabet = "abcdefghijklmopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ1234567890", }, } return { options = opts, highlights = derive_colors(opts.style_preset) } end --- Convert highlights specified as tables to the correct existing colours ---@param map bufferline.Highlights local function resolve_user_highlight_links(map) if not map or vim.tbl_isempty(map) then return {} end -- we deep copy the highlights table as assigning the attributes -- will only pass the references so will mutate the original table otherwise local updated = vim.deepcopy(map) for hl, attributes in pairs(map) do for attribute, value in pairs(attributes) do if type(value) == "table" then if value.highlight and value.attribute then updated[hl][attribute] = colors.get_color({ name = value.highlight, attribute = value.attribute, cterm = attribute:match("cterm") ~= nil, }) else updated[hl][attribute] = nil utils.notify(fmt("removing %s as it is not formatted correctly", hl), "warn") end end end end return updated end --- Resolve (and update) any incompatible options based on the values of other options --- e.g. in tabline only certain values are valid/certain options no longer make sense. function Config:resolve(defaults) local user, hl = self.user.highlights, self.highlights if type(user) == "function" then hl = user(defaults) end self.highlights = resolve_user_highlight_links(hl) local indicator_icon = vim.tbl_get(self, "options", "indicator_icon") if indicator_icon then self.options.indicator = { icon = indicator_icon, style = "icon" } end if self:is_tabline() then local opts = defaults.options opts.sort_by = "tabs" if opts.show_tab_indicators then opts.show_tab_indicators = false end opts.close_command = utils.close_tab opts.right_mouse_command = "tabclose %d" opts.left_mouse_command = api.nvim_set_current_tabpage end return hl end ---Generate highlight groups from user ---@param map {[string]: {fg: string, bg: string}} --- TODO: can this become part of a metatable for each highlight group so it is done at the point ---of usage local function set_highlight_names(map) for name, opts in pairs(map) do opts.hl_group = highlights.generate_name(name) end end ---Add highlight groups for a group ---@param hls bufferline.Highlights local function set_group_highlights(hls) for _, group in pairs(groups.get_all()) do local group_hl, name = group.highlight, group.name if group_hl and type(group_hl) == "table" then local sep_name = fmt("%s_separator", name) local label_name = fmt("%s_label", name) local selected_name = fmt("%s_selected", name) local visible_name = fmt("%s_visible", name) hls[sep_name] = { fg = group_hl.fg or group_hl.sp or hls.group_separator.fg, bg = hls.fill.bg, } hls[label_name] = { fg = hls.fill.bg, bg = group_hl.fg or group_hl.sp or hls.group_separator.fg, } hls[name] = vim.tbl_extend("keep", group_hl, hls.buffer) hls[visible_name] = vim.tbl_extend("keep", group_hl, hls.buffer_visible) hls[selected_name] = vim.tbl_extend("keep", group_hl, hls.buffer_selected) hls[name].hl_group = highlights.generate_name(name) hls[sep_name].hl_group = highlights.generate_name(sep_name) hls[label_name].hl_group = highlights.generate_name(label_name) hls[visible_name].hl_group = highlights.generate_name(visible_name) hls[selected_name].hl_group = highlights.generate_name(selected_name) end end end --- Merge user config with defaults --- @param quiet boolean? whether or not to validate the configuration --- @return bufferline.Config function M.apply(quiet) local defaults = get_defaults() local resolved = config:resolve(defaults) if not quiet then config:validate(defaults, resolved) end config:merge(defaults) set_highlight_names(config.highlights) set_group_highlights(config.highlights) return config end ---Keep track of a users config for use throughout the plugin as well as ensuring ---defaults are set. This is also so we can diff what the user set this is useful ---for setting the highlight groups etc. once this has been merged with the defaults ---@param c bufferline.UserConfig? function M.setup(c) config = Config:new(c or {}) end ---Update highlight colours when the colour scheme changes by resetting the user config ---to what was initially passed in and reload the highlighting function M.update_highlights() M.setup(config.user) M.apply(true) return config end ---Get the user's configuration or a key from it ---@return bufferline.Config? function M.get() if config then return config end end if _G.__TEST then function M.__reset() config = nil end end M.STYLE_PRESETS = PRESETS return setmetatable(M, { __index = function(_, k) return config[k] end, }) akinsho-bufferline.nvim-655133c/lua/bufferline/constants.lua000066400000000000000000000016111474150523700241400ustar00rootroot00000000000000local M = {} ---------------------------------------------------------------------------// -- Constants ---------------------------------------------------------------------------// M.padding = " " M.indicator = "▎" M.sep_names = { thin = "thin", thick = "thick", slant = "slant", slope = "slope", padded_slant = "padded_slant", padded_slope = "padded_slope", } ---@type table M.sep_chars = { [M.sep_names.thin] = { "▏", "▕" }, [M.sep_names.thick] = { "▌", "▐" }, [M.sep_names.slant] = { "", "" }, [M.sep_names.slope] = { "", "" }, [M.sep_names.padded_slant] = { "" .. M.padding, "" .. M.padding }, [M.sep_names.padded_slope] = { "" .. M.padding, "" .. M.padding }, } M.positions_key = "BufferlinePositions" M.visibility = { SELECTED = 3, INACTIVE = 2, NONE = 1, } M.FOLDER_ICON = "" M.ELLIPSIS = "…" return M akinsho-bufferline.nvim-655133c/lua/bufferline/custom_area.lua000066400000000000000000000045051474150523700244330ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local config = lazy.require("bufferline.config") ---@module "bufferline.config" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local highlights = lazy.require("bufferline.highlights") ---@module "bufferline.highlights" local M = {} local api = vim.api local fmt = string.format ---generate a custom highlight group ---@param index integer ---@param side string ---@param section {fg: string, bg: string, text: string, link: string} ---@param bg string? local function create_hl(index, side, section, bg) if section.link then return highlights.hl(section.link) end local name = fmt("BufferLine%sCustomAreaText%d", side:gsub("^%l", string.upper), index) section.bg = section.bg or bg section.default = true -- We need to be able to constantly override these highlights so they should always be default highlights.set(name, section) return highlights.hl(name) end ---@param text string ---@return number local function get_size(text) ---@type table local data = api.nvim_eval_statusline(text, { use_tabline = true }) return data.width end ---Create tabline segment for custom user specified sections ---@return integer ---@return string ---@return string function M.get() local size = 0 local left = "" local right = "" ---@type table local areas = config.options.custom_areas if areas then for side, section_fn in pairs(areas) do if type(section_fn) ~= "function" then utils.notify(fmt("each side should be a function but you passed in %s", vim.inspect(side)), "error") return 0, "", "" end -- if the user doesn't specify a background use the default local hls = config.highlights or {} local bg = hls.fill and hls.fill.bg or nil local ok, section = pcall(section_fn) if ok and section and not vim.tbl_isempty(section) then for i, item in ipairs(section) do if item.text and type(item.text) == "string" then local hl = create_hl(i, side, item, bg) size = size + get_size(item.text) if side == "left" then left = left .. hl .. item.text else right = right .. hl .. item.text end end end end end end return size, left, right end return M akinsho-bufferline.nvim-655133c/lua/bufferline/diagnostics.lua000066400000000000000000000135531474150523700244430ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local config = lazy.require("bufferline.config") ---@module "bufferline.config" local ui = lazy.require("bufferline.ui") ---@module "bufferline.ui" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local M = {} local fn = vim.fn local fmt = string.format local severity_name = utils.tbl_add_reverse_lookup({ [1] = "error", [2] = "warning", [3] = "info", [4] = "hint", [5] = "other", }) setmetatable(severity_name, { __index = function() return "other" end, }) local last_diagnostics_result = {} local function get_err_dict(errs) local ds = {} local max = #severity_name for _, err in ipairs(errs) do if err then -- calculate max severity local sev_num = err.severity local sev_level = severity_name[sev_num] if sev_num < max then max = sev_num end -- increment diagnostics dict if ds[sev_level] then ds[sev_level] = ds[sev_level] + 1 else ds[sev_level] = 1 end end end local max_severity = severity_name[max] return { level = max_severity, errors = ds } end local mt = { __index = function(_, _) return { count = 0, level = nil } end, } local is_valid_version = fn.has("nvim-0.5") > 0 local function is_disabled(diagnostics) if not diagnostics or not vim.tbl_contains({ "nvim_lsp", "coc" }, diagnostics) -- check if the current nvim version is one that will have either vim.diagnostics or vim.lsp.diagnostics or (diagnostics == "nvim_lsp" and not is_valid_version) or (diagnostics == "coc" and vim.g.coc_service_initialized ~= 1) then return true end return false end local function is_insert() -- insert or replace local mode = vim.api.nvim_get_mode().mode return mode == "i" or mode == "ic" or mode == "ix" or mode == "R" or mode == "Rc" or mode == "Rx" end local function diagnostic_is_enabled(d) if vim.fn.has("nvim-0.10") == 1 then return vim.diagnostic.is_enabled({ ns_id = d.namespace, bufnr = d.bufnr }) else -- neovim 0.9.x return not (vim.diagnostic.is_disabled and vim.diagnostic.is_disabled(d.bufnr, d.namespace)) end end local get_diagnostics = { nvim_lsp = function() local results = {} local diagnostics = vim.diagnostic.get() for _, d in pairs(diagnostics) do if diagnostic_is_enabled(d) then if not results[d.bufnr] then results[d.bufnr] = {} end table.insert(results[d.bufnr], d) end end return results end, coc = (function() local diagnostics = {} function M.refresh_coc_diagnostics() pcall(fn.CocActionAsync, "diagnosticList", function(err, res) if err ~= vim.NIL then return end res = type(res) == "table" and res or {} local result = {} local bufname2bufnr = {} for _, diagnostic in ipairs(res) do local bufname = diagnostic.file local bufnr = bufname2bufnr[bufname] if not bufnr then bufnr = vim.fn.bufnr(bufname) bufname2bufnr[bufname] = bufnr end if bufnr ~= -1 then result[bufnr] = result[bufnr] or {} table.insert(result[bufnr], { severity = diagnostic.level }) end end diagnostics = result end) end vim.cmd([[autocmd User CocDiagnosticChange lua require('bufferline.diagnostics').refresh_coc_diagnostics()]]) return function() return diagnostics end end)(), } function M.combine(diagnostics) if #diagnostics < 1 then return {} end local result = { sev_code = severity_name[diagnostics[1].level], errors = {}, count = 0, } for _, diag in pairs(diagnostics) do result.sev_code = math.min(result.sev_code, severity_name[diag.level]) for severity, count in pairs(diag.errors) do result.errors[severity] = count + (result.errors[severity] or 0) result.count = count + result.count end end return { level = severity_name[result.sev_code], errors = result.errors, count = result.count, } end ---@param opts table function M.get(opts) if is_disabled(opts.diagnostics) then return setmetatable({}, mt) end local update_nvim = opts.diagnostics == "nvim_lsp" and vim.diagnostic.config().update_in_insert local update_coc = opts.diagnostics == "coc" and opts.diagnostics_update_in_insert if is_insert() and not update_nvim and not update_coc then return setmetatable(last_diagnostics_result, mt) end local diagnostics = get_diagnostics[opts.diagnostics]() local result = {} for buf_num, items in pairs(diagnostics) do local d = get_err_dict(items) result[buf_num] = { count = #items, level = d.level, errors = d.errors, } end last_diagnostics_result = result return setmetatable(result, mt) end ---@param context bufferline.RenderContext ---@return bufferline.Segment? function M.component(context) local opts = config.options if is_disabled(opts.diagnostics) then return end local user_indicator = opts.diagnostics_indicator local highlights = context.current_highlights local element = context.tab local diagnostics = element.diagnostics if not diagnostics or not diagnostics.count or diagnostics.count < 1 then return end local indicator = "" if user_indicator and type(user_indicator) == "function" then local ctx = { buffer = element, tab = element } indicator = user_indicator(diagnostics.count, diagnostics.level, diagnostics.errors, ctx) elseif indicator == nil then indicator = fmt(" (%s)", diagnostics.count) end local highlight = highlights[diagnostics.level] or "" local diag_highlight = highlights[diagnostics.level .. "_diagnostic"] or highlights.diagnostic or "" return { text = indicator, highlight = diag_highlight, attr = { extends = { { id = ui.components.id.name, highlight = highlight }, { id = ui.components.id.groups, highlight = highlight }, }, }, } end return M akinsho-bufferline.nvim-655133c/lua/bufferline/duplicates.lua000066400000000000000000000065101474150523700242640ustar00rootroot00000000000000local M = {} local lazy = require("bufferline.lazy") local config = lazy.require("bufferline.config") ---@module "bufferline.config" local utils = require("bufferline.utils") ---@module "bufferline.utils" local duplicates = {} local api = vim.api function M.reset() duplicates = {} end local function is_same_path(a, b, depth) local a_path = vim.split(a, utils.path_sep) local b_path = vim.split(b, utils.path_sep) local a_index = depth <= #a_path and (#a_path - depth) + 1 or 1 local b_index = depth <= #b_path and (#b_path - depth) + 1 or 1 return b_path[b_index] == a_path[a_index] end --- This function marks any duplicate buffers granted --- the buffer names have changes ---@param elements bufferline.TabElement[] ---@return bufferline.TabElement[] function M.mark(elements) local options = config.options local duplicates_across_groups = options.duplicates_across_groups return utils.map(function(current) if current.path == "" then return current end local duplicate = duplicates[current.name] if not duplicate then duplicates[current.name] = { current } else local depth, limit, is_same_buffer = 1, 10, false for _, element in ipairs(duplicate) do if (duplicates_across_groups == false) and (current.group ~= element.group) then return current end local element_depth = 1 is_same_buffer = current.path == element.path while is_same_path(current.path, element.path, element_depth) and not is_same_buffer do if element_depth >= limit then break end element_depth = element_depth + 1 end if element_depth > depth then depth = element_depth end elements[element.ordinal].prefix_count = element_depth elements[element.ordinal].duplicated = is_same_buffer and "element" or "path" end current.prefix_count = depth current.duplicated = is_same_buffer and "element" or "path" duplicate[#duplicate + 1] = current end return current end, elements) end --- @param dir string --- @param depth number --- @param max_size number local function truncate(dir, depth, max_size) if api.nvim_strwidth(dir) <= max_size then return dir end -- we truncate any section of the ancestor which is too long -- by dividing the allotted space for each section by the depth i.e. -- the amount of ancestors which will be prefixed local allowed_size = math.floor(max_size / depth) + 1 -- Add one to account for the path separator local truncated = utils.map( function(part) return utils.truncate_name(part, allowed_size) end, vim.split(dir, utils.path_sep) ) return table.concat(truncated, utils.path_sep) end --- @param context bufferline.RenderContext --- @return bufferline.Segment? function M.component(context) local element = context.tab local hl = context.current_highlights local options = config.options -- there is no way to enforce a regular tab size as specified by the -- user if we are going to potentially increase the tab length by -- prefixing it with the parent dir(s) if element.duplicated and options.show_duplicate_prefix and not options.enforce_regular_tabs then local dir = element:ancestor( element.prefix_count, function(dir, depth) return truncate(dir, depth, options.max_prefix_length) end ) return { text = dir, highlight = hl.duplicate } end end return M akinsho-bufferline.nvim-655133c/lua/bufferline/groups.lua000066400000000000000000000447561474150523700234640ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local ui = lazy.require("bufferline.ui") ---@module "bufferline.ui" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local models = lazy.require("bufferline.models") ---@module "bufferline.models" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local commands = lazy.require("bufferline.commands") ---@module "bufferline.commands" local state = lazy.require("bufferline.state") ---@module "bufferline.state" local C = lazy.require("bufferline.constants") ---@module "bufferline.constants" local fn = vim.fn ---------------------------------------------------------------------------------------------------- -- CONSTANTS ---------------------------------------------------------------------------------------------------- local PINNED_ID = "pinned" local PINNED_NAME = "pinned" local UNGROUPED_NAME = "ungrouped" local UNGROUPED_ID = "ungrouped" local PINNED_KEY = "BufferlinePinnedBuffers" local api = vim.api local fmt = string.format local M = {} ---------------------------------------------------------------------------------------------------- -- UTILS ---------------------------------------------------------------------------------------------------- --- Remove illegal characters from a group name name ---@param name string local function format_name(name) return name:gsub("[^%w]+", "_") end ---------------------------------------------------------------------------------------------------- -- SEPARATORS ---------------------------------------------------------------------------------------------------- local separator = {} local function space_end(hl_groups) return { { highlight = hl_groups.fill.hl_group, text = C.padding } } end ---@param group bufferline.Group, ---@param hls table> ---@param count string ---@return bufferline.Separators function separator.pill(group, hls, count) local bg_hl = hls.fill.hl_group local name, display_name = group.name, group.display_name local sep_grp, label_grp = hls[fmt("%s_separator", name)], hls[fmt("%s_label", name)] local sep_hl = sep_grp and sep_grp.hl_group or hls.group_separator.hl_group local label_hl = label_grp and label_grp.hl_group or hls.group_label.hl_group local left, right = "█", "█" local indicator = { { text = C.padding, highlight = bg_hl }, { text = left, highlight = sep_hl }, { text = display_name .. count, highlight = label_hl }, { text = right, highlight = sep_hl }, { text = C.padding, highlight = bg_hl }, } return { sep_start = indicator, sep_end = space_end(hls) } end ---@param group bufferline.Group, ---@param hls table> ---@param count string ---@return bufferline.Separators ---@type GroupSeparator function separator.tab(group, hls, count) local hl = hls.fill.hl_group local indicator_hl = hls.buffer.hl_group local indicator = { { highlight = hl, text = C.padding }, { highlight = indicator_hl, text = C.padding .. group.name .. count .. C.padding }, { highlight = hl, text = C.padding }, } return { sep_start = indicator, sep_end = space_end(hls) } end ---@type GroupSeparator function separator.none() return { sep_start = {}, sep_end = {} } end ---------------------------------------------------------------------------------------------------- -- BUILTIN GROUPS ---------------------------------------------------------------------------------------------------- local builtin = {} --- @type bufferline.Group local Group = {} ---@param o bufferline.Group ---@param index number? ---@return bufferline.Group function Group:new(o, index) o = o or { priority = index } self.__index = self local name = format_name(o.name) o = vim.tbl_extend("force", o, { id = o.id or name, hidden = o.hidden == nil and false or o.hidden, name = name, display_name = o.name, priority = o.priority or index, }) return setmetatable(o, self) end function Group:with(o) for key, value in pairs(o) do self[key] = value end return self end builtin.ungrouped = Group:new({ id = UNGROUPED_ID, name = UNGROUPED_NAME, separator = { style = separator.none, }, }) builtin.pinned = Group:new({ id = PINNED_ID, name = PINNED_NAME, icon = "📌", priority = 1, separator = { style = separator.none, }, }) ---------------------------------------------------------------------------------------------------- -- STATE ---------------------------------------------------------------------------------------------------- --- @type bufferline.GroupState local group_state = { -- A table of buffers mapped to a specific group by a user manual_groupings = {}, user_groups = {}, --- Represents a list of maps of type `{id = buf_id, index = position in list}` --- so that rather than storing the components we store their positions --- with an easy way to look them up later. --- e.g. `[[group 1, {id = 12, index = 1}, {id = 10, index 2}], [group 2, {id = 5, index = 3}]]` components_by_group = {}, } --- Store a list of pinned buffer as a string of ids e.g. "12,10,5" --- in a vim.g global variable that can be persisted across vim sessions local function persist_pinned_buffers() local pinned = {} for buf, group in pairs(group_state.manual_groupings) do if group == PINNED_ID then table.insert(pinned, api.nvim_buf_get_name(buf)) end end if #pinned == 0 then vim.g[PINNED_KEY] = "" else vim.g[PINNED_KEY] = table.concat(pinned, ",") end end ---@param element bufferline.TabElement ---@return string local function get_manual_group(element) return group_state.manual_groupings[element.id] end --- Wrapper to abstract interacting directly with manual groups as the access mechanism -- can vary i.e. buffer id or path and this should be changed in a centralised way. ---@param id number ---@param group_id string? local function set_manual_group(id, group_id) group_state.manual_groupings[id] = group_id end ---A temporary helper to inform user of the full buffer object that using it's full value is deprecated. ---@param obj table ---@return table local function with_deprecation(obj) return setmetatable(obj, { __index = function(_, k) vim.schedule(function() -- stylua: ignore vim.deprecate(k, "the buffer ID to get any other value option you need", "v4.0.0", "bufferline") end) end, }) end ---Group buffers based on user criteria ---buffers only carry a copy of the group ID which is then used to retrieve the correct group ---@param buffer bufferline.Buffer ---@return string? function M.set_id(buffer) if vim.tbl_isempty(group_state.user_groups) then return end local manual_group = get_manual_group(buffer) if manual_group then return manual_group end for id, group in pairs(group_state.user_groups) do if type(group.matcher) == "function" then local matched = group.matcher(with_deprecation({ id = buffer.id, name = buffer.name, path = buffer.path, modified = buffer.modified, buftype = buffer.buftype, })) if matched then return id end end end return UNGROUPED_ID end ---@param id string ---@return bufferline.Group function M.get_by_id(id) return group_state.user_groups[id] end local function generate_sublists(size) local list = {} for i = 1, size do list[i] = {} end return list end ---Add group styling to the buffer component ---@param ctx bufferline.RenderContext ---@return bufferline.Segment? function M.component(ctx) local element = ctx.tab local hls = ctx.current_highlights local group = group_state.user_groups[element.group] if not group then return end local group_hl = hls[group.name] local hl = group_hl or hls.buffer if not group.icon then return nil end local extends = { { id = ui.components.id.name } } if group_hl then extends[#extends + 1] = { id = ui.components.id.duplicates } end return { text = group.icon, highlight = hl, attr = { extends = extends }, } end --- Pull pinned buffers saved in a vim.g global variable and restore them --- to the manual_groupings table. local function restore_pinned_buffers() local pinned = vim.g[PINNED_KEY] if not pinned then return end local manual_groupings = vim.split(pinned, ",") or {} for _, path in ipairs(manual_groupings) do local buf_id = fn.bufnr(path --[[@as integer]]) if buf_id ~= -1 then set_manual_group(buf_id, PINNED_ID) persist_pinned_buffers() end end ui.refresh() end --- NOTE: this function mutates the user's configuration by adding group highlights to the user highlights table. --- ---@param conf bufferline.UserConfig function M.setup(conf) if not conf then return end local groups = vim.tbl_get(conf, "options", "groups", "items") or {} ---@type bufferline.Group[] -- if the user has already set the pinned builtin themselves -- then we want each group to have a priority based on it's position in the list -- otherwise we want to shift the priorities of their groups by 1 to accommodate the pinned group local has_set_pinned = not vim.tbl_isempty(vim.tbl_filter(function(group) return group.id == PINNED_ID end, groups)) for index, current in ipairs(groups) do local priority = has_set_pinned and index or index + 1 local group = Group:new(current, priority) group_state.user_groups[group.id] = group end -- We only set the builtin groups after we know what the user has configured if not group_state.user_groups[PINNED_ID] then group_state.user_groups[PINNED_ID] = builtin.pinned end if not group_state.user_groups[UNGROUPED_ID] then group_state.user_groups[UNGROUPED_ID] = builtin.ungrouped:with({ priority = vim.tbl_count(group_state.user_groups) + 1, }) end -- Restore pinned buffer from the previous session api.nvim_create_autocmd("SessionLoadPost", { once = true, callback = restore_pinned_buffers }) end ---Execute a command on each buffer of a group ---@param group_name string ---@param callback fun(b: bufferline.Buffer) local function command(group_name, callback) local group = utils.find(function(list) return list.name == group_name end, group_state.components_by_group) if not group then return end utils.for_each(callback, group) end ---@generic T ---@param attr string ---@return fun(arg: T): bufferline.Group local function group_by(attr) return function(value) for _, grp in pairs(group_state.user_groups) do if grp[attr] == value then return grp end end end end local group_by_name = group_by("name") local group_by_priority = group_by("priority") ---@param element bufferline.TabElement function M._is_pinned(element) return get_manual_group(element) == PINNED_ID end --- Add a buffer to a group manually ---@param group_name string ---@param element bufferline.TabElement? function M.add_element(group_name, element) local group = group_by_name(group_name) if group and element then set_manual_group(element.id, group.id) persist_pinned_buffers() end end ---@param group_name string ---@param element bufferline.TabElement function M.remove_element(group_name, element) local group = group_by_name(group_name) if group then local id = get_manual_group(element) set_manual_group(element.id, id ~= group.id and id or nil) if group_name == PINNED_ID then persist_pinned_buffers() end end end ---@param id number function M.remove_id_from_manual_groupings(id) group_state.manual_groupings[id] = nil end ---@param id string ---@param value boolean function M.set_hidden(id, value) assert(id, "You must pass in a group ID to set its state") local group = group_state.user_groups[id] if group then group.hidden = value end end ---@param priority number? ---@param name string? function M.toggle_hidden(priority, name) local group = priority and group_by_priority(priority) or group_by_name(name) if group then group.hidden = not group.hidden end end ---Get the names for all bufferline groups ---@param include_empty boolean? ---@return string[] local function names(include_empty) if not group_state.user_groups then return {} end local result = {} for _, group in pairs(group_state.components_by_group) do if include_empty or (group and #group > 0) then table.insert(result, group.name) end end return result end ---@param arg_lead string ---@param cmd_line string ---@param cursor_pos number ---@return string[] ---@diagnostic disable-next-line: unused-local function M.complete(arg_lead, cmd_line, cursor_pos) return names() end --- Draw the separator start component for a group ---@param group bufferline.Group ---@param hls bufferline.Highlights? ---@param count number ---@return bufferline.Separators local function create_indicator(group, hls, count) hls = hls or {} local count_item = group.hidden and fmt("(%s)", count) or "" local seps = group.separator.style(group, hls, count_item) if seps.sep_start then table.insert(seps.sep_start, ui.make_clickable("handle_group_click", group.priority, { attr = { global = true } })) end return seps end ---Create the visual indicators bookending buffer groups ---@param group_id string ---@param components bufferline.Component[] ---@return bufferline.Component? ---@return bufferline.Component? local function get_group_marker(group_id, components) local group = group_state.user_groups[group_id] if not group then return end local GroupView = models.GroupView local hl_groups = config.highlights group.separator = group.separator or {} --- NOTE: the default buffer group style is the pill group.separator.style = group.separator.style or separator.pill if not group.separator.style then return end local seps = create_indicator(group, hl_groups, #components) local s_start, s_end = seps.sep_start, seps.sep_end local group_start, group_end local s_start_length = ui.get_component_size(s_start) local s_end_length = ui.get_component_size(s_end) if s_start_length > 0 then group_start = GroupView:new({ type = "group_start", length = s_start_length, component = function() return s_start end, }) end if s_end_length > 0 then group_end = GroupView:new({ type = "group_end", length = s_end_length, component = function() return s_end end, }) end return group_start, group_end end --- The aim is to have buffers easily accessible by key as well as a list of sorted and prioritized --- buffers for things like navigation. This function takes advantage of lua's ability --- to sort string keys as well as numerical keys in a table, this way each sublist has --- not only the group information but contains it's buffers ---@param components bufferline.Component[] ---@return bufferline.Component[], bufferline.ComponentsByGroup local function sort_by_groups(components) local sorted = {} local clustered = generate_sublists(vim.tbl_count(group_state.user_groups)) for index, tab in ipairs(components) do local buf = tab:as_element() if buf then local group = group_state.user_groups[buf.group] local sublist = clustered[group.priority] if not sublist.name then sublist.id = group.id sublist.name = group.name sublist.priorty = group.priority sublist.hidden = group.hidden sublist.display_name = group.display_name end table.insert(sublist, { id = buf.id, index = index }) table.insert(sorted, buf) end end return sorted, clustered end function M.get_all() return group_state.user_groups end ---@alias group_actions "close" | "toggle" ---Execute an action on a group of buffers ---@param name string ---@param action group_actions | fun(b: bufferline.Buffer) function M.action(name, action) assert(name, "A name must be passed to execute a group action") if action == "close" then command(name, function(b) api.nvim_buf_delete(b.id, { force = true }) end) ui.refresh() if name == PINNED_NAME then vim.g[PINNED_KEY] = {} end for buf, group_id in pairs(group_state.manual_groupings) do if group_id == name then group_state.manual_groupings[buf] = nil end end elseif action == "toggle" then M.toggle_hidden(nil, name) ui.refresh() elseif type(action) == "function" then command(name, action) end end function M.toggle_pin() local _, element = commands.get_current_element_index(state) if not element then return end if M._is_pinned(element) then M.remove_element("pinned", element) else M.add_element("pinned", element) end ui.refresh() end function M.handle_group_enter() local options = config.options local _, element = commands.get_current_element_index(state, { include_hidden = true }) if not element or not element.group then return end local current_group = M.get_by_id(element.group) if options.groups.options.toggle_hidden_on_enter then if current_group.hidden then M.set_hidden(current_group.id, false) end end utils.for_each(function(tab) local group = M.get_by_id(tab.group) if group and group.auto_close and group.id ~= current_group.id then M.set_hidden(group.id, true) end end, state.components) end -- FIXME: this function does a lot of looping that can maybe be consolidated -- ---@param components bufferline.Component[] ---@param sorter fun(list: bufferline.Component[]):bufferline.Component[] ---@return bufferline.Component[] function M.render(components, sorter) components, group_state.components_by_group = sort_by_groups(components) if vim.tbl_isempty(group_state.components_by_group) then return components end local result = {} ---@type bufferline.Component[] for _, sublist in ipairs(group_state.components_by_group) do local buf_group_id = sublist.id local buf_group = group_state.user_groups[buf_group_id] --- convert our components by group which is essentially and index of tab positions and ids --- to the actual tab by pulling the full value out of the tab map local items = utils.map(function(map) local t = components[map.index] --- filter out tab views that are hidden t.hidden = buf_group and buf_group.hidden return t end, sublist) --- Sort *each* group, TODO: in the future each group should be able to have it's own sorter items = sorter(items) if #sublist > 0 then local group_start, group_end = get_group_marker(buf_group_id, sublist) if group_start then table.insert(items, 1, group_start) items[#items + 1] = group_end end end result = utils.merge_lists(result, items) end return result end M.builtin = builtin M.separator = separator if utils.is_test() then M.state = group_state M.sort_by_groups = sort_by_groups M.get_manual_group = get_manual_group M.set_manual_group = set_manual_group end return M akinsho-bufferline.nvim-655133c/lua/bufferline/highlights.lua000066400000000000000000000135561474150523700242710ustar00rootroot00000000000000local fmt = string.format local lazy = require("bufferline.lazy") local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local constants = lazy.require("bufferline.constants") ---@module "bufferline.constants" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local groups = lazy.require("bufferline.groups") ---@module "bufferline.groups" local colors = require("bufferline.colors") ---@module "bufferline.colors" local log = lazy.require("bufferline.utils.log") ---@module "bufferline.utils.log" local api = vim.api local visibility = constants.visibility ---------------------------------------------------------------------------// -- Highlights ---------------------------------------------------------------------------// local M = {} local PREFIX = "BufferLine" --- Generate highlight groups names i.e --- convert 'bufferline_value' to 'BufferlineValue' -> snake to pascal ---@param name string function M.generate_name(name) return PREFIX .. name:gsub("_(.)", name.upper):gsub("^%l", string.upper) end --- Wrap a string in vim's tabline highlight syntax ---@param item string ---@return string function M.hl(item) if not item then return "" end return fmt("%%#%s#", item) end local hl_keys = { fg = true, bg = true, sp = true, default = true, link = true, italic = true, bold = true, underline = true, undercurl = true, underdot = true, } ---These values will error if a theme does not set a normal ctermfg or ctermbg @see: #433 if not vim.opt.termguicolors:get() then hl_keys.ctermfg = "ctermfg" hl_keys.ctermbg = "ctermbg" hl_keys.cterm = "cterm" end local function filter_invalid_keys(hl) return utils.fold(function(accum, item, key) if hl_keys[key] then accum[key] = item end return accum end, hl) end ---Apply a single highlight ---@param name string ---@param opts {[string]: string | boolean} ---@return {[string]: string | boolean}? function M.set(name, opts) if not opts or vim.tbl_isempty(opts) then return end local hl = filter_invalid_keys(opts) hl.default = vim.F.if_nil(opts.default, config.options.themable) local ok, msg = pcall(api.nvim_set_hl, 0, name, hl) if ok then return hl end utils.notify(fmt("Failed setting %s highlight, something isn't configured correctly: %s", name, msg), "error") end --- @param conf bufferline.Config function M.set_all(conf) local msgs = {} for name, opts in pairs(conf.highlights) do if not opts or not opts.hl_group then msgs[#msgs + 1] = fmt("* %s - %s", name, vim.inspect(opts)) else M.set(opts.hl_group, opts) end end if next(msgs) then utils.notify(fmt("Error setting highlight group(s) for: \n", table.concat(msgs, "\n")), "error") end end local icon_hl_cache = {} function M.reset_icon_hl_cache() icon_hl_cache = {} end --- Generate and set a highlight for an element's icon --- this value is cached until the colorscheme changes to prevent --- redundant calls to set the same highlight constantly ---@param state bufferline.Visibility ---@param hls bufferline.Highlights ---@param base_hl string ---@return string function M.set_icon_highlight(state, hls, base_hl) local state_props = ({ [visibility.INACTIVE] = { "Inactive", hls.buffer_visible }, [visibility.SELECTED] = { "Selected", hls.buffer_selected }, [visibility.NONE] = { "", hls.background }, })[state] local icon_hl, parent = PREFIX .. base_hl .. state_props[1], state_props[2] if icon_hl_cache[icon_hl] then return icon_hl end local color_icons = config.options.color_icons local color = not color_icons and "NONE" local hl_colors = vim.tbl_extend("force", parent, { fg = color or colors.get_color({ name = base_hl, attribute = "fg" }), ctermfg = color or colors.get_color({ name = base_hl, attribute = "fg", cterm = true }), italic = false, bold = false, hl_group = icon_hl, }) M.set(icon_hl, hl_colors) icon_hl_cache[icon_hl] = true return icon_hl end ---@param vis bufferline.Visibility ---@param hls bufferline.Highlights ---@param name string ---@param base string? ---@return string local function get_hl_group_for_state(vis, hls, name, base) if not base then base = name end local state = ({ [visibility.INACTIVE] = "visible", [visibility.SELECTED] = "selected" })[vis] local hl_name = state and fmt("%s_%s", name, state) or base if hls[hl_name].hl_group then return hls[hl_name].hl_group end log.debug(fmt("%s highlight not found", name)) return "" end --- Return the correct highlight groups for each element i.e. --- if the element is selected, visible or inactive it's highlights should differ ---@param element bufferline.Buffer | bufferline.Tab ---@return table function M.for_element(element) local hl = {} ---@param name string ---@param fallback string? local function hl_group(name, fallback) return get_hl_group_for_state(element:visibility(), config.highlights, name, fallback) end hl.modified = hl_group("modified") hl.duplicate = hl_group("duplicate") hl.pick = hl_group("pick") hl.separator = hl_group("separator") hl.diagnostic = hl_group("diagnostic") hl.error = hl_group("error") hl.error_diagnostic = hl_group("error_diagnostic") hl.warning = hl_group("warning") hl.warning_diagnostic = hl_group("warning_diagnostic") hl.info = hl_group("info") hl.info_diagnostic = hl_group("info_diagnostic") hl.hint = hl_group("hint") hl.hint_diagnostic = hl_group("hint_diagnostic") hl.close_button = hl_group("close_button") hl.numbers = hl_group("numbers") hl.buffer = hl_group("buffer", "background") hl.background = hl.buffer -- If the element is part of a group then the highlighting for the elements name will be changed -- to match highlights for that group if element.group then local group = groups.get_all()[element.group] if group and group.name and group.highlight then hl.buffer = hl_group(group.name) end end return hl end return M akinsho-bufferline.nvim-655133c/lua/bufferline/hover.lua000066400000000000000000000026401474150523700232520ustar00rootroot00000000000000---@diagnostic disable: param-type-mismatch local fn, api, map = vim.fn, vim.api, vim.keymap.set local AUGROUP = api.nvim_create_augroup("BufferLineHover", { clear = true }) local version = vim.version() local delay = 500 local timer = nil local previous_pos = nil local M = {} local function on_hover(current) if vim.o.showtabline == 0 then return end if current.screenrow == 1 then api.nvim_exec_autocmds("User", { pattern = "BufferLineHoverOver", data = { cursor_pos = current.screencol }, }) elseif previous_pos and previous_pos.screenrow == 1 and current.screenrow ~= 1 then api.nvim_exec_autocmds("User", { pattern = "BufferLineHoverOut", data = {}, }) end previous_pos = current end ---@param conf bufferline.Config function M.setup(conf) local opts = conf.options.hover if not opts.enabled or version.minor < 8 or not vim.o.mousemoveevent then return end delay = opts.delay or delay map({ "", "i" }, "", function() if timer then timer:close() end timer = vim.defer_fn(function() timer = nil local ok, pos = pcall(fn.getmousepos) if not ok then return end on_hover(pos) end, delay) return "" end, { expr = true }) api.nvim_create_autocmd("VimLeavePre", { group = AUGROUP, callback = function() if timer then timer:close() timer = nil end end, }) end return M akinsho-bufferline.nvim-655133c/lua/bufferline/lazy.lua000066400000000000000000000006751474150523700231140ustar00rootroot00000000000000local lazy = {} --- Require on index. --- --- Will only require the module after the first index of a module. --- Only works for modules that export a table. ---@param require_path string ---@return table lazy.require = function(require_path) return setmetatable({}, { __index = function(_, key) return require(require_path)[key] end, __newindex = function(_, key, value) require(require_path)[key] = value end, }) end return lazy akinsho-bufferline.nvim-655133c/lua/bufferline/models.lua000066400000000000000000000206541474150523700234170ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local log = lazy.require("bufferline.utils.log") ---@module "bufferline.utils.log" local constants = lazy.require("bufferline.constants") ---@module "bufferline.constants" local M = {} local api = vim.api local fn = vim.fn local fmt = string.format local visibility = constants.visibility --[[ -----------------------------------------------------------------------------// -- Models -----------------------------------------------------------------------------// This file contains all the different kinds of entities that are shown in the tabline They are all subtypes of Components which specifies the base interface for all types i.e. - A [component] - which is a function that returns the string to be rendered - A current method - to indicate if the entity is selected (if possible) - An end_component method - to indicate if the component represents the end of a block - [length] - the size of the string component minus highlights which don't render - [type] - a string enum representing the type of an entity * this list is not exhaustive --]] --- The base class that represents a visual tab in the tabline --- i.e. not necessarily representative of a vim tab or buffer ---@class bufferline.Component local Component = {} ---@param field string local function not_implemented(field) log.debug(debug.traceback("Stack trace:")) error(fmt("%s is not implemented yet", field)) end ---@generic T ---@param t table ---@return T function Component:new(t) assert(t.type, "all components must have a type") self.length = t.length or 0 self.focusable = true if t.focusable ~= nil then self.focusable = t.focusable end self.component = t.component or function() not_implemented("component") end setmetatable(t, self) self.__index = self return t end -- TODO: this should be handled based on the type of entity -- e.g. a buffer should report if it's current but other things shouldn't function Component:current() not_implemented("current") end ---Determine if the current view tab should be treated as the end of a section ---@return boolean function Component:is_end() return self.type:match("group") end ---@return bufferline.TabElement? function Component:as_element() -- TODO: Figure out how to correctly type cast a component to a TabElement ---@diagnostic disable-next-line: return-type-mismatch if vim.tbl_contains({ "buffer", "tab" }, self.type) then return self end end ---Find the directory prefix of an element up to a certain depth ---@param depth integer ---@param formatter (fun(path: string, depth: integer): string)? ---@return string function Component:__ancestor(depth, formatter) if self.type ~= "buffer" and self.type ~= "tab" then return "" end local parts = vim.split(self.path, utils.path_sep, { trimempty = true }) local index = (depth and depth > #parts) and 1 or (#parts - depth) + 1 local dir = table.concat(parts, utils.path_sep, index, #parts - 1) .. utils.path_sep if dir == "" then return "" end if formatter then dir = formatter(dir, depth) end return dir end local GroupView = Component:new({ type = "group", focusable = false }) function GroupView:new(group) assert(group, "The type should be passed to a group on create") assert(group.component, "a group MUST have a component") self.type = group.type or self.type setmetatable(group, self) self.__index = self return group end function GroupView:current() return false end ---@type bufferline.Tab local Tabpage = Component:new({ type = "tab" }) local function get_modified_state(buffers) for _, buf in pairs(buffers) do if vim.bo[buf].modified then return true end end return false end function Tabpage:new(tab) tab.name = fn.fnamemodify(tab.path, ":t") assert(tab.buf, fmt("A tab must a have a buffer: %s", vim.inspect(tab))) tab.modifiable = vim.bo[tab.buf].modifiable tab.modified = get_modified_state(tab.buffers) tab.buftype = vim.bo[tab.buf].buftype tab.extension = fn.fnamemodify(tab.path, ":e") tab.icon, tab.icon_highlight = utils.get_icon({ filetype = vim.bo[tab.buf].filetype, directory = fn.isdirectory(tab.path) > 0, path = tab.path, extension = tab.extension, type = tab.buftype, }) if tab.name_formatter and type(tab.name_formatter) == "function" then tab.name = tab.name_formatter({ name = tab.name, path = tab.path, bufnr = tab.buf, tabnr = tab.id, buffers = tab.buffers, }) or tab.name end setmetatable(tab, self) self.__index = self return tab end --- @return bufferline.Visibility function Tabpage:visibility() if self:current() then return visibility.SELECTED end if self:visible() then return visibility.INACTIVE end return visibility.NONE end function Tabpage:current() return api.nvim_get_current_tabpage() == self.id end --- NOTE: A visible tab page is the current tab page function Tabpage:visible() return api.nvim_get_current_tabpage() == self.id end --- @param depth number --- @param formatter fun(string, number) --- @return string function Tabpage:ancestor(depth, formatter) if self.duplicated == "element" then return "(duplicated) " end return self:__ancestor(depth, formatter) end ---@alias BufferComponent fun(index: integer, buf_count: integer): bufferline.Segment[] ---@type bufferline.Buffer local Buffer = Component:new({ type = "buffer" }) ---create a new buffer class ---@param buf bufferline.Buffer ---@return bufferline.Buffer function Buffer:new(buf) assert(buf, "A buffer must be passed to create a buffer class") buf.modifiable = vim.bo[buf.id].modifiable buf.modified = vim.bo[buf.id].modified buf.buftype = vim.bo[buf.id].buftype buf.extension = fn.fnamemodify(buf.path, ":e") local is_directory = fn.isdirectory(buf.path) > 0 buf.icon, buf.icon_highlight = utils.get_icon({ filetype = vim.bo[buf.id].filetype, directory = is_directory, path = buf.path, extension = buf.extension, type = buf.buftype, }) local name = "[No Name]" if buf.path and #buf.path > 0 then name = fn.fnamemodify(buf.path, ":t") name = is_directory and name .. "/" or name end if buf.name_formatter and type(buf.name_formatter) == "function" then name = buf.name_formatter({ name = name, path = buf.path, bufnr = buf.id }) or name end buf.name = name setmetatable(buf, self) self.__index = self return buf end ---@return bufferline.Visibility function Buffer:visibility() if self:current() then return visibility.SELECTED end if self:visible() then return visibility.INACTIVE end return visibility.NONE end function Buffer:current() return api.nvim_get_current_buf() == self.id end --- If the buffer is already part of state then it is existing --- otherwise it is new ---@param components bufferline.TabElement[] ---@return boolean function Buffer:previously_opened(components) return utils.find(function(component) return component.id == self.id end, components) ~= nil end --- Find and return the index of the matching buffer (by id) in the list in state ---@param components bufferline.TabElement[] function Buffer:find_index(components) for index, component in ipairs(components) do if component.id == self.id then return index end end end ---@param components bufferline.TabElement[] function Buffer:newly_opened(components) return not self:previously_opened(components) end function Buffer:visible() return fn.bufwinnr(self.id) > 0 end --- @param depth integer --- @param formatter fun(string, integer) --- @return string function Buffer:ancestor(depth, formatter) return self:__ancestor(depth, formatter) end ---@type bufferline.Section local Section = {} ---Create a segment of tab views ---@param n bufferline.Section? ---@return bufferline.Section function Section:new(n) local t = n or { length = 0, items = {} } setmetatable(t, self) self.__index = self return t end function Section.__add(a, b) return a.length + b.length end -- Take a section and remove an item arbitrarily -- reducing the length is very important as otherwise we don't know -- a section is actually smaller now function Section:drop(index) if self.items[index] ~= nil then self.length = self.length - self.items[index].length table.remove(self.items, index) return self end end ---@param item bufferline.Component function Section:add(item) table.insert(self.items, item) self.length = self.length + item.length end M.Buffer = Buffer M.Tabpage = Tabpage M.Section = Section M.GroupView = GroupView return M akinsho-bufferline.nvim-655133c/lua/bufferline/numbers.lua000066400000000000000000000051641474150523700236060ustar00rootroot00000000000000local config = require("bufferline.config") ---@class NumbersFuncOpts ---@field ordinal number ---@field id number ---@field lower number_helper ---@field raise number_helper ---@alias number_helper fun(num: number): string ---@alias numbers_func fun(opts: NumbersFuncOpts): string ---@alias numbers_opt '"buffer_id"' | '"ordinal"' | '"both"' | numbers_func local M = {} local superscript_numbers = { ["0"] = "⁰", ["1"] = "¹", ["2"] = "²", ["3"] = "³", ["4"] = "⁴", ["5"] = "⁵", ["6"] = "⁶", ["7"] = "⁷", ["8"] = "⁸", ["9"] = "⁹", } local subscript_numbers = { ["0"] = "₀", ["1"] = "₁", ["2"] = "₂", ["3"] = "₃", ["4"] = "₄", ["5"] = "₅", ["6"] = "₆", ["7"] = "₇", ["8"] = "₈", ["9"] = "₉", } local maps = { superscript = superscript_numbers, subscript = subscript_numbers, } --- convert single or multi-digit strings to {sub|super}script --- or return the plain number if there is no corresponding number table ---@param num number ---@param map table? ---@return string local function construct_number(num, map) if not map then return num .. "." end local str = tostring(num) local match = str:gsub(".", function(c) return map[c] or "" end) return match end local function to_style(map) return function(num) return construct_number(num, map) end end local lower, raise = to_style(subscript_numbers), to_style(superscript_numbers) ---Add a number prefix to the buffer matching a user's preference ---@param buffer bufferline.TabElement ---@param numbers numbers_opt ---@return string local function prefix(buffer, numbers) if type(numbers) == "function" then local ok, number = pcall(numbers, { ordinal = buffer.ordinal, id = buffer.id, lower = lower, raise = raise, }) return ok and number or "" end -- if mode is both, numbers will look similar to lightline-bufferline, -- buffer_id at top left and ordinal number at bottom right if numbers == "both" then return construct_number(buffer.id) .. construct_number(buffer.ordinal, maps.subscript) end return construct_number(numbers == "ordinal" and buffer.ordinal or buffer.id) end --- @param context bufferline.RenderContext --- @return bufferline.Segment? function M.component(context) local element = context.tab local options = config.options if options.numbers == "none" then return end local number_prefix = prefix(element, options.numbers) if not number_prefix then return end return { highlight = context.current_highlights.numbers, text = number_prefix } end if require("bufferline.utils").is_test() then M.prefix = prefix end return M akinsho-bufferline.nvim-655133c/lua/bufferline/offset.lua000066400000000000000000000136741474150523700234260ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local config = require("bufferline.config") ---@module "bufferline.config" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local highlights = lazy.require("bufferline.highlights") ---@module "bufferline.highlights" local constants = lazy.require("bufferline.constants") ---@module "bufferline.constants" local M = {} local api = vim.api local fn = vim.fn local padding = constants.padding local t = { LEAF = "leaf", ROW = "row", COLUMN = "col", } local supported_win_types = { [t.LEAF] = true, [t.COLUMN] = true, } ---Format the content of a neighbouring offset's text ---@param size integer ---@param highlight table ---@param offset table ---@param is_left boolean? ---@return string local function get_section_text(size, highlight, offset, is_left) local text = offset.text if type(text) == "function" then text = text() end text = text or padding:rep(size - 2) local text_size, left, right = api.nvim_strwidth(text), 0, 0 local alignment = offset.text_align or "center" if text_size + 2 >= size then text, left, right = utils.truncate_name(text, size - 2), 1, 1 else local remainder = size - text_size local is_even, side = remainder % 2 == 0, remainder / 2 if alignment == "center" then if not is_even then left, right = math.ceil(side), math.floor(side) else left, right = side, side end elseif alignment == "left" then left, right = 1, remainder - 1 else left, right = remainder - 1, 1 end end local str = highlight.text .. padding:rep(left) .. text .. padding:rep(right) if not offset.separator then return str end local sep_icon = type(offset.separator) == "string" and offset.separator or "│" local sep = highlight.sep .. sep_icon return (not is_left and sep or "") .. str .. (is_left and sep or "") end ---A heuristic to attempt to derive a windows background color from a winhighlight ---@param win_id integer ---@param attribute string? ---@param match string? ---@return string|nil local function guess_window_highlight(win_id, attribute, match) assert(win_id, 'A window id must be passed to "guess_window_highlight"') attribute = attribute or "bg" match = match or "Normal" local hl = vim.wo[win_id].winhighlight if not hl then return end local parts = vim.split(hl, ",") for i = #parts, 1, -1 do local grp, hl_name = unpack(vim.split(parts[i], ":")) if grp and grp:match(match) then return hl_name end end return match end --- This helper checks to see if bufferline supports creating an offset for the given layout --- Valid window layouts can be --- * A list of full height splits in a row: --- `{'row', ['leaf', id], ['leaf', id]}` --- * A row of splits where one on either edge is not full height but the matching --- split is on the top: --- e.g. the vertical tool bar is split in two such as for undo tree --- `{'row', ['col', ['leaf', id], ['leaf', id]], ['leaf', id]}` --- ---@param windows any[] ---@return boolean, number local function is_valid_layout(windows) local win_type, win_id = windows[1], windows[2] if utils.is_list(win_id) and win_type == t.COLUMN then win_id = win_id[1][2] end return supported_win_types[win_type] and type(win_id) == "number", win_id end --- Test if the windows within a layout row contain the correct panel buffer --- NOTE: this only tests the first and last windows as those are the only --- ones that it makes sense to add a panel for ---@param windows any[] ---@param offset table ---@return boolean ---@return number? ---@return boolean? local function is_offset_section(windows, offset) local wins = { windows[1] } if #windows > 1 then wins[#wins + 1] = windows[#windows] end for idx, win in ipairs(wins) do local valid_layout, win_id = is_valid_layout(win) if valid_layout then local buf = api.nvim_win_get_buf(win_id) local valid = buf and vim.bo[buf].filetype == offset.filetype local is_left = idx == 1 if valid then return valid, win_id, is_left end end end return false, nil, nil end --- Iterate over COLUMN layout by always picking the first element. --- Assuming this is the topmost window, it's the one that should --- dictate the tabline offset. ---@param layout any[] ---@return any[] local function iterate_col_layout(layout) if layout[1] == t.COLUMN then return iterate_col_layout(layout[2][1]) else return layout end end ---@class OffsetData ---@field total_size number ---@field left string ---@field right string ---@field left_size integer ---@field right_size integer ---Calculate the size of padding required to offset the bufferline ---@return OffsetData function M.get() local offsets, hls = config.options.offsets, config.highlights local left = "" local right = "" local left_size = 0 local right_size = 0 local total_size = 0 local sep_hl = highlights.hl(hls.offset_separator.hl_group) if offsets and #offsets > 0 then local layout = iterate_col_layout(fn.winlayout()) for _, offset in ipairs(offsets) do -- don't bother proceeding if there are no vertical splits if layout[1] == t.ROW then local is_valid, win_id, is_left = is_offset_section(layout[2], offset) if is_valid and win_id then local width = api.nvim_win_get_width(win_id) + (offset.padding or 0) local hl_name = offset.highlight or guess_window_highlight(win_id) or config.highlights.fill.hl_group local hl = highlights.hl(hl_name) local component = get_section_text(width, { text = hl, sep = sep_hl }, offset, is_left) total_size = total_size + width if is_left then left, left_size = component, left_size + width else right, right_size = component, right_size + width end end end end end return { left = left, right = right, left_size = left_size, right_size = right_size, total_size = total_size, } end return M akinsho-bufferline.nvim-655133c/lua/bufferline/pick.lua000066400000000000000000000040431474150523700230540ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local state = lazy.require("bufferline.state") ---@module "bufferline.state" local ui = lazy.require("bufferline.ui") ---@module "bufferline.ui" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local M = {} local fn = vim.fn local strwidth = vim.api.nvim_strwidth M.current = {} function M.reset() M.current = {} end -- Prompts user to select a buffer then applies a function to the buffer ---@param func fun(id: number) function M.choose_then(func) state.is_picking = true ui.refresh() -- NOTE: handle keyboard interrupts by catching any thrown errors local ok, char = pcall(fn.getchar) if ok then local letter = fn.nr2char(char) for _, item in ipairs(state.components) do local element = item:as_element() if element and letter == element.letter then func(element.id) end end end state.is_picking = false ui.refresh() end ---@param element bufferline.Tab|bufferline.Buffer ---@return string? function M.get(element) local valid_alphabet = config.options.pick.alphabet local first_letter = element.name:sub(1, 1) local is_valid_char = first_letter:match("[" .. valid_alphabet .. "]") if not M.current[first_letter] and is_valid_char then M.current[first_letter] = element.id return first_letter end for letter in valid_alphabet:gmatch(".") do if not M.current[letter] then M.current[letter] = element.id return letter end end end ---@param ctx bufferline.RenderContext ---@return bufferline.Segment? function M.component(ctx) local padding = require("bufferline.constants").padding local element = ctx.tab local hl = ctx.current_highlights local letter = element.letter if config.options.show_buffer_icons and element.icon then local right = string.rep(padding, math.ceil((strwidth(element.icon) - 1) / 2)) local left = string.rep(padding, math.floor((strwidth(element.icon) - 1) / 2)) letter = left .. element.letter .. right end return { text = letter, highlight = hl.pick } end return M akinsho-bufferline.nvim-655133c/lua/bufferline/sorters.lua000066400000000000000000000116601474150523700236320ustar00rootroot00000000000000local config = require("bufferline.config") ---@module "bufferline.config" local M = {} ---------------------------------------------------------------------------// -- Sorters ---------------------------------------------------------------------------// local api, fn = vim.api, vim.fn ---@param path string local function full_path(path) return fn.fnamemodify(path, ":p") end ---@param path string local function is_relative_path(path) return full_path(path) ~= path end ---@type bufferline.Sorter local function sort_by_extension(a, b) return fn.fnamemodify(a.name, ":e") < fn.fnamemodify(b.name, ":e") end ---@type bufferline.Sorter local function sort_by_relative_directory(a, b) local ra = is_relative_path(a.path) local rb = is_relative_path(b.path) if ra and not rb then return false end if rb and not ra then return true end return a.path < b.path end ---@type bufferline.Sorter local function sort_by_directory(a, b) return full_path(a.path) < full_path(b.path) end ---@type bufferline.Sorter local function sort_by_id(a, b) if not a and b then return true elseif a and not b then return false end return a.id < b.id end --- @param buf bufferline.Buffer local function get_buf_tabnr(buf) local maxinteger = 1000000000 -- If the buffer is visible, then its initial value shouldn't be -- maxed to prevent sorting it to the end of the list. if next(fn.win_findbuf(buf.id)) ~= nil then return 0 end -- We use the max integer as a default tab number for hidden buffers, -- to order them at the end of the buffer list, since they won't be -- found in tab pages. return maxinteger end ---@type bufferline.Sorter local function sort_by_tabpage_number(a, b) local tab_a = api.nvim_tabpage_get_number(a.id) local tab_b = api.nvim_tabpage_get_number(b.id) return tab_a < tab_b end ---@type bufferline.Sorter local function sort_by_tabs(a, b) local buf_a_tabnr = get_buf_tabnr(a) local buf_b_tabnr = get_buf_tabnr(b) local tabs = fn.gettabinfo() for _, tab in ipairs(tabs) do local buffers = fn.tabpagebuflist(tab.tabnr) if buffers ~= 0 then for _, buf_id in ipairs(buffers) do if buf_id == a.id then buf_a_tabnr = tab.tabnr elseif buf_id == b.id then buf_b_tabnr = tab.tabnr end end end end return buf_a_tabnr < buf_b_tabnr end ---@param components bufferline.TabElement[] ---@return bufferline.Sorter local sort_by_new_after_existing = function(components) return function(item_a, item_b) if item_a:newly_opened(components) and item_b:previously_opened(components) then return false elseif item_a:previously_opened(components) and item_b:newly_opened(components) then return true end return item_a.id < item_b.id end end ---@param prev_components bufferline.TabElement[] ---@return bufferline.Sorter local sort_by_new_after_current = function(prev_components, current_index) return function(item_a, item_b) local a_index = item_a:find_index(prev_components) local a_is_new = item_a:newly_opened(prev_components) local b_index = item_b:find_index(prev_components) local b_is_new = item_b:newly_opened(prev_components) current_index = current_index or 1 if not a_is_new and not b_is_new then -- If both buffers are either before or after (inclusive) the current buffer, respect the current order. if (a_index - current_index) * (b_index - current_index) >= 0 then return a_index < b_index end return a_index < current_index elseif not a_is_new and b_is_new then return a_index <= current_index elseif a_is_new and not b_is_new then return current_index < b_index end return item_a.id < item_b.id end end --- sorts a list of buffers in place --- @param elements bufferline.TabElement[] --- @param opts bufferline.SorterOptions function M.sort(elements, opts) opts = opts or {} local sort_by = opts.sort_by or config.options.sort_by -- the user has manually sorted the buffers don't try to re-sort them if opts.custom_sort then return elements end if sort_by == "none" then return elements elseif sort_by == "insert_after_current" then table.sort(elements, sort_by_new_after_current(opts.prev_components, opts.current_index)) elseif sort_by == "insert_at_end" then table.sort(elements, sort_by_new_after_existing(opts.prev_components)) elseif sort_by == "extension" then table.sort(elements, sort_by_extension) elseif sort_by == "directory" then table.sort(elements, sort_by_directory) elseif sort_by == "relative_directory" then table.sort(elements, sort_by_relative_directory) elseif sort_by == "id" then table.sort(elements, sort_by_id) elseif sort_by == "tabs" then table.sort(elements, config:is_tabline() and sort_by_tabpage_number or sort_by_tabs) elseif type(sort_by) == "function" then table.sort(elements, sort_by) end for index, buf in ipairs(elements) do buf.ordinal = index end return elements end return M akinsho-bufferline.nvim-655133c/lua/bufferline/state.lua000066400000000000000000000024031474150523700232440ustar00rootroot00000000000000local M = {} local lazy = require("bufferline.lazy") local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" -----------------------------------------------------------------------------// -- State -----------------------------------------------------------------------------// ---@type bufferline.State local state = { is_picking = false, hovered = nil, custom_sort = nil, current_element_index = nil, components = {}, __components = {}, visible_components = {}, left_offset_size = 0, right_offset_size = 0, } ---@param list bufferline.Component[] ---@return bufferline.Component[] local function filter_invisible(list) return utils.fold(function(accum, item) if item.focusable ~= false and not item.hidden then table.insert(accum, item) end return accum end, list, {}) end local component_keys = { "components", "visible_components" } ---@param new_state bufferline.State function M.set(new_state) for key, value in pairs(new_state) do if value == vim.NIL then value = nil end if vim.tbl_contains(component_keys, key) then value = filter_invisible(value --[=[@as bufferline.Component[]]=]) end state[key] = value end end return setmetatable(M, { __index = function(_, k) return state[k] end, }) akinsho-bufferline.nvim-655133c/lua/bufferline/tabpages.lua000066400000000000000000000126631474150523700237230ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local ui = lazy.require("bufferline.ui") ---@module "bufferline.ui" local pick = lazy.require("bufferline.pick") ---@module "bufferline.pick" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local constants = lazy.require("bufferline.constants") ---@module "bufferline.constants" local duplicates = lazy.require("bufferline.duplicates") ---@module "bufferline.duplicates" local diagnostics = lazy.require("bufferline.diagnostics") ---@module "bufferline.diagnostics" local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local models = lazy.require("bufferline.models") ---@module "bufferline.models" local api = vim.api local M = {} local padding = constants.padding local function tab_click_component(num) return "%" .. num .. "T" end ---@param tabpage { tabnr: number, variables: { name: string }, windows: number[] } ---@param is_active boolean ---@param style string | { [1]: string, [2]: string } ---@param highlights bufferline.Highlights local function render(tabpage, is_active, style, highlights) local h = highlights local hl = is_active and h.tab_selected.hl_group or h.tab.hl_group local separator_hl = is_active and h.tab_separator_selected.hl_group or h.tab_separator.hl_group local chars = type(style) == "table" and style or constants.sep_chars[style] or constants.sep_chars.thin local name = padding .. (tabpage.variables.name or tabpage.tabnr) .. padding local char_order = ({ thick = { 1, 2 }, thin = { 1, 2 } })[style] or { 2, 1 } return { { highlight = separator_hl, text = chars[char_order[1]] }, { highlight = hl, text = name, attr = { prefix = tab_click_component(tabpage.tabnr) }, }, { highlight = separator_hl, text = chars[char_order[2]] }, } end function M.rename_tab(tabnr, name) if name == "" then name = tostring(tabnr) end api.nvim_tabpage_set_var(0, "name", name) ui.refresh() end function M.get() local tabs = vim.fn.gettabinfo() local current_tab = vim.fn.tabpagenr() local highlights = config.highlights local style = config.options.separator_style return utils.map(function(tab) local is_active_tab = current_tab == tab.tabnr local components = render(tab, is_active_tab, style, highlights) return { component = components, id = tab.tabnr, windows = tab.windows, } end, tabs) end ---@param tab_num integer ---@return integer local function get_active_buf_for_tab(tab_num) local window = api.nvim_tabpage_get_win(tab_num) return api.nvim_win_get_buf(window) end ---Choose the active window's buffer as the buffer for that tab ---@param buf integer ---@return string local function get_buffer_name(buf) local name = (buf and api.nvim_buf_is_valid(buf)) and api.nvim_buf_get_name(buf) if not name or name == "" then name = "[No Name]" end return name end local function get_valid_tabs() return vim.tbl_filter(function(t) return api.nvim_tabpage_is_valid(t) end, api.nvim_list_tabpages()) end ---Filter the buffers to show based on the user callback passed in ---@param buf_nums integer[] ---@param callback fun(buf: integer, bufs: integer[]): boolean ---@return integer[] local function apply_buffer_filter(buf_nums, callback) if type(callback) ~= "function" then return buf_nums end local filtered = {} for _, buf in ipairs(buf_nums) do if callback(buf, buf_nums) then table.insert(filtered, buf) end end return next(filtered) and filtered or buf_nums end --- Get tab buffers based on open windows within the tab --- this is similar to tabpagebuflist but doesn't involve --- the viml round trip or the quirk where it occasionally returns --- a number ---@param tab_num number ---@return number[] local function get_tab_buffers(tab_num) return vim.tbl_map(api.nvim_win_get_buf, api.nvim_tabpage_list_wins(tab_num)) end local function get_diagnostics(buffers, options) local all_diagnostics = diagnostics.get(options) local buffer_diagnostics = {} local included_paths = {} for buffer, item in pairs(all_diagnostics) do local path = get_buffer_name(buffer) if vim.tbl_contains(buffers, buffer) and not vim.tbl_contains(included_paths, path) then table.insert(included_paths, path) table.insert(buffer_diagnostics, item) end end return diagnostics.combine(buffer_diagnostics) end ---@param state bufferline.State ---@return bufferline.Tab[] function M.get_components(state) local options = config.options local tabs = get_valid_tabs() local Tabpage = models.Tabpage ---@type bufferline.Tab[] local components = {} pick.reset() duplicates.reset() local filter = options.custom_filter for i, tab_num in ipairs(tabs) do local active_buf = get_active_buf_for_tab(tab_num) local buffers = get_tab_buffers(tab_num) local buffer if filter then buffers = apply_buffer_filter(buffers, filter) buffer = filter(active_buf) and active_buf or buffers[1] else buffer = active_buf end local path = get_buffer_name(buffer) local tab = Tabpage:new({ path = path, buf = buffer, buffers = buffers, id = tab_num, ordinal = i, diagnostics = get_diagnostics(buffers, options), name_formatter = options.name_formatter, hidden = false, focusable = true, }) tab.letter = pick.get(tab) components[#components + 1] = tab end return vim.tbl_map(function(tab) return ui.element(state, tab) end, duplicates.mark(components)) end return M akinsho-bufferline.nvim-655133c/lua/bufferline/types.lua000066400000000000000000000233641474150523700233010ustar00rootroot00000000000000---@meta _ ---@class bufferline.DebugOpts ---@field logging boolean ---@class bufferline.GroupOptions ---@field toggle_hidden_on_enter? boolean re-open hidden groups on bufenter ---@class bufferline.GroupOpts ---@field options? bufferline.GroupOptions ---@field items bufferline.Group[] ---@class bufferline.Indicator ---@field style? "underline" | "icon" | "none" ---@field icon? string? ---@alias bufferline.Mode 'tabs' | 'buffers' ---@alias bufferline.DiagnosticIndicator fun(count: number, level: string, errors: table, ctx: table): string ---@alias bufferline.HoverOptions {reveal: string[], delay: integer, enabled: boolean} ---@alias bufferline.IconFetcherOpts {directory: boolean, path: string, extension: string, filetype: string?} ---@class bufferline.Options ---@field public mode? bufferline.Mode ---@field public style_preset? bufferline.StylePreset | bufferline.StylePreset[] ---@field public view? string ---@field public debug? bufferline.DebugOpts ---@field public numbers? 'none' | 'ordinal' | 'buffer_id' | 'both' | fun(opts: { ordinal: number, id: number, lower: number_helper, raise: number_helper }): string ---@field public buffer_close_icon? string ---@field public modified_icon? string ---@field public close_icon? string ---@field public close_command? string | function ---@field public custom_filter? fun(buf: number, bufnums: number[]): boolean ---@field public left_mouse_command? string | function ---@field public right_mouse_command? string | function ---@field public middle_mouse_command? (string | function)? ---@field public indicator? bufferline.Indicator ---@field public left_trunc_marker? string ---@field public right_trunc_marker? string ---@field public separator_style? string | {[1]: string, [2]: string} ---@field public name_formatter? (fun(path: string):string)? ---@field public tab_size? number ---@field public truncate_names? boolean ---@field public max_name_length? number ---@field public color_icons? boolean ---@field public show_buffer_icons? boolean ---@field public show_buffer_close_icons? boolean ---@field public show_buffer_default_icon? boolean ---@field public get_element_icon? fun(opts: bufferline.IconFetcherOpts): string?, string? ---@field public show_close_icon? boolean ---@field public show_tab_indicators? boolean ---@field public show_duplicate_prefix? boolean ---@field public duplicates_across_groups? boolean ---@field public enforce_regular_tabs? boolean ---@field public always_show_bufferline? boolean ---@field public auto_toggle_bufferline? boolean ---@field public persist_buffer_sort? boolean ---@field public move_wraps_at_ends? boolean ---@field public max_prefix_length? number ---@field public sort_by? 'insert_after_current' | 'insert_at_end' | 'id' | 'extension' | 'relative_directory' | 'directory' | 'tabs' | fun(buffer_a: bufferline.Buffer, buffer_b: bufferline.Buffer): boolean ---@field public diagnostics? boolean | 'nvim_lsp' | 'coc' ---@field public diagnostics_indicator? bufferline.DiagnosticIndicator ---@field public diagnostics_update_in_insert? boolean ---@field public diagnostics_update_on_event? boolean ---@field public offsets? table[] ---@field public groups? bufferline.GroupOpts ---@field public themable? boolean ---@field public hover? bufferline.HoverOptions ---@class bufferline.HLLink ---@field attribute 'fg' | 'bg' ---@field highlight string ---@class bufferline.HLGroup ---@field fg? string | bufferline.HLLink ---@field bg? string | bufferline.HLLink ---@field sp? string ---@field special? string ---@field bold? boolean ---@field italic? boolean ---@field underline? boolean ---@field undercurl? boolean ---@field hl_group? string ---@field hl_name? string ---@alias bufferline.Highlights table ---@class bufferline.UserConfig ---@field public options? bufferline.Options ---@field public highlights? bufferline.Highlights | fun(BufferlineHighlights): bufferline.Highlights ---@class bufferline.Config ---@field public options? bufferline.Options ---@field public highlights? bufferline.Highlights ---@field user bufferline.UserConfig original copy of user preferences ---@field merge fun(self: bufferline.Config, defaults: bufferline.Config): bufferline.Config ---@field validate fun(self: bufferline.Config, defaults: bufferline.Config, resolved: bufferline.Highlights): nil ---@field resolve fun(self: bufferline.Config, defaults: bufferline.Config): bufferline.Config ---@field is_tabline fun(self: bufferline.Config):boolean --- @alias bufferline.Visibility 1 | 2 | 3 --- @alias bufferline.Duplicate "path" | "element" | nil ---@class bufferline.Component ---@field name? string ---@field id integer ---@field path? string ---@field length integer ---@field component fun(BufferlineState): bufferline.Segment[] ---@field hidden boolean ---@field focusable boolean ---@field type 'group_end' | 'group_start' | 'buffer' | 'tabpage' ---@generic T ---@alias bufferline.AncestorSearch fun(self: T, depth: integer, formatter: (fun(string, integer): string)?): string ---@alias bufferline.TabElement bufferline.Tab|bufferline.Buffer ---@class bufferline.Tab ---@field public id integer ---@field public buf integer ---@field public icon string ---@field public name string ---@field public group string ---@field public letter string ---@field public modified boolean ---@field public modifiable boolean ---@field public duplicated bufferline.Duplicate ---@field public extension string the file extension ---@field public path string the full path to the file ---@field public visibility fun(self: bufferline.Tab): integer ---@field public current fun(self: bufferline.Tab): boolean ---@field public visible fun(self: bufferline.Tab): boolean ---@field public ancestor bufferline.AncestorSearch ---@field public __ancestor bufferline.AncestorSearch -- A single buffer class -- this extends the [Component] class ---@class bufferline.Buffer ---@field public extension string the file extension ---@field public path string the full path to the file ---@field public name_formatter function? dictates how the name should be shown ---@field public id integer the buffer number ---@field public name string the visible name for the file ---@field public filename string ---@field public icon string the icon ---@field public icon_highlight string? ---@field public diagnostics table ---@field public modified boolean ---@field public modifiable boolean ---@field public buftype string ---@field public letter string? ---@field public ordinal integer ---@field public duplicated bufferline.Duplicate ---@field public prefix_count integer ---@field public component BufferComponent ---@field public group string? ---@field public group_fn string ---@field public length integer the length of the buffer component ---@field public visibility fun(self: bufferline.Buffer): integer ---@field public current fun(self: bufferline.Buffer): boolean ---@field public visible fun(self: bufferline.Buffer): boolean ---@field public ancestor bufferline.AncestorSearch ---@field public __ancestor bufferline.AncestorSearch ---@field public find_index fun(Buffer, BufferlineState): integer? ---@field public newly_opened fun(Buffer, BufferlineState): boolean ---@field public previously_opened fun(Buffer, BufferlineState): boolean ---@alias bufferline.ComponentsByGroup (bufferline.Group | bufferline.Component[])[] --- @class bufferline.GroupState --- @field manual_groupings table --- @field user_groups table --- @field components_by_group bufferline.ComponentsByGroup --- @class bufferline.Separators --- @field sep_start bufferline.Segment[] --- @field sep_end bufferline.Segment[] ---@alias GroupSeparator fun(group:bufferline.Group, hls: bufferline.HLGroup, count_item: string?): bufferline.Separators ---@alias GroupSeparators table ---@class bufferline.Group ---@field public id? string used for identifying the group in the tabline ---@field public name? string 'formatted name of the group' ---@field public display_name? string original name including special characters ---@field public matcher? fun(b: bufferline.Buffer): boolean? ---@field public separator? GroupSeparators ---@field public priority? number ---@field public highlight? table ---@field public icon? string ---@field public hidden? boolean ---@field public with? fun(Group, Group): bufferline.Group ---@field auto_close boolean when leaving the group automatically close it ---@class bufferline.RenderContext ---@field preferences bufferline.Config ---@field current_highlights {[string]: string} ---@field tab bufferline.TabElement ---@field is_picking boolean ---@class bufferline.SegmentAttribute ---@field global boolean whether or not the attribute applies to other elements apart from the current one ---@field prefix string ---@field suffix string ---@field extends number how many positions the attribute extends for ---@class bufferline.Segment ---@field text string ---@field highlight string ---@field attr bufferline.SegmentAttribute ---@class bufferline.Section ---@field items bufferline.Component[] ---@field length integer ---@field drop fun(self: bufferline.Section, count: integer): bufferline.Section? ---@field add fun(self: bufferline.Section, item: bufferline.Component) ---@class bufferline.State ---@field components bufferline.Component[]? ---@field current_element_index number? ---@field is_picking boolean? ---@field visible_components bufferline.Component[]? ---@field __components bufferline.Component[]? ---@field custom_sort number[]? ---@field left_offset_size number? ---@field right_offset_size number? ---@alias bufferline.Sorter fun(buf_a: bufferline.Buffer, buf_b: bufferline.Buffer): boolean ---@class bufferline.SorterOptions ---@field sort_by (string|function)? ---@field current_index integer? ---@field custom_sort boolean? ---@field prev_components bufferline.TabElement[] akinsho-bufferline.nvim-655133c/lua/bufferline/ui.lua000066400000000000000000000631751474150523700225560ustar00rootroot00000000000000-----------------------------------------------------------------------------// -- UI -----------------------------------------------------------------------------// local lazy = require("bufferline.lazy") local utils = lazy.require("bufferline.utils") ---@module "bufferline.utils" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local constants = lazy.require("bufferline.constants") ---@module "bufferline.constants" local highlights = lazy.require("bufferline.highlights") ---@module "bufferline.highlights" local pick = lazy.require("bufferline.pick") ---@module "bufferline.pick" local groups = lazy.require("bufferline.groups") ---@module "bufferline.groups" local diagnostics = lazy.require("bufferline.diagnostics") ---@module "bufferline.diagnostics" local duplicates = lazy.require("bufferline.duplicates") ---@module "bufferline.duplicates" local numbers = lazy.require("bufferline.numbers") ---@module "bufferline.numbers" local custom_area = lazy.require("bufferline.custom_area") ---@module "bufferline.custom_area" local offset = lazy.require("bufferline.offset") ---@module "bufferline.offset" local state = lazy.require("bufferline.state") ---@module "bufferline.state" local M = {} local api = vim.api local sep_names = constants.sep_names local sep_chars = constants.sep_chars -- string.len counts number of bytes and so the unicode icons are counted -- larger than their display width. So we use nvim's strwidth local strwidth = vim.api.nvim_strwidth local padding = constants.padding local components = { id = { diagnostics = "diagnostics", name = "name", icon = "icon", number = "number", groups = "groups", duplicates = "duplicates", close = "close", modified = "modified", pick = "pick", }, } ---------------------------------------------------------------------------------------------------- -- Hover events ---------------------------------------------------------------------------------------------------- ---@param item bufferline.Component? local function set_hover_state(item) state.set({ hovered = item }) M.refresh() end ---@class HoverOpts ---@field cursor_pos integer ---@param _ integer ---@param opts HoverOpts function M.on_hover_over(_, opts) local mouse_pos, pos = opts.cursor_pos, state.left_offset_size for _, item in pairs(state.visible_components) do -- This value can be incorrect as truncation markers might push things off center local next_pos = pos + item.length if mouse_pos >= pos and mouse_pos <= next_pos then return set_hover_state(item) end pos = next_pos end end function M.on_hover_out() set_hover_state(vim.NIL) end ---@param component bufferline.Segment? ---@param id string ---@return bufferline.Segment? local function set_id(component, id) if component then component.attr = component.attr or {} component.attr.__id = id end return component end local function get_id(component) return component and component.attr and component.attr.__id end -----------------------------------------------------------------------------// -- Context -----------------------------------------------------------------------------// ---@type bufferline.RenderContext local Context = {} ---@param ctx bufferline.RenderContext ---@return bufferline.RenderContext function Context:new(ctx) assert(ctx.tab, "A tab view entity is required to create a context") self.tab = ctx.tab self.__index = self return setmetatable(ctx, self) end -----------------------------------------------------------------------------// ---@param s bufferline.Segment? ---@return boolean local function has_text(s) if s == nil or s.text == nil or s.text == "" then return false end return true end ---@param parts bufferline.Segment[] ---@return bufferline.Segment[] local function filter_invalid(parts) local result = {} for _, p in pairs(parts) do if p ~= nil then result[#result + 1] = p end end return result end ---@param segments bufferline.Segment[] ---@return integer local function get_component_size(segments) assert(utils.is_list(segments), "Segments must be a list") local sum = 0 for _, s in pairs(segments) do if has_text(s) then sum = sum + strwidth(tostring(s.text)) end end return sum end local function get_marker_size(count, element_size) return count > 0 and strwidth(tostring(count)) + element_size or 0 end function M.refresh() vim.schedule(function() vim.cmd.redrawtabline() end) end ---Add click action to a component ---@param func_name string ---@param id number ---@param component bufferline.Segment function M.make_clickable(func_name, id, component) component.attr = component.attr or {} component.attr.prefix = "%" .. id .. "@v:lua.___bufferline_private." .. func_name .. "@" -- the %X works as a closing label. @see :h tabline component.attr.suffix = "%X" return component end ---@class PadSide ---@field size integer ---@field hl string ---@class PadOpts ---@field left PadSide? ---@field right PadSide? ---Add padding to either side of a component ---@param opts PadOpts ---@return bufferline.Segment, bufferline.Segment local function pad(opts) opts.left, opts.right = opts.left or {}, opts.right or {} local left, left_hl = opts.left.size or 0, opts.left.hl or "" local right, right_hl = opts.right.size or 0, opts.right.hl or left_hl local left_p, right_p = string.rep(padding, left), string.rep(padding, right) return { text = left_p, highlight = left_hl }, { text = right_p, highlight = right_hl } end ---@param options bufferline.Options ---@param hls bufferline.Highlights ---@return bufferline.Segment[] local function get_tab_close_button(options, hls) if options.show_close_icon and (#vim.api.nvim_list_tabpages() > 1) then return { { text = padding .. options.close_icon .. padding, highlight = hls.tab_close.hl_group, attr = { prefix = "%999X" }, }, } end return {} end ---@param items bufferline.Component[] ---@return bufferline.Section ---@return bufferline.Section ---@return bufferline.Section local function get_sections(items) local Section = require("bufferline.models").Section local current = Section:new() local before = Section:new() local after = Section:new() for _, tab_view in ipairs(items) do if not tab_view.hidden then if tab_view:current() then current:add(tab_view) elseif current.length == 0 then -- We haven't reached the current buffer yet before:add(tab_view) else after:add(tab_view) end end end return before, current, after end ---@param ctx bufferline.RenderContext ---@param length number ---@return bufferline.Segment?, bufferline.Segment? local function add_space(ctx, length) local options = config.options local curr_hl = ctx.current_highlights local left_size, right_size = 0, 0 local icon = options.buffer_close_icon -- pad each tab smaller than the max tab size to make it consistent local difference = options.tab_size - length if difference > 0 then local size = math.floor(difference / 2) left_size, right_size = size + left_size, size + right_size end if not options.show_buffer_close_icons then right_size = right_size > 0 and right_size - strwidth(icon) or right_size left_size = left_size + strwidth(icon) end return pad({ left = { size = left_size, hl = curr_hl.buffer }, right = { size = right_size }, }) end --- @param buffer bufferline.TabElement --- @param hl_defs bufferline.Highlights --- @return bufferline.Segment? local function get_icon(buffer, hl_defs) local icon = buffer.icon local original_hl = buffer.icon_highlight if not icon or icon == "" then return end if not original_hl or original_hl == "" then return { text = icon } end local icon_hl = highlights.set_icon_highlight(buffer:visibility(), hl_defs, original_hl) return { text = icon, highlight = icon_hl, attr = { text = "%*" } } end ---Determine if the separator style is one of the slant options ---@param style string ---@return boolean local function is_slant(style) return vim.tbl_contains({ sep_names.slant, sep_names.padded_slant, sep_names.slope, sep_names.padded_slope }, style) end --- "▍" "░" --- Reference: https://en.wikipedia.org/wiki/Block_Elements --- @param focused boolean --- @param style table | string local function get_separator(focused, style) if type(style) == "table" then return focused and style[1] or style[2] end ---@diagnostic disable-next-line: undefined-field local chars = sep_chars[style] or sep_chars.thin if is_slant(style) then return chars[1], chars[2] end return focused and chars[1] or chars[2] end --- @param buf_id number --- @return bufferline.Segment? local function get_close_icon(buf_id, context) local options = config.options if options.hover.enabled and not context.tab:current() and vim.tbl_contains(options.hover.reveal, "close") then if not state.hovered or state.hovered.id ~= context.tab.id then return end end local buffer_close_icon = options.buffer_close_icon local close_button_hl = context.current_highlights.close_button if not options.show_buffer_close_icons then return end return M.make_clickable("handle_close", buf_id, { text = buffer_close_icon, highlight = close_button_hl, }) end --- @param context bufferline.RenderContext --- @return bufferline.Segment? local function add_indicator(context) local element = context.tab local hl = config.highlights local curr_hl = context.current_highlights local options = config.options local style = options.separator_style local symbol, highlight = padding, nil if is_slant(style) then return { text = symbol, highlight = highlight } end local is_current = element:current() symbol = is_current and options.indicator.icon or symbol highlight = is_current and hl.indicator_selected.hl_group or element:visible() and hl.indicator_visible.hl_group or curr_hl.buffer if options.indicator.style ~= "icon" then return { text = padding, highlight = highlight } end -- since all non-current buffers do not have an indicator they need -- to be padded to make up the difference in size return { text = symbol, highlight = highlight } end --- @param context bufferline.RenderContext --- @return bufferline.Segment? local function add_icon(context) local element = context.tab local options = config.options if context.is_picking and element.letter then return pick.component(context) elseif options.show_buffer_icons and element.icon then return get_icon(element, config.highlights) end end --- The suffix can be either the modified icon, space to replace the icon if --- a user has turned them off or the close icon if the element is not currently modified --- @param context bufferline.RenderContext --- @return bufferline.Segment? local function add_suffix(context) local element = context.tab local hl = context.current_highlights local symbol = config.options.modified_icon -- If the buffer is modified add an icon, if it isn't pad -- the buffer so it doesn't "jump" when it becomes modified i.e. due -- to the sudden addition of a new character local modified = { text = element.modified and symbol or string.rep(padding, strwidth(symbol)), highlight = element.modified and hl.modified or nil, } local close = get_close_icon(element.id, context) return not element.modified and close or modified end --- TODO: We increment the buffer length by the separator although the final --- buffer will not have a separator so we are technically off by 1 --- @param context bufferline.RenderContext --- @return bufferline.Segment?, bufferline.Segment local function add_separators(context) local hl = config.highlights local options = config.options local style = options.separator_style local focused = context.tab:current() or context.tab:visible() local right_sep, left_sep = get_separator(focused, style) local sep_hl = is_slant(style) and context.current_highlights.separator or hl.separator.hl_group local left_separator = left_sep and { text = left_sep, highlight = sep_hl } or nil local right_separator = { text = right_sep, highlight = sep_hl } return left_separator, right_separator end -- if we are enforcing regular tab size then all components will try and fit -- into the maximum tab size. If not we enforce a minimum tab size -- and allow components to be larger than the max. ---@param context bufferline.RenderContext ---@return number local function get_max_length(context) local modified = config.options.modified_icon local options = config.options local element = context.tab local icon_size = strwidth(element.icon) local padding_size = strwidth(padding) * 2 local max_length = options.max_name_length local autosize = not options.truncate_names and not options.enforce_regular_tabs local name_size = strwidth(context.tab.name) if autosize and name_size >= max_length then return name_size end if not options.enforce_regular_tabs then return max_length end -- estimate the maximum allowed size of a filename given that it will be -- padded and prefixed with a file icon return options.tab_size - strwidth(modified) - icon_size - padding_size end ---@param ctx bufferline.RenderContext ---@return bufferline.Segment local function get_name(ctx) local name = utils.truncate_name(ctx.tab.name, get_max_length(ctx)) -- escape filenames that contain "%" as this breaks in statusline patterns name = name:gsub("%%", "%%%1") return { text = name, highlight = ctx.current_highlights.buffer } end ---Create the render function that components need to position their ---separators once rendering calculations are complete ---@param left_separator bufferline.Segment? ---@param right_separator bufferline.Segment? ---@param component bufferline.Segment[] ---@return fun(next: bufferline.Component): bufferline.Segment[] local function create_renderer(left_separator, right_separator, component) --- We return a function from render buffer as we do not yet have access to --- information regarding which buffers will actually be rendered --- @param next_item bufferline.Component --- @return string return function(next_item) -- if using the non-slanted tab style then we must check if the component is at the end of -- of a section e.g. the end of a group and if so it should not be wrapped with separators -- as it can use those of the next item if not is_slant(config.options.separator_style) and next_item and next_item:is_end() then return component end if left_separator then table.insert(component, 1, left_separator) table.insert(component, right_separator) return component end if next_item then table.insert(component, right_separator) end return component end end ---@param id number ---@return bufferline.Segment local function tab_click_handler(id) return M.make_clickable("handle_click", id, { attr = { global = true } }) end ---@class SpacingOpts ---@field when any ---@field highlight string ---Create a spacing component that can be dependent on other items in a component ---@param opts SpacingOpts? ---@return bufferline.Segment? local function spacing(opts) opts = opts or { when = true } if not opts.when then return end return { text = constants.padding, highlight = opts.highlight } end ---@param trunc_icon string ---@param count_hl string ---@param icon_hl string ---@param count number ---@return bufferline.Segment[]? local function get_trunc_marker(trunc_icon, count_hl, icon_hl, count) if count > 0 then return { { highlight = count_hl, text = padding .. count .. padding }, { highlight = icon_hl, text = trunc_icon .. padding }, } end end ---@param tab_indicators table ---@param options bufferline.Options ---@return bufferline.Segment[] ---@return integer local function get_tab_indicator(tab_indicators, options) local items, length = {}, 0 if not options.show_tab_indicators or #tab_indicators <= 1 then return items, length end for _, tab in ipairs(tab_indicators) do local component = tab.component table.insert(items, component) length = length + get_component_size(component) end return items, length end --- @param current_state bufferline.State --- @param element bufferline.TabElement --- @return bufferline.TabElement function M.element(current_state, element) local curr_hl = highlights.for_element(element) local ctx = Context:new({ tab = element, current_highlights = curr_hl, is_picking = current_state.is_picking, }) local duplicate_prefix = duplicates.component(ctx) local group_item = element.group and groups.component(ctx) or nil local diagnostic = diagnostics.component(ctx) local icon = add_icon(ctx) local number_item = numbers.component(ctx) local suffix = add_suffix(ctx) local indicator = add_indicator(ctx) local left, right = add_separators(ctx) local name = get_name(ctx) -- Guess how much space there will for padding based on the buffer's name local name_size = get_component_size({ duplicate_prefix, name, spacing(), icon, suffix }) local left_space, right_space = add_space(ctx, name_size) local component = filter_invalid({ tab_click_handler(element.id), indicator, left_space, set_id(number_item, components.id.number), spacing({ when = number_item }), set_id(icon, components.id.icon), spacing({ when = icon }), set_id(group_item, components.id.groups), spacing({ when = group_item }), set_id(duplicate_prefix, components.id.duplicates), set_id(name, components.id.name), spacing({ when = name, highlight = curr_hl.buffer }), set_id(diagnostic, components.id.diagnostics), spacing({ when = diagnostic and #diagnostic.text > 0 }), right_space, suffix, spacing({ when = suffix }), }) element.component = create_renderer(left, right, component) -- NOTE: we must count the size of the separators here although we do not -- add them yet, since by the time they are added the component will already have rendered element.length = get_component_size(filter_invalid({ left, right, unpack(component) })) return element end -- The extends field means that the components highlights should be applied -- to another with a matching ID. This function does an initial scan of the component -- parts and updates the highlights for any part that has an extension. ---@param component bufferline.Segment[] ---@return bufferline.Segment[] local function extend_highlight(component) local locations, extension_map = {}, {} for index, part in pairs(component) do local id = get_id(part) if id then locations[id] = index end local extends = vim.tbl_get(part, "attr", "extends") if extends then for _, target in pairs(extends) do extension_map[target.id] = target.highlight or part.highlight end end end for id, hl in pairs(extension_map) do local target = component[locations[id]] if target then target.highlight = hl end end return component end --- Takes a list of Segments of the shape {text = , highlight = , attr = } --- and converts them into an nvim tabline format string i.e. `%#HL#text`. It handles cases --- like applying global or local attributes like click handlers. As well as extending highlights ---@param component bufferline.Segment[] local function to_tabline_str(component) component = component or {} local str = {} local globals = {} extend_highlight(component) for _, part in ipairs(component) do local attr = part.attr if attr and attr.global then table.insert(globals, { attr.prefix or "", attr.suffix or "" }) end local hl = highlights.hl(part.highlight) table.insert(str, { hl, ((attr and not attr.global) and attr.prefix or ""), (part.text or ""), ((attr and not attr.global) and attr.suffix or ""), }) end for _, attr in ipairs(globals) do table.insert(str, 1, attr[1]) table.insert(str, #str + 1, attr[1]) end return table.concat(utils.tbl_flatten(str)) end --- PREREQUISITE: active buffer always remains in view --- 1. Find amount of available space in the window --- 2. Find the amount of space the bufferline will take up --- 3. If the bufferline will be too long remove one tab from the before or after --- section --- 4. Re-check the size, if still too long truncate recursively till it fits --- 5. Add the number of truncated buffers as an indicator ---@param before bufferline.Section ---@param current bufferline.Section ---@param after bufferline.Section ---@param available_width number ---@param marker table ---@param visible bufferline.Component[] ---@return bufferline.Segment[][] ---@return table ---@return bufferline.Buffer[] local function truncate(before, current, after, available_width, marker, visible) visible = visible or {} local left_trunc_marker = get_marker_size(marker.left_count, marker.left_element_size) local right_trunc_marker = get_marker_size(marker.right_count, marker.right_element_size) local markers_length = left_trunc_marker + right_trunc_marker local total_length = before.length + current.length + after.length + markers_length if available_width >= total_length then local items = {} visible = utils.merge_lists(before.items, current.items, after.items) for index, item in ipairs(visible) do table.insert(items, item.component(visible[index + 1])) end return items, marker, visible -- if we aren't even able to fit the current buffer into the -- available space that means the window is really narrow -- so don't show anything elseif available_width < current.length then return {}, marker, visible else if before.length >= after.length then before:drop(1) marker.left_count = marker.left_count + 1 else after:drop(#after.items) marker.right_count = marker.right_count + 1 end -- drop the markers if the window is too narrow -- this assumes we have dropped both before and after -- sections since if the space available is this small -- we have likely removed these if (current.length + markers_length) > available_width then marker.left_count = 0 marker.right_count = 0 end return truncate(before, current, after, available_width, marker, visible) end end ---@param list bufferline.Segment[][] local function join(list) local str = "" for _, item in pairs(list) do str = str .. to_tabline_str(item) end return str end --- Get the width of statusline/tabline format string ---@vararg string ---@return integer local function statusline_str_width(...) local str = table.concat({ ... }, "") return api.nvim_eval_statusline(str, { use_tabline = true }).width end ---@class BufferlineTablineData ---@field str string ---@field left_offset_size integer ---@field right_offset_size integer ---@field segments bufferline.Segment[][] ---@field visible_components bufferline.TabElement[] --- TODO: All components should return Segment[] that are then combined in one go into a tabline --- @param items bufferline.Component[] --- @param tab_indicators bufferline.Segment[] --- @return BufferlineTablineData function M.tabline(items, tab_indicators) local options = config.options local hl = config.highlights local right_align = { { highlight = hl.fill.hl_group, text = "%=" } } local tab_close_button = get_tab_close_button(options, hl) local tab_close_button_length = get_component_size(tab_close_button) local tab_indicator_segments, tab_indicator_length = get_tab_indicator(tab_indicators, options) -- NOTE: this estimates the size of the truncation marker as we don't know how big it will be yet local left_trunc_icon = options.left_trunc_marker local right_trunc_icon = options.right_trunc_marker local max_padding = string.rep(padding, 2) local left_element_size = utils.measure(max_padding, left_trunc_icon, max_padding) local right_element_size = utils.measure(max_padding, right_trunc_icon, max_padding) local offsets = offset.get() local custom_area_size, left_area, right_area = custom_area.get() local available_width = vim.o.columns - custom_area_size - offsets.total_size - tab_indicator_length - tab_close_button_length local before, current, after = get_sections(items) local segments, marker, visible_components = truncate(before, current, after, available_width, { left_count = 0, right_count = 0, left_element_size = left_element_size, right_element_size = right_element_size, }) local marker_hl = hl.trunc_marker.hl_group local left_marker = get_trunc_marker(left_trunc_icon, marker_hl, marker_hl, marker.left_count) local right_marker = get_trunc_marker(right_trunc_icon, marker_hl, marker_hl, marker.right_count) local core = join( utils.merge_lists( { left_marker }, segments, { right_marker, right_align }, tab_indicator_segments, { tab_close_button } ) ) --- NOTE: the custom areas are essentially mini tablines a user can define so they can't --- be safely converted to segments so they are concatenated to string and joined with --- the rest of the tabline local tabline = utils.join(offsets.left, left_area, core, right_area, offsets.right) local left_offset_size = offsets.left_size + statusline_str_width(left_area) local left_marker_size = left_marker and get_component_size(left_marker) or 0 local right_offset_size = offsets.right_size + statusline_str_width(right_area) local right_marker_size = right_marker and get_component_size(right_marker) or 0 return { str = tabline, segments = segments, visible_components = visible_components, right_offset_size = right_offset_size + right_marker_size, left_offset_size = left_offset_size + left_marker_size, } end M.get_component_size = get_component_size M.components = components if utils.is_test() then M.to_tabline_str = to_tabline_str M.set_id = set_id M.add_indicator = add_indicator M.get_name = get_name end return M akinsho-bufferline.nvim-655133c/lua/bufferline/utils/000077500000000000000000000000001474150523700225625ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/lua/bufferline/utils/init.lua000066400000000000000000000214421474150523700242330ustar00rootroot00000000000000---------------------------------------------------------------------------// -- HELPERS ---------------------------------------------------------------------------// local lazy = require("bufferline.lazy") local constants = lazy.require("bufferline.constants") ---@module "bufferline.constants" local config = lazy.require("bufferline.config") ---@module "bufferline.config" local M = {} local fn, api = vim.fn, vim.api local strwidth = api.nvim_strwidth local is_version_11 = fn.has("nvim-0.11") == 1 function M.is_test() ---@diagnostic disable-next-line: undefined-global return __TEST end ---Takes a list of items and runs the callback ---on each updating the initial value ---@generic T, S ---@param callback fun(accum:S, item: T, key: integer|string): S ---@param list table ---@param accum S ---@return S ---@overload fun(callback: fun(accum: any, item: any, key: (integer|string)): any, list: any[]): any function M.fold(callback, list, accum) assert(callback, "a callback must be passed to fold") if type(accum) == "function" and type(callback) == "table" then list, callback, accum = callback, accum, {} end accum = accum or {} for i, v in pairs(list) do accum = callback(accum, v, i) end return accum end ---Variant of some that sums up the display size of characters ---@vararg string ---@return integer function M.measure(...) return M.fold(function(accum, item) return accum + strwidth(tostring(item)) end, { ... }, 0) end ---Concatenate a series of strings together ---@vararg string ---@return string function M.join(...) return M.fold(function(accum, item) return accum .. item end, { ... }, "") end ---@generic T ---@param callback fun(item: T, index: integer): T ---@param list T[] ---@return T[] function M.map(callback, list) local accum = {} for index, item in ipairs(list) do accum[index] = callback(item, index) end return accum end --- Search for an item in a list like table returning the item and its index --- if the predicate returns true for the item ---@generic T ---@param list T[] ---@param callback fun(item: T, index: number): boolean ---@return T?, number? function M.find(callback, list) for i, v in ipairs(list) do if callback(v, i) then return v, i end end end -- return a new array containing the concatenation of all of its -- parameters. Scalar parameters are included in place, and array -- parameters have their values shallow-copied to the final array. -- Note that userdata and function values are treated as scalar. -- https://stackoverflow.com/questions/1410862/concatenation-of-tables-in-lua --- @generic T --- @vararg T --- @return T[] function M.merge_lists(...) local t = {} for n = 1, select("#", ...) do local arg = select(n, ...) if type(arg) == "table" then for _, v in pairs(arg) do t[#t + 1] = v end else t[#t + 1] = arg end end return t end ---Execute a callback for each item or only those that match if a matcher is passed ---@generic T ---@param list T[] ---@param callback fun(item: T) ---@param matcher (fun(item: T):boolean)? function M.for_each(callback, list, matcher) for _, item in ipairs(list) do if not matcher or matcher(item) then callback(item) end end end --- creates a table whose keys are tbl's values and the value of these keys --- is their key in tbl (similar to vim.tbl_add_reverse_lookup) --- this assumes that the values in tbl are unique and hashable (no nil/NaN) --- @generic K,V --- @param tbl table --- @return table function M.tbl_reverse_lookup(tbl) local ret = {} for k, v in pairs(tbl) do ret[v] = k end return ret end --- creates a table containing the forward and reversed mapping from the provided --- table. --- similar to the now deprecated vim.tbl_add_reverse_lookup --- @generic K,V --- @param tbl table --- @return table function M.tbl_add_reverse_lookup(tbl) local ret = {} for k, v in pairs(tbl) do ret[k] = v ret[v] = k end return ret end M.path_sep = fn.has("win32") == 1 and "\\" or "/" -- The provided api nvim_is_buf_valid filters out all invalid or unlisted buffers --- @param buf table function M.is_valid(buf) if not buf.bufnr or buf.bufnr < 1 then return false end local valid = vim.api.nvim_buf_is_valid(buf.bufnr) if not valid then return false end return buf.listed == 1 end ---@return integer function M.get_buf_count() return #fn.getbufinfo({ buflisted = 1 }) end ---@return integer[] function M.get_valid_buffers() local bufs = vim.fn.getbufinfo() local valid_bufs = {} for _, buf in ipairs(bufs) do if M.is_valid(buf) then table.insert(valid_bufs, buf.bufnr) end end return valid_bufs end ---@return integer function M.get_tab_count() return #fn.gettabinfo() end function M.close_tab(tabhandle) vim.cmd("tabclose " .. api.nvim_tabpage_get_number(tabhandle)) end --- Wrapper around `vim.notify` that adds message metadata ---@param msg string | string[] ---@param level "error" | "warn" | "info" | "debug" | "trace" function M.notify(msg, level, opts) opts = opts or {} level = vim.log.levels[level:upper()] if type(msg) == "table" then msg = table.concat(msg, "\n") end local nopts = { title = "Bufferline" } if opts.once then return vim.schedule(function() vim.notify_once(msg, level, nopts) end) end vim.schedule(function() vim.notify(msg, level, nopts) end) end ---@return number[]? function M.restore_positions() local str = vim.g[constants.positions_key] local ok, paths = pcall(vim.json.decode, str) if not ok or type(paths) ~= "table" or #paths == 0 then return nil end local ids = vim.tbl_map(function(path) local escaped = fn.fnameescape(path) return fn.bufnr("^" .. escaped .. "$" --[[@as integer]]) end, paths) return vim.tbl_filter(function(id) return id ~= -1 end, ids) end ---@param ids number[] function M.save_positions(ids) local paths = vim.tbl_map(function(id) return vim.api.nvim_buf_get_name(id) end, ids) vim.g[constants.positions_key] = vim.json.encode(paths) end --- @param elements bufferline.TabElement[] --- @return number[] function M.get_ids(elements) return vim.tbl_map(function(item) return item.id end, elements) end ---Get an icon for a filetype using either nvim-web-devicons or vim-devicons ---if using the lua plugin this also returns the icon's highlights ---@param opts bufferline.IconFetcherOpts ---@return string, string? function M.get_icon(opts) local user_func = config.options.get_element_icon if user_func and vim.is_callable(user_func) then local icon, hl = user_func(opts) if icon then return icon, hl end end if not config.options.show_buffer_icons then return "", "" end local loaded, webdev_icons = pcall(require, "nvim-web-devicons") if opts.directory then local hl = loaded and "DevIconDefault" or nil return constants.FOLDER_ICON, hl end if not loaded then -- TODO: deprecate this in favour of nvim-web-devicons if fn.exists("*WebDevIconsGetFileTypeSymbol") > 0 then return fn.WebDevIconsGetFileTypeSymbol(opts.path), "" end return "", "" end if type == "terminal" then return webdev_icons.get_icon(type) end local icon, hl = webdev_icons.get_icon(fn.fnamemodify(opts.path, ":t"), nil, { default = true, }) if not icon then return "", "" end return icon, hl end -- truncate a string based on number of display columns/cells it occupies -- so that multibyte characters are not broken up mid character ---@param str string ---@param col_limit integer ---@return string local function truncate_by_cell(str, col_limit) if str and str:len() == strwidth(str) then return fn.strcharpart(str, 0, col_limit) end local short = fn.strcharpart(str, 0, col_limit) local width = strwidth(short) while width > 1 and width > col_limit do short = fn.strcharpart(short, 0, fn.strchars(short) - 1) width = strwidth(short) end return short end --- Truncate a name being mindful of multibyte characters and append an ellipsis ---@param name string ---@param word_limit integer ---@return string function M.truncate_name(name, word_limit) if strwidth(name) <= word_limit then return name end -- truncate nicely by seeing if we can drop the extension first -- to make things fit if not then truncate abruptly local ext = fn.fnamemodify(name, ":e") if ext ~= "" and ext:match("^%w+$") then local truncated = name:gsub("%." .. ext, "", 1) if strwidth(truncated) < word_limit then return truncated .. constants.ELLIPSIS end end return truncate_by_cell(name, word_limit - 1) .. constants.ELLIPSIS end ---@diagnostic disable: deprecated -- TODO: deprecate this in nvim-0.11 or use strict lists. Determine which list-check function to use M.is_list = vim.isarray or vim.islist or vim.tbl_isarray or vim.tbl_islist ---@diagnostic disable: deprecated function M.tbl_flatten(t) return is_version_11 and vim.iter(t):flatten(math.huge):totable() or vim.tbl_flatten(t) end return M akinsho-bufferline.nvim-655133c/lua/bufferline/utils/log.lua000066400000000000000000000010571474150523700240510ustar00rootroot00000000000000local lazy = require("bufferline.lazy") local config = lazy.require("bufferline.config") ---@module "bufferline.config" local utils = lazy.require("bufferline.utils") ---@module "bufferline.config" local M = {} local fmt = string.format ---@return boolean local function check_logging() return config.options.debug.logging end ---@param msg string function M.debug(msg) if check_logging() then local info = debug.getinfo(2, "S") utils.notify(fmt("%s\n%s:%s", msg, info.linedefined, info.short_src), "debug", { once = true }) end end return M akinsho-bufferline.nvim-655133c/stylua.toml000066400000000000000000000002011474150523700207270ustar00rootroot00000000000000column_width = 120 indent_type = "Spaces" quote_style = "AutoPreferDouble" indent_width = 2 collapse_simple_statement = "Always" akinsho-bufferline.nvim-655133c/tests/000077500000000000000000000000001474150523700176625ustar00rootroot00000000000000akinsho-bufferline.nvim-655133c/tests/bufferline_spec.lua000066400000000000000000000202161474150523700235210ustar00rootroot00000000000000local utils = require("tests.utils") describe("Bufferline tests:", function() vim.opt.swapfile = false vim.opt.hidden = true vim.opt.termguicolors = true local bufferline local state ---@module "bufferline.state" local icons ---@module "nvim-web-devicons" local config ---@module "bufferline.config" before_each(function() package.loaded["bufferline"] = nil package.loaded["bufferline.state"] = nil package.loaded["nvim-web-devicons"] = nil -- dependent modules need to also be reset as -- they keep track of state themselves now package.loaded["bufferline.config"] = nil package.loaded["bufferline.commands"] = nil bufferline = require("bufferline") state = require("bufferline.state") config = require("bufferline.config") icons = require("nvim-web-devicons") icons.setup({ default = true }) end) after_each(function() vim.cmd("silent %bwipeout!") end) describe("render buffer - ", function() it("should create corresponding buffers in state", function() bufferline.setup() vim.cmd("edit test-1.txt") vim.cmd("edit test-2.txt") local tabline = nvim_bufferline() assert.truthy(tabline) assert.is.equal(#state.components, 2) end) it("should allow configuring the indicator icon", function() local icon = "R" bufferline.setup({ options = { indicator = { icon = { icon } }, }, }) vim.cmd("edit test.txt") local tabline = nvim_bufferline() assert.truthy(tabline) assert.is_truthy(tabline:match(icon)) end) it("should allow specifying how icons are fetched", function() local icon = "Q" bufferline.setup({ options = { get_element_icon = function() return icon end, }, }) vim.cmd("edit test.txt") local tabline = nvim_bufferline() assert.truthy(tabline) assert.is_truthy(tabline:match(icon)) end) it("should allow formatting names", function() bufferline.setup({ options = { name_formatter = function(buf) if buf.path:match("test.txt") then return "TEST" end end, }, }) vim.cmd("edit test.txt") local tabline = nvim_bufferline() assert.truthy(tabline) assert.truthy(tabline:match("TEST")) end) end) describe("Snapshots - ", function() local snapshots = { "  a.txt  ▕  b.txt  ▕▎  c.txt  ", "  a.txt ▕  b.txt ▕▎  c.txt ", "  a.txt    b.txt    c.txt  ", } it("should add correct padding if close icons are present", function() bufferline.setup({ options = { get_element_icon = function() return "" end } }) vim.cmd("file! a.txt") vim.cmd("edit b.txt") vim.cmd("edit c.txt") local tabline, components = nvim_bufferline() assert.is_truthy(tabline) local snapshot = utils.tabline_from_components(components) assert.is_equal(snapshot, snapshots[1]) end) it("should add correct padding if close icons are absent", function() bufferline.setup({ options = { get_element_icon = function() return "" end, show_buffer_close_icons = false, }, }) vim.cmd("file! a.txt") vim.cmd("edit b.txt") vim.cmd("edit c.txt") local tabline, components = nvim_bufferline() assert.is_truthy(tabline) local snapshot = utils.tabline_from_components(components) assert.is_equal(snapshot, snapshots[2]) end) it("should show the correct separators", function() bufferline.setup({ options = { get_element_icon = function() return "" end, separator_style = "slant", }, }) vim.cmd("file! a.txt") vim.cmd("edit b.txt") vim.cmd("edit c.txt") local tabline, components = nvim_bufferline() assert.is_truthy(tabline) local snapshot = utils.tabline_from_components(components) assert.is_equal(snapshot, snapshots[3]) end) it("should show a default icon if specified", function() bufferline.setup({}) vim.cmd("edit test.rrj") local _, components = nvim_bufferline() local snapshot = utils.tabline_from_components(components) local icon = icons.get_icon("") assert.is_true(snapshot:match(icon) ~= nil) end) end) describe("clicking - ", function() it("should left handle mouse clicks correctly", function() local bufnum = vim.api.nvim_get_current_buf() bufferline.setup({ options = { left_mouse_command = "vertical sbuffer %d", }, }) ___bufferline_private.handle_click(bufnum, nil, "l") vim.wait(10) assert.is_equal(#vim.api.nvim_list_wins(), 2) end) it("should middle handle mouse clicks correctly", function() local bufnum = vim.api.nvim_get_current_buf() bufferline.setup({ options = { middle_mouse_command = function(bufid) vim.bo[bufid].filetype = "test" end, }, }) ___bufferline_private.handle_click(bufnum, nil, "m") assert.is_equal(vim.bo[bufnum].filetype, "test") end) it("should right handle mouse clicks correctly", function() local bufnum = vim.api.nvim_get_current_buf() bufferline.setup({ options = { right_mouse_command = "setfiletype egg", }, }) ___bufferline_private.handle_click(bufnum, nil, "r") vim.wait(10) assert.is_equal(vim.bo.filetype, "egg") end) it("should handle close click correctly", function() local bufnum = vim.api.nvim_get_current_buf() local count = 1 local expected = bufnum + count bufferline.setup({ options = { close_command = function(bufid) count = count + bufid end, }, }) ___bufferline_private.handle_close(bufnum) assert.is_equal(count, expected) end) end) -- FIXME: nvim_bufferline() needs to be manually called describe("commands - ", function() it("should close buffers to the right of the current buffer", function() bufferline.setup({ options = { close_command = function(bufid) vim.api.nvim_buf_delete(bufid, { force = true }) end, }, }) vim.cmd("file! a.txt") vim.cmd("edit b.txt") vim.cmd("edit c.txt") vim.cmd("edit d.txt") vim.cmd("edit e.txt") nvim_bufferline() vim.cmd("edit c.txt") bufferline.close_in_direction("right") local bufs = vim.api.nvim_list_bufs() assert.is_equal(3, #bufs) end) it("should close buffers to the left of the current buffer", function() bufferline.setup({ options = { close_command = function(bufid) vim.api.nvim_buf_delete(bufid, { force = true }) end, }, }) vim.cmd("edit! a.txt") vim.cmd("edit b.txt") vim.cmd("edit c.txt") vim.cmd("edit d.txt") vim.cmd("edit e.txt") nvim_bufferline() assert.is.equal(5, #state.components) local bufs = vim.api.nvim_list_bufs() assert.is_equal(5, #bufs) bufferline.close_in_direction("left") bufs = vim.api.nvim_list_bufs() assert.is_equal(1, #bufs) end) end) describe("Theme - ", function() it("should update the colors if the colorscheme changes", function() vim.cmd("colorscheme blue") local colors = require("bufferline.colors") local blue_bg = colors.get_color({ name = "Normal", attribute = "bg" }) local blue_fg = colors.get_color({ name = "Normal", attribute = "fg" }) bufferline.setup() assert.equal(config.highlights.buffer_selected.bg, blue_bg) assert.equal(config.highlights.buffer_selected.fg, blue_fg) vim.cmd("colorscheme desert") local desert_bg = colors.get_color({ name = "Normal", attribute = "bg" }) local desert_fg = colors.get_color({ name = "Normal", attribute = "fg" }) assert.equal(config.highlights.buffer_selected.bg, desert_bg) assert.equal(config.highlights.buffer_selected.fg, desert_fg) assert.not_equal(blue_bg, desert_bg) end) end) end) akinsho-bufferline.nvim-655133c/tests/colors_spec.lua000066400000000000000000000034021474150523700226770ustar00rootroot00000000000000local colors = require("bufferline.colors") local fn = vim.fn local fmt = string.format describe("Colors:", function() local color1 = "#FFB13B" local hl1 = "TestHighlight" before_each(function() -- Set a specific colorscheme that is bundled with vim and predictable vim.cmd("colorscheme industry") vim.cmd(fmt("highlight %s guifg=%s", hl1, color1)) end) describe("get_color - ", function() it("should correctly derive normal color", function() local norm_fg = colors.get_color({ name = "Normal", attribute = "fg" }) assert.is_truthy(norm_fg) assert.is_true(#norm_fg > 0) end) it("should use fallback if main is unavailable", function() local normal = fn.synIDattr(fn.hlID("Normal"), "fg#", "gui") local norm_fg = colors.get_color({ name = "FakeHighlight", attribute = "fg", fallback = { name = "Normal", attribute = "fg" }, }) assert.is_truthy(norm_fg) assert.equal(norm_fg, normal) end) it("should use fallbacks recursively", function() local actual_fg = fn.synIDattr(fn.hlID(hl1), "fg#", "gui") local test_fg = colors.get_color({ name = "FakeHighlight", attribute = "fg", fallback = { name = "NextFakeHighlight", attribute = "fg", fallback = { name = hl1, attribute = "fg" }, }, }) assert.is_truthy(test_fg) assert.equal(test_fg, actual_fg:lower()) end) it("should not return a value if it set to not_match", function() local normal = fn.synIDattr(fn.hlID("Normal"), "fg#", "gui") local norm_fg = colors.get_color({ name = "Normal", attribute = "fg", not_match = normal, }) assert.equal(norm_fg, "NONE") end) end) end) akinsho-bufferline.nvim-655133c/tests/config_spec.lua000066400000000000000000000111231474150523700226420ustar00rootroot00000000000000local fmt = string.format describe("Config tests", function() local whitesmoke = "#F5F5F5" local config = require("bufferline.config") before_each(function() vim.opt.termguicolors = true end) after_each(function() config.__reset() end) describe("Setting config", function() it("should add defaults to user values", function() config.setup({ options = { show_close_icon = false, }, }) local under_test = config.apply() assert.is_false(under_test.options.show_close_icon) assert.is_false(vim.tbl_isempty(under_test.highlights)) assert.is_true(vim.tbl_count(under_test.highlights) > 10) end) it("should derive colors from the existing highlights", function() vim.cmd(fmt("hi Comment guifg=%s", whitesmoke)) config.setup({}) local under_test = config.apply() assert.equal(whitesmoke:lower(), under_test.highlights.info.fg) end) it('should not underline anything if options.indicator.style = "icon"', function() config.setup({ options = { indicator = { style = "icon" } } }) local conf = config.apply() for _, value in pairs(conf.highlights) do assert.is_falsy(value.underline) end end) it('should only underline valid fields if options.indicator.style = "underline"', function() config.setup({ options = { indicator = { style = "underline" } } }) local conf = config.apply() local valid = { "numbers_selected", "buffer_selected", "modified_selected", "indicator_selected", "tab_selected", "close_button_selected", "tab_separator_selected", "duplicate_selected", "separator_selected", "pick_selected", "close_button_selected", "diagnostic_selected", "error_selected", "error_diagnostic_selected", "info_selected", "info_diagnostic_selected", "warning_selected", "warning_diagnostic_selected", "hint_selected", "hint_diagnostic_selected", } for hl, value in pairs(conf.highlights) do if vim.tbl_contains(valid, hl) then assert.is_true(value.underline) else assert.is_falsy(value.underline) end end end) describe("- Style Presets - ", function() it("should disable all bolding if the preset contains no bold", function() config.setup({ options = { style_preset = config.STYLE_PRESETS.no_bold } }) local conf = config.apply() local some_italic = false for _, value in pairs(conf.highlights) do if not some_italic and value.italic then some_italic = true end assert.is_falsy(value.bold) end assert.is_true(some_italic) end) it("should disable all italics if the preset contains no italic", function() config.setup({ options = { style_preset = config.STYLE_PRESETS.no_italic } }) local conf = config.apply() local some_bold = false for _, value in pairs(conf.highlights) do if not some_bold and value.bold then some_bold = true end assert.is_falsy(value.italic) end assert.is_true(some_bold) end) it("should disable both italics and bold, if no_bold and no_italic are specified", function() config.setup({ options = { style_preset = { config.STYLE_PRESETS.no_italic, config.STYLE_PRESETS.no_bold }, }, }) local conf = config.apply() for _, value in pairs(conf.highlights) do assert.is_falsy(value.italic) assert.is_falsy(value.bold) end end) end) end) describe("Resetting config -", function() it("should use updated colors when the colorscheme changes", function() vim.cmd("colorscheme blue") local colors = require("bufferline.colors") local blue_bg = colors.get_color({ name = "Normal", attribute = "bg" }) local blue_fg = colors.get_color({ name = "Normal", attribute = "fg" }) config.setup() config.apply() assert.equal(config.highlights.buffer_selected.bg, blue_bg) assert.equal(config.highlights.buffer_selected.fg, blue_fg) vim.cmd("colorscheme desert") local desert_bg = colors.get_color({ name = "Normal", attribute = "bg" }) local desert_fg = colors.get_color({ name = "Normal", attribute = "fg" }) config.update_highlights() assert.equal(config.highlights.buffer_selected.bg, desert_bg) assert.equal(config.highlights.buffer_selected.fg, desert_fg) assert.not_equal(blue_bg, desert_bg) end) end) end) akinsho-bufferline.nvim-655133c/tests/custom_area_spec.lua000066400000000000000000000033201474150523700236770ustar00rootroot00000000000000describe("Custom areas -", function() local areas = require("bufferline.custom_area") local bufferline before_each(function() package.loaded["bufferline"] = nil bufferline = require("bufferline") end) it("should generate a custom area from config", function() bufferline.setup({ options = { custom_areas = { left = function() return { { text = "test", fg = "red", bg = "black" } } end, }, }, }) local size, left = areas.get() assert.is_truthy(left) assert.is_equal(4, size) assert.is_equal("%#BufferLineLeftCustomAreaText1#test", left) end) it("should handle sides correctly", function() bufferline.setup({ highlights = { fill = { fg = "#000000", }, }, options = { custom_areas = { left = function() return { { text = "test", fg = "red", bg = "black" } } end, right = function() return { { text = "test1", italic = true } } end, }, }, }) local size, left, right = areas.get() assert.is_equal(9, size) assert.is_truthy(left) assert.is_equal("%#BufferLineLeftCustomAreaText1#test", left) assert.is_truthy(right) assert.is_equal("%#BufferLineRightCustomAreaText1#test1", right) end) it("should handle user errors gracefully", function() bufferline.setup({ options = { custom_areas = { left = function() return { { text = { "test" }, fg = "red", bg = "black" } } end, right = function() error("This failed mysteriously") end, }, }, }) local size, left, right = areas.get() assert.is_equal(0, size) assert.is_equal("", left) assert.is_equal("", right) end) end) akinsho-bufferline.nvim-655133c/tests/duplicates_spec.lua000066400000000000000000000125251474150523700235410ustar00rootroot00000000000000local Tabpage = require("bufferline.models").Tabpage describe("Duplicate Tests - ", function() local duplicates local config before_each(function() package.loaded["bufferline.duplicates"] = nil duplicates = require("bufferline.duplicates") config = require("bufferline.config") end) it("should mark duplicate files", function() config.setup({}) config.apply() local result = duplicates.mark({ { path = "/test/dir_a/dir_b/file.txt", name = "file.txt", ordinal = 1, }, { path = "/test/dir_a/dir_c/file.txt", name = "file.txt", ordinal = 2, }, { path = "/test/dir_a/result.txt", name = "result.txt", ordinal = 3, }, }) assert.is_equal(result[1].duplicated, "path") assert.is_equal(result[2].duplicated, "path") assert.falsy(result[3].duplicated) assert.is_equal(#result, 3) end) it("should show duplicates across groups", function() config.setup({ options = { duplicates_across_groups = true } }) config.apply() local result = duplicates.mark({ { path = "/test/dir_a/dir_b/file.txt", name = "file.txt", ordinal = 1, group = "A", }, { path = "/test/dir_a/dir_c/file.txt", name = "file.txt", ordinal = 2, group = "A", }, { path = "/test/dir_a/dir_d/file.txt", name = "file.txt", ordinal = 1, group = "B", }, }) assert.is_equal(result[1].duplicated, "path") assert.is_equal(result[2].duplicated, "path") assert.is_equal(result[3].duplicated, "path") end) it("should not show duplicates across groups", function() config.setup({ options = { duplicates_across_groups = false } }) config.apply() local result = duplicates.mark({ { path = "/test/dir_a/dir_b/file.txt", name = "file.txt", ordinal = 1, group = "A", }, { path = "/test/dir_a/dir_c/file.txt", name = "file.txt", ordinal = 2, group = "A", }, { path = "/test/dir_a/dir_d/file.txt", name = "file.txt", ordinal = 1, group = "B", }, }) assert.is_equal(result[1].duplicated, "path") assert.is_equal(result[2].duplicated, "path") assert.falsy(result[3].duplicated) end) it("should return the correct prefix count", function() local result = duplicates.mark({ { path = "/test/dir_a/dir_b/file.txt", name = "file.txt", ordinal = 1, }, { path = "/test/dir_a/dir_c/file.txt", name = "file.txt", ordinal = 2, }, { path = "/test/dir_a/result.txt", name = "result.txt", ordinal = 3, }, }) assert.equal(result[1].prefix_count, 2) assert.equal(result[2].prefix_count, 2) assert.falsy(result[3].prefix_count) end) it("should indicate if a buffer is exactly the same as another", function() local result = duplicates.mark({ { path = "/test/dir_a/dir_b/file.txt", name = "file.txt", ordinal = 1, }, { path = "/test/dir_a/dir_b/file.txt", name = "file.txt", ordinal = 2, }, { path = "/test/dir_a/result.txt", name = "result.txt", ordinal = 3, }, }) assert.equal(result[1].duplicated, "element") assert.equal(result[2].duplicated, "element") assert.falsy(result[3].prefix_count) end) it("should return a prefixed element if duplicated", function() config.setup({ options = { enforce_regular_tabs = false } }) config.apply() local component = duplicates.component({ current_highlights = { duplicate = "TestHighlight" }, tab = Tabpage:new({ path = "very_long_directory_name/test/dir_a/result.txt", buf = 1, buffers = { 1 }, id = 1, ordinal = 1, diagnostics = {}, hidden = false, focusable = true, duplicated = true, prefix_count = 2, }), }) assert.truthy(component.text) assert.is_equal(component.text, "dir_a/") component = duplicates.component({ current_highlights = { duplicate = "TestHighlight" }, tab = Tabpage:new({ path = "very_long_directory_name/test/dir_a/result.txt", buf = 1, buffers = { 1 }, id = 1, ordinal = 1, diagnostics = {}, hidden = false, focusable = true, duplicated = true, prefix_count = 3, }), }) assert.truthy(component.text) assert.is_equal(component.text, "test/dir_a/") end) it("should truncate a very long directory name", function() config.setup({ options = { enforce_regular_tabs = false, max_prefix_length = 10 } }) config.apply() local component = duplicates.component({ current_highlights = { duplicate = "TestHighlight" }, tab = Tabpage:new({ path = "very_long_directory_name/dir_a/result.txt", buf = 1, buffers = { 1 }, id = 1, ordinal = 1, diagnostics = {}, hidden = false, focusable = true, duplicated = true, prefix_count = 3, }), }) assert.is_true(vim.api.nvim_strwidth(component.text) <= 10) assert.is_equal(component.text, "ver…/dir…/") end) end) akinsho-bufferline.nvim-655133c/tests/groups_spec.lua000066400000000000000000000162561474150523700227300ustar00rootroot00000000000000local utils = require("tests.utils") local Buffer = utils.MockBuffer --- NOTE: The pinned group is group 1 and so all groups must appear after this --- all group are moved down by one because of this describe("Group tests - ", function() local groups ---@module "bufferline.groups" local state ---@module "bufferline.state" local config ---@module "bufferline.config" local bufferline ---@module "bufferline" before_each(function() package.loaded["bufferline"] = nil package.loaded["bufferline.groups"] = nil package.loaded["bufferline.state"] = nil package.loaded["bufferline.config"] = nil groups = require("bufferline.groups") bufferline = require("bufferline") state = require("bufferline.state") config = require("bufferline.config") end) local function set_buf_group(buffer) buffer.group = groups.set_id(buffer) return buffer end it("should add user groups on setup", function() groups.setup({ options = { groups = { items = { { name = "test-group", matcher = function(buf) return buf.name:match("dummy") end, }, }, }, }, }) -- One for the pinned group, another for the ungrouped and the last for the new group assert.is_equal(vim.tbl_count(groups.state.user_groups), 3) end) it("should sanitise invalid names", function() groups.setup({ options = { groups = { items = { { name = "test group", matcher = function(buf) return buf.name:match("dummy") end, }, }, }, }, }) assert.is_truthy(groups.state.user_groups["test_group"]) end) it("should set highlights on setup", function() local c = { highlights = { buffer_selected = { fg = "black", bg = "white", }, buffer_visible = { fg = "black", bg = "white", }, buffer = { fg = "black", bg = "white", }, fill = { fg = "Red", bg = "Green", }, }, options = { groups = { items = { { name = "test-group", highlight = { fg = "red" }, matcher = function(buf) return buf.name:match("dummy") end, }, }, }, }, } groups.setup(c) config.setup(c) local conf = config.apply() local hls = conf.highlights assert.truthy(hls.test_group_selected) assert.truthy(hls.test_group_visible) assert.truthy(hls.test_group) assert.equal(hls.test_group.fg, "red") end) it("should sort components by groups", function() groups.setup({ options = { groups = { items = { { name = "test-group", matcher = function(buf) return buf.name:match("dummy") end, }, }, }, }, }) local components = vim.tbl_map(set_buf_group, { Buffer:new({ name = "dummy-1.txt" }), Buffer:new({ name = "dummy-2.txt" }), Buffer:new({ name = "file-2.txt" }), }) local sorted, components_by_group = groups.sort_by_groups(components) assert.is_equal(#sorted, 3) assert.equal(sorted[1]:as_element().name, "dummy-1.txt") assert.equal(sorted[#sorted]:as_element().name, "file-2.txt") assert.is_equal(vim.tbl_count(components_by_group), 3) end) it("should add group markers", function() local conf = { highlights = {}, options = { groups = { items = { { name = "test-group", matcher = function(buf) return buf.name:match("dummy") end, }, }, }, }, } bufferline.setup(conf) local components = { Buffer:new({ name = "dummy-1.txt" }), Buffer:new({ name = "dummy-2.txt" }), Buffer:new({ name = "file-2.txt" }), } components = vim.tbl_map(set_buf_group, components) components = groups.render(components, function(t) return t end) assert.equal(5, #components) local g_start = components[1] local g_end = components[4] assert.is_equal(g_start.type, "group_start") assert.is_equal(g_end.type, "group_end") local component = g_start.component() assert.is_true(utils.find_text(component, "test-group")) end) it("should sort each group individually", function() local conf = { highlights = {}, options = { groups = { items = { { name = "A", matcher = function(buf) return buf.name:match("%.txt") end, }, { name = "B", matcher = function(buf) return buf.name:match("%.js") end, }, { name = "C", matcher = function(buf) return buf.name:match("%.dart") end, }, }, }, }, } bufferline.setup(conf) local components = { Buffer:new({ name = "a.txt" }), Buffer:new({ name = "b.txt" }), Buffer:new({ name = "d.dart" }), Buffer:new({ name = "c.dart" }), Buffer:new({ name = "h.js" }), Buffer:new({ name = "g.js" }), } components = vim.tbl_map(set_buf_group, components) components = groups.render(components, function(t) table.sort(t, function(a, b) return a:as_element().name > b:as_element().name end) return t end) assert.is_equal(components[2]:as_element().name, "b.txt") assert.is_equal(components[3]:as_element().name, "a.txt") assert.is_equal(components[6]:as_element().name, "h.js") assert.is_equal(components[7]:as_element().name, "g.js") assert.is_equal(components[10]:as_element().name, "d.dart") assert.is_equal(components[11]:as_element().name, "c.dart") end) it("should pin a buffer", function() bufferline.setup() vim.cmd("edit dummy-1.txt") nvim_bufferline() vim.cmd("BufferLineTogglePin") nvim_bufferline() local buf = utils.find_buffer("dummy-1.txt", state) local group = buf and groups.get_manual_group(buf) assert.is_truthy(group and group:match("pinned")) end) it("should unpin a pinned buffer", function() bufferline.setup() vim.cmd("edit dummy-1.txt") nvim_bufferline() vim.cmd("BufferLineTogglePin") nvim_bufferline() local buf = utils.find_buffer("dummy-1.txt", state) local group = buf and groups.get_manual_group(buf) assert.is_truthy(group and group:match("pinned")) vim.cmd("BufferLineTogglePin") nvim_bufferline() group = buf and groups.get_manual_group(buf) assert.is_falsy(group) end) it("pinning should override other groups", function() bufferline.setup({ options = { groups = { items = { { name = "A", matcher = function(buf) return buf.name:match("%.txt") end, }, }, }, }, }) vim.cmd("edit dummy-1.txt") nvim_bufferline() vim.cmd("BufferLineTogglePin") nvim_bufferline() local buf = utils.find_buffer("dummy-1.txt", state) local group = buf and groups.get_manual_group(buf) assert.is_truthy(group and group:match("pinned")) end) end) akinsho-bufferline.nvim-655133c/tests/highlights_spec.lua000066400000000000000000000016601474150523700235340ustar00rootroot00000000000000---@diagnostic disable: need-check-nil describe("Highlights -", function() local highlights ---@module "bufferline.highlights" local config ---@module "bufferline.config" before_each(function() package.loaded["bufferline.highlights"] = nil package.loaded["bufferline.config"] = nil highlights = require("bufferline.highlights") config = require("bufferline.config") end) it("should set highlights as default", function() config.setup({ options = { themable = true } }) config.apply() local hl = highlights.set("BufferLineBufferSelected", { bold = true }) assert.truthy(hl) assert.is_true(hl.default) end) it("should not set highlights as default if themable = false", function() config.setup({ options = { themable = true } }) config.apply() local hl = highlights.set("BufferLineBufferSelected", { bold = true }) assert.truthy(hl) assert.is_true(hl.default) end) end) akinsho-bufferline.nvim-655133c/tests/minimal_init.lua000066400000000000000000000020611474150523700230350ustar00rootroot00000000000000local M = {} function M.root(root) local f = debug.getinfo(1, "S").source:sub(2) return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (root or "") end ---@param plugin string function M.load(plugin) local name = plugin:match(".*/(.*)") local package_root = M.root(".tests/site/pack/deps/start/") if not vim.loop.fs_stat(package_root .. name) then print("Installing " .. plugin) vim.fn.mkdir(package_root, "p") vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/" .. plugin .. ".git", package_root .. "/" .. name, }) end end function M.setup() vim.cmd([[set runtimepath=$VIMRUNTIME]]) vim.opt.runtimepath:append(M.root()) vim.opt.packpath = { M.root(".tests/site") } M.load("nvim-lua/plenary.nvim") M.load("nvim-tree/nvim-web-devicons") vim.env.XDG_CONFIG_HOME = M.root(".tests/config") vim.env.XDG_DATA_HOME = M.root(".tests/data") vim.env.XDG_STATE_HOME = M.root(".tests/state") vim.env.XDG_CACHE_HOME = M.root(".tests/cache") end vim.o.swapfile = false _G.__TEST = true M.setup() akinsho-bufferline.nvim-655133c/tests/numbers_spec.lua000066400000000000000000000022221474150523700230500ustar00rootroot00000000000000describe("Number tests: ", function() local prefix = require("bufferline.numbers").prefix local test_buf = { id = 100, ordinal = 2, } it("should return an ordinal in the default style", function() local result = prefix(test_buf, "ordinal") assert.equal(result, "2.") end) it("should return a buffer id in the default style", function() local result = prefix(test_buf, "buffer_id") assert.equal(result, "100.") end) it("should return the correct default for both style", function() local result = prefix(test_buf, "both") assert.equal(result, "100.₂") end) it("should handle a custom numbers function", function() local function numbers_func(opts) return string.format("%s·%s", opts.raise(opts.id), opts.lower(opts.ordinal)) end local result = prefix(test_buf, numbers_func) assert.equal(result, "¹⁰⁰·₂") end) it("should return two superscript numbers", function() local function numbers_func(opts) return string.format("%s·%s", opts.raise(opts.id), opts.raise(opts.ordinal)) end local result = prefix(test_buf, numbers_func) assert.equal(result, "¹⁰⁰·²") end) end) akinsho-bufferline.nvim-655133c/tests/offset_spec.lua000066400000000000000000000124031474150523700226650ustar00rootroot00000000000000local fn = vim.fn local api = vim.api local fmt = string.format local constants = require("bufferline.constants") local filetype = "test" local function open_test_panel(direction, ft, on_open) direction = direction or "H" ft = ft or filetype local win = api.nvim_get_current_win() vim.cmd(fmt("vnew %s", fn.tempname())) local win_id = api.nvim_get_current_win() vim.cmd(fmt("wincmd %s", direction)) local new_ft = fmt("%s_%d", ft, win_id) vim.cmd(fmt("setfiletype %s", new_ft)) api.nvim_win_set_width(api.nvim_get_current_win(), 20) if on_open then on_open() end api.nvim_set_current_win(win) vim.wo[win_id].winfixwidth = true return new_ft, win_id end local function remove_highlight(str) return str:gsub("%%#Normal#", "") end describe("Offset tests:", function() local bufferline local offsets = require("bufferline.offset") vim.o.hidden = true vim.o.swapfile = false before_each(function() package.loaded["bufferline"] = nil bufferline = require("bufferline") end) after_each(function() vim.cmd("silent only") --- FIXME: open a new tab so that new windows get assigned each time vim.cmd("tabnew") end) it("should not trigger if no offsets are specified", function() bufferline.setup({ options = {}, highlights = {} }) local data = offsets.get() assert.equal(0, data.total_size) assert.equal(data.left, "") assert.equal(data.right, "") end) it("should create an offset if a compatible panel if open", function() local ft = open_test_panel() local opts = { highlights = {}, options = { offsets = { { filetype = ft } } }, } bufferline.setup(opts) local data = offsets.get() assert.equal(20, data.total_size) assert.equal(data.right, "") assert.is_truthy(data.left:match(" ")) end) it("should include padded text if text is specified", function() local ft = open_test_panel() bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft, text = "Test buffer" } } }, }) local data = offsets.get() assert.equal(20, data.total_size) assert.equal(data.right, "") assert.is_truthy(data.left:match(" Test buffer ")) end) it("should add the offset to the correct side", function() local ft = open_test_panel("L") bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft, text = "Test buffer" } }, }, }) local data = offsets.get() assert.equal(20, data.total_size) assert.is_truthy(data.right:match("Test buffer")) assert.equal("", data.left) end) it("should correctly truncate offset text", function() local ft = open_test_panel() bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft, text = "Test buffer buffer buffer buffer" } }, }, }) local data = offsets.get() assert.equal(20, data.total_size) assert.equal("", data.right) assert.is_equal(fmt(" Test buffer buffe%s ", constants.ELLIPSIS), remove_highlight(data.left)) end) it("should allow left and right offsets", function() local ft1 = open_test_panel() local ft2 = open_test_panel("L") bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft1, text = "Left" }, { filetype = ft2, text = "Right" } }, }, }) local data = offsets.get() assert.is_truthy(data.left:match("Left")) assert.is_truthy(data.right:match("Right")) assert.equal(40, data.total_size) end) it("should allow setting some extra padding", function() local ft1 = open_test_panel() bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft1, text = "Left", padding = 5 } }, }, }) local data = offsets.get() assert.is_truthy(data.left:match("Left")) assert.equal(25, data.total_size) end) it("should align the text to the right if specified", function() local ft1 = open_test_panel() local text = "Text" bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft1, text = text, text_align = "right" } }, }, }) local data = offsets.get() assert.equal(20, data.total_size) assert.equal(remove_highlight(data.left), string.rep(" ", data.total_size - (#text + 1)) .. text .. " ") end) it("should align the text to the left if specified", function() local text = "Text" local ft1 = open_test_panel() bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft1, text = text, text_align = "left" } }, }, }) local data = offsets.get() assert.equal(20, data.total_size) assert.equal(remove_highlight(data.left), " " .. text .. string.rep(" ", data.total_size - (#text + 1))) end) it("should handle a vertical panel with horizontal splits inside it", function() local ft = open_test_panel("H", filetype, function() -- add some child horizontal splits to the panel vim.cmd("split") vim.cmd("split") end) bufferline.setup({ highlights = {}, options = { offsets = { { filetype = ft } } }, }) local data = offsets.get() assert.equal(20, data.total_size) assert.equal(data.right, "") assert.is_truthy(data.left:match(" ")) end) end) akinsho-bufferline.nvim-655133c/tests/sorters_spec.lua000066400000000000000000000064341474150523700231070ustar00rootroot00000000000000---@diagnostic disable: need-check-nil describe("Sorters - ", function() local utils = require("bufferline.utils") local bufferline ---@module "bufferline" local sorters ---@module "bufferline.sorters" local state ---@type bufferline.State before_each(function() package.loaded["bufferline"] = nil package.loaded["bufferline.state"] = nil package.loaded["bufferline.commands"] = nil package.loaded["bufferline.sorters"] = nil bufferline = require("bufferline") state = require("bufferline.state") sorters = require("bufferline.sorters") end) after_each(function() vim.cmd("silent %bwipeout!") end) it("should always return a list", function() bufferline.setup({ options = { sort_by = "none" } }) local bufs = { { id = 12 }, { id = 2 }, { id = 3 }, { id = 8 } } local list = sorters.sort(bufs) assert.is_true(utils.is_list(list)) end) it("should return an unsorted list sort is none", function() bufferline.setup({ options = { sort_by = "none" } }) local bufs = { { id = 12 }, { id = 2 }, { id = 3 }, { id = 8 } } local list = sorters.sort(bufs) assert.is_true(utils.is_list(list)) local ids = vim.tbl_map(function(buf) return buf.id end, list) assert.same(ids, { 12, 2, 3, 8 }) end) it("should sort by ID correctly", function() bufferline.setup({ options = { sort_by = "id" } }) local bufs = { { id = 12 }, { id = 2 }, { id = 3 }, { id = 8 } } sorters.sort(bufs) local ids = vim.tbl_map(function(buf) return buf.id end, bufs) assert.same(ids, { 2, 3, 8, 12 }) end) it("should sort by components correctly", function() bufferline.setup({ options = { sort_by = "tabs" } }) vim.cmd("e file1.txt") vim.cmd("tabnew file2.txt") vim.cmd("tabnew file3.txt") vim.cmd("bunload file2.txt") local bufs = vim.tbl_map(function(id) return { id = id } end, vim.api.nvim_list_bufs()) sorters.sort(bufs) local buf_names = vim.tbl_map( function(buf) return vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf.id), ":p:t") end, bufs ) assert.same({ "file1.txt", "file3.txt", "file2.txt" }, buf_names) end) it("should add to the end of the buffer list", function() bufferline.setup({ options = { sort_by = "insert_at_end", }, }) vim.cmd("edit! a.txt") vim.cmd("edit b.txt") vim.cmd("edit c.txt") vim.cmd("edit d.txt") vim.cmd("edit e.txt") nvim_bufferline() vim.cmd("b b.txt") nvim_bufferline() assert.is_equal(2, state.current_element_index) vim.cmd("edit g.txt") nvim_bufferline() local comp = state.components[#state.components]:as_element() assert.is_truthy(comp) assert.is_true(comp.name:match("g.txt") ~= nil) end) it("should open the new buffer beside the current", function() bufferline.setup({ options = { sort_by = "insert_after_current", }, }) vim.cmd("edit! a.txt") vim.cmd("edit b.txt") vim.cmd("edit c.txt") vim.cmd("edit d.txt") vim.cmd("edit e.txt") nvim_bufferline() vim.cmd("b b.txt") nvim_bufferline() assert.is_equal(2, state.current_element_index) vim.cmd("edit g.txt") nvim_bufferline() local comp = state.components[3] assert.is_truthy(comp) assert.is_true(comp.name:match("g.txt") ~= nil) end) end) akinsho-bufferline.nvim-655133c/tests/ui_spec.lua000066400000000000000000000123531474150523700220200ustar00rootroot00000000000000---@diagnostic disable: need-check-nil local utils = require("tests.utils") local constants = require("bufferline.constants") local MockBuffer = utils.MockBuffer describe("UI Tests", function() local ui ---@module 'bufferline.ui' local config ---@module 'bufferline.config' local state ---@module 'bufferline.state' before_each(function() ui = utils.reload("bufferline.ui") config = utils.reload("bufferline.config") state = utils.reload("bufferline.state") end) describe("Render tabline", function() it("should convert a list of segments to a tabline string", function() local components = { { text = "|", highlight = "BufferlineIndicatorSelected" }, { text = " ", highlight = "BufferlineSelected" }, { text = "buffer.txt", highlight = "BufferlineSelected", attr = { extends = { { id = "example" } } }, }, ui.set_id({ text = " ", highlight = "BufferlineExample" }, "example"), { text = "x", highlight = "BufferlineCloseButton" }, } local str = ui.to_tabline_str(components) assert.equal( "%#BufferlineIndicatorSelected#|%#BufferlineSelected# %#BufferlineSelected#buffer.txt%#BufferlineSelected# %#BufferlineCloseButton#x", str ) end) it("should not render an indicator if the style is underline", function() config.setup({ options = { indicator = { style = "underline" } } }) config.apply() local result = ui.add_indicator({ tab = MockBuffer:new({}), highlights = {} }) assert.is_truthy(result) assert.is_equal(result.text, " ") end) it("should render an indicator if the style is icon", function() config.setup({ highlights = { indicator_selected = { hl_group = "IndicatorSelected" } } }) config.apply() local result = ui.add_indicator({ tab = MockBuffer:new({}), highlights = {} }) assert.is_truthy(result) assert.is_equal(result.text, constants.indicator) end) it("should not truncate the tab name if disabled", function() config.setup({ options = { truncate_names = false } }) config.apply() local segment = ui.get_name({ tab = { name = "a_very_very_very_very_long_name_that_i_use.js", icon = "x" }, current_highlights = {}, }) assert.is_equal(segment.text, "a_very_very_very_very_long_name_that_i_use.js") end) it("should truncate the tab name if enabled", function() config.setup({ options = { truncate_names = true } }) config.apply() local segment = ui.get_name({ tab = { name = "a_very_very_very_very_long_name_that_i_use.js", icon = "x" }, current_highlights = {}, }) assert.is_equal(segment.text, "a_very_very_very_…") end) end) describe("Hover events - ", function() it("should set the state with on hover of a tab", function() config.setup({ options = { hover = { reveal = { "close" } } } }) config.apply() state.set({ visible_components = { { id = 1, name = "file-1.text", length = 10, }, { id = 2, name = "file-2.text", length = 15, }, }, }) ui.on_hover_over(0, { cursor_pos = 12 }) assert.is_truthy(state.hovered) assert.equal(state.hovered.id, 2) end) it("should remove the hovered item on mouse out", function() config.setup({ options = { hover = { reveal = { "close" } } } }) config.apply() state.set({ visible_components = { { id = 1, name = "file-1.text", length = 10, }, { id = 2, name = "file-2.text", length = 15, }, }, hovered = { id = 2, name = "file-2.text", length = 15, }, }) ui.on_hover_out() assert.is_falsy(state.hovered) end) it("should not render a close icon if not hovered", function() config.setup({ options = { hover = { enabled = true, reveal = { "close" } } } }) config.apply() local buf = MockBuffer:new({ id = 1, name = "file.txt", _is_current = false }) local el = ui.element({}, buf) local segment = ui.to_tabline_str(el:component(1)) assert.is_falsy(segment:match(config.options.buffer_close_icon)) end) it("should render a close icon if hovered", function() config.setup({ options = { hover = { enabled = true, reveal = { "close" } } } }) config.apply() local buf1 = MockBuffer:new({ id = 1, name = "file.txt", length = 10, _is_current = true }) local buf2 = MockBuffer:new({ id = 2, name = "next.txt", length = 10, _is_current = false, _is_visible = true, }) state.set({ visible_components = { buf1, buf2 } }) ui.on_hover_over(0, { cursor_pos = 5 }) assert.equal(state.hovered, buf1) local b1 = ui.element({}, buf1) local b2 = ui.element({}, buf2) local s1 = ui.to_tabline_str(b1:component(1)) assert.is_truthy(s1:match(config.options.buffer_close_icon)) local s2 = ui.to_tabline_str(b2:component(1)) assert.is_falsy(s2:match(config.options.buffer_close_icon)) end) end) end) akinsho-bufferline.nvim-655133c/tests/utils.lua000066400000000000000000000027271474150523700215350ustar00rootroot00000000000000local M = {} local fn = vim.fn local MockBuffer = {} function M.tabline_from_components(components) local str = "" for _, c in ipairs(components) do for _, v in ipairs(c) do str = str .. (v.text or "") end end return str end function M.reload(module) package.loaded[module] = nil return require(module) end ---helper to find text in a Segment[] ---@param component bufferline.Segment[] ---@param text string ---@return boolean function M.find_text(component, text) local found = false for _, item in ipairs(component) do if item.text == text then found = true end end return found end function MockBuffer:new(o) self.icon = o.icon or "" self.__index = self setmetatable(o, self) o.type = "buffer" return o end function MockBuffer:is_end() return vim.F.if_nil(self.is_end, false) end function MockBuffer:current() return vim.F.if_nil(self._is_current, true) end function MockBuffer:as_element() return self end function MockBuffer:visibility() return vim.F.if_nil(self._visiblity, 0) end function MockBuffer:visible() return vim.F.if_nil(self._is_visible, true) end ---@param name string ---@param state bufferline.State ---@return bufferline.Buffer? function M.find_buffer(name, state) for _, component in ipairs(state.components) do local element = component:as_element() --[[@as bufferline.Buffer]] if element and fn.matchstr(element.name, name) ~= "" then return element end end end M.MockBuffer = MockBuffer return M akinsho-bufferline.nvim-655133c/tests/utils_spec.lua000066400000000000000000000036551474150523700225500ustar00rootroot00000000000000local api = vim.api local utils = require("bufferline.utils") local constants = require("bufferline.constants") describe("Utils tests", function() it("should correctly truncate a long name", function() local truncated = utils.truncate_name("/user/test/long/file/folder/name/extension", 10) assert.is_true(api.nvim_strwidth(truncated) <= 10) end) it("should prefer dropping a filename extension in order to meet word limit", function() local truncated = utils.truncate_name("filename.md", 10) assert.is_equal(truncated, "filename" .. constants.ELLIPSIS) end) it("should prefer dropping a SINGLE filename extension in order to meet word limit", function() local truncated = utils.truncate_name("filename.md.md", 13) assert.is_equal(truncated, "filename.md" .. constants.ELLIPSIS) end) it("should save/restore positions correctly", function() -- remove existing buffers vim.cmd("silent %bwipeout!") local names = { "c.txt", "a.txt", "d.txt", "e.txt", "b.txt" } local bufs = {} for _, name in ipairs(names) do vim.cmd.edit(name) bufs[name] = api.nvim_get_current_buf() end local ids = { bufs["a.txt"], bufs["b.txt"], bufs["c.txt"], bufs["d.txt"], bufs["e.txt"], } utils.save_positions(ids) assert.same(utils.restore_positions(), ids) -- restore_positions should not return invalid bufids vim.cmd("bwipeout! " .. bufs["c.txt"]) ids = { bufs["a.txt"], bufs["b.txt"], bufs["d.txt"], bufs["e.txt"], } assert.same(utils.restore_positions(), ids) vim.g[constants.positions_key] = '["INVALID_PATH"]' assert.same(utils.restore_positions(), {}) -- empty or invalid JSON should return nil vim.g[constants.positions_key] = "[]" assert.is_equal(utils.restore_positions(), nil) vim.g[constants.positions_key] = "" assert.is_equal(utils.restore_positions(), nil) end) end)