├── .github └── workflows │ └── build.yml ├── .luacheckrc ├── LICENSE ├── README.md ├── autoload └── scrollview.vim ├── doc ├── scrollview.txt └── tags ├── lua ├── scrollview.lua └── scrollview │ ├── contrib │ ├── README │ ├── coc.lua │ └── gitsigns.lua │ ├── signs │ ├── changelist.lua │ ├── conflicts.lua │ ├── cursor.lua │ ├── diagnostics.lua │ ├── folds.lua │ ├── indent.lua │ ├── keywords.lua │ ├── latestchange.lua │ ├── loclist.lua │ ├── marks.lua │ ├── quickfix.lua │ ├── search.lua │ ├── spell.lua │ ├── textwidth.lua │ └── trail.lua │ └── utils.lua ├── plugin └── scrollview.vim └── tests ├── run.py ├── test_linewise_simple_consistency.vim └── test_linewise_spanwise_consistency.vim /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | # When the 'permissions' key is specified, unspecified permission scopes (e.g., 3 | # actions, checks, etc.) are set to no access (none). 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: [main] 11 | schedule: 12 | # Run weekly (* is a special character in YAML, so quote the string) 13 | - cron: '0 0 * * 0' 14 | workflow_dispatch: 15 | inputs: 16 | # When git-ref is empty, HEAD will be checked out. 17 | git-ref: 18 | description: Optional git ref (branch, tag, or full SHA) 19 | required: false 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | with: 29 | # When the ref is empty, HEAD will be checked out. 30 | ref: ${{ github.event.inputs.git-ref }} 31 | 32 | - name: Dependencies 33 | run: | 34 | sudo apt-get update 35 | sudo apt-get install lua-check neovim 36 | 37 | - name: Check Help Tags 38 | run: | 39 | # Check if the help tags file should be updated 40 | nvim -c 'helptags doc/' -c quit 41 | test -z "$(git status --porcelain doc/)" 42 | 43 | - name: Luacheck 44 | run: luacheck . 45 | 46 | - name: Tests 47 | run: | 48 | mkdir -p ~/.local/share/nvim/site/pack/plugins/start/ 49 | ln -s "$PWD" ~/.local/share/nvim/site/pack/plugins/start/ 50 | tests/run.py 51 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | read_globals = { 2 | vim = { 3 | other_fields = true, 4 | fields = { 5 | g = { 6 | read_only = false, 7 | other_fields = true 8 | } 9 | } 10 | } 11 | } 12 | include_files = {'lua/', '*.lua'} 13 | exclude_files = {'lua/scrollview/contrib/README'} 14 | std = 'luajit' 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel Steinberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build][badge_thumbnail]][badge_link] 2 | 3 | # nvim-scrollview 4 | 5 | `nvim-scrollview` is a Neovim plugin that displays interactive vertical 6 | scrollbars and signs. The plugin is customizable (see `:help 7 | scrollview-configuration`). 8 | 9 | 10 | 11 | (a scrollbar and signs can be seen near the right edge of the preceding image) 12 | 13 | ## Features 14 | 15 | * Handling for folds 16 | * Signs (e.g., `diagnostics` and `search` enabled by default, and `marks` too 17 | for `nvim>=0.10`) 18 | * Scrollbars can be dragged with the mouse, and signs can be clicked for 19 | navigation or right-clicked for information 20 | 21 | ## Requirements 22 | 23 | * `nvim>=0.6` 24 | * Mouse functionality requires mouse support (see `:help 'mouse'`) 25 | * Signs require `nvim>=0.9` 26 | 27 | ## Installation 28 | 29 | A package manager can be used to install `nvim-scrollview`. 30 |
Examples
31 | 32 | * [Vim8 packages][vim8pack]: 33 | - `git clone https://github.com/dstein64/nvim-scrollview ~/.local/share/nvim/site/pack/plugins/start/nvim-scrollview` 34 | * [Vundle][vundle]: 35 | - Add `Plugin 'dstein64/nvim-scrollview'` to `~/.config/nvim/init.vim` 36 | - `:PluginInstall` or `$ nvim +PluginInstall +qall` 37 | * [Pathogen][pathogen]: 38 | - `git clone --depth=1 https://github.com/dstein64/nvim-scrollview ~/.local/share/nvim/site/bundle/nvim-scrollview` 39 | * [vim-plug][vimplug]: 40 | - Add `Plug 'dstein64/nvim-scrollview', { 'branch': 'main' }` to `~/.config/nvim/init.vim` 41 | - `:PlugInstall` or `$ nvim +PlugInstall +qall` 42 | * [dein.vim][dein]: 43 | - Add `call dein#add('dstein64/nvim-scrollview')` to `~/.config/nvim/init.vim` 44 | - `:call dein#install()` 45 | * [NeoBundle][neobundle]: 46 | - Add `NeoBundle 'dstein64/nvim-scrollview'` to `~/.config/nvim/init.vim` 47 | - Re-open Neovim or execute `:source ~/.config/nvim/init.vim` 48 | * [packer.nvim][packer]: 49 | - Add `use 'dstein64/nvim-scrollview'` to the packer startup function 50 | - `:PackerInstall` 51 | 52 |
53 | 54 | ## Usage 55 | 56 | * `nvim-scrollview` works automatically, displaying interactive scrollbars. 57 | * `:ScrollViewDisable` disables the plugin. When arguments are given, 58 | the specified sign groups are disabled. 59 | * `:ScrollViewEnable` enables the plugin. This is only necessary if 60 | nvim-scrollview has previously been disabled. When arguments are given, 61 | the specified sign groups are enabled. 62 | * `:ScrollViewToggle` toggles the plugin. When arguments are given, the 63 | specified sign groups are toggled. 64 | * `:ScrollViewRefresh` refreshes the scrollbars and signs. This is relevant 65 | when the scrollbars or signs are out-of-sync, which can occur as a result of 66 | some window arrangement actions. 67 | * `:ScrollViewNext`, `:ScrollViewPrev`, `:ScrollViewFirst`, and 68 | `:ScrollViewLast` move the cursor to lines with signs. Arguments can specify 69 | which sign groups are considered. 70 | * `:ScrollViewLegend` shows a legend for the plugin. This can be helpful if 71 | you're unsure what a sign represents. With the `!` variant of the command, 72 | `:ScrollViewLegend!`, the legend will include the scrollbar and all 73 | registered signs (even those from disabled groups), regardless of their 74 | display status. 75 | * The scrollbars are draggable with a mouse. Signs can be clicked for 76 | navigation or right-clicked for information. If `mousemoveevent` is set, 77 | scrollbars and signs are highlighted when the mouse pointer hovers. 78 | 79 | ## Signs 80 | 81 | There is built-in support for various types of signs (referred to as "sign 82 | groups"), listed below. The functionality is similar to the sign column, but 83 | with the same positioning logic as the scrollbar. 84 | 85 | * `changelist`: change list items (previous, current, and next) 86 | * `conflicts`: git merge conflicts 87 | * `cursor`: cursor position 88 | * `diagnostics`: errors, warnings, info, and hints 89 | * `folds`: closed folds 90 | * `indent`: unexpected indentation characters (e.g., tabs when `expandtab` is 91 | set) 92 | * `keywords`: FIX, FIXME, HACK, TODO, WARN, WARNING, and XXX (see `:help 93 | scrollview-signs-keywords` for customization info) 94 | * `latestchange`: latest change 95 | * `loclist`: items on the location list 96 | * `marks` 97 | * `quickfix`: items on the quickfix list 98 | * `search` 99 | * `spell`: spell check items when the `spell` option is enabled 100 | * `textwidth`: line lengths exceeding the value of the `textwidth` option, when 101 | non-zero 102 | * `trail`: trailing whitespace 103 | 104 | `search` and `diagnostics` groups are enabled by default (`marks` too for 105 | `nvim>=0.10`). To modify which sign groups are enabled, set 106 | `scrollview_signs_on_startup` accordingly in your Neovim configuation (see 107 | `:help scrollview_signs_on_startup`), or use `:ScrollViewEnable {group1} 108 | {group2} ...` to enable sign groups in the current Neovim session. 109 | 110 | Clicking on a sign will navigate to its associated line. If a sign is linked to 111 | multiple lines, successive clicks will cycle through these lines. Right-clicking 112 | a sign reveals additional information, including its sign group and the 113 | corresponding lines, which can be selected for navigation. Identifying the sign 114 | group can be helpful if you are unsure what a sign represents. 115 | 116 | The plugin was written so that it's possible to extend the sign functionality 117 | in a Neovim configuration file or with a plugin. See the documentation for 118 | details. 119 | 120 | The [contrib](lua/scrollview/contrib) directory contains sign group 121 | implementations that are not built-in (e.g., `coc`, `gitsigns`), but may be 122 | useful to some users. The code there does not receive the same level of support 123 | as the main source code, and may be less stable. Use at your own risk. For 124 | installation instructions and other documentation, see the source code files. 125 | 126 | ## Configuration 127 | 128 | There are various settings that can be configured. Please see the documentation 129 | for details. The code below only shows a few of the possible settings. 130 | 131 | #### Vimscript Example 132 | 133 | ```vim 134 | let g:scrollview_excluded_filetypes = ['nerdtree'] 135 | let g:scrollview_current_only = v:true 136 | " Position the scrollbar at the 80th character of the buffer 137 | let g:scrollview_base = 'buffer' 138 | let g:scrollview_column = 80 139 | " Enable all sign groups (defaults to ['diagnostics', 'search']). 140 | " Set to the empty list to disable all sign groups. 141 | let g:scrollview_signs_on_startup = ['all'] 142 | " Show diagnostic signs only for errors. 143 | let g:scrollview_diagnostics_severities = 144 | \ [luaeval('vim.diagnostic.severity.ERROR')] 145 | ``` 146 | 147 | #### Lua Example 148 | 149 | A Lua `setup()` function is provided for convenience, to set globally scoped 150 | options (the 'scrollview_' prefix is omitted). 151 | 152 | ```lua 153 | require('scrollview').setup({ 154 | excluded_filetypes = {'nerdtree'}, 155 | current_only = true, 156 | base = 'buffer', 157 | column = 80, 158 | signs_on_startup = {'all'}, 159 | diagnostics_severities = {vim.diagnostic.severity.ERROR} 160 | }) 161 | ``` 162 | 163 | Alternatively, configuration variables can be set without calling `setup()`. 164 | 165 | ```lua 166 | vim.g.scrollview_excluded_filetypes = {'nerdtree'}, 167 | vim.g.scrollview_current_only = true, 168 | vim.g.scrollview_base = 'buffer', 169 | vim.g.scrollview_column = 80, 170 | vim.g.scrollview_signs_on_startup = {'all'}, 171 | vim.g.scrollview_diagnostics_severities = {vim.diagnostic.severity.ERROR} 172 | ``` 173 | ## Documentation 174 | 175 | Documentation can be accessed with: 176 | 177 | ```nvim 178 | :help nvim-scrollview 179 | ``` 180 | 181 | The underlying markup is in [scrollview.txt](doc/scrollview.txt). 182 | 183 | #### Issues 184 | 185 | Documentation for issues, along with some workarounds, can be accessed with: 186 | 187 | ```nvim 188 | :help scrollview-issues 189 | ``` 190 | 191 | Some of the known issues are regarding scrollbar synchronization, error messages, session 192 | restoration, and scrollbar floating windows being included in the window count returned by 193 | `winnr('$')`. 194 | 195 | ## License 196 | 197 | The source code has an [MIT License](https://en.wikipedia.org/wiki/MIT_License). 198 | 199 | See [LICENSE](LICENSE). 200 | 201 | [badge_link]: https://github.com/dstein64/nvim-scrollview/actions/workflows/build.yml 202 | [badge_thumbnail]: https://github.com/dstein64/nvim-scrollview/actions/workflows/build.yml/badge.svg 203 | [dein]: https://github.com/Shougo/dein.vim 204 | [gitsigns.nvim]: https://github.com/lewis6991/gitsigns.nvim 205 | [gitsigns_example]: https://gist.github.com/dstein64/b5d9431ebeacae1fb963efc3f2c94cf4 206 | [neobundle]: https://github.com/Shougo/neobundle.vim 207 | [packer]: https://github.com/wbthomason/packer.nvim 208 | [pathogen]: https://github.com/tpope/vim-pathogen 209 | [vim8pack]: http://vimhelp.appspot.com/repeat.txt.html#packages 210 | [vimplug]: https://github.com/junegunn/vim-plug 211 | [vundle]: https://github.com/gmarik/vundle 212 | -------------------------------------------------------------------------------- /autoload/scrollview.vim: -------------------------------------------------------------------------------- 1 | " ************************************************* 2 | " * Utils 3 | " ************************************************* 4 | 5 | " Converts 1 and 0 to v:true and v:false. 6 | function! s:ToBool(x) abort 7 | if a:x 8 | return v:true 9 | else 10 | return v:false 11 | endif 12 | endfunction 13 | 14 | " ************************************************* 15 | " * User Configuration 16 | " ************************************************* 17 | 18 | " === General === 19 | 20 | let g:scrollview_auto_mouse = get(g:, 'scrollview_auto_mouse', v:true) 21 | let g:scrollview_base = get(g:, 'scrollview_base', 'right') 22 | " The plugin enters a restricted state when the number of buffer bytes exceeds 23 | " the limit. Use -1 for no limit. 24 | let g:scrollview_byte_limit = get(g:, 'scrollview_byte_limit', 1000000) 25 | let g:scrollview_character = get(g:, 'scrollview_character', '') 26 | let g:scrollview_column = get(g:, 'scrollview_column', 1) 27 | let g:scrollview_consider_border = 28 | \ get(g:, 'scrollview_consider_border', v:false) 29 | let g:scrollview_current_only = get(g:, 'scrollview_current_only', v:false) 30 | let g:scrollview_excluded_filetypes = 31 | \ get(g:, 'scrollview_excluded_filetypes', []) 32 | let g:scrollview_excluded_info_signs = 33 | \ get(g:, 'scrollview_excluded_info_signs', 34 | \ ['changelist', 'cursor', 'latestchange']) 35 | let g:scrollview_floating_windows = 36 | \ get(g:, 'scrollview_floating_windows', v:false) 37 | let g:scrollview_signs_hidden_for_insert = 38 | \ get(g:, 'scrollview_signs_hidden_for_insert', []) 39 | let g:scrollview_hide_bar_for_insert = 40 | \ get(g:, 'scrollview_hide_bar_for_insert', v:false) 41 | let g:scrollview_hide_on_cursor_intersect = 42 | \ get(g:, 'scrollview_hide_on_cursor_intersect', v:false) 43 | " Use the old option, scrollview_hide_on_intersect, if it's set. 44 | if has_key(g:, 'scrollview_hide_on_intersect') 45 | \ && !has_key(g:, 'scrollview_hide_on_float_intersect') 46 | let g:scrollview_hide_on_float_intersect = g:scrollview_hide_on_intersect 47 | endif 48 | let g:scrollview_hide_on_float_intersect = 49 | \ get(g:, 'scrollview_hide_on_float_intersect', v:false) 50 | let g:scrollview_hide_on_text_intersect = 51 | \ get(g:, 'scrollview_hide_on_text_intersect', v:false) 52 | let g:scrollview_hover = get(g:, 'scrollview_hover', v:true) 53 | let g:scrollview_include_end_region = 54 | \ get(g:, 'scrollview_include_end_region', v:false) 55 | " The plugin enters a restricted state when the number of buffer lines exceeds 56 | " the limit. Use -1 for no limit. 57 | let g:scrollview_line_limit = get(g:, 'scrollview_line_limit', 20000) 58 | let g:scrollview_mode = get(g:, 'scrollview_mode', 'auto') 59 | " If the old option, scrollview_auto_mouse, is set to false, disable the mouse 60 | " functionality. 61 | if !get(g:, 'scrollview_auto_mouse', v:true) 62 | if !has_key(g:, 'scrollview_mouse_primary') 63 | let g:scrollview_mouse_primary = v:null 64 | endif 65 | if !has_key(g:, 'scrollview_mouse_secondary') 66 | let g:scrollview_mouse_secondary = v:null 67 | endif 68 | endif 69 | let g:scrollview_mouse_primary = get(g:, 'scrollview_mouse_primary', 'left') 70 | let g:scrollview_mouse_secondary = 71 | \ get(g:, 'scrollview_mouse_secondary', 'right') 72 | let g:scrollview_on_startup = get(g:, 'scrollview_on_startup', v:true) 73 | " Use the old option, scrollview_always_show, if it's set. 74 | if has_key(g:, 'scrollview_always_show') 75 | \ && !has_key(g:, 'scrollview_visibility') 76 | let g:scrollview_visibility = g:scrollview_always_show ? 'always' : 'overflow' 77 | endif 78 | let g:scrollview_visibility = get(g:, 'scrollview_visibility', 'overflow') 79 | let g:scrollview_winblend = get(g:, 'scrollview_winblend', 50) 80 | let g:scrollview_winblend_gui = get(g:, 'scrollview_winblend_gui', 0) 81 | " The default zindex for floating windows is 50. A smaller value is used here 82 | " by default so that scrollbars don't cover floating windows. 83 | let g:scrollview_zindex = get(g:, 'scrollview_zindex', 40) 84 | 85 | " === Signs === 86 | 87 | " Internal list of all built-in sign groups, populated automatically. 88 | let s:available_signs = readdir(expand(':p:h') .. '/../lua/scrollview/signs') 89 | let s:available_signs = filter(s:available_signs, 'v:val =~# "\\.lua$"') 90 | call map(s:available_signs, {_, val -> fnamemodify(val, ':r')}) 91 | " Internal list of sign groups that are enabled on startup by default. 92 | let s:default_signs = ['diagnostics', 'search'] 93 | " Enable mark signs by default, but only with nvim>=0.10, since :delmarks 94 | " doesn't persist on earlier versions (Neovim #4288, #4925, #24963). 95 | if has('nvim-0.10') 96 | call add(s:default_signs, 'marks') 97 | endif 98 | 99 | " *** General sign settings *** 100 | " The maximum number of signs shown per row. Set to -1 to have no limit. 101 | " Set to 0 to disable signs. 102 | let g:scrollview_signs_max_per_row = 103 | \ get(g:, 'scrollview_signs_max_per_row', -1) 104 | " Sign groups to enable on startup. If 'all' is included, it effectively 105 | " expands to all built-in plugins. If 'defaults' is included, it effectively 106 | " expands to built-in plugins that would ordinarily be enabled by default. 107 | let g:scrollview_signs_on_startup = 108 | \ get(g:, 'scrollview_signs_on_startup', s:default_signs) 109 | " Specifies the sign overflow direction ('left' or 'right'). 110 | let g:scrollview_signs_overflow = get(g:, 'scrollview_signs_overflow', 'left') 111 | " Whether signs in folds should be shown or hidden. 112 | let g:scrollview_signs_show_in_folds = 113 | \ get(g:, 'scrollview_signs_show_in_folds', v:false) 114 | 115 | " *** Change list signs *** 116 | let g:scrollview_changelist_previous_priority = 117 | \ get(g:, 'scrollview_changelist_previous_priority', 15) 118 | let g:scrollview_changelist_previous_symbol = 119 | \ get(g:, 'scrollview_changelist_previous_symbol', nr2char(0x21b0)) 120 | let g:scrollview_changelist_current_priority = 121 | \ get(g:, 'scrollview_changelist_current_priority', 10) 122 | let g:scrollview_changelist_current_symbol = 123 | \ get(g:, 'scrollview_changelist_current_symbol', '@') 124 | let g:scrollview_changelist_next_priority = 125 | \ get(g:, 'scrollview_changelist_next_priority', 5) 126 | let g:scrollview_changelist_next_symbol = 127 | \ get(g:, 'scrollview_changelist_next_symbol', nr2char(0x21b3)) 128 | 129 | " *** Conflict signs *** 130 | let g:scrollview_conflicts_bottom_priority = 131 | \ get(g:, 'scrollview_conflicts_bottom_priority', 80) 132 | let g:scrollview_conflicts_bottom_symbol = 133 | \ get(g:, 'scrollview_conflicts_bottom_symbol', '>') 134 | let g:scrollview_conflicts_middle_priority = 135 | \ get(g:, 'scrollview_conflicts_middle_priority', 75) 136 | let g:scrollview_conflicts_middle_symbol = 137 | \ get(g:, 'scrollview_conflicts_middle_symbol', '=') 138 | let g:scrollview_conflicts_top_priority = 139 | \ get(g:, 'scrollview_conflicts_top_priority', 70) 140 | let g:scrollview_conflicts_top_symbol = 141 | \ get(g:, 'scrollview_conflicts_top_symbol', '<') 142 | 143 | " *** Cursor signs *** 144 | let g:scrollview_cursor_priority = get(g:, 'scrollview_cursor_priority', 0) 145 | " Use a small square, resembling a block cursor, for the default symbol. 146 | let g:scrollview_cursor_symbol = 147 | \ get(g:, 'scrollview_cursor_symbol', nr2char(0x25aa)) 148 | 149 | " *** Diagnostics signs *** 150 | let g:scrollview_diagnostics_error_priority = 151 | \ get(g:, 'scrollview_diagnostics_error_priority', 60) 152 | let g:scrollview_diagnostics_hint_priority = 153 | \ get(g:, 'scrollview_diagnostics_hint_priority', 30) 154 | let g:scrollview_diagnostics_info_priority = 155 | \ get(g:, 'scrollview_diagnostics_info_priority', 40) 156 | if !has_key(g:, 'scrollview_diagnostics_severities') 157 | let g:scrollview_diagnostics_severities = [ 158 | \ luaeval('vim.diagnostic.severity.ERROR'), 159 | \ luaeval('vim.diagnostic.severity.HINT'), 160 | \ luaeval('vim.diagnostic.severity.INFO'), 161 | \ luaeval('vim.diagnostic.severity.WARN'), 162 | \ ] 163 | endif 164 | let g:scrollview_diagnostics_warn_priority = 165 | \ get(g:, 'scrollview_diagnostics_warn_priority', 50) 166 | " Set the diagnostic symbol to the corresponding Neovim sign text if defined, 167 | " or the default otherwise. 168 | let s:diagnostics_symbol_data = [ 169 | \ [ 170 | \ 'scrollview_diagnostics_error_symbol', 171 | \ 'E', 172 | \ 'DiagnosticSignError', 173 | \ luaeval('vim.diagnostic.severity.ERROR'), 174 | \ 'ERROR', 175 | \ ], 176 | \ [ 177 | \ 'scrollview_diagnostics_hint_symbol', 178 | \ 'H', 179 | \ 'DiagnosticSignHint', 180 | \ luaeval('vim.diagnostic.severity.HINT'), 181 | \ 'HINT', 182 | \ ], 183 | \ [ 184 | \ 'scrollview_diagnostics_info_symbol', 185 | \ 'I', 186 | \ 'DiagnosticSignInfo', 187 | \ luaeval('vim.diagnostic.severity.INFO'), 188 | \ 'INFO', 189 | \ ], 190 | \ [ 191 | \ 'scrollview_diagnostics_warn_symbol', 192 | \ 'W', 193 | \ 'DiagnosticSignWarn', 194 | \ luaeval('vim.diagnostic.severity.WARN'), 195 | \ 'WARN', 196 | \ ], 197 | \ ] 198 | for [s:key, s:fallback, s:sign, s:severity, s:name] in s:diagnostics_symbol_data 199 | if !has_key(g:, s:key) 200 | try 201 | if has('nvim-0.10') 202 | " The key for configuring text can be a severity code (e.g., 203 | " vim.diagnostic.severity.ERROR) or a severity name (e.g., 'ERROR'). 204 | " Code and name keys can both be used in the same table, so we can't 205 | " use luaeval() directly on the table ("E5100: Cannot convert given 206 | " lua table: table should either have a sequence of positive integer 207 | " keys or contain only string key"). When the same type of diagnostic 208 | " has both a code and name key, the code key takes precedence. 209 | " https://github.com/neovim/neovim/pull/26193#issue-2009346914 210 | " WARN: Neovim diagnostic signs can be configured with a function 211 | " (that takes namespace and bufnr). That's not supported here. 212 | " The value can also be a boolean. 213 | if luaeval('type(vim.diagnostic.config().signs)') ==# 'table' 214 | \ && luaeval('vim.diagnostic.config().signs.text') isnot# v:null 215 | let g:[s:key] = 216 | \ luaeval('vim.diagnostic.config().signs.text[_A]', s:severity) 217 | if g:[s:key] is# v:null 218 | let g:[s:key] = 219 | \ luaeval('vim.diagnostic.config().signs.text[_A]', s:name) 220 | endif 221 | endif 222 | if g:[s:key] is# v:null 223 | let g:[s:key] = s:fallback 224 | endif 225 | else 226 | let g:[s:key] = trim(sign_getdefined(s:sign)[0].text) 227 | endif 228 | catch 229 | let g:[s:key] = s:fallback 230 | endtry 231 | endif 232 | endfor 233 | 234 | " *** Fold signs *** 235 | let g:scrollview_folds_priority = get(g:, 'scrollview_folds_priority', 30) 236 | " Default symbol: a right pointing triangle, similar to what's shown in the 237 | " browser for a hidden
/. 238 | let g:scrollview_folds_symbol = 239 | \ get(g:, 'scrollview_folds_symbol', nr2char(0x25b6)) 240 | 241 | " *** Indent signs *** 242 | let g:scrollview_indent_spaces_condition = 243 | \ get(g:, 'scrollview_indent_spaces_condition', 'noexpandtab') 244 | let g:scrollview_indent_spaces_priority = 245 | \ get(g:, 'scrollview_indent_spaces_priority', 25) 246 | let g:scrollview_indent_spaces_symbol = 247 | \ get(g:, 'scrollview_indent_spaces_symbol', '-') 248 | let g:scrollview_indent_tabs_condition = 249 | \ get(g:, 'scrollview_indent_tabs_condition', 'expandtab') 250 | let g:scrollview_indent_tabs_priority = 251 | \ get(g:, 'scrollview_indent_tabs_priority', 25) 252 | let g:scrollview_indent_tabs_symbol = 253 | \ get(g:, 'scrollview_indent_tabs_symbol', '>') 254 | 255 | " *** Keyword signs *** 256 | let g:scrollview_keywords_fix_priority = 257 | \ get(g:, 'scrollview_keywords_fix_priority', 20) 258 | let g:scrollview_keywords_fix_scope = 259 | \ get(g:, 'scrollview_keywords_fix_scope', 'auto') 260 | let g:scrollview_keywords_fix_symbol = 261 | \ get(g:, 'scrollview_keywords_fix_symbol', 'F') 262 | let g:scrollview_keywords_hack_priority = 263 | \ get(g:, 'scrollview_keywords_hack_priority', 20) 264 | let g:scrollview_keywords_hack_scope = 265 | \ get(g:, 'scrollview_keywords_hack_scope', 'auto') 266 | let g:scrollview_keywords_hack_symbol = 267 | \ get(g:, 'scrollview_keywords_hack_symbol', 'H') 268 | let g:scrollview_keywords_todo_priority = 269 | \ get(g:, 'scrollview_keywords_todo_priority', 20) 270 | let g:scrollview_keywords_todo_scope = 271 | \ get(g:, 'scrollview_keywords_todo_scope', 'auto') 272 | let g:scrollview_keywords_todo_symbol = 273 | \ get(g:, 'scrollview_keywords_todo_symbol', 'T') 274 | let g:scrollview_keywords_warn_priority = 275 | \ get(g:, 'scrollview_keywords_warn_priority', 20) 276 | let g:scrollview_keywords_warn_scope = 277 | \ get(g:, 'scrollview_keywords_warn_scope', 'auto') 278 | let g:scrollview_keywords_warn_symbol = 279 | \ get(g:, 'scrollview_keywords_warn_symbol', 'W') 280 | let g:scrollview_keywords_xxx_priority = 281 | \ get(g:, 'scrollview_keywords_xxx_priority', 20) 282 | let g:scrollview_keywords_xxx_scope = 283 | \ get(g:, 'scrollview_keywords_xxx_scope', 'auto') 284 | let g:scrollview_keywords_xxx_symbol = 285 | \ get(g:, 'scrollview_keywords_xxx_symbol', 'X') 286 | 287 | let s:scrollview_keywords_fix_patterns = 288 | \ ['%f[%w_]FIX%f[^%w_]', '%f[%w_]FIXME%f[^%w_]'] 289 | let s:scrollview_keywords_hack_patterns = ['%f[%w_]HACK%f[^%w_]'] 290 | let s:scrollview_keywords_todo_patterns = ['%f[%w_]TODO%f[^%w_]'] 291 | let s:scrollview_keywords_warn_patterns = 292 | \ ['%f[%w_]WARN%f[^%w_]', '%f[%w_]WARNING%f[^%w_]'] 293 | let s:scrollview_keywords_xxx_patterns = ['%f[%w_]XXX%f[^%w_]'] 294 | 295 | let s:default_built_ins = ['fix', 'hack', 'todo', 'warn', 'xxx'] 296 | let g:scrollview_keywords_built_ins = 297 | \ get(g:, 'scrollview_keywords_built_ins', s:default_built_ins) 298 | 299 | for s:built_in in g:scrollview_keywords_built_ins 300 | let s:capitalized= substitute(s:built_in, '\v^.', '\u&', '') 301 | let s:spec = { 302 | \ 'highlight': 'ScrollViewKeywords' .. s:capitalized, 303 | \ 'patterns': s:['scrollview_keywords_' .. s:built_in .. '_patterns'], 304 | \ 'priority': g:['scrollview_keywords_' .. s:built_in .. '_priority'], 305 | \ 'symbol': g:['scrollview_keywords_' .. s:built_in .. '_symbol'], 306 | \ 'scope': g:['scrollview_keywords_' .. s:built_in .. '_scope'], 307 | \ } 308 | let s:key = 'scrollview_keywords_' .. s:built_in .. '_spec' 309 | let g:[s:key] = get(g:, s:key, s:spec) 310 | endfor 311 | 312 | " *** Latest change signs *** 313 | let g:scrollview_latestchange_priority = 314 | \ get(g:, 'scrollview_latestchange_priority', 10) 315 | " Default symbol: the Greek uppercase letter delta, which denotes change. 316 | let g:scrollview_latestchange_symbol = 317 | \ get(g:, 'scrollview_latestchange_symbol', nr2char(0x0394)) 318 | 319 | " *** Location list signs *** 320 | let g:scrollview_loclist_priority = get(g:, 'scrollview_loclist_priority', 45) 321 | " Default symbol: a small circle 322 | let g:scrollview_loclist_symbol = 323 | \ get(g:, 'scrollview_loclist_symbol', nr2char(0x2022)) 324 | 325 | " *** Mark signs *** 326 | " Characters for which mark signs will be shown. 327 | if !has_key(g:, 'scrollview_marks_characters') 328 | " Default to a-z ("lowercase marks, valid within one file") and A-Z 329 | " ("uppercase marks, also called file marks, valid between files"). 330 | " Don't include numbered marks. These are set automatically ("They 331 | " are only present when using a shada file"). 332 | let g:scrollview_marks_characters = [] 333 | let s:codes = range(char2nr('a'), char2nr('z')) 334 | call extend(s:codes, range(char2nr('A'), char2nr('Z'))) 335 | for s:code in s:codes 336 | call add(g:scrollview_marks_characters, nr2char(s:code)) 337 | endfor 338 | endif 339 | let g:scrollview_marks_priority = get(g:, 'scrollview_marks_priority', 50) 340 | 341 | " *** Quickfix signs *** 342 | let g:scrollview_quickfix_priority = get(g:, 'scrollview_quickfix_priority', 45) 343 | " Default symbol: a small circle 344 | let g:scrollview_quickfix_symbol = 345 | \ get(g:, 'scrollview_quickfix_symbol', nr2char(0x2022)) 346 | 347 | " *** Search signs *** 348 | let g:scrollview_search_priority = get(g:, 'scrollview_search_priority', 70) 349 | " Default symbols: (1,2) equals, (>=3) triple bar 350 | let g:scrollview_search_symbol = 351 | \ get(g:, 'scrollview_search_symbol', ['=', '=', nr2char(0x2261)]) 352 | 353 | " *** Spell signs *** 354 | let g:scrollview_spell_priority = get(g:, 'scrollview_spell_priority', 20) 355 | let g:scrollview_spell_symbol = get(g:, 'scrollview_spell_symbol', '~') 356 | 357 | " *** Textwidth signs *** 358 | let g:scrollview_textwidth_priority = 359 | \ get(g:, 'scrollview_textwidth_priority', 20) 360 | " Default symbol: two adjacent small '>' symbols. 361 | let g:scrollview_textwidth_symbol = 362 | \ get(g:, 'scrollview_textwidth_symbol', nr2char(0xbb)) 363 | 364 | " *** Trail signs *** 365 | let g:scrollview_trail_priority = get(g:, 'scrollview_trail_priority', 50) 366 | " Default symbol: an outlined square 367 | let g:scrollview_trail_symbol = 368 | \ get(g:, 'scrollview_trail_symbol', nr2char(0x25a1)) 369 | 370 | " ************************************************* 371 | " * Global State 372 | " ************************************************* 373 | 374 | " External global state is specified here. 375 | " Internal global state is primarily represented with local variables in 376 | " lua/scrollview.lua, but specified here when more convenient. 377 | 378 | let g:scrollview_enabled = v:false 379 | 380 | " A flag that gets set to true while scrollbars are being refreshed. #88 381 | let g:scrollview_refreshing = v:false 382 | 383 | " Tracks buffer line count in insert mode, so scrollbars can be refreshed when 384 | " the line count changes. 385 | let g:scrollview_ins_mode_buf_lines = 0 386 | 387 | " A string for the echo() function, to avoid having to handle character 388 | " escaping. 389 | let g:scrollview_echo_string = v:null 390 | 391 | " Keep track of the initial mouse settings. These are only used for nvim<0.11. 392 | let g:scrollview_init_mouse_primary = g:scrollview_mouse_primary 393 | let g:scrollview_init_mouse_secondary = g:scrollview_mouse_secondary 394 | 395 | " Stores the sign group that should be disabled (from right-clicking a sign, 396 | " clicking the group name, then selecting 'disable'). 397 | let g:scrollview_disable_sign_group = v:null 398 | 399 | " ************************************************* 400 | " * Versioning 401 | " ************************************************* 402 | 403 | " An integer to be incremented when the interface for using signs changes. 404 | " For example, this would correspond to the register_sign_spec function 405 | " interface and the format for saving sign information in buffers. 406 | let g:scrollview_signs_version = 2 407 | 408 | " ************************************************* 409 | " * Commands 410 | " ************************************************* 411 | 412 | " Returns a list of groups for command completion. A 'custom' function is used 413 | " instead of a 'customlist' function, for the automatic filtering that is 414 | " conducted for the former, but not the latter. 415 | " XXX: This currently returns the full list of groups, including entries that 416 | " may not be relevant for the current command (for example, a disabled group 417 | " would not be relevant for :ScrollViewFirst). 418 | function! s:Complete(...) abort 419 | let l:groups = luaeval('require("scrollview").get_sign_groups()') 420 | call sort(l:groups) 421 | return join(l:groups, "\n") 422 | endfunction 423 | 424 | " CompleteWithAll is similar to Complete, but also includes 'all'. 425 | function! s:CompleteWithAll(...) abort 426 | let l:groups = luaeval('require("scrollview").get_sign_groups()') 427 | call add(l:groups, 'all') 428 | call sort(l:groups) 429 | return join(l:groups, "\n") 430 | endfunction 431 | 432 | " A helper for :ScrollViewEnable, :ScrollViewDisable, and :ScrollViewToggle to 433 | " call the underlying functions. Set state to v:true to enable, v:false to 434 | " disable, and v:null to toggle. Additional arguments specify sign groups. 435 | function! s:DispatchStateCommand(state, ...) abort 436 | let s:module = luaeval('require("scrollview")') 437 | if empty(a:000) 438 | " The command had no arguments, so is for the plugin. 439 | call s:module.set_state(a:state) 440 | else 441 | " The command had arguments, so is for signs. 442 | let l:groups = [] 443 | for l:group in a:000 444 | if l:group ==# 'all' 445 | call extend(l:groups, luaeval('require("scrollview").get_sign_groups()')) 446 | else 447 | call add(l:groups, l:group) 448 | endif 449 | endfor 450 | for l:group in l:groups 451 | call s:module.set_sign_group_state(l:group, a:state) 452 | endfor 453 | endif 454 | endfunction 455 | 456 | if !exists(':ScrollViewDisable') 457 | command -bar -nargs=* -complete=custom,s:CompleteWithAll ScrollViewDisable 458 | \ call s:DispatchStateCommand(v:false, ) 459 | endif 460 | 461 | if !exists(':ScrollViewEnable') 462 | command -bar -nargs=* -complete=custom,s:CompleteWithAll ScrollViewEnable 463 | \ call s:DispatchStateCommand(v:true, ) 464 | endif 465 | 466 | if !exists(':ScrollViewFirst') 467 | command -bar -nargs=* -complete=custom,s:Complete ScrollViewFirst 468 | \ lua require('scrollview').first( 469 | \ #{} > 0 and {} or nil) 470 | endif 471 | 472 | if !exists(':ScrollViewLast') 473 | command -bar -nargs=* -complete=custom,s:Complete ScrollViewLast 474 | \ lua require('scrollview').last( 475 | \ #{} > 0 and {} or nil) 476 | endif 477 | 478 | if !exists(':ScrollViewLegend') 479 | command -bang -bar -nargs=* -complete=custom,s:Complete ScrollViewLegend 480 | \ lua require('scrollview').legend( 481 | \ #{} > 0 and {} or nil, '' == '!') 482 | endif 483 | 484 | if !exists(':ScrollViewNext') 485 | command -count=1 -bar -nargs=* -complete=custom,s:Complete ScrollViewNext 486 | \ lua require('scrollview').next( 487 | \ #{} > 0 and {} or nil, ) 488 | endif 489 | 490 | if !exists(':ScrollViewPrev') 491 | command -count=1 -bar -nargs=* -complete=custom,s:Complete ScrollViewPrev 492 | \ lua require('scrollview').prev( 493 | \ #{} > 0 and {} or nil, ) 494 | endif 495 | 496 | if !exists(':ScrollViewRefresh') 497 | command -bar ScrollViewRefresh lua require('scrollview').refresh() 498 | endif 499 | 500 | if !exists(':ScrollViewToggle') 501 | command -bar -nargs=* -complete=custom,s:CompleteWithAll ScrollViewToggle 502 | \ call s:DispatchStateCommand(v:null, ) 503 | endif 504 | 505 | " ************************************************* 506 | " * Mappings 507 | " ************************************************* 508 | 509 | function! scrollview#HandleMouseFromMapping(button, is_primary) abort 510 | let l:button_repr = nvim_replace_termcodes( 511 | \ printf('<%smouse>', a:button), v:true, v:true, v:true) 512 | let l:packed = luaeval( 513 | \ '{require("scrollview").should_handle_mouse(_A)}', l:button_repr) 514 | let l:should_handle = l:packed[0] 515 | if l:should_handle 516 | let l:data = l:packed[1] 517 | call luaeval( 518 | \ 'require("scrollview").handle_mouse(' 519 | \ .. '_A.button, _A.is_primary, _A.props, _A.mousepos)', l:data) 520 | else 521 | " Process the click as it would ordinarily be processed. 522 | call feedkeys(l:button_repr, 'ni') 523 | endif 524 | endfunction 525 | 526 | function! s:SetUpMouseMappings(button, primary) abort 527 | if a:button isnot# v:null 528 | " Create a mouse mapping only if mappings don't already exist and "!" is 529 | " not used at the end of the button. For example, a mapping may already 530 | " exist if the user uses swapped buttons from $VIMRUNTIME/pack/dist/opt 531 | " /swapmouse/plugin/swapmouse.vim. Handling for that scenario would 532 | " require modifications (e.g., possibly by updating the non-initial 533 | " feedkeys calls in handle_mouse() to remap keys). 534 | let l:force = v:false 535 | let l:button = a:button 536 | if strcharpart(l:button, strchars(l:button, 1) - 1, 1) ==# '!' 537 | let l:force = v:true 538 | let l:button = 539 | \ strcharpart(l:button, 0, strchars(l:button, 1) - 1) 540 | endif 541 | for l:mapmode in ['n', 'v', 'i'] 542 | execute printf( 543 | \ 'silent! %snoremap %s <%smouse>' 544 | \ .. ' call scrollview#HandleMouseFromMapping("%s", %s)', 545 | \ l:mapmode, 546 | \ l:force ? '' : '', 547 | \ l:button, 548 | \ l:button, 549 | \ a:primary, 550 | \ ) 551 | endfor 552 | endif 553 | endfunction 554 | 555 | " With Neovim 0.11, mouse functionality is handled with vim.on_key, not 556 | " mappings. 557 | if !has('nvim-0.11') 558 | call s:SetUpMouseMappings(g:scrollview_mouse_primary, v:true) 559 | " :popup doesn't work for nvim<0.8. 560 | if has('nvim-0.8') 561 | call s:SetUpMouseMappings(g:scrollview_mouse_secondary, v:false) 562 | endif 563 | endif 564 | 565 | " Additional mappings are defined for convenience of creating 566 | " user-defined mappings that call nvim-scrollview functionality. However, 567 | " since the usage of mappings requires recursive map commands, this 568 | " prevents mappings that both call functions and have the 569 | " left-hand-side key sequences repeated not at the beginning of the 570 | " right-hand-side (see :help recursive_mapping for details). Experimentation 571 | " suggests is not necessary for mappings, but it's added to 572 | " make it explicit. 573 | noremap (ScrollViewDisable) ScrollViewDisable 574 | inoremap (ScrollViewDisable) ScrollViewDisable 575 | noremap (ScrollViewEnable) ScrollViewEnable 576 | inoremap (ScrollViewEnable) ScrollViewEnable 577 | noremap (ScrollViewFirst) ScrollViewFirst 578 | inoremap (ScrollViewFirst) ScrollViewFirst 579 | noremap (ScrollViewLast) ScrollViewLast 580 | inoremap (ScrollViewLast) ScrollViewLast 581 | noremap (ScrollViewLegend) ScrollViewLegend 582 | inoremap (ScrollViewLegend) ScrollViewLegend 583 | noremap (ScrollViewLegend!) ScrollViewLegend! 584 | inoremap (ScrollViewLegend!) ScrollViewLegend! 585 | noremap (ScrollViewNext) ScrollViewNext 586 | inoremap (ScrollViewNext) ScrollViewNext 587 | noremap (ScrollViewPrev) ScrollViewPrev 588 | inoremap (ScrollViewPrev) ScrollViewPrev 589 | noremap (ScrollViewRefresh) ScrollViewRefresh 590 | inoremap (ScrollViewRefresh) ScrollViewRefresh 591 | noremap (ScrollViewToggle) ScrollViewToggle 592 | inoremap (ScrollViewToggle) ScrollViewToggle 593 | 594 | " ************************************************* 595 | " * Sign Group Initialization 596 | " ************************************************* 597 | 598 | " === Initialize built-in sign groups (for nvim>=0.9) === 599 | 600 | if has('nvim-0.9') 601 | let s:lookup = {} " maps sign groups to state (enabled/disabled) 602 | for s:group in s:available_signs 603 | let s:lookup[s:group] = v:false 604 | endfor 605 | for s:group in g:scrollview_signs_on_startup 606 | if s:group ==# 'all' 607 | for s:group2 in s:available_signs 608 | let s:lookup[s:group2] = v:true 609 | endfor 610 | break 611 | elseif s:group ==# 'defaults' 612 | for s:group2 in s:default_signs 613 | let s:lookup[s:group2] = v:true 614 | endfor 615 | else 616 | let s:lookup[s:group] = v:true 617 | endif 618 | endfor 619 | for s:group in s:available_signs 620 | let s:module = luaeval('require("scrollview.signs.' .. s:group .. '")') 621 | call s:module.init(s:lookup[s:group]) 622 | endfor 623 | endif 624 | 625 | " ************************************************* 626 | " * Enable/Disable scrollview 627 | " ************************************************* 628 | 629 | " Enable nvim-scrollview if scrollview_on_startup is true. 630 | if g:scrollview_on_startup 631 | lua require('scrollview').set_state(true) 632 | endif 633 | 634 | " ************************************************* 635 | " * Initialization 636 | " ************************************************* 637 | 638 | function! scrollview#Initialize() abort 639 | " The first call to this function will result in executing this file's code. 640 | endfunction 641 | -------------------------------------------------------------------------------- /doc/scrollview.txt: -------------------------------------------------------------------------------- 1 | *scrollview.txt* Plugin that shows interactive vertical scrollbars and signs 2 | *nvim-scrollview* 3 | 4 | Author: Daniel Steinberg - https://www.dannyadam.com 5 | Web: https://github.com/dstein64/nvim-scrollview 6 | 7 | 1. Requirements |scrollview-requirements| 8 | 2. Installation |scrollview-installation| 9 | 3. Usage |scrollview-usage| 10 | 4. Configuration |scrollview-configuration| 11 | 5. Sign Extensibility |scrollview-signs-extensibility| 12 | 6. Issues |scrollview-issues| 13 | 14 | |nvim-scrollview| is a plugin that displays interactive vertical scrollbars and 15 | signs. The plugin is customizable (see |scrollview-configuration|). 16 | 17 | Type |gO| to see the table of contents. 18 | 19 | ============================================================================ 20 | 1. Requirements *scrollview-requirements* 21 | 22 | * `nvim>=0.6` 23 | * Scrollbar mouse functionality requires mouse support (see |'mouse'|) 24 | * Signs require `nvim>=0.9` 25 | 26 | ============================================================================ 27 | 2. Installation *scrollview-installation* 28 | 29 | Use |packages| or one of the various package managers. 30 | 31 | ============================================================================ 32 | 3. Usage *scrollview-usage* 33 | 34 | * |nvim-scrollview| works automatically, displaying interactive scrollbars and 35 | signs. 36 | *scrollview-mouse* 37 | 38 | The scrollbars are draggable with a mouse. Signs can be clicked for navigation 39 | or right-clicked for information. If |'mousemoveevent'| is set, scrollbars and 40 | signs are highlighted when the mouse pointer hovers. 41 | 42 | *scrollview-commands* 43 | 44 | *:ScrollViewDisable* 45 | :ScrollViewDisable Disable the plugin. 46 | 47 | :ScrollViewDisable {group1} ... 48 | Disable the specified sign groups. 49 | 50 | *:ScrollViewEnable* 51 | :ScrollViewEnable Enable the plugin. This is only necessary if 52 | nvim-scrollview has previously been disabled. 53 | 54 | :ScrollViewEnable {group1} ... 55 | Enable the specified sign groups. 56 | 57 | *:ScrollViewToggle* 58 | :ScrollViewToggle Toggle the plugin. 59 | 60 | :ScrollViewToggle {group1} ... 61 | Toggle the specified sign groups. 62 | 63 | *:ScrollViewRefresh* 64 | :ScrollViewRefresh Refresh the scrollbars and signs. This is relevant when 65 | the state becomes out-of-sync, which can occur e.g., as 66 | a result of some window arrangement actions (see 67 | |scrollview-issues|). 68 | 69 | :ScrollViewLegend[!] [{group1} ...] *:ScrollViewLegend* 70 | Show a legend for the scrollbar and signs. The optional 71 | trailing arguments specify which sign groups are 72 | considered (all groups when not specified). Without 73 | [!], the legend will only contain items that are 74 | currently visible. With [!], the legend will include 75 | the scrollbar and all registered signs (even those from 76 | disabled groups), regardless of their display status. 77 | 78 | :[count]ScrollViewNext [{group1} ...] *:ScrollViewNext* 79 | Move the cursor to the [count]'th next line with a 80 | sign. If the |'wrapscan'| option is set, the movement 81 | wraps around the end of the buffer when necessary. 82 | Otherwise, the movement stops at the last line with a 83 | sign. The optional trailing arguments specify which 84 | sign groups are considered (all groups when not 85 | specified). 86 | 87 | :[count]ScrollViewPrev [{group1} ...] *:ScrollViewPrev* 88 | Move the cursor to the [count]'th previous line with 89 | a sign. If the |'wrapscan'| option is set, the movement 90 | wraps around the beginning of the buffer when 91 | necessary. Otherwise, the movement stops at the first 92 | line with a sign. The optional trailing arguments 93 | specify which sign groups are considered (all groups 94 | when not specified). 95 | 96 | :ScrollViewFirst [{group1} ...] *:ScrollViewFirst* 97 | Move the cursor to the first line with a sign. The 98 | optional trailing arguments specify which sign groups 99 | are considered (all groups when not specified). 100 | 101 | :ScrollViewLast [{group1} ...] *:ScrollViewLast* 102 | Move the cursor to the last line with a sign. The 103 | optional trailing arguments specify which sign groups 104 | are considered (all groups when not specified). 105 | 106 | *scrollview-mappings* 107 | The following || mappings are defined for convenience. 108 | 109 | * `(ScrollViewDisable)` 110 | * `(ScrollViewEnable)` 111 | * `(ScrollViewFirst)` 112 | * `(ScrollViewLast)` 113 | * `(ScrollViewLegend)` 114 | * `(ScrollViewLegend!)` 115 | * `(ScrollViewNext)` 116 | * `(ScrollViewPrev)` 117 | * `(ScrollViewRefresh)` 118 | * `(ScrollViewToggle)` 119 | 120 | *scrollview-enabled-global* 121 | *scrollview_enabled* 122 | A global variable, `scrollview_enabled`, is set to |v:true| when the plugin is 123 | enabled, and |v:false| otherwise. It can be accessed from Lua 124 | (`vim.g.scrollview_enabled`) or Vimscript (`g:scrollview_enabled`). Do not 125 | modify its value; it should be treated as read-only. Use |:ScrollViewEnable|, 126 | |:ScrollViewDisable|, and |:ScrollViewToggle| to enable, disable, or toggle 127 | the plugin. 128 | 129 | *scrollview-refreshing-global* 130 | *scrollview_refreshing* 131 | A global variable, `scrollview_refreshing`, is set to |v:true| while scrollbars 132 | are being refreshed, and |v:false| otherwise. It can be accessed from Lua 133 | (`vim.g.scrollview_refreshing`) or Vimscript (`g:scrollview_refreshing`). 134 | 135 | The plugin executes commands with `:normal!`, which are treated as if they are 136 | typed. `scrollview_refreshing` may be useful for ignoring the corresponding keys 137 | when using |vim.on_key()|. 138 | > 139 | vim.on_key(function(char) 140 | if vim.g.scrollview_refreshing then return end 141 | ... 142 | end) 143 | < 144 | *scrollview-signs* 145 | |nvim-scrollview| signs are similar to Neovim's built-in |signs|, but with 146 | positioning logic matching that of the scrollbar. The plugin includes a set 147 | of built-in sign groups (e.g., `search` signs show where in the document there 148 | are search matches). 149 | 150 | For configuring signs generally, and the built-in groups specifically, see 151 | |scrollview-signs-configuration|. For extending sign functionality (e.g., in a 152 | Neovim configuration file or with a plugin), see 153 | |scrollview-signs-extensibility|. 154 | 155 | When there are multiple signs displayed on the same row, they will be 156 | positioned based on their priority. The higher the priority of a sign, which 157 | is configurable, the closer it will be to the scrollbar. When the number of 158 | signs exceeds the value specified by |scrollview_signs_max_per_row|, the lower 159 | priority signs will be dropped. 160 | 161 | Clicking on a sign will navigate to its associated line. If a sign is linked 162 | to multiple lines, successive clicks will cycle through these lines. 163 | Right-clicking a sign reveals additional information, including its sign group 164 | and the corresponding lines, which can be selected for navigation. Identifying 165 | the sign group can be helpful if you are unsure what a sign represents. 166 | 167 | Built-in Sign Groups ~ 168 | *scrollview-signs-built-in* 169 | Group Information 170 | ----- ----------- 171 | `changelist` change list items (previous, current, and next) 172 | `conflicts` git merge conflicts 173 | `cursor` cursor position 174 | `diagnostics` errors, warnings, info, and hints via |vim.diagnostic| 175 | `folds` closed folds 176 | `indent` unexpected indentation characters (e.g., tabs when |expandtab| 177 | is set) 178 | `latestchange` latest change 179 | `keywords` FIX, FIXME, HACK, TODO, WARN, WARNING, and XXX (see 180 | |scrollview-signs-keywords| for customization info) 181 | `loclist` items on the location list 182 | `marks` 183 | `quickfix` items on the quickfix list 184 | `search` 185 | `spell` spell check items when the |'spell'| option is enabled 186 | `textwidth` line lengths exceeding the value of the |'textwidth'| option, when 187 | non-zero 188 | `trail` trailing whitespace 189 | 190 | *scrollview-restricted* 191 | Under certain scenarios, the plugin enters a restricted mode to prevent a 192 | slowdown. Under restricted operation, the |scrollview-mode| is set to `simple` 193 | and signs are not displayed. |scrollview_byte_limit| and |scrollview_line_limit| 194 | can be set to control when restricted mode is entered. 195 | 196 | ============================================================================ 197 | 4. Configuration *scrollview-configuration* 198 | 199 | Configuration Variables ~ 200 | *scrollview_base* 201 | scrollview_base |String| specifying where the scrollbar is anchored. 202 | Possible values are `'left'` or `'right'` for corresponding 203 | window edges, or `'buffer'`. Defaults to `'right'`. 204 | 205 | *scrollview_byte_limit* 206 | scrollview_byte_limit |Number| specifying the buffer size threshold (in 207 | bytes) for entering restricted mode, to prevent slow 208 | operation. Defaults to `1,000,000`. Use `-1` for no limit. 209 | 210 | *scrollview_character* 211 | scrollview_character |String| specifying a character to display on scrollbars. 212 | Defaults to `''`. Scrollbar transparency (via 213 | |scrollview_winblend|) is not possible when a scrollbar 214 | character is used. 215 | 216 | *scrollview_column* 217 | scrollview_column |Number| specifying the scrollbar column (relative to 218 | |scrollview_base|). Defaults to `1`. Must be an integer 219 | greater than or equal to `1`. 220 | 221 | scrollview_consider_border *scrollview_consider_border* 222 | (experimental) |Boolean| specifying whether floating window borders are 223 | taken into account for positioning scrollbars and signs. 224 | When set to |v:true|, borders are considered as part of 225 | the window; scrollbars and signs can be positioned 226 | where there are borders. When set to |v:false|, borders 227 | are ignored. Defaults to |v:false|. The setting is only 228 | applicable for floating windows that have borders, and 229 | only relevant when |scrollview_floating_windows| is 230 | turned on and |scrollview_base| is set to `'left'` or 231 | `'right'`. 232 | 233 | scrollview_current_only *scrollview_current_only* 234 | |Boolean| specifying whether scrollbars and signs should 235 | only be displayed in the current window. Defaults to 236 | |v:false|. 237 | 238 | scrollview_excluded_filetypes *scrollview_excluded_filetypes* 239 | |List| of |String|s specifying optional file types for 240 | which scrollbars should not be displayed. Defaults to 241 | `[]`. 242 | 243 | scrollview_excluded_info_signs *scrollview_excluded_info_signs* 244 | |List| of |String|s specifying sign groups to exclude from 245 | visibility consideration when |scrollview_visibility| is 246 | set to `'info'` and there is no overflow. Defaults to 247 | `['changelist', 'cursor', 'latestchange']`. 248 | 249 | scrollview_floating_windows *scrollview_floating_windows* 250 | (experimental) |Boolean| specifying whether scrollbars and signs are 251 | shown in floating windows. Defaults to |v:false|. 252 | 253 | scrollview_hide_bar_for_insert *scrollview_hide_bar_for_insert* 254 | |Boolean| specifying whether the scrollbar is hidden in 255 | the current window for insert mode. Defaults to |v:false|. 256 | See |scrollview_signs_hidden_for_insert| for hiding signs. 257 | 258 | scrollview_hide_on_cursor_intersect *scrollview_hide_on_cursor_intersect* 259 | |Boolean| specifying whether each scrollbar or sign 260 | becomes hidden (not shown) when it would otherwise 261 | intersect with the cursor. Defaults to |v:false|. 262 | Requires `nvim>=0.7`. 263 | 264 | scrollview_hide_on_float_intersect *scrollview_hide_on_float_intersect* 265 | |Boolean| specifying whether each scrollbar or sign 266 | becomes hidden (not shown) when it would otherwise 267 | intersect with a floating window. Defaults to |v:false|. 268 | 269 | scrollview_hide_on_text_intersect *scrollview_hide_on_text_intersect* 270 | (experimental) |Boolean| specifying whether each scrollbar or sign 271 | becomes hidden (not shown) when it would otherwise 272 | intersect with text (excluding virtual text). The 273 | functionality may not work for lines where the last 274 | character spans multiple cells (only the first cell of 275 | the character would be considered for intersection). 276 | For lines with concealed text, signs and the scrollbar 277 | can become hidden when they don't intersect text. The 278 | processing demands when enabling this option may result 279 | in noticeable lag under certain environments, 280 | especially on older hardware. Defaults to |v:false|. 281 | 282 | scrollview_hover *scrollview_hover* 283 | |Boolean| specifying whether the highlighting of 284 | scrollbars and signs should change when hovering with 285 | the mouse. Requires mouse support (see |'mouse'|) with 286 | |'mousemoveevent'| set. Defaults to |v:true|. 287 | 288 | scrollview_include_end_region *scrollview_include_end_region* 289 | |Boolean| specifying whether the region beyond the last 290 | line is considered as containing ordinary lines for the 291 | calculation of scrollbar height and positioning. 292 | Defaults to |v:false|. See Issue #58 for details. 293 | 294 | *scrollview_line_limit* 295 | scrollview_line_limit |Number| specifying the buffer size threshold (in 296 | lines) for entering restricted mode, to prevent slow 297 | operation. Defaults to `20,000`. Use `-1` for no limit. 298 | 299 | *scrollview_mode* 300 | scrollview_mode |String| specifying what the scrollbar position and size 301 | correspond to. See |scrollview-modes| for details on the 302 | available modes. Defaults to `'auto'`. 303 | 304 | scrollview_mouse_primary *scrollview_mouse_primary* 305 | |String| specifying the button for primary mouse 306 | operations (dragging scrollbars and clicking signs). 307 | Possible values include `'left'`, `'middle'`, `'right'`, 308 | `'x1'`, and `'x2'`. These can be prepended with `'c-'` or 309 | `'m-'` for the control-key and alt-key variants (e.g., 310 | `'c-left'` for control-left). Set to `v:null` to disable 311 | the functionality. Defaults to `'left'`. For `nvim<0.11`, 312 | an existing mapping will not be clobbered, unless `'!'` 313 | is added at the end (e.g., `'left!'`). For `nvim<0.11`, 314 | the option is considered only when the plugin is loaded. 315 | 316 | scrollview_mouse_secondary *scrollview_mouse_secondary* 317 | |String| specifying the button for secondary mouse 318 | operations (clicking signs for additional information). 319 | See |scrollview_mouse_primary| for the possible values, 320 | including how `'c-'`, `'m-'`, and `v:null` can be utilized, 321 | as well as the behavior specific to `nvim<0.11`. Defaults 322 | to `'right'`. 323 | 324 | *scrollview_on_startup* 325 | scrollview_on_startup |Boolean| specifying whether scrollbars are enabled on 326 | startup. Defaults to |v:true|. Considered only when the 327 | plugin is loaded. 328 | 329 | *scrollview_visibility* 330 | scrollview_visibility |String| specifying the visibility level of scrollbars 331 | and signs. Possible values are `'always'`, `'overflow'`, 332 | and `'info'`. Defaults to `'overflow'`. 333 | * `always` Always show scrollbars and signs. 334 | * `overflow` Show scrollbars and signs only when content 335 | overflows the window (when there are lines that are 336 | not visible). 337 | * `info` Show scrollbars and signs when content overflows 338 | the window. Otherwise, show when there are signs to 339 | display (excluding those from sign groups included in 340 | |scrollview_excluded_info_signs|). 341 | 342 | *scrollview_winblend* 343 | scrollview_winblend |Number| specifying the level of transparency for 344 | scrollbars when a GUI is not running and 'termguicolors' 345 | is not set. Defaults to `50`. Must be between `0` (opaque) 346 | and `100` (transparent). This option is ignored for 347 | scrollview windows whose highlight group has 'reverse' 348 | or 'inverse' specified as a cterm attribute; see 349 | |attr-list|. Such windows will have no transparency, as a 350 | workaround for Neovim #24159. This option is ignored 351 | for scrollview windows shown for floating windows; see 352 | |scrollview_floating_windows|. Such windows will have no 353 | transparency, as a workaround for Neovim #14624. 354 | 355 | scrollview_winblend_gui *scrollview_winblend_gui* 356 | (experimental) |Number| specifying the level of transparency for 357 | scrollbars when a GUI is running or 'termguicolors' is 358 | set. Defaults to `0`. Must be between `0` (opaque) and `100` 359 | (transparent). This option is ignored for scrollview 360 | windows whose highlight group has 'reverse' or 'inverse' 361 | specified as a gui attribute; see |attr-list|. Such 362 | windows will have no transparency, as a workaround for 363 | Neovim #24159. This option is ignored for scrollview 364 | windows shown for floating windows; see 365 | |scrollview_floating_windows|. Such windows will have no 366 | transparency, as a workaround for Neovim #14624. This 367 | option can interact with highlight settings 368 | (|scrollview-color-customization|); careful adjustment of 369 | the winblend setting and highlighting may be necessary 370 | to achieve the desired result. 371 | 372 | scrollview_zindex *scrollview_zindex* 373 | |Number| specifying the z-index for scrollbars and signs. 374 | Must be larger than zero. Defaults to `40`. 375 | 376 | *scrollview-signs-configuration* 377 | Sign Configuration Variables ~ 378 | 379 | scrollview_signs_hidden_for_insert *scrollview_signs_hidden_for_insert* 380 | |List| of |String|s specifying sign groups to hide in the 381 | current window for insert mode. Set to `['all']` to hide 382 | all sign groups. Defaults to `[]`. See 383 | |scrollview_hide_bar_for_insert| for hiding the scrollbar. 384 | 385 | scrollview_signs_max_per_row *scrollview_signs_max_per_row* 386 | |Number| specifying the maximum number of signs per row. 387 | Set to `-1` to have no limit. Defaults to `-1`. 388 | 389 | scrollview_signs_on_startup *scrollview_signs_on_startup* 390 | |List| of |String|s specifying built-in sign groups to 391 | enable on startup. Set to `[]` to disable all built-in 392 | sign groups on startup. Set to `['all']` to enable all 393 | sign groups. `['diagnostics', 'search']` is the default 394 | for `nvim<0.10`. `['diagnostics', 'marks', 'search']` is 395 | the default for `nvim>=0.10`. Considered only when the 396 | plugin is loaded. 397 | 398 | scrollview_signs_overflow *scrollview_signs_overflow* 399 | |String| specifying the sign overflow direction (to 400 | avoid overlapping the scrollbar or other signs). 401 | Possible values are `'left'` or `'right'`. Defaults to 402 | `'left'`. 403 | 404 | scrollview_signs_show_in_folds *scrollview_signs_show_in_folds* 405 | |Boolean| specifying whether signs on lines within hidden 406 | folds should be shown. Sign groups can override this 407 | setting (e.g., the built-in cursor sign group does). 408 | Defaults to |v:false|. 409 | 410 | *scrollview-signs-built-in-config* 411 | Configuration Variables for Built-in Sign Groups ~ 412 | 413 | scrollview_changelist_previous_priority *scrollview_changelist_previous_priority* 414 | |Number| specifying the priority for the previous item 415 | changelist sign. Defaults to `15`. Considered only when 416 | the plugin is loaded. 417 | 418 | scrollview_changelist_previous_symbol *scrollview_changelist_previous_symbol* 419 | |String| specifying the symbol for the previous item 420 | changelist sign. Defaults to an upwards arrow with tip 421 | leftwards. Considered only when the plugin is loaded. 422 | 423 | scrollview_changelist_current_priority *scrollview_changelist_current_priority* 424 | |Number| specifying the priority for the current item 425 | changelist sign. Defaults to `10`. Considered only when 426 | the plugin is loaded. 427 | 428 | scrollview_changelist_current_symbol *scrollview_changelist_current_symbol* 429 | |String| specifying the symbol for the current item 430 | changelist sign. Defaults to `'@'`. Considered only when 431 | the plugin is loaded. 432 | 433 | scrollview_changelist_next_priority *scrollview_changelist_next_priority* 434 | |Number| specifying the priority for the next item 435 | changelist sign. Defaults to `5`. Considered only when 436 | the plugin is loaded. 437 | 438 | scrollview_changelist_next_symbol *scrollview_changelist_next_symbol* 439 | |String| specifying the symbol for the next item 440 | changelist sign. Defaults to a downwards arrow with tip 441 | rightwards. Considered only when the plugin is loaded. 442 | 443 | scrollview_conflicts_bottom_priority *scrollview_conflicts_bottom_priority* 444 | |Number| specifying the priority for conflict bottom 445 | signs. Defaults to `80`. Considered only when the 446 | plugin is loaded. 447 | 448 | scrollview_conflicts_bottom_symbol *scrollview_conflicts_bottom_symbol* 449 | |String| specifying the symbol for conflict bottom signs. 450 | Defaults to `'>'`. A |List| of |String|s can also be 451 | used; the symbol is selected using the index equal to 452 | the number of lines corresponding to the sign, or the 453 | last element when the retrieval would be out-of-bounds. 454 | Considered only when the plugin is loaded. 455 | 456 | scrollview_conflicts_middle_priority *scrollview_conflicts_middle_priority* 457 | |Number| specifying the priority for conflict middle 458 | signs. Defaults to `75`. Considered only when the plugin 459 | is loaded. 460 | 461 | scrollview_conflicts_middle_symbol *scrollview_conflicts_middle_symbol* 462 | |String| specifying the symbol for conflict middle signs. 463 | Defaults to `'='`. A |List| of |String|s can also be used; 464 | the symbol is selected using the index equal to the 465 | number of lines corresponding to the sign, or the last 466 | element when the retrieval would be out-of-bounds. 467 | Considered only when the plugin is loaded. 468 | 469 | scrollview_conflicts_top_priority *scrollview_conflicts_top_priority* 470 | |Number| specifying the priority for conflict top signs. 471 | Defaults to `70`. Considered only when the plugin is 472 | loaded. 473 | 474 | scrollview_conflicts_top_symbol *scrollview_conflicts_top_symbol* 475 | |String| specifying the symbol for conflict top signs. 476 | Defaults to `'<'`. A |List| of |String|s can also be used; 477 | the symbol is selected using the index equal to the 478 | number of lines corresponding to the sign, or the last 479 | element when the retrieval would be out-of-bounds. 480 | Considered only when the plugin is loaded. 481 | 482 | scrollview_cursor_priority *scrollview_cursor_priority* 483 | |Number| specifying the priority for cursor signs. 484 | Defaults to `0`. Considered only when the plugin is 485 | loaded. 486 | 487 | scrollview_cursor_symbol *scrollview_cursor_symbol* 488 | |String| specifying the symbol for cursor signs. Defaults 489 | to a small square, resembling a block cursor. 490 | Considered only when the plugin is loaded. 491 | 492 | scrollview_diagnostics_error_priority *scrollview_diagnostics_error_priority* 493 | |Number| specifying the priority for diagnostic error 494 | signs. Defaults to `60`. Considered only when the plugin 495 | is loaded. 496 | 497 | scrollview_diagnostics_error_symbol *scrollview_diagnostics_error_symbol* 498 | |String| specifying the symbol for diagnostic error 499 | signs. Defaults to the trimmed sign text for 500 | DiagnosticSignError if defined, or `'E'` otherwise. A 501 | |List| of |String|s can also be used; the symbol is 502 | selected using the index equal to the number of lines 503 | corresponding to the sign, or the last element when the 504 | retrieval would be out-of-bounds. Considered only when 505 | the plugin is loaded. 506 | 507 | scrollview_diagnostics_hint_priority *scrollview_diagnostics_hint_priority* 508 | |Number| specifying the priority for diagnostic hint 509 | signs. Defaults to `30`. Considered only when the 510 | plugin is loaded. 511 | 512 | scrollview_diagnostics_hint_symbol *scrollview_diagnostics_hint_symbol* 513 | |String| specifying the symbol for diagnostic hint signs. 514 | Defaults to the trimmed sign text for DiagnosticSignHint 515 | if defined, or `'H'` otherwise. A |List| of |String|s can 516 | also be used; the symbol is selected using the index 517 | equal to the number of lines corresponding to the sign, 518 | or the last element when the retrieval would be 519 | out-of-bounds. Considered only when the plugin is 520 | loaded. 521 | 522 | scrollview_diagnostics_info_priority *scrollview_diagnostics_info_priority* 523 | |Number| specifying the priority for diagnostic info 524 | signs. Defaults to `40`. Considered only when the plugin 525 | is loaded. 526 | 527 | scrollview_diagnostics_info_symbol *scrollview_diagnostics_info_symbol* 528 | |String| specifying the symbol for diagnostic info signs. 529 | Defaults to the trimmed sign text for DiagnosticSignInfo 530 | if defined, or `'I'` otherwise. A |List| of |String|s can 531 | also be used; the symbol is selected using the index 532 | equal to the number of lines corresponding to the sign, 533 | or the last element when the retrieval would be 534 | out-of-bounds. Considered only when the plugin is 535 | loaded. 536 | 537 | scrollview_diagnostics_severities *scrollview_diagnostics_severities* 538 | |List| of |Number|s specifying the diagnostic severities 539 | for which signs will be shown. The default includes 540 | |vim.diagnostic.severity.ERROR|, |vim.diagnostic.severity.HINT|, 541 | |vim.diagnostic.severity.INFO|, and |vim.diagnostic.severity.WARN|. 542 | Considered only when the plugin is loaded. 543 | 544 | scrollview_diagnostics_warn_priority *scrollview_diagnostics_warn_priority* 545 | |Number| specifying the priority for diagnostic warn 546 | signs. Defaults to `50`. Considered only when the plugin 547 | is loaded. 548 | 549 | scrollview_diagnostics_warn_symbol *scrollview_diagnostics_warn_symbol* 550 | |String| specifying the symbol for diagnostic warn signs. 551 | Defaults to the trimmed sign text for DiagnosticSignWarn 552 | if defined, or `'W'` otherwise. A |List| of |String|s can 553 | also be used; the symbol is selected using the index 554 | equal to the number of lines corresponding to the sign, 555 | or the last element when the retrieval would be 556 | out-of-bounds. Considered only when the plugin is 557 | loaded. 558 | 559 | scrollview_folds_priority *scrollview_folds_priority* 560 | |Number| specifying the priority for fold signs. Defaults 561 | to `30`. Considered only when the plugin is loaded. 562 | 563 | scrollview_folds_symbol *scrollview_folds_symbol* 564 | |String| specifying the symbol for fold signs. Defaults 565 | to a right-pointing triangle. A |List| of |String|s can 566 | also be used; the symbol is selected using the index 567 | equal to the number of lines corresponding to the sign, 568 | or the last element when the retrieval would be 569 | out-of-bounds. Considered only when the plugin is 570 | loaded. 571 | 572 | scrollview_indent_spaces_condition *scrollview_indent_spaces_condition* 573 | |String| specifying the condition when signs will be 574 | shown for leading spaces. Possible values are `'always'` 575 | `'never'`, `'expandtab'`, and `'noexpandtab'`. Defaults to 576 | `'noexpandtab'`, which will show signs for leading 577 | spaces when the |expandtab| option is off. 578 | 579 | scrollview_indent_spaces_priority *scrollview_indent_spaces_priority* 580 | |Number| specifying the priority for indent spaces signs. 581 | Defaults to `25`. Considered only when the plugin is 582 | loaded. 583 | 584 | scrollview_indent_spaces_symbol *scrollview_indent_spaces_symbol* 585 | |String| specifying the symbol for indent spaces signs. 586 | Defaults to `'-'`. Considered only when the plugin is 587 | loaded. 588 | 589 | scrollview_indent_tabs_condition *scrollview_indent_tabs_condition* 590 | |String| specifying the condition when signs will be 591 | shown for leading tabs. Possible values are `'always'` 592 | `'never'`, `'expandtab'`, and `'noexpandtab'`. Defaults to 593 | `'expandtab'`, which will show signs for leading 594 | tabs when the |expandtab| option is set. 595 | 596 | scrollview_indent_tabs_priority *scrollview_indent_tabs_priority* 597 | |Number| specifying the priority for indent tabs signs. 598 | Defaults to `25`. Considered only when the plugin is 599 | loaded. 600 | 601 | scrollview_indent_tabs_symbol *scrollview_indent_tabs_symbol* 602 | |String| specifying the symbol for indent tabs signs. 603 | Defaults to `'>'`. Considered only when the plugin is 604 | loaded. 605 | 606 | scrollview_keywords_built_ins *scrollview_keywords_built_ins* 607 | |List| of |String|s specifying the enabled built-in 608 | keyword groups for the keyword sign group (see 609 | |scrollview-signs-keywords|). Defaults to `['fix', 'hack',` 610 | `'todo', 'warn', 'xxx']`. Considered only when the plugin 611 | is loaded. 612 | 613 | scrollview_keywords_fix_priority *scrollview_keywords_fix_priority* 614 | |Number| specifying the priority for fix keyword signs. 615 | Defaults to `20`. Considered only when the plugin is 616 | loaded. 617 | 618 | scrollview_keywords_fix_scope *scrollview_keywords_fix_scope* 619 | |String| specifying the search scope for fix keyword 620 | signs. See |scrollview-signs-keyword-scopes| for details. 621 | Defaults to `'auto'`. Considered only when the plugin is 622 | loaded. 623 | 624 | scrollview_keywords_fix_symbol *scrollview_keywords_fix_symbol* 625 | |String| specifying the symbol for fix keyword signs. 626 | Defaults to `'F'`. Considered only when the plugin is 627 | loaded. 628 | 629 | scrollview_keywords_hack_priority *scrollview_keywords_hack_priority* 630 | |Number| specifying the priority for hack keyword signs. 631 | Defaults to `20`. Considered only when the plugin is 632 | loaded. 633 | 634 | scrollview_keywords_hack_scope *scrollview_keywords_hack_scope* 635 | |String| specifying the search scope for hack keyword 636 | signs. See |scrollview-signs-keyword-scopes| for details. 637 | Defaults to `'auto'`. Considered only when the plugin is 638 | loaded. 639 | 640 | scrollview_keywords_hack_symbol *scrollview_keywords_hack_symbol* 641 | |String| specifying the symbol for hack keyword signs. 642 | Defaults to `'H'`. Considered only when the plugin is 643 | loaded. 644 | 645 | scrollview_keywords_todo_priority *scrollview_keywords_todo_priority* 646 | |Number| specifying the priority for todo keyword signs. 647 | Defaults to `20`. Considered only when the plugin is 648 | loaded. 649 | 650 | scrollview_keywords_todo_scope *scrollview_keywords_todo_scope* 651 | |String| specifying the search scope for todo keyword 652 | signs. See |scrollview-signs-keyword-scopes| for details. 653 | Defaults to `'auto'`. Considered only when the plugin is 654 | loaded. 655 | 656 | scrollview_keywords_todo_symbol *scrollview_keywords_todo_symbol* 657 | |String| specifying the symbol for todo keyword signs. 658 | Defaults to `'T'`. Considered only when the plugin is 659 | loaded. 660 | 661 | scrollview_keywords_warn_priority *scrollview_keywords_warn_priority* 662 | |Number| specifying the priority for warn keyword signs. 663 | Defaults to `20`. Considered only when the plugin is 664 | loaded. 665 | 666 | scrollview_keywords_warn_scope *scrollview_keywords_warn_scope* 667 | |String| specifying the search scope for warn keyword 668 | signs. See |scrollview-signs-keyword-scopes| for details. 669 | Defaults to `'auto'`. Considered only when the plugin is 670 | loaded. 671 | 672 | scrollview_keywords_warn_symbol *scrollview_keywords_warn_symbol* 673 | |String| specifying the symbol for warn keyword signs. 674 | Defaults to `'W'`. Considered only when the plugin is 675 | loaded. 676 | 677 | scrollview_keywords_xxx_priority *scrollview_keywords_xxx_priority* 678 | |Number| specifying the priority for xxx keyword signs. 679 | Defaults to `20`. Considered only when the plugin is 680 | loaded. 681 | 682 | scrollview_keywords_xxx_scope *scrollview_keywords_xxx_scope* 683 | |String| specifying the search scope for xxx keyword 684 | signs. See |scrollview-signs-keyword-scopes| for details. 685 | Defaults to `'auto'`. Considered only when the plugin is 686 | loaded. 687 | 688 | scrollview_keywords_xxx_symbol *scrollview_keywords_xxx_symbol* 689 | |String| specifying the symbol for xxx keyword signs. 690 | Defaults to `'X'`. Considered only when the plugin is 691 | loaded. 692 | 693 | scrollview_latestchange_priority *scrollview_latestchange_priority* 694 | |Number| specifying the priority for latestchange signs. 695 | Defaults to `10`. Considered only when the plugin is 696 | loaded. 697 | 698 | scrollview_latestchange_symbol *scrollview_latestchange_symbol* 699 | |String| specifying the symbol for latestchange signs. 700 | Defaults to a Greek uppercase delta. Considered only 701 | when the plugin is loaded. 702 | 703 | scrollview_loclist_priority *scrollview_loclist_priority* 704 | |Number| specifying the priority for loclist signs. 705 | Defaults to `45`. Considered only when the plugin is 706 | loaded. 707 | 708 | scrollview_loclist_symbol *scrollview_loclist_symbol* 709 | |String| specifying the symbol for loclist signs. 710 | Defaults to a small circle. A |List| of |String|s can also 711 | be used; the symbol is selected using the index equal 712 | to the number of lines corresponding to the sign, or 713 | the last element when the retrieval would be 714 | out-of-bounds. Considered only when the plugin is 715 | loaded. 716 | 717 | scrollview_marks_characters *scrollview_marks_characters* 718 | |List| of |String|s specifying characters for which mark 719 | signs will be shown. Defaults to characters `a-z` and 720 | `A-Z`. Considered only when the plugin is loaded. 721 | 722 | scrollview_marks_priority *scrollview_marks_priority* 723 | |List| specifying the priority for mark signs. Defaults 724 | to `50`. Considered only when the plugin is loaded. 725 | 726 | scrollview_quickfix_priority *scrollview_quickfix_priority* 727 | |Number| specifying the priority for quickfix signs. 728 | Defaults to `45`. Considered only when the plugin is 729 | loaded. 730 | 731 | scrollview_quickfix_symbol *scrollview_quickfix_symbol* 732 | |String| specifying the symbol for quickfix signs. 733 | Defaults to a small circle. A |List| of |String|s can also 734 | be used; the symbol is selected using the index equal 735 | to the number of lines corresponding to the sign, or 736 | the last element when the retrieval would be 737 | out-of-bounds. Considered only when the plugin is 738 | loaded. 739 | 740 | scrollview_search_priority *scrollview_search_priority* 741 | |Number| specifying the priority for search signs. 742 | Defaults to `70`. Considered only when the plugin is 743 | loaded. 744 | 745 | scrollview_search_symbol *scrollview_search_symbol* 746 | |String| specifying the symbol for search signs. A |List| 747 | of |String|s can also be used; the symbol is selected 748 | using the index equal to the number of lines 749 | corresponding to the sign, or the last element when the 750 | retrieval would be out-of-bounds. Defaults to ['=', 751 | '=', nr2char(0x2261)], where the third element is the 752 | triple bar. Considered only when the plugin is loaded. 753 | 754 | scrollview_spell_priority *scrollview_spell_priority* 755 | |Number| specifying the priority for spell signs. 756 | Defaults to `20`. Considered only when the plugin is 757 | loaded. 758 | 759 | scrollview_spell_symbol *scrollview_spell_symbol* 760 | |String| specifying the symbol for spell signs. Defaults 761 | to `'~'`. A |List| of |String|s can also be used; the symbol 762 | is selected using the index equal to the number of 763 | lines corresponding to the sign, or the last element 764 | when the retrieval would be out-of-bounds. Considered 765 | only when the plugin is loaded. 766 | 767 | scrollview_textwidth_priority *scrollview_textwidth_priority* 768 | |Number| specifying the priority for textwidth signs. 769 | Defaults to `20`. Considered only when the plugin is 770 | loaded. 771 | 772 | scrollview_textwidth_symbol *scrollview_textwidth_symbol* 773 | |String| specifying the symbol for textwidth signs. 774 | Defaults to a right-pointing double angle quotation 775 | mark. A |List| of |String|s can also be used; the symbol is 776 | selected using the index equal to the number of lines 777 | corresponding to the sign, or the last element when the 778 | retrieval would be out-of-bounds. Considered only when 779 | the plugin is loaded. 780 | 781 | scrollview_trail_priority *scrollview_trail_priority* 782 | |Number| specifying the priority for trail signs. 783 | Defaults to `50`. Considered only when the plugin is 784 | loaded. 785 | 786 | scrollview_trail_symbol *scrollview_trail_symbol* 787 | |String| specifying the symbol for trail signs. Defaults 788 | to an outlined square. A |List| of |String|s can also be 789 | used; the symbol is selected using the index equal to 790 | the number of lines corresponding to the sign, or the 791 | last element when the retrieval would be out-of-bounds. 792 | Considered only when the plugin is loaded. 793 | 794 | Configuration Example ~ 795 | *scrollview-configuration-example* 796 | The variables can be customized in your |init.vim|, as shown in the following 797 | example. 798 | > 799 | let g:scrollview_excluded_filetypes = ['nerdtree'] 800 | let g:scrollview_current_only = v:true 801 | " Position the scrollbar at the 80th character of the buffer 802 | let g:scrollview_base = 'buffer' 803 | let g:scrollview_column = 80 804 | " Enable all sign groups (defaults to ['diagnostics', 'search']) 805 | let g:scrollview_signs_on_startup = ['all'] 806 | 807 | Lua Configuration ~ 808 | *scrollview.setup()* 809 | A Lua `setup()` function is provided for convenience, to set globally scoped 810 | options (the 'scrollview_' prefix is omitted). For example: 811 | > 812 | require('scrollview').setup({ 813 | excluded_filetypes = {'nerdtree'}, 814 | current_only = true, 815 | -- Position the scrollbar at the 80th character of the buffer 816 | base = 'buffer', 817 | column = 80, 818 | signs_on_startup = {'all'} 819 | }) 820 | 821 | Alternatively, configuration variables can be set without calling `setup()`. 822 | > 823 | vim.g.scrollview_excluded_filetypes = {'nerdtree'}, 824 | vim.g.scrollview_current_only = true, 825 | -- Position the scrollbar at the 80th character of the buffer 826 | vim.g.scrollview_base = 'buffer', 827 | vim.g.scrollview_column = 80, 828 | vim.g.scrollview_signs_on_startup = {'all'} 829 | 830 | Scrollview Modes ~ 831 | *scrollview-modes* 832 | The following modes are available for the |scrollview_mode| configuration 833 | setting (set as a |string|). Without (1) closed |folds|, (2) wrapped lines, 834 | (3) diff filler, and (4) virtual text lines, all modes work the same way. 835 | 836 | Mode Description 837 | ---- ----------- 838 | `simple` The scrollbar top position reflects the window's top line number 839 | relative to the document's line count. The scrollbar height 840 | reflects the size of the window relative to the document's line 841 | count. Dragging the scrollbars with the mouse may result in 842 | unresponsive scrolling when there are closed folds. 843 | 844 | `virtual` The scrollbar position and height are calculated similarly as for 845 | `simple` mode, but line numbers and the document line count are 846 | implicitly updated to virtual counterparts that account for closed 847 | folds. The scrollbar top position reflects the window's top virtual 848 | line number relative to the document's virtual line count. The 849 | scrollbar height reflects the size of the window relative to the 850 | document's virtual line count. This mode works slower than `simple` 851 | mode. 852 | 853 | `proper` The scrollbar position and height are calculated similarly as for 854 | `virtual` mode, but wrapped lines are considered in addition to 855 | folds. On `nvim>=0.10`, diff filler and virtual text lines are also 856 | considered. This mode works slower than `virtual` mode. 857 | 858 | `auto` Under `auto` mode, the effective mode is set to `proper` when there are 859 | relatively few buffer lines and luajit is available; `virtual` mode 860 | is used otherwise. 861 | 862 | Color Customization ~ 863 | *scrollview-color-customization* 864 | The following highlight groups can be configured to change |nvim-scrollview|'s 865 | colors. 866 | 867 | `Name` 868 | Default Target 869 | ---- ------ 870 | `ScrollView` scrollbar 871 | Visual 872 | `ScrollViewChangeListPrevious` changelist previous signs 873 | SpecialKey 874 | `ScrollViewChangeListCurrent` changelist current signs 875 | SpecialKey 876 | `ScrollViewChangeListNext` changelist next signs 877 | SpecialKey 878 | `ScrollViewConflictsTop` top conflict signs 879 | DiffAdd 880 | `ScrollViewConflictsMiddle` middle conflict signs 881 | DiffAdd 882 | `ScrollViewConflictsBottom` bottom conflict signs 883 | DiffAdd 884 | `ScrollViewCursor` cursor signs 885 | Identifier 886 | `ScrollViewDiagnosticsError` diagnostic error signs 887 | The sign text highlight 888 | for DiagnosticSignError 889 | if defined, or DiagnosticError 890 | otherwise 891 | `ScrollViewDiagnosticsHint` diagnostic hint signs 892 | The sign text highlight 893 | for DiagnosticSignHint 894 | if defined, or DiagnosticHint 895 | otherwise 896 | `ScrollViewDiagnosticsInfo` diagnostic info signs 897 | The sign text highlight 898 | for DiagnosticSignInfo 899 | if defined, or DiagnosticInfo 900 | otherwise 901 | `ScrollViewDiagnosticsWarn` diagnostic warn signs 902 | The sign text highlight 903 | for DiagnosticSignWarn 904 | if defined, or DiagnosticWarn 905 | otherwise 906 | `ScrollViewFolds` fold signs 907 | Directory 908 | `ScrollViewHover` scrollbar and signs on hover 909 | CurSearch for nvim>=0.9.2 910 | and WildMenu otherwise 911 | `ScrollViewIndentSpaces` indent spaces signs 912 | LineNr 913 | `ScrollViewIndentTabs` indent spaces tabs 914 | LineNr 915 | `ScrollViewKeywordsFix` fix keyword signs 916 | ColorColumn 917 | `ScrollViewKeywordsHack` hack keyword signs 918 | ColorColumn 919 | `ScrollViewKeywordsTodo` todo keyword signs 920 | ColorColumn 921 | `ScrollViewKeywordsWarn` warn keyword signs 922 | ColorColumn 923 | `ScrollViewKeywordsXxx` xxx keyword signs 924 | ColorColumn 925 | `ScrollViewLatestChange` latestchange signs 926 | SpecialKey 927 | `ScrollViewLocList` loclist signs 928 | LineNr 929 | `ScrollViewMarks` mark signs 930 | Identifier 931 | `ScrollViewQuickFix` quickfix signs 932 | Constant 933 | `ScrollViewRestricted` |scrollview-restricted| scrollbar 934 | CurSearch for nvim>=0.9.2 935 | and MatchParen otherwise 936 | `ScrollViewSearch` search signs 937 | NonText 938 | `ScrollViewSpell` spell signs 939 | Statement 940 | `ScrollViewTextWidth` textwidth signs 941 | Question 942 | 943 | The highlight groups can be customized in your |init.vim|, as shown in the 944 | following example. 945 | > 946 | " Link ScrollView highlight to Pmenu highlight 947 | highlight link ScrollView Pmenu 948 | 949 | " Specify custom highlighting for ScrollView 950 | highlight ScrollView ctermbg=159 guibg=LightCyan 951 | 952 | ============================================================================ 953 | 5. Sign Extensibility *scrollview-signs-extensibility* 954 | 955 | The plugin was written so that it's possible to extend the sign functionality 956 | in a Neovim configuration file or with a plugin. 957 | 958 | *scrollview-signs-version-global* 959 | *scrollview_signs_version* 960 | A global variable, `scrollview_signs_version`, has an integer representing the 961 | version of the sign exensibility functionality. This can be accessed from Lua 962 | (`vim.g.scrollview_signs_version`) or Vimscript (`g:scrollview_signs_version`). 963 | 964 | Functions ~ 965 | 966 | deregister_sign_group({group}, {refresh}) *scrollview.deregister_sign_group()* 967 | Deregister a sign group. 968 | 969 | Parameters: ~ 970 | * {group} (string) Group name. 971 | * {refresh} (boolean|nil) Optional argument specifying whether 972 | scrollview refreshes afterwards. Defaults to `true`. 973 | 974 | *scrollview.deregister_sign_spec()* 975 | deregister_sign_spec({spec_id}, {refresh}) 976 | Deregister a sign specification. 977 | 978 | Parameters: ~ 979 | * {spec_id} (integer) Specification identifier. 980 | * {refresh} (boolean|nil) Optional argument specifying whether 981 | scrollview refreshes afterwards. Defaults to `true`. 982 | 983 | *scrollview-signs-functions* 984 | Lua code is necessary for adding new sign functionality to `nvim-scrollview`. 985 | > 986 | local scrollview = require('scrollview') 987 | 988 | get_sign_eligible_windows() *scrollview.get_sign_eligible_windows()* 989 | Returns |window-ID|s that are eligible for signs to be shown. 990 | 991 | Return: ~ 992 | (list) a list-like table with |window-ID|s. 993 | 994 | is_sign_group_active({group}) *scrollview.is_sign_group_active()* 995 | Checks whether a sign group is active. A group is considered active if 996 | `nvim-scrollview` is enabled and the sign group is enabled. 997 | 998 | Parameters: ~ 999 | * {group} (string) Group name. 1000 | 1001 | Return: ~ 1002 | (boolean) `true` if active, else `false`. 1003 | 1004 | register_sign_group({group}) *scrollview.register_sign_group()* 1005 | Register a sign group. 1006 | 1007 | Parameters: ~ 1008 | * {group} (string) Group name. 1009 | 1010 | register_sign_spec({spec}) *scrollview.register_sign_spec()* 1011 | Register a sign specification. 1012 | 1013 | Parameters: ~ 1014 | * {spec} (table) Keyword arguments |kwargs|: 1015 | + current_only (boolean): Show only in an current window (the 1016 | window with the cursor at the time of display). Defaults to 1017 | `false`. 1018 | + extend (boolean): Show each sign on all corresponding rows 1019 | of the scrollview column, instead of just a single row. This 1020 | is relevant when the window is not scrollable (see 1021 | |scrollview_visibility|). 1022 | + group (string): Defaults to `'other'`. 1023 | + highlight (string): Defaults to `'Pmenu'`. 1024 | + priority (integer): Defaults to `50`. 1025 | + show_in_folds (boolean|nil): Include signs for lines in 1026 | closed folds. When non-`nil`, overrides the value of 1027 | |scrollview_signs_show_in_folds|. Defaults to `nil`. 1028 | + symbol (string): Defaults to `''`. 1029 | + type (string): `'b'` for buffer-local signs, `'w'` for 1030 | window-local signs. Defaults to `'b'`. 1031 | + variant (string|nil): Used for groups that include multiple 1032 | variants. Defaults to `nil`. 1033 | 1034 | Return: ~ 1035 | (table) a table with the following key-value pairs. 1036 | + id (integer): A unique identifier. 1037 | + name (string): Variable name that should be used for saving 1038 | a list-like table with line numbers corresponding to signs. 1039 | The variable should be saved to the buffer if the 1040 | registration specified type `'b'`, or to the window if the 1041 | registration specified type `'w'`. 1042 | 1043 | *scrollview.set_sign_group_callback()* 1044 | set_sign_group_callback({group}, {callback}) 1045 | Set the refresh callback for a sign group. 1046 | 1047 | Parameters: ~ 1048 | * {group} (string) Group name. 1049 | * {callback} (function|nil) Callback function or `nil` to unset. 1050 | 1051 | set_sign_group_state({group}, {enable}) *scrollview.set_sign_group_state()* 1052 | Enable, disable, or toggle a sign group. 1053 | 1054 | Parameters: ~ 1055 | * {group} (string) Group name. 1056 | * {enable} (boolean|nil) `true` to enable, `false` to disable, `nil` to 1057 | toggle. 1058 | 1059 | Example ~ 1060 | *scrollview-signs-example* 1061 | The following example shows how to implement sign functionality for showing 1062 | the cursor position. This is for the purpose of illustration; cursor signs are 1063 | already built in to `nvim-scrollview` (enable with `:ScrollViewEnable cursor`). 1064 | > 1065 | local scrollview = require('scrollview') 1066 | 1067 | local group = 'cursor' 1068 | scrollview.register_sign_group(group) 1069 | local registration = scrollview.register_sign_spec({ 1070 | current_only = true, 1071 | group = group, 1072 | highlight = 'SpellCap', 1073 | show_in_folds = true, 1074 | }) 1075 | local name = registration.name 1076 | scrollview.set_sign_group_state(group, true) 1077 | 1078 | scrollview.set_sign_group_callback(group, function() 1079 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 1080 | local bufnr = vim.api.nvim_win_get_buf(winid) 1081 | vim.b[bufnr][name] = {vim.fn.line('.')} 1082 | end 1083 | end) 1084 | 1085 | vim.api.nvim_create_autocmd({'CursorMoved', 'CursorMovedI'}, { 1086 | callback = function() 1087 | if not scrollview.is_sign_group_active(group) then return end 1088 | local lines = vim.b[name] 1089 | if lines == nil or lines[1] ~= vim.fn.line('.') then 1090 | vim.cmd('silent! ScrollViewRefresh') 1091 | end 1092 | end 1093 | }) 1094 | < 1095 | ============================================================================ 1096 | 6. Keyword Signs *scrollview-signs-keywords* 1097 | 1098 | The keyword sign group has built-in support for a set of keyword groups 1099 | (not to be confused with sign groups). 1100 | 1101 | Group Keywords 1102 | ----- -------- 1103 | `fix` FIX, FIXME 1104 | `hack` HACK 1105 | `todo` TODO 1106 | `warn` WARN, WARNING 1107 | `xxx` XXX 1108 | 1109 | The signs for keywords can be customized. The settings described here are 1110 | considered only when the plugin is loaded (e.g., through an |init.vim| or 1111 | |init.lua| configuration file). 1112 | 1113 | The built-in keyword signs can be customized by modifying their configuration 1114 | variables (see those starting with `'scrollview_keywords_'` under 1115 | |scrollview-signs-built-in-config|). For example, the following code changes the 1116 | symbols for the "fix" and "todo" keyword signs. 1117 | 1118 | Vimscript: > 1119 | let g:scrollview_keywords_fix_symbol = nr2char(0x1F527) 1120 | let g:scrollview_keywords_todo_symbol = nr2char(0x2705) 1121 | 1122 | Lua: > 1123 | vim.g.scrollview_keywords_fix_symbol = vim.fn.nr2char(0x1F527) 1124 | vim.g.scrollview_keywords_todo_symbol = vim.fn.nr2char(0x2705) 1125 | < 1126 | The built-in keyword signs can be disabled by modifying 1127 | |scrollview_keywords_built_ins|. For example, the following code disables some 1128 | keyword signs by enabling only a specfic subset. 1129 | 1130 | Vimscript: > 1131 | let g:scrollview_keywords_built_ins = ['fix', 'todo'] 1132 | 1133 | Lua: > 1134 | vim.g.scrollview_keywords_built_ins = {'fix', 'todo'} 1135 | < 1136 | Additional groups of keyword signs can be added by creating a dictionary with 1137 | a specific form for its name, `g:scrollview_keywords_GROUP_spec` (with `GROUP` 1138 | replaced accordingly). It is comprised of the following items. 1139 | 1140 | * `patterns`: a list of strings, each containing a Lua pattern that will be 1141 | searched for 1142 | * `highlight`: an optional highlight group 1143 | * `priority`: an optional number indicating sign priority 1144 | * `scope`: an optional string to specify the scope for keyword searching (see 1145 | |scrollview-signs-keyword-scopes|). Defaults to `'auto'`. 1146 | * `symbol`: an optional string to use for the sign symbol 1147 | 1148 | For example, with the following Vimscript code, |nvim-scrollview| will show 1149 | signs for lines that have the text `'NOTE'`. 1150 | > 1151 | highlight ScrollViewKeywordsNote ctermfg=4 guibg=Blue 1152 | let g:scrollview_keywords_note_spec = { 1153 | \ 'patterns': ['NOTE'], 1154 | \ 'highlight': 'ScrollViewKeywordsNote', 1155 | \ 'priority': 20, 1156 | \ 'symbol': 'N', 1157 | \ } 1158 | < 1159 | Keyword Scopes ~ 1160 | *scrollview-signs-keyword-scopes* 1161 | The scope settings for keyword signs are described below. The scope setting 1162 | controls the parts of a buffer that will be searched for keywords. 1163 | 1164 | Scope Description 1165 | ----- ----------- 1166 | `full` Search the entire buffer. 1167 | 1168 | `comments` Search comments, using |treesitter| or |'commentstring'| to detect 1169 | comments. 1170 | 1171 | `auto` Same as `comments` when |treesitter| is available or |'commentstring'| is 1172 | set; same as `full` otherwise. 1173 | 1174 | ============================================================================ 1175 | 7. Issues *scrollview-issues* 1176 | 1177 | Synchronization Issues ~ 1178 | *scrollview-synchronization-issues* 1179 | * Scrollbars can become out-of-sync after modifying window arrangement with 1180 | |:wincmd|. |:ScrollViewRefresh|, or scrolling can be used to refresh the 1181 | scrollbars. 1182 | 1183 | * Scrollbars become out-of-sync after ":" fold commands when using a 1184 | |scrollview-mode| that accounts for folds. Because there are no |autocmd-events| 1185 | for folding, the plugin is unable to refresh the scrollbars. 1186 | |:ScrollViewRefresh|, or scrolling can be used to refresh the scrollbars. 1187 | 1188 | Error Message Issues ~ 1189 | *scrollview-error-message-issues* 1190 | * For `nvim<=0.6.1`, when attempting to close the last window in a tab with 1191 | |:close| or `c`, with the scrollbar displayed, and at least one 1192 | other tab, the following error is shown: > 1193 | E5601: Cannot close window, only floating window would remain 1194 | < Neovim Issue #11440 is the corresponding issue, opened November 23, 2019 1195 | and closed February 11, 2022. 1196 | + Workaround 1: Use |:quit|, |ZZ|, |ZQ|, or another command that quits the 1197 | current window (as opposed to a window-closing command). 1198 | + Workaround 2: Use |:only| or `o` on the last window of the tab, 1199 | which has the side-effect of closing all floating windows (including 1200 | scrollbars). A subsequent window-closing command works without error. 1201 | + Workaround 3: Use |:tabclose|. 1202 | + Workaround 4: Remap the problematic key sequence accordingly. > 1203 | nnoremap c 1204 | \ silent! ScrollViewDisable 1205 | \execute "normal! \c" 1206 | \silent! ScrollViewEnable 1207 | < This won't resolve the issue in all cases (e.g., when using |:close| or 1208 | |:wincmd|). || mappings cannot be used, as they require recursive maps, 1209 | and c would infinitely recurse, as its usage on the right-hand-side 1210 | is not at the beginning (see |recursive_mapping| for details). 1211 | 1212 | * For `nvim<=0.6.1`, deleting a buffer with |:bdelete|, when it's the only visible 1213 | buffer and there are multiple buffers on the buffer list, results in the 1214 | following error (with duplicate lines, as shown): > 1215 | E444: Cannot close last window 1216 | E444: Cannot close last window 1217 | < The buffer may not be deleted from the buffer list. Neovim Issue #13628 is 1218 | the corresponding issue, opened December 28, 2020 and closed March 23, 2022. 1219 | + Workaround 1: Use |:only| or `o`, which has the side-effect of closing 1220 | all floating windows (including scrollbars). A subsequent |:bdelete| command 1221 | works without error. 1222 | + Workaround 2: Create a `Bdelete` command that avoids the issue. > 1223 | command Bdelete 1224 | \ silent! ScrollViewDisable 1225 | \ | bdelete 1226 | \ | silent! ScrollViewEnable 1227 | < 1228 | Other Issues ~ 1229 | *scrollview-other-issues* 1230 | * For `nvim<=0.7.0`, sessions cannot be properly restored by |:mksession| scripts 1231 | created when the plugin is active (Issue #71). Neovim Issue #18432 is the 1232 | corresponding issue, opened May 5, 2022. 1233 | + Workaround: Create a `MkSession` command that avoids the problem. > 1234 | command -bang -nargs=? MkSession 1235 | \ silent! ScrollViewDisable 1236 | \ | mksession 1237 | \ | silent! ScrollViewEnable 1238 | 1239 | * Because scrollbars are floating windows, they are included in the window 1240 | count returned by `winnr('$')`. 1241 | + Workaround 1: Use a Vimscript function that omits external and floating 1242 | windows in the count. > 1243 | function! WindowCount() 1244 | let l:result = 0 1245 | for l:winnr in range(1, winnr('$')) 1246 | let l:config = nvim_win_get_config(win_getid(l:winnr)) 1247 | if !get(l:config, 'external', 0) 1248 | \ && get(l:config, 'relative', '') ==# '' 1249 | let l:result += 1 1250 | endif 1251 | endfor 1252 | return l:result 1253 | endfunction 1254 | < + Workaround 2: Use a Lua function that omits external and floating windows 1255 | in the count. > 1256 | local window_count = function() 1257 | local result = 0 1258 | for winnr = 1, vim.fn.winnr('$') do 1259 | local config = vim.api.nvim_win_get_config(vim.fn.win_getid(winnr)) 1260 | local not_external = config.external == nil or not config.external 1261 | local not_floating = config.relative == nil or config.relative == '' 1262 | if not_external and not_floating then 1263 | result = result + 1 1264 | end 1265 | end 1266 | return result 1267 | end 1268 | 1269 | ============================================================================ 1270 | vim:tw=78:ts=4:ft=help:norl: 1271 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | :ScrollViewDisable scrollview.txt /*:ScrollViewDisable* 2 | :ScrollViewEnable scrollview.txt /*:ScrollViewEnable* 3 | :ScrollViewFirst scrollview.txt /*:ScrollViewFirst* 4 | :ScrollViewLast scrollview.txt /*:ScrollViewLast* 5 | :ScrollViewLegend scrollview.txt /*:ScrollViewLegend* 6 | :ScrollViewNext scrollview.txt /*:ScrollViewNext* 7 | :ScrollViewPrev scrollview.txt /*:ScrollViewPrev* 8 | :ScrollViewRefresh scrollview.txt /*:ScrollViewRefresh* 9 | :ScrollViewToggle scrollview.txt /*:ScrollViewToggle* 10 | nvim-scrollview scrollview.txt /*nvim-scrollview* 11 | scrollview-color-customization scrollview.txt /*scrollview-color-customization* 12 | scrollview-commands scrollview.txt /*scrollview-commands* 13 | scrollview-configuration scrollview.txt /*scrollview-configuration* 14 | scrollview-configuration-example scrollview.txt /*scrollview-configuration-example* 15 | scrollview-enabled-global scrollview.txt /*scrollview-enabled-global* 16 | scrollview-error-message-issues scrollview.txt /*scrollview-error-message-issues* 17 | scrollview-installation scrollview.txt /*scrollview-installation* 18 | scrollview-issues scrollview.txt /*scrollview-issues* 19 | scrollview-mappings scrollview.txt /*scrollview-mappings* 20 | scrollview-modes scrollview.txt /*scrollview-modes* 21 | scrollview-mouse scrollview.txt /*scrollview-mouse* 22 | scrollview-other-issues scrollview.txt /*scrollview-other-issues* 23 | scrollview-refreshing-global scrollview.txt /*scrollview-refreshing-global* 24 | scrollview-requirements scrollview.txt /*scrollview-requirements* 25 | scrollview-restricted scrollview.txt /*scrollview-restricted* 26 | scrollview-signs scrollview.txt /*scrollview-signs* 27 | scrollview-signs-built-in scrollview.txt /*scrollview-signs-built-in* 28 | scrollview-signs-built-in-config scrollview.txt /*scrollview-signs-built-in-config* 29 | scrollview-signs-configuration scrollview.txt /*scrollview-signs-configuration* 30 | scrollview-signs-example scrollview.txt /*scrollview-signs-example* 31 | scrollview-signs-extensibility scrollview.txt /*scrollview-signs-extensibility* 32 | scrollview-signs-functions scrollview.txt /*scrollview-signs-functions* 33 | scrollview-signs-keyword-scopes scrollview.txt /*scrollview-signs-keyword-scopes* 34 | scrollview-signs-keywords scrollview.txt /*scrollview-signs-keywords* 35 | scrollview-signs-version-global scrollview.txt /*scrollview-signs-version-global* 36 | scrollview-synchronization-issues scrollview.txt /*scrollview-synchronization-issues* 37 | scrollview-usage scrollview.txt /*scrollview-usage* 38 | scrollview.deregister_sign_group() scrollview.txt /*scrollview.deregister_sign_group()* 39 | scrollview.deregister_sign_spec() scrollview.txt /*scrollview.deregister_sign_spec()* 40 | scrollview.get_sign_eligible_windows() scrollview.txt /*scrollview.get_sign_eligible_windows()* 41 | scrollview.is_sign_group_active() scrollview.txt /*scrollview.is_sign_group_active()* 42 | scrollview.register_sign_group() scrollview.txt /*scrollview.register_sign_group()* 43 | scrollview.register_sign_spec() scrollview.txt /*scrollview.register_sign_spec()* 44 | scrollview.set_sign_group_callback() scrollview.txt /*scrollview.set_sign_group_callback()* 45 | scrollview.set_sign_group_state() scrollview.txt /*scrollview.set_sign_group_state()* 46 | scrollview.setup() scrollview.txt /*scrollview.setup()* 47 | scrollview.txt scrollview.txt /*scrollview.txt* 48 | scrollview_base scrollview.txt /*scrollview_base* 49 | scrollview_byte_limit scrollview.txt /*scrollview_byte_limit* 50 | scrollview_changelist_current_priority scrollview.txt /*scrollview_changelist_current_priority* 51 | scrollview_changelist_current_symbol scrollview.txt /*scrollview_changelist_current_symbol* 52 | scrollview_changelist_next_priority scrollview.txt /*scrollview_changelist_next_priority* 53 | scrollview_changelist_next_symbol scrollview.txt /*scrollview_changelist_next_symbol* 54 | scrollview_changelist_previous_priority scrollview.txt /*scrollview_changelist_previous_priority* 55 | scrollview_changelist_previous_symbol scrollview.txt /*scrollview_changelist_previous_symbol* 56 | scrollview_character scrollview.txt /*scrollview_character* 57 | scrollview_column scrollview.txt /*scrollview_column* 58 | scrollview_conflicts_bottom_priority scrollview.txt /*scrollview_conflicts_bottom_priority* 59 | scrollview_conflicts_bottom_symbol scrollview.txt /*scrollview_conflicts_bottom_symbol* 60 | scrollview_conflicts_middle_priority scrollview.txt /*scrollview_conflicts_middle_priority* 61 | scrollview_conflicts_middle_symbol scrollview.txt /*scrollview_conflicts_middle_symbol* 62 | scrollview_conflicts_top_priority scrollview.txt /*scrollview_conflicts_top_priority* 63 | scrollview_conflicts_top_symbol scrollview.txt /*scrollview_conflicts_top_symbol* 64 | scrollview_consider_border scrollview.txt /*scrollview_consider_border* 65 | scrollview_current_only scrollview.txt /*scrollview_current_only* 66 | scrollview_cursor_priority scrollview.txt /*scrollview_cursor_priority* 67 | scrollview_cursor_symbol scrollview.txt /*scrollview_cursor_symbol* 68 | scrollview_diagnostics_error_priority scrollview.txt /*scrollview_diagnostics_error_priority* 69 | scrollview_diagnostics_error_symbol scrollview.txt /*scrollview_diagnostics_error_symbol* 70 | scrollview_diagnostics_hint_priority scrollview.txt /*scrollview_diagnostics_hint_priority* 71 | scrollview_diagnostics_hint_symbol scrollview.txt /*scrollview_diagnostics_hint_symbol* 72 | scrollview_diagnostics_info_priority scrollview.txt /*scrollview_diagnostics_info_priority* 73 | scrollview_diagnostics_info_symbol scrollview.txt /*scrollview_diagnostics_info_symbol* 74 | scrollview_diagnostics_severities scrollview.txt /*scrollview_diagnostics_severities* 75 | scrollview_diagnostics_warn_priority scrollview.txt /*scrollview_diagnostics_warn_priority* 76 | scrollview_diagnostics_warn_symbol scrollview.txt /*scrollview_diagnostics_warn_symbol* 77 | scrollview_enabled scrollview.txt /*scrollview_enabled* 78 | scrollview_excluded_filetypes scrollview.txt /*scrollview_excluded_filetypes* 79 | scrollview_excluded_info_signs scrollview.txt /*scrollview_excluded_info_signs* 80 | scrollview_floating_windows scrollview.txt /*scrollview_floating_windows* 81 | scrollview_folds_priority scrollview.txt /*scrollview_folds_priority* 82 | scrollview_folds_symbol scrollview.txt /*scrollview_folds_symbol* 83 | scrollview_hide_bar_for_insert scrollview.txt /*scrollview_hide_bar_for_insert* 84 | scrollview_hide_on_cursor_intersect scrollview.txt /*scrollview_hide_on_cursor_intersect* 85 | scrollview_hide_on_float_intersect scrollview.txt /*scrollview_hide_on_float_intersect* 86 | scrollview_hide_on_text_intersect scrollview.txt /*scrollview_hide_on_text_intersect* 87 | scrollview_hover scrollview.txt /*scrollview_hover* 88 | scrollview_include_end_region scrollview.txt /*scrollview_include_end_region* 89 | scrollview_indent_spaces_condition scrollview.txt /*scrollview_indent_spaces_condition* 90 | scrollview_indent_spaces_priority scrollview.txt /*scrollview_indent_spaces_priority* 91 | scrollview_indent_spaces_symbol scrollview.txt /*scrollview_indent_spaces_symbol* 92 | scrollview_indent_tabs_condition scrollview.txt /*scrollview_indent_tabs_condition* 93 | scrollview_indent_tabs_priority scrollview.txt /*scrollview_indent_tabs_priority* 94 | scrollview_indent_tabs_symbol scrollview.txt /*scrollview_indent_tabs_symbol* 95 | scrollview_keywords_built_ins scrollview.txt /*scrollview_keywords_built_ins* 96 | scrollview_keywords_fix_priority scrollview.txt /*scrollview_keywords_fix_priority* 97 | scrollview_keywords_fix_scope scrollview.txt /*scrollview_keywords_fix_scope* 98 | scrollview_keywords_fix_symbol scrollview.txt /*scrollview_keywords_fix_symbol* 99 | scrollview_keywords_hack_priority scrollview.txt /*scrollview_keywords_hack_priority* 100 | scrollview_keywords_hack_scope scrollview.txt /*scrollview_keywords_hack_scope* 101 | scrollview_keywords_hack_symbol scrollview.txt /*scrollview_keywords_hack_symbol* 102 | scrollview_keywords_todo_priority scrollview.txt /*scrollview_keywords_todo_priority* 103 | scrollview_keywords_todo_scope scrollview.txt /*scrollview_keywords_todo_scope* 104 | scrollview_keywords_todo_symbol scrollview.txt /*scrollview_keywords_todo_symbol* 105 | scrollview_keywords_warn_priority scrollview.txt /*scrollview_keywords_warn_priority* 106 | scrollview_keywords_warn_scope scrollview.txt /*scrollview_keywords_warn_scope* 107 | scrollview_keywords_warn_symbol scrollview.txt /*scrollview_keywords_warn_symbol* 108 | scrollview_keywords_xxx_priority scrollview.txt /*scrollview_keywords_xxx_priority* 109 | scrollview_keywords_xxx_scope scrollview.txt /*scrollview_keywords_xxx_scope* 110 | scrollview_keywords_xxx_symbol scrollview.txt /*scrollview_keywords_xxx_symbol* 111 | scrollview_latestchange_priority scrollview.txt /*scrollview_latestchange_priority* 112 | scrollview_latestchange_symbol scrollview.txt /*scrollview_latestchange_symbol* 113 | scrollview_line_limit scrollview.txt /*scrollview_line_limit* 114 | scrollview_loclist_priority scrollview.txt /*scrollview_loclist_priority* 115 | scrollview_loclist_symbol scrollview.txt /*scrollview_loclist_symbol* 116 | scrollview_marks_characters scrollview.txt /*scrollview_marks_characters* 117 | scrollview_marks_priority scrollview.txt /*scrollview_marks_priority* 118 | scrollview_mode scrollview.txt /*scrollview_mode* 119 | scrollview_mouse_primary scrollview.txt /*scrollview_mouse_primary* 120 | scrollview_mouse_secondary scrollview.txt /*scrollview_mouse_secondary* 121 | scrollview_on_startup scrollview.txt /*scrollview_on_startup* 122 | scrollview_quickfix_priority scrollview.txt /*scrollview_quickfix_priority* 123 | scrollview_quickfix_symbol scrollview.txt /*scrollview_quickfix_symbol* 124 | scrollview_refreshing scrollview.txt /*scrollview_refreshing* 125 | scrollview_search_priority scrollview.txt /*scrollview_search_priority* 126 | scrollview_search_symbol scrollview.txt /*scrollview_search_symbol* 127 | scrollview_signs_hidden_for_insert scrollview.txt /*scrollview_signs_hidden_for_insert* 128 | scrollview_signs_max_per_row scrollview.txt /*scrollview_signs_max_per_row* 129 | scrollview_signs_on_startup scrollview.txt /*scrollview_signs_on_startup* 130 | scrollview_signs_overflow scrollview.txt /*scrollview_signs_overflow* 131 | scrollview_signs_show_in_folds scrollview.txt /*scrollview_signs_show_in_folds* 132 | scrollview_signs_version scrollview.txt /*scrollview_signs_version* 133 | scrollview_spell_priority scrollview.txt /*scrollview_spell_priority* 134 | scrollview_spell_symbol scrollview.txt /*scrollview_spell_symbol* 135 | scrollview_textwidth_priority scrollview.txt /*scrollview_textwidth_priority* 136 | scrollview_textwidth_symbol scrollview.txt /*scrollview_textwidth_symbol* 137 | scrollview_trail_priority scrollview.txt /*scrollview_trail_priority* 138 | scrollview_trail_symbol scrollview.txt /*scrollview_trail_symbol* 139 | scrollview_visibility scrollview.txt /*scrollview_visibility* 140 | scrollview_winblend scrollview.txt /*scrollview_winblend* 141 | scrollview_winblend_gui scrollview.txt /*scrollview_winblend_gui* 142 | scrollview_zindex scrollview.txt /*scrollview_zindex* 143 | -------------------------------------------------------------------------------- /lua/scrollview/contrib/README: -------------------------------------------------------------------------------- 1 | This directory contains sign group implementations that are not built-in, but 2 | may be useful to some users. The code here does not receive the same level of 3 | support as the main source code, and may be less stable. Use at your own risk. 4 | -------------------------------------------------------------------------------- /lua/scrollview/contrib/coc.lua: -------------------------------------------------------------------------------- 1 | -- Requirements: 2 | -- - coc.nvim (https://github.com/neoclide/coc.nvim) 3 | -- Usage: 4 | -- require('scrollview.contrib.coc').setup([{config}]) 5 | -- {config} is an optional table with the following attributes: 6 | -- - enabled (boolean): Whether signs are enabled immediately. If false, 7 | -- use ':ScrollViewEnable coc' to enable later. Defaults to true. 8 | -- - error_highlight (string): Defaults to 'CocErrorSign'. 9 | -- - error_priority (number): Defaults to 60. 10 | -- - error_symbol (string): Defaults to 'E'. 11 | -- - hint_highlight (string): Defaults to 'CocHintSign'. 12 | -- - hint_priority (number): Defaults to 30. 13 | -- - hint_symbol (string): Defaults to 'H'. 14 | -- - info_highlight (string): Defaults to 'CocInfoSign'. 15 | -- - info_priority (number): Defaults to 40. 16 | -- - info_symbol (string): Defaults to 'I'. 17 | -- - warn_highlight (string): Defaults to 'CocWarningSign'. 18 | -- - warn_priority (number): Defaults to 50. 19 | -- - warn_symbol (string): Defaults to 'W'. 20 | -- - severities (string[]): A list of severities for which diagnostic 21 | -- signs will be shown. Defaults to {'error', 'hint', 'info', 'warn'}. 22 | 23 | local api = vim.api 24 | local fn = vim.fn 25 | local scrollview = require('scrollview') 26 | local utils = require('scrollview.utils') 27 | local copy = utils.copy 28 | local to_bool = utils.to_bool 29 | 30 | local M = {} 31 | 32 | function M.setup(config) 33 | if api.nvim_create_autocmd == nil then 34 | return 35 | end 36 | 37 | config = config or {} 38 | config = copy(config) -- create a copy, since this is modified 39 | 40 | local defaults = { 41 | enabled = true, 42 | error_highlight = 'CocErrorSign', 43 | error_priority = 60, 44 | error_symbol = 'E', 45 | hint_highlight = 'CocHintSign', 46 | hint_priority = 30, 47 | hint_symbol = 'H', 48 | info_highlight = 'CocInfoSign', 49 | info_priority = 40, 50 | info_symbol = 'I', 51 | warn_highlight = 'CocWarningSign', 52 | warn_priority = 50, 53 | warn_symbol = 'W', 54 | severities = {'error', 'hint', 'info', 'warn'}, 55 | } 56 | 57 | for key, val in pairs(defaults) do 58 | if config[key] == nil then 59 | config[key] = val 60 | end 61 | end 62 | 63 | local group = 'coc' 64 | scrollview.register_sign_group(group) 65 | 66 | local spec_data = {} 67 | for _, severity in ipairs(config.severities) do 68 | local value 69 | if severity == 'error' then 70 | value = { 71 | 'error', 72 | config.error_priority, 73 | config.error_symbol, 74 | config.error_highlight 75 | } 76 | elseif severity == 'hint' then 77 | value = { 78 | 'hint', 79 | config.hint_priority, 80 | config.hint_symbol, 81 | config.hint_highlight 82 | } 83 | elseif severity == 'info' then 84 | value = { 85 | 'info', 86 | config.info_priority, 87 | config.info_symbol, 88 | config.info_highlight 89 | } 90 | elseif severity == 'warn' then 91 | value = { 92 | 'warn', 93 | config.warn_priority, 94 | config.warn_symbol, 95 | config.warn_highlight 96 | } 97 | end 98 | if value ~= nil then 99 | local key = ({error = 'E', hint = 'H', info = 'I', warn = 'W'})[severity] 100 | spec_data[key] = value 101 | end 102 | end 103 | if vim.tbl_isempty(spec_data) then return end 104 | local names = {} -- maps severity to registration name 105 | for severity, item in pairs(spec_data) do 106 | local variant, priority, symbol, highlight = unpack(item) 107 | local registration = scrollview.register_sign_spec({ 108 | group = group, 109 | highlight = highlight, 110 | priority = priority, 111 | symbol = symbol, 112 | variant = variant, 113 | }) 114 | names[severity] = registration.name 115 | end 116 | scrollview.set_sign_group_state(group, config.enabled) 117 | 118 | scrollview.set_sign_group_callback(group, function() 119 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 120 | local bufnr = api.nvim_win_get_buf(winid) 121 | for _, name in pairs(names) do 122 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 123 | -- Check if coc_diagnostic_info is set for the buffer. This will not 124 | -- be set if e.g., diagnosticToggle or diagnosticToggleBuffer were 125 | -- used to disable diagnostics. However, we can't use that variable's 126 | -- contents to get diagnostic info. It only has the number of 127 | -- diagnostics of each severity, and the minimum line number that 128 | -- there is a diagnostic for each severity. 129 | if vim.b[bufnr].coc_diagnostic_info == nil then 130 | vim.b[bufnr][name] = {} 131 | end 132 | end 133 | end 134 | end) 135 | 136 | -- The last updated buffers, reset on each CocDiagnosticChange. This is a 137 | -- dictionary used as a set. 138 | local active_bufnrs = {} 139 | 140 | api.nvim_create_autocmd('User', { 141 | pattern = 'CocDiagnosticChange', 142 | callback = function() 143 | -- CocActionAsync('diagnosticList') is used intentionally instead of 144 | -- CocAction('diagnosticList'). Using the latter results in an error when 145 | -- CocAction('diagnosticRefresh') is called. Although that is not used by 146 | -- nvim-scrollview, as CocActionAsync('diagnosticRefresh') is used 147 | -- instead, the possibility of the error elsewhere (e.g., other plugins, 148 | -- user configs) is avoided by using the asynchronous approach for 149 | -- getting the diagnostic list. coc.nvim #4806 150 | if to_bool(vim.fn.exists('*CocActionAsync')) then 151 | fn.CocActionAsync('diagnosticList', function(err, diagnostic_list) 152 | -- Clear diagnostic info for existing buffers. 153 | for bufnr, _ in pairs(active_bufnrs) do 154 | for _, name in pairs(names) do 155 | if to_bool(vim.fn.bufexists(bufnr)) then 156 | -- luacheck: ignore 122 (setting read-only field b.?.? of 157 | -- global vim) 158 | vim.b[bufnr][name] = {} 159 | end 160 | end 161 | end 162 | active_bufnrs = {} 163 | if err ~= vim.NIL then return end 164 | -- See Coc's src/diagnostic/util.ts::getSeverityName for severity 165 | -- names. 166 | local lookup = { 167 | Hint = 'H', 168 | Error = 'E', 169 | Information = 'I', 170 | Warning = 'W', 171 | } 172 | -- 'diagnostics' maps buffer numbers to a mapping of severity ('H', 173 | -- 'E', 'I', 'W') to line numbers. 174 | local diagnostics = {} 175 | for _, item in ipairs(diagnostic_list) do 176 | local uri = item.location.uri 177 | local bufnr = vim.uri_to_bufnr(uri) 178 | active_bufnrs[bufnr] = true 179 | if diagnostics[bufnr] == nil then 180 | diagnostics[bufnr] = {H = {}, E = {}, I = {}, W = {}} 181 | end 182 | for lnum = item.lnum, item.end_lnum do 183 | local key = lookup[item.severity] 184 | if key ~= nil then 185 | table.insert(diagnostics[bufnr][key], lnum) 186 | end 187 | end 188 | end 189 | for bufnr, lines_lookup in pairs(diagnostics) do 190 | for severity, lines in pairs(lines_lookup) do 191 | local name = names[severity] 192 | if name ~= nil then 193 | vim.b[bufnr][name] = lines 194 | end 195 | end 196 | end 197 | -- Checking whether the sign group is active is deferred to here so 198 | -- that the proper coc diagnostics state is maintained even when the 199 | -- sign group is inactive. This way, signs will be properly set when 200 | -- the sign group is enabled. 201 | if scrollview.is_sign_group_active(group) then 202 | scrollview.refresh() 203 | end 204 | end) 205 | end 206 | end 207 | }) 208 | 209 | -- Refresh diagnostics to trigger CocDiagnosticChange (otherwise existing 210 | -- diagnostics wouldn't be reflected on the scrollbar until the next 211 | -- CocDiagnosticChange). 212 | pcall(function() 213 | vim.fn.CocActionAsync('diagnosticRefresh') 214 | end) 215 | end 216 | 217 | return M 218 | -------------------------------------------------------------------------------- /lua/scrollview/contrib/gitsigns.lua: -------------------------------------------------------------------------------- 1 | -- Requirements: 2 | -- - gitsigns.nvim (https://github.com/lewis6991/gitsigns.nvim) 3 | -- Usage: 4 | -- require('scrollview.contrib.gitsigns').setup([{config}]) 5 | -- {config} is an optional table with the following attributes: 6 | -- - add_highlight (string): Defaults to a value from gitsigns config 7 | -- when available, otherwise 'DiffAdd'. 8 | -- - add_priority (number): Defaults to 90. 9 | -- - add_symbol (string): Defaults to a value from gitsigns config when 10 | -- available, otherwise box drawing heavy vertical. 11 | -- - change_highlight (string): Defaults to a value from gitsigns config 12 | -- when available, otherwise 'DiffChange'. 13 | -- - change_priority (number): Defaults to 90. 14 | -- - change_symbol (string): Defaults to a value from gitsigns config 15 | -- when available, otherwise box drawing heavy vertical. 16 | -- - delete_highlight (string): Defaults to a value from gitsigns config 17 | -- when available, otherwise 'DiffDelete'. 18 | -- - delete_priority (number): Defaults to 90. 19 | -- - delete_symbol (string): Defaults to a value from gitsigns config 20 | -- when available, otherwise lower one-eigth block. 21 | -- - enabled (boolean): Whether signs are enabled immediately. If false, 22 | -- use ':ScrollViewEnable gitsigns' to enable later. Defaults to true. 23 | -- - hide_full_add (boolean): Whether to hide signs for a hunk if the 24 | -- hunk lines cover the entire buffer. Defaults to true. 25 | -- - only_first_line: Whether a sign is shown only for the first line of 26 | -- each hunk. Defaults to false. 27 | -- The setup() function should be called after gitsigns.setup(). 28 | 29 | local api = vim.api 30 | local fn = vim.fn 31 | local scrollview = require('scrollview') 32 | local utils = require('scrollview.utils') 33 | local copy = utils.copy 34 | local to_bool = utils.to_bool 35 | 36 | local M = {} 37 | 38 | function M.setup(config) 39 | config = config or {} 40 | config = copy(config) -- create a copy, since this is modified 41 | 42 | local defaults = { 43 | enabled = true, 44 | hide_full_add = true, 45 | only_first_line = false, 46 | add_priority = 90, 47 | change_priority = 90, 48 | delete_priority = 90, 49 | } 50 | 51 | -- Try setting highlight and symbol defaults from gitsigns config. 52 | pcall(function() 53 | local signs = require('gitsigns.config').config.signs 54 | defaults.add_highlight = signs.add.hl 55 | defaults.change_highlight = signs.change.hl 56 | defaults.delete_highlight = signs.delete.hl 57 | defaults.add_symbol = signs.add.text 58 | defaults.change_symbol = signs.change.text 59 | defaults.delete_symbol = signs.delete.text 60 | end) 61 | 62 | -- Try setting highlight and symbol defaults from gitsigns defaults. 63 | pcall(function() 64 | local default = require('gitsigns.config').schema.signs.default 65 | defaults.add_highlight = defaults.add_highlight or default.add.hl 66 | defaults.change_highlight = defaults.change_highlight or default.change.hl 67 | defaults.delete_highlight = defaults.delete_highlight or default.delete.hl 68 | defaults.add_symbol = defaults.add_symbol or default.add.text 69 | defaults.change_symbol = defaults.change_symbol or default.change.text 70 | defaults.delete_symbol = defaults.delete_symbol or default.delete.text 71 | end) 72 | 73 | -- Try setting highlight and symbol defaults from fixed values. 74 | defaults.add_highlight = defaults.add_highlight or 'DiffAdd' 75 | defaults.change_highlight = defaults.change_highlight or 'DiffChange' 76 | defaults.delete_highlight = defaults.delete_highlight or 'DiffDelete' 77 | defaults.add_symbol = defaults.add_symbol or fn.nr2char(0x2503) 78 | defaults.change_symbol = defaults.change_symbol or fn.nr2char(0x2503) 79 | defaults.delete_symbol = defaults.delete_symbol or fn.nr2char(0x2581) 80 | 81 | -- Set missing config values with defaults. 82 | for key, val in pairs(defaults) do 83 | if config[key] == nil then 84 | config[key] = val 85 | end 86 | end 87 | 88 | local group = 'gitsigns' 89 | scrollview.register_sign_group(group) 90 | 91 | local add = scrollview.register_sign_spec({ 92 | extend = true, 93 | group = group, 94 | highlight = config.add_highlight, 95 | priority = config.add_priority, 96 | symbol = config.add_symbol, 97 | variant = 'add', 98 | }).name 99 | 100 | local change = scrollview.register_sign_spec({ 101 | extend = true, 102 | group = group, 103 | highlight = config.change_highlight, 104 | priority = config.change_priority, 105 | symbol = config.change_symbol, 106 | variant = 'change', 107 | }).name 108 | 109 | local delete = scrollview.register_sign_spec({ 110 | extend = true, 111 | group = group, 112 | highlight = config.delete_highlight, 113 | priority = config.delete_priority, 114 | symbol = config.delete_symbol, 115 | variant = 'delete', 116 | }).name 117 | 118 | scrollview.set_sign_group_state(group, config.enabled) 119 | 120 | -- The last updated buffers, reset on each GitSignsUpdate. This is a 121 | -- dictionary used as a set. 122 | local active_bufnrs = {} 123 | 124 | api.nvim_create_autocmd('User', { 125 | pattern = 'GitSignsUpdate', 126 | callback = function() 127 | -- WARN: Ordinarily, the code that follows would be handled in a 128 | -- ScrollViewRefresh User autocommand callback, and code here would just 129 | -- be a call to ScrollViewRefresh. That approach is avoided for better 130 | -- handling of the 'hide_full_add' scenario that avoids an entire column 131 | -- being covered (the ScrollViewRefresh User autocommand approach could 132 | -- result in brief occurrences of full coverage when hide_full_add=true). 133 | local gitsigns = require('gitsigns') 134 | -- Clear gitsigns info for existing buffers. 135 | for bufnr, _ in pairs(active_bufnrs) do 136 | if to_bool(vim.fn.bufexists(bufnr)) then 137 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 138 | vim.b[bufnr][add] = {} 139 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 140 | vim.b[bufnr][change] = {} 141 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 142 | vim.b[bufnr][delete] = {} 143 | end 144 | end 145 | for _, tabpage in ipairs(api.nvim_list_tabpages()) do 146 | local tabwins = api.nvim_tabpage_list_wins(tabpage) 147 | for _, winid in ipairs(tabwins) do 148 | local bufnr = api.nvim_win_get_buf(winid) 149 | local hunks = gitsigns.get_hunks(bufnr) or {} 150 | if not vim.tbl_isempty(hunks) then 151 | active_bufnrs[bufnr] = true 152 | end 153 | local lines_add = {} 154 | local lines_change = {} 155 | local lines_delete = {} 156 | for _, hunk in ipairs(hunks) do 157 | if hunk.type == 'add' then 158 | local full = hunk.added.count >= api.nvim_buf_line_count(bufnr) 159 | if not config.hide_full_add or not full then 160 | local first = hunk.added.start 161 | local last = hunk.added.start 162 | if not config.only_first_line then 163 | last = last + hunk.added.count - 1 164 | end 165 | for line = first, last do 166 | table.insert(lines_add, line) 167 | end 168 | end 169 | elseif hunk.type == 'change' then 170 | -- WARN: A change hunk can be comprised of a change (the removed 171 | -- lines) and an add (lines added after the removed lines). #129 172 | local first = hunk.added.start 173 | local last = first 174 | if not config.only_first_line then 175 | last = last + hunk.added.count - 1 176 | if hunk.added.count > hunk.removed.count then 177 | last = last - (hunk.added.count - hunk.removed.count) 178 | end 179 | end 180 | for line = first, last do 181 | table.insert(lines_change, line) 182 | end 183 | if hunk.added.count > hunk.removed.count then 184 | first = hunk.added.start + hunk.removed.count 185 | last = first 186 | if not config.only_first_line then 187 | last = last + hunk.added.count - hunk.removed.count - 1 188 | end 189 | for line = first, last do 190 | table.insert(lines_add, line) 191 | end 192 | end 193 | elseif hunk.type == 'delete' then 194 | table.insert(lines_delete, hunk.added.start) 195 | end 196 | end 197 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 198 | vim.b[bufnr][add] = lines_add 199 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 200 | vim.b[bufnr][change] = lines_change 201 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 202 | vim.b[bufnr][delete] = lines_delete 203 | end 204 | end 205 | -- Checking whether the sign group is active is deferred to here so that 206 | -- the proper gitsigns state is maintained even when the sign group is 207 | -- inactive. This way, signs will be properly set when the sign group is 208 | -- enabled. 209 | if not scrollview.is_sign_group_active(group) then return end 210 | vim.cmd('silent! ScrollViewRefresh') 211 | end 212 | }) 213 | 214 | -- Refresh gitsigns to trigger GitSignsUpdate (otherwise existing signs 215 | -- from gitsigns wouldn't be reflected on the scrollbar until the next 216 | -- GitSignsUpdate). 217 | pcall(function() 218 | require('gitsigns').refresh() 219 | end) 220 | end 221 | 222 | return M 223 | -------------------------------------------------------------------------------- /lua/scrollview/signs/changelist.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | local PREVIOUS = 0 8 | local CURRENT = 1 9 | local NEXT = 2 10 | 11 | function M.init(enable) 12 | if api.nvim_create_autocmd == nil then 13 | return 14 | end 15 | 16 | local group = 'changelist' 17 | scrollview.register_sign_group(group) 18 | 19 | local spec_data = { 20 | [PREVIOUS] = { 21 | 'previous', 22 | vim.g.scrollview_changelist_previous_priority, 23 | vim.g.scrollview_changelist_previous_symbol, 24 | 'ScrollViewChangeListPrevious' 25 | }, 26 | [CURRENT] = { 27 | 'current', 28 | vim.g.scrollview_changelist_current_priority, 29 | vim.g.scrollview_changelist_current_symbol, 30 | 'ScrollViewChangeListCurrent' 31 | }, 32 | [NEXT] = { 33 | 'next', 34 | vim.g.scrollview_changelist_next_priority, 35 | vim.g.scrollview_changelist_next_symbol, 36 | 'ScrollViewChangeListNext' 37 | }, 38 | } 39 | local names = {} -- maps direction to registration name 40 | for direction, item in pairs(spec_data) do 41 | local variant, priority, symbol, highlight = unpack(item) 42 | local registration = scrollview.register_sign_spec({ 43 | group = group, 44 | highlight = highlight, 45 | priority = priority, 46 | symbol = symbol, 47 | variant = variant, 48 | }) 49 | names[direction] = registration.name 50 | end 51 | scrollview.set_sign_group_state(group, enable) 52 | 53 | -- Refresh scrollbars after jumping through the change list. 54 | scrollview.register_key_sequence_callback('g;', 'nv', scrollview.refresh) 55 | scrollview.register_key_sequence_callback('g,', 'nv', scrollview.refresh) 56 | 57 | scrollview.set_sign_group_callback(group, function() 58 | -- Track visited buffers, to prevent duplicate computation when multiple 59 | -- windows are showing the same buffer. 60 | local visited = {} 61 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 62 | local bufnr = api.nvim_win_get_buf(winid) 63 | if not visited[bufnr] then 64 | local bufvars = vim.b[bufnr] 65 | for direction, name in pairs(names) do 66 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 67 | bufvars[name] = {} 68 | local locations, position = unpack(fn.getchangelist(bufnr)) 69 | position = position + 1 70 | if direction == PREVIOUS 71 | and #locations > 0 72 | and position - 1 > 0 73 | and position - 1 <= #locations then 74 | bufvars[name] = {locations[position - 1].lnum} 75 | end 76 | if direction == CURRENT 77 | and #locations > 0 78 | and position > 0 79 | and position <= #locations then 80 | bufvars[name] = {locations[position].lnum} 81 | end 82 | if direction == NEXT 83 | and #locations > 0 84 | and position + 1 > 0 85 | and position + 1 <= #locations then 86 | bufvars[name] = {locations[position + 1].lnum} 87 | end 88 | end 89 | visited[bufnr] = true 90 | end 91 | end 92 | end) 93 | 94 | api.nvim_create_autocmd('InsertLeave', { 95 | callback = function() 96 | if not scrollview.is_sign_group_active(group) then return end 97 | scrollview.refresh() 98 | end 99 | }) 100 | 101 | api.nvim_create_autocmd('InsertEnter', { 102 | callback = function() 103 | if not scrollview.is_sign_group_active(group) then return end 104 | api.nvim_create_autocmd('TextChangedI', { 105 | callback = function() 106 | if not scrollview.is_sign_group_active(group) then return end 107 | scrollview.refresh() 108 | end, 109 | once = true 110 | }) 111 | end 112 | }) 113 | end 114 | 115 | return M 116 | -------------------------------------------------------------------------------- /lua/scrollview/signs/conflicts.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | local utils = require('scrollview.utils') 5 | 6 | local M = {} 7 | 8 | local TOP = 0 9 | local MIDDLE = 1 10 | local BOTTOM = 2 11 | 12 | function M.init(enable) 13 | if api.nvim_create_autocmd == nil then 14 | return 15 | end 16 | 17 | local group = 'conflicts' 18 | scrollview.register_sign_group(group) 19 | local spec_data = { 20 | [TOP] = { 21 | 'top', 22 | vim.g.scrollview_conflicts_top_priority, 23 | vim.g.scrollview_conflicts_top_symbol, 24 | 'ScrollViewConflictsTop' 25 | }, 26 | [MIDDLE] = { 27 | 'middle', 28 | vim.g.scrollview_conflicts_middle_priority, 29 | vim.g.scrollview_conflicts_middle_symbol, 30 | 'ScrollViewConflictsMiddle' 31 | }, 32 | [BOTTOM] = { 33 | 'bottom', 34 | vim.g.scrollview_conflicts_bottom_priority, 35 | vim.g.scrollview_conflicts_bottom_symbol, 36 | 'ScrollViewConflictsBottom' 37 | }, 38 | } 39 | local names = {} -- maps position to registration name 40 | for position, item in pairs(spec_data) do 41 | local variant, priority, symbol, highlight = unpack(item) 42 | local registration = scrollview.register_sign_spec({ 43 | group = group, 44 | highlight = highlight, 45 | priority = priority, 46 | symbol = symbol, 47 | variant = variant, 48 | }) 49 | names[position] = registration.name 50 | end 51 | scrollview.set_sign_group_state(group, enable) 52 | 53 | scrollview.set_sign_group_callback(group, function() 54 | -- Track visited buffers, to prevent duplicate computation when multiple 55 | -- windows are showing the same buffer. 56 | local visited = {} 57 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 58 | local bufnr = api.nvim_win_get_buf(winid) 59 | if not visited[bufnr] then 60 | local bufvars = vim.b[bufnr] 61 | local changedtick = bufvars.changedtick 62 | local changedtick_cached = 63 | bufvars.scrollview_conflicts_changedtick_cached 64 | local cache_hit = changedtick_cached == changedtick 65 | for position, name in pairs(names) do 66 | local lines = {} 67 | if cache_hit then 68 | lines = bufvars[name] 69 | else 70 | local line_count = api.nvim_buf_line_count(bufnr) 71 | for line = 1, line_count do 72 | local match = false 73 | local str = fn.getbufline(bufnr, line)[1] 74 | if position == TOP then 75 | match = vim.startswith(str, '<<<<<<< ') 76 | elseif position == MIDDLE then 77 | match = str == '=======' 78 | elseif position == BOTTOM then 79 | match = vim.startswith(str, '>>>>>>> ') 80 | else 81 | error('Unknown position: ' .. position) 82 | end 83 | if match then 84 | table.insert(lines, line) 85 | end 86 | end 87 | end 88 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 89 | bufvars[name] = lines 90 | end 91 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 92 | bufvars.scrollview_conflicts_changedtick_cached = changedtick 93 | visited[bufnr] = true 94 | end 95 | end 96 | end) 97 | 98 | api.nvim_create_autocmd('TextChangedI', { 99 | callback = function() 100 | if not scrollview.is_sign_group_active(group) then return end 101 | local bufnr = api.nvim_get_current_buf() 102 | local line = fn.line('.') 103 | local str = fn.getbufline(bufnr, line)[1] 104 | for position, name in pairs(names) do 105 | local expect_sign = nil 106 | if position == TOP then 107 | expect_sign = vim.startswith(str, '<<<<<<< ') 108 | elseif position == MIDDLE then 109 | expect_sign = str == '=======' 110 | elseif position == BOTTOM then 111 | expect_sign = vim.startswith(str, '>>>>>>> ') 112 | else 113 | error('Unknown position: ' .. position) 114 | end 115 | local idx = -1 116 | local lines = vim.b[bufnr][name] 117 | if lines ~= nil then 118 | idx = utils.binary_search(lines, line) 119 | if lines[idx] ~= line then 120 | idx = -1 121 | end 122 | end 123 | local has_sign = idx ~= -1 124 | if expect_sign ~= has_sign then 125 | scrollview.refresh() 126 | break 127 | end 128 | end 129 | end 130 | }) 131 | end 132 | 133 | return M 134 | -------------------------------------------------------------------------------- /lua/scrollview/signs/cursor.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | function M.init(enable) 8 | if api.nvim_create_autocmd == nil then 9 | return 10 | end 11 | 12 | local group = 'cursor' 13 | scrollview.register_sign_group(group) 14 | local registration = scrollview.register_sign_spec({ 15 | current_only = true, 16 | group = group, 17 | highlight = 'ScrollViewCursor', 18 | priority = vim.g.scrollview_cursor_priority, 19 | show_in_folds = true, 20 | symbol = vim.g.scrollview_cursor_symbol, 21 | }) 22 | local name = registration.name 23 | scrollview.set_sign_group_state(group, enable) 24 | 25 | scrollview.set_sign_group_callback(group, function() 26 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 27 | local bufnr = api.nvim_win_get_buf(winid) 28 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 29 | vim.b[bufnr][name] = {fn.line('.')} 30 | end 31 | end) 32 | 33 | api.nvim_create_autocmd({'CursorMoved', 'CursorMovedI'}, { 34 | callback = function() 35 | if not scrollview.is_sign_group_active(group) then return end 36 | local lines = vim.b[name] 37 | if lines == nil or lines[1] ~= fn.line('.') then 38 | scrollview.refresh() 39 | end 40 | end 41 | }) 42 | end 43 | 44 | return M 45 | -------------------------------------------------------------------------------- /lua/scrollview/signs/diagnostics.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | local utils = require('scrollview.utils') 5 | local to_bool = utils.to_bool 6 | 7 | local M = {} 8 | 9 | function M.init(enable) 10 | if api.nvim_create_autocmd == nil or vim.diagnostic == nil then 11 | return 12 | end 13 | 14 | local group = 'diagnostics' 15 | scrollview.register_sign_group(group) 16 | local spec_data = {} 17 | for _, severity in ipairs(vim.g.scrollview_diagnostics_severities) do 18 | local value 19 | if severity == vim.diagnostic.severity.ERROR then 20 | value = { 21 | 'error', 22 | vim.g.scrollview_diagnostics_error_priority, 23 | vim.g.scrollview_diagnostics_error_symbol, 24 | 'ScrollViewDiagnosticsError' 25 | } 26 | elseif severity == vim.diagnostic.severity.HINT then 27 | value = { 28 | 'hint', 29 | vim.g.scrollview_diagnostics_hint_priority, 30 | vim.g.scrollview_diagnostics_hint_symbol, 31 | 'ScrollViewDiagnosticsHint' 32 | } 33 | elseif severity == vim.diagnostic.severity.INFO then 34 | value = { 35 | 'info', 36 | vim.g.scrollview_diagnostics_info_priority, 37 | vim.g.scrollview_diagnostics_info_symbol, 38 | 'ScrollViewDiagnosticsInfo' 39 | } 40 | elseif severity == vim.diagnostic.severity.WARN then 41 | value = { 42 | 'warn', 43 | vim.g.scrollview_diagnostics_warn_priority, 44 | vim.g.scrollview_diagnostics_warn_symbol, 45 | 'ScrollViewDiagnosticsWarn' 46 | } 47 | end 48 | if value ~= nil then 49 | spec_data[severity] = value 50 | end 51 | end 52 | if vim.tbl_isempty(spec_data) then return end 53 | local names = {} -- maps severity to registration name 54 | for severity, item in pairs(spec_data) do 55 | local variant, priority, symbol, highlight = unpack(item) 56 | local registration = scrollview.register_sign_spec({ 57 | group = group, 58 | highlight = highlight, 59 | priority = priority, 60 | symbol = symbol, 61 | variant = variant, 62 | }) 63 | names[severity] = registration.name 64 | end 65 | scrollview.set_sign_group_state(group, enable) 66 | 67 | scrollview.set_sign_group_callback(group, function() 68 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 69 | local bufnr = api.nvim_win_get_buf(winid) 70 | local diagnostics_enabled 71 | -- vim.diagnostic.is_disabled was deprecated in Neovim v0.10. 72 | if vim.diagnostic.is_enabled ~= nil then 73 | diagnostics_enabled = vim.diagnostic.is_enabled({bufnr = bufnr}) 74 | else 75 | diagnostics_enabled = not vim.diagnostic.is_disabled(bufnr) 76 | end 77 | if not diagnostics_enabled then 78 | for _, name in pairs(names) do 79 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 80 | vim.b[bufnr][name] = {} 81 | end 82 | else 83 | local lookup = {} -- maps diagnostic type to a list of line numbers 84 | for severity, _ in pairs(names) do 85 | lookup[severity] = {} 86 | end 87 | local diagnostics = vim.diagnostic.get(bufnr) 88 | local line_count = vim.api.nvim_buf_line_count(bufnr) 89 | for _, x in ipairs(diagnostics) do 90 | if lookup[x.severity] ~= nil then 91 | -- Diagnostics can be reported for lines beyond the last line in 92 | -- the buffer. Treat these as if they were reported for the last 93 | -- line, matching what Neovim does for displaying diagnostic 94 | -- signs in the sign column. 95 | local lnum = math.min(x.lnum + 1, line_count) 96 | table.insert(lookup[x.severity], lnum) 97 | end 98 | end 99 | for severity, lines in pairs(lookup) do 100 | local name = names[severity] 101 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 102 | vim.b[bufnr][name] = lines 103 | end 104 | end 105 | end 106 | end) 107 | 108 | api.nvim_create_autocmd('DiagnosticChanged', { 109 | callback = function() 110 | if not scrollview.is_sign_group_active(group) then return end 111 | if fn.mode() ~= 'i' or vim.diagnostic.config().update_in_insert then 112 | -- Refresh scrollbars immediately when update_in_insert is set or the 113 | -- current mode is not insert mode. 114 | scrollview.refresh() 115 | else 116 | -- Refresh scrollbars once leaving insert mode. Overwrite an existing 117 | -- autocmd configured to already do this. 118 | local augroup = api.nvim_create_augroup('scrollview_diagnostics', { 119 | clear = true 120 | }) 121 | api.nvim_create_autocmd('InsertLeave', { 122 | group = augroup, 123 | callback = function() 124 | scrollview.refresh() 125 | end, 126 | once = true, 127 | }) 128 | end 129 | end 130 | }) 131 | 132 | api.nvim_create_autocmd('CmdlineLeave', { 133 | callback = function() 134 | if not scrollview.is_sign_group_active(group) then return end 135 | if to_bool(vim.v.event.abort) then 136 | return 137 | end 138 | if fn.expand('') ~= ':' then 139 | return 140 | end 141 | -- Refresh scrollbars after the following commands. 142 | -- vim.diagnostic.enable() 143 | -- vim.diagnostic.disable() 144 | -- WARN: CmdlineLeave is not executed for command mappings (). 145 | -- WARN: CmdlineLeave is not executed for commands executed from Lua 146 | -- (e.g., vim.cmd('help')). 147 | local cmdline = fn.getcmdline() 148 | if string.match(cmdline, 'vim%.diagnostic%.enable') ~= nil 149 | or string.match(cmdline, 'vim%.diagnostic%.disable') ~= nil then 150 | scrollview.refresh() 151 | end 152 | end 153 | }) 154 | end 155 | 156 | return M 157 | -------------------------------------------------------------------------------- /lua/scrollview/signs/folds.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | function M.init(enable) 8 | if api.nvim_create_autocmd == nil then 9 | return 10 | end 11 | 12 | local group = 'folds' 13 | scrollview.register_sign_group(group) 14 | local registration = scrollview.register_sign_spec({ 15 | group = group, 16 | highlight = 'ScrollViewFolds', 17 | priority = vim.g.scrollview_folds_priority, 18 | show_in_folds = true, -- so that cursor sign shows on fold start 19 | symbol = vim.g.scrollview_folds_symbol, 20 | type = 'w', 21 | }) 22 | local name = registration.name 23 | scrollview.set_sign_group_state(group, enable) 24 | 25 | scrollview.set_sign_group_callback(group, function() 26 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 27 | local bufnr = api.nvim_win_get_buf(winid) 28 | local lines = scrollview.with_win_workspace(winid, function() 29 | local result = {} 30 | -- Linewise computation can be faster when there are many folds. See 31 | -- the comment in scrollview.lua::virtual_line_count for details. The 32 | -- same multiple, .006, is used here (it was not separately optimized 33 | -- for this scenario). 34 | local line_count = api.nvim_buf_line_count(bufnr) 35 | local threshold = math.floor(line_count * .006) 36 | if scrollview.fold_count_exceeds(1, line_count, threshold) then 37 | -- Linewise. 38 | local line = 1 39 | while line <= line_count do 40 | local foldclosedend = fn.foldclosedend(line) 41 | if foldclosedend ~= -1 then 42 | table.insert(result, line) 43 | line = foldclosedend 44 | end 45 | line = line + 1 46 | end 47 | else 48 | -- Foldwise. 49 | vim.cmd('keepjumps normal! gg') 50 | while true do 51 | local line = fn.line('.') 52 | if fn.foldclosed(line) ~= -1 then 53 | table.insert(result, line) 54 | end 55 | vim.cmd('keepjumps normal! zj') 56 | if line == fn.line('.') then 57 | break 58 | end 59 | end 60 | end 61 | return result 62 | end) 63 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 64 | vim.w[winid][name] = lines 65 | end 66 | end) 67 | end 68 | 69 | return M 70 | -------------------------------------------------------------------------------- /lua/scrollview/signs/indent.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | local utils = require('scrollview.utils') 5 | 6 | local M = {} 7 | 8 | local SPACES = 0 9 | local TABS = 1 10 | 11 | local should_show = function(option, expandtab) 12 | if option == 'always' then 13 | return true 14 | elseif option == 'never' then 15 | return false 16 | elseif option == 'expandtab' then 17 | return expandtab 18 | elseif option == 'noexpandtab' then 19 | return not expandtab 20 | else 21 | -- Unknown option. Don't show. 22 | return false 23 | end 24 | end 25 | 26 | function M.init(enable) 27 | if api.nvim_create_autocmd == nil then 28 | return 29 | end 30 | 31 | local group = 'indent' 32 | scrollview.register_sign_group(group) 33 | local names = { 34 | [SPACES] = scrollview.register_sign_spec({ 35 | group = group, 36 | highlight = 'ScrollViewIndentSpaces', 37 | priority = vim.g.scrollview_indent_spaces_priority, 38 | symbol = vim.g.scrollview_indent_spaces_symbol, 39 | variant = 'spaces', 40 | }).name, 41 | [TABS] = scrollview.register_sign_spec({ 42 | group = group, 43 | highlight = 'ScrollViewIndentTabs', 44 | priority = vim.g.scrollview_indent_tabs_priority, 45 | symbol = vim.g.scrollview_indent_tabs_symbol, 46 | variant = 'tabs', 47 | }).name, 48 | } 49 | 50 | scrollview.set_sign_group_state(group, enable) 51 | 52 | scrollview.set_sign_group_callback(group, function() 53 | -- Track visited buffers, to prevent duplicate computation when multiple 54 | -- windows are showing the same buffer. 55 | local visited = {} 56 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 57 | local bufnr = api.nvim_win_get_buf(winid) 58 | local expandtab = api.nvim_buf_get_option(bufnr, 'expandtab') 59 | if not visited[bufnr] then 60 | local bufvars = vim.b[bufnr] 61 | local lines = { 62 | [SPACES] = {}, 63 | [TABS] = {}, 64 | } 65 | local changedtick = bufvars.changedtick 66 | local changedtick_cached = bufvars.scrollview_indent_changedtick_cached 67 | local spaces_condition_cached = 68 | bufvars.scrollview_indent_spaces_condition_cached 69 | local tabs_condition_cached = 70 | bufvars.scrollview_indent_tabs_condition_cached 71 | local expandtab_cached = 72 | bufvars.scrollview_indent_expandtab_option_cached 73 | local cache_hit = changedtick_cached == changedtick 74 | and expandtab_cached == expandtab 75 | and spaces_condition_cached == vim.g.scrollview_indent_spaces_condition 76 | and tabs_condition_cached == vim.g.scrollview_indent_tabs_condition 77 | if cache_hit then 78 | lines[SPACES] = bufvars.scrollview_indent_spaces_cached 79 | lines[TABS] = bufvars.scrollview_indent_tabs_cached 80 | else 81 | local line_count = api.nvim_buf_line_count(bufnr) 82 | local show_spaces_signs = 83 | should_show(vim.g.scrollview_indent_spaces_condition, expandtab) 84 | local show_tabs_signs = 85 | should_show(vim.g.scrollview_indent_tabs_condition, expandtab) 86 | for line = 1, line_count do 87 | local str = fn.getbufline(bufnr, line)[1] 88 | local sub = string.sub(str, 1, 1) 89 | if sub == ' ' then 90 | if show_spaces_signs then 91 | table.insert(lines[SPACES], line) 92 | end 93 | elseif sub == '\t' then 94 | if show_tabs_signs then 95 | table.insert(lines[TABS], line) 96 | end 97 | end 98 | end 99 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 100 | bufvars.scrollview_indent_expandtab_option_cached = expandtab 101 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 102 | bufvars.scrollview_indent_spaces_condition_cached = 103 | vim.g.scrollview_indent_spaces_condition 104 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 105 | bufvars.scrollview_indent_tabs_condition_cached = 106 | vim.g.scrollview_indent_tabs_condition 107 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 108 | bufvars.scrollview_indent_changedtick_cached = changedtick 109 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 110 | bufvars.scrollview_indent_spaces_cached = lines[SPACES] 111 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 112 | bufvars.scrollview_indent_tabs_cached = lines[TABS] 113 | end 114 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 115 | bufvars[names[SPACES]] = lines[SPACES] 116 | bufvars[names[TABS]] = lines[TABS] 117 | visited[bufnr] = true 118 | end 119 | end 120 | end) 121 | 122 | api.nvim_create_autocmd('TextChangedI', { 123 | callback = function() 124 | if not scrollview.is_sign_group_active(group) then return end 125 | local bufnr = api.nvim_get_current_buf() 126 | local expandtab = api.nvim_buf_get_option(bufnr, 'expandtab') 127 | local line = fn.line('.') 128 | local str = fn.getbufline(bufnr, line)[1] 129 | local sub = string.sub(str, 1, 1) 130 | for _, mode in ipairs({SPACES, TABS}) do 131 | local expect_sign = false 132 | if mode == SPACES then 133 | local show_signs = 134 | should_show(vim.g.scrollview_indent_spaces_condition, expandtab) 135 | expect_sign = sub == ' ' and show_signs 136 | elseif mode == TABS then 137 | local show_tabs = 138 | should_show(vim.g.scrollview_indent_tabs_condition, expandtab) 139 | expect_sign = sub == '\t' and show_tabs 140 | else 141 | error('Unknown mode: ' .. mode) 142 | end 143 | local lines = vim.b[bufnr][names[mode]] 144 | local idx = -1 145 | if lines ~= nil then 146 | idx = utils.binary_search(lines, line) 147 | if lines[idx] ~= line then 148 | idx = -1 149 | end 150 | end 151 | local has_sign = idx ~= -1 152 | if expect_sign ~= has_sign then 153 | scrollview.refresh() 154 | break 155 | end 156 | end 157 | end 158 | }) 159 | 160 | api.nvim_create_autocmd('OptionSet', { 161 | pattern = 'expandtab', 162 | callback = function() 163 | if not scrollview.is_sign_group_active(group) then return end 164 | if vim.g.scrollview_indent_spaces_condition == 'expandtab' 165 | or vim.g.scrollview_indent_spaces_condition == 'noexpandtab' 166 | or vim.g.scrollview_indent_tabs_condition == 'expandtab' 167 | or vim.g.scrollview_indent_tabs_condition == 'noexpandtab' then 168 | scrollview.refresh() 169 | end 170 | end 171 | }) 172 | end 173 | 174 | return M 175 | -------------------------------------------------------------------------------- /lua/scrollview/signs/keywords.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | local SCOPE_FULL = 0 8 | local SCOPE_COMMENTS = 1 9 | local SCOPE_AUTO = 2 10 | 11 | -- Check if the text between idx1 and idx2 from the specified line is in a 12 | -- comment, using Treesitter. 13 | local check_in_comment_ts = function(bufnr, line, idx1, idx2) 14 | local got_parser, parser = pcall(vim.treesitter.get_parser, bufnr) 15 | if not got_parser or not parser then return nil end 16 | local trees = parser:trees() 17 | if #trees == 0 then return nil end 18 | for _, tree in ipairs(trees) do 19 | local root = tree:root() 20 | local node = root:named_descendant_for_range( 21 | line - 1, idx1 - 1, line - 1, idx2 - 1) 22 | while node ~= nil do 23 | if node:type() == 'comment' then 24 | return true 25 | end 26 | node = node:parent() 27 | end 28 | end 29 | return false 30 | end 31 | 32 | -- Escape the pattern characters in a string. 33 | local escape = function(str) 34 | return str:gsub("([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") 35 | end 36 | 37 | -- Check if the substring sub is in a commment in str, using 'commentstring'. 38 | local check_in_comment_cs = function(commentstring, str, sub) 39 | if not string.find(commentstring, '%%s') then 40 | return nil 41 | end 42 | commentstring = commentstring:gsub('%s+', '') -- remove whitespace 43 | commentstring = escape(commentstring) 44 | local pattern = commentstring:gsub('%%%%s', '.*' .. escape(sub) .. '.*', 1) 45 | return string.find(str, pattern) ~= nil 46 | end 47 | 48 | function M.init(enable) 49 | if api.nvim_create_autocmd == nil then 50 | return 51 | end 52 | 53 | local group = 'keywords' 54 | scrollview.register_sign_group(group) 55 | local keyword_groups = {} 56 | local registration_lookup = {} -- maps keyword to registration 57 | local patterns_lookup = {} -- maps keyword to patterns 58 | local scope_lookup = {} -- maps keyword to scope 59 | for _, key in ipairs(vim.fn.eval('keys(g:)')) do 60 | local keyword_group = key:match('^scrollview_keywords_(.+)_spec$') 61 | if keyword_group ~= nil then 62 | local spec = vim.g['scrollview_keywords_' .. keyword_group .. '_spec'] 63 | if type(spec) == 'table' then 64 | if not vim.tbl_isempty(spec.patterns) then 65 | local scope = SCOPE_AUTO 66 | if spec.scope ~= nil then 67 | scope = ({ 68 | full = SCOPE_FULL, 69 | auto = SCOPE_AUTO, 70 | comments = SCOPE_COMMENTS, 71 | })[spec.scope] 72 | end 73 | table.insert(keyword_groups, keyword_group) 74 | patterns_lookup[keyword_group] = spec.patterns 75 | local registration = scrollview.register_sign_spec({ 76 | group = group, 77 | highlight = spec.highlight, 78 | priority = spec.priority, 79 | symbol = spec.symbol, 80 | variant = keyword_group, 81 | }) 82 | registration_lookup[keyword_group] = registration 83 | scope_lookup[keyword_group] = scope 84 | end 85 | end 86 | end 87 | end 88 | scrollview.set_sign_group_state(group, enable) 89 | 90 | scrollview.set_sign_group_callback(group, function() 91 | -- Track visited buffers, to prevent duplicate computation when multiple 92 | -- windows are showing the same buffer. 93 | local visited = {} 94 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 95 | local bufnr = api.nvim_win_get_buf(winid) 96 | local commentstring = api.nvim_buf_get_option(bufnr, 'commentstring') 97 | -- Don't update when in insert mode. This way, pressing 'o' to start a 98 | -- new line won't trigger a new sign when there is indentation. 99 | local mode = api.nvim_win_call(winid, fn.mode) 100 | if not visited[bufnr] and mode ~= 'i' then 101 | local bufvars = vim.b[bufnr] 102 | local lines_lookup = {} 103 | for _, keyword_group in ipairs(keyword_groups) do 104 | lines_lookup[keyword_group] = {} 105 | end 106 | local changedtick = bufvars.changedtick 107 | local changedtick_cached = bufvars.scrollview_keywords_changedtick_cached 108 | local commentstring_cached = bufvars.scrollview_keywords_commentstring_cached 109 | local cache_hit = changedtick_cached == changedtick 110 | and commentstring_cached == commentstring 111 | if cache_hit then 112 | for _, keyword_group in ipairs(keyword_groups) do 113 | lines_lookup[keyword_group] = 114 | bufvars['scrollview_keywords_group_' .. keyword_group .. '_cached'] 115 | end 116 | else 117 | local line_count = api.nvim_buf_line_count(bufnr) 118 | for line = 1, line_count do 119 | local str = fn.getbufline(bufnr, line)[1] 120 | for _, keyword_group in ipairs(keyword_groups) do 121 | local scope = scope_lookup[keyword_group] 122 | local patterns = patterns_lookup[keyword_group] 123 | for _, pattern in ipairs(patterns) do 124 | local start = 1 125 | while true do 126 | local idx1, idx2 = string.find(str, pattern, start) 127 | if not idx1 then break end 128 | local match = false 129 | if scope == SCOPE_FULL then 130 | match = true 131 | else 132 | local in_comment_ts = check_in_comment_ts( 133 | bufnr, line, idx1, idx2) 134 | if in_comment_ts then 135 | match = true 136 | else 137 | local in_comment_cs = check_in_comment_cs( 138 | commentstring, str, string.sub(str, idx1, idx2)) 139 | if in_comment_cs then 140 | match = true 141 | else 142 | -- The string wasn't found in a comment using 143 | -- Treesitter nor 'commentstring'. 144 | if scope == SCOPE_AUTO then 145 | match = in_comment_ts == nil and in_comment_cs == nil 146 | elseif scope == SCOPE_COMMENTS then 147 | match = false 148 | else 149 | error('Unknown scope: ' .. scope) 150 | end 151 | end 152 | end 153 | end 154 | if match then 155 | table.insert(lines_lookup[keyword_group], line) 156 | break 157 | end 158 | start = idx2 + 1 159 | end 160 | end 161 | end 162 | end 163 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 164 | bufvars.scrollview_keywords_changedtick_cached = changedtick 165 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 166 | bufvars.scrollview_keywords_commentstring_cached = commentstring 167 | for _, keyword_group in ipairs(keyword_groups) do 168 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 169 | bufvars['scrollview_keywords_group_' .. keyword_group .. '_cached'] 170 | = lines_lookup[keyword_group] 171 | end 172 | end 173 | for _, keyword_group in ipairs(keyword_groups) do 174 | local registration = registration_lookup[keyword_group] 175 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 176 | bufvars[registration.name] = lines_lookup[keyword_group] 177 | end 178 | visited[bufnr] = true 179 | end 180 | end 181 | end) 182 | 183 | api.nvim_create_autocmd('InsertLeave', { 184 | callback = function() 185 | if not scrollview.is_sign_group_active(group) then return end 186 | scrollview.refresh() 187 | end 188 | }) 189 | 190 | api.nvim_create_autocmd('OptionSet', { 191 | pattern = 'commentstring', 192 | callback = function() 193 | if not scrollview.is_sign_group_active(group) then return end 194 | scrollview.refresh() 195 | end 196 | }) 197 | end 198 | 199 | return M 200 | -------------------------------------------------------------------------------- /lua/scrollview/signs/latestchange.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | function M.init(enable) 8 | if api.nvim_create_autocmd == nil then 9 | return 10 | end 11 | 12 | local group = 'latestchange' 13 | scrollview.register_sign_group(group) 14 | local registration = scrollview.register_sign_spec({ 15 | group = group, 16 | highlight = 'ScrollViewLatestChange', 17 | priority = vim.g.scrollview_latestchange_priority, 18 | symbol = vim.g.scrollview_latestchange_symbol, 19 | }) 20 | local name = registration.name 21 | scrollview.set_sign_group_state(group, enable) 22 | 23 | scrollview.set_sign_group_callback(group, function() 24 | -- Track visited buffers, to prevent duplicate computation when multiple 25 | -- windows are showing the same buffer. 26 | local visited = {} 27 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 28 | local bufnr = api.nvim_win_get_buf(winid) 29 | if not visited[bufnr] then 30 | local bufvars = vim.b[bufnr] 31 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 32 | bufvars[name] = {} 33 | local latestchange = api.nvim_win_call(winid, function() 34 | return fn.line("'.") 35 | end) 36 | if latestchange > 0 then 37 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 38 | bufvars[name] = {latestchange} 39 | end 40 | visited[bufnr] = true 41 | end 42 | end 43 | end) 44 | 45 | api.nvim_create_autocmd('InsertLeave', { 46 | callback = function() 47 | if not scrollview.is_sign_group_active(group) then return end 48 | scrollview.refresh() 49 | end 50 | }) 51 | 52 | api.nvim_create_autocmd('InsertEnter', { 53 | callback = function() 54 | if not scrollview.is_sign_group_active(group) then return end 55 | api.nvim_create_autocmd('TextChangedI', { 56 | callback = function() 57 | if not scrollview.is_sign_group_active(group) then return end 58 | scrollview.refresh() 59 | end, 60 | once = true 61 | }) 62 | end 63 | }) 64 | end 65 | 66 | return M 67 | -------------------------------------------------------------------------------- /lua/scrollview/signs/loclist.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | function M.init(enable) 8 | if api.nvim_create_autocmd == nil then 9 | return 10 | end 11 | 12 | local group = 'loclist' 13 | scrollview.register_sign_group(group) 14 | local registration = scrollview.register_sign_spec({ 15 | group = group, 16 | highlight = 'ScrollViewLocList', 17 | priority = vim.g.scrollview_loclist_priority, 18 | symbol = vim.g.scrollview_loclist_symbol, 19 | type = 'w', 20 | }) 21 | local name = registration.name 22 | scrollview.set_sign_group_state(group, enable) 23 | 24 | scrollview.set_sign_group_callback(group, function() 25 | local winlines = {} -- maps winids to a list of loclist lines 26 | local sign_winids = scrollview.get_sign_eligible_windows() 27 | for _, winid in ipairs(sign_winids) do 28 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 29 | vim.w[winid][name] = nil 30 | end 31 | for _, winid in ipairs(sign_winids) do 32 | local bufnr = api.nvim_win_get_buf(winid) 33 | for _, item in ipairs(fn.getloclist(winid)) do 34 | if item.bufnr == bufnr then 35 | if winlines[winid] == nil then 36 | winlines[winid] = {} 37 | end 38 | table.insert(winlines[winid], item.lnum) 39 | end 40 | end 41 | end 42 | for winid, lines in pairs(winlines) do 43 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 44 | vim.w[winid][name] = lines 45 | end 46 | end) 47 | 48 | -- WARN: QuickFixCmdPost won't fire for some cases where loclist can be 49 | -- updated (e.g., setloclist). 50 | api.nvim_create_autocmd('QuickFixCmdPost', { 51 | callback = function() 52 | if not scrollview.is_sign_group_active(group) then return end 53 | scrollview.refresh() 54 | end 55 | }) 56 | end 57 | 58 | return M 59 | -------------------------------------------------------------------------------- /lua/scrollview/signs/marks.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | local utils = require('scrollview.utils') 5 | local concat = utils.concat 6 | local to_bool = utils.to_bool 7 | 8 | local M = {} 9 | 10 | -- WARN: Prior to Neovim 0.10, the outcome of :delmarks does not persist across 11 | -- Neovim sessions (Neovim #4288, #4925, #24963). Workaround: run :wshada! 12 | -- after deleting marks (however, this could delete information, like the 13 | -- changelist for files not edited in the current session). 14 | 15 | function M.init(enable) 16 | if api.nvim_create_autocmd == nil then 17 | return 18 | end 19 | 20 | local group = 'marks' 21 | scrollview.register_sign_group(group) 22 | local names = {} -- maps character to registration name 23 | for _, char in ipairs(vim.g.scrollview_marks_characters) do 24 | local registration = scrollview.register_sign_spec({ 25 | group = group, 26 | highlight = 'ScrollViewMarks', 27 | priority = vim.g.scrollview_marks_priority, 28 | symbol = char, 29 | variant = char, 30 | }) 31 | names[char] = registration.name 32 | end 33 | if vim.tbl_isempty(names) then return end 34 | scrollview.set_sign_group_state(group, enable) 35 | 36 | -- Refresh scrollbars after adding marks. 37 | for _, char in ipairs(vim.g.scrollview_marks_characters) do 38 | local seq = 'm' .. char 39 | scrollview.register_key_sequence_callback(seq, 'nv', scrollview.refresh) 40 | end 41 | 42 | scrollview.set_sign_group_callback(group, function() 43 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 44 | local bufnr = api.nvim_win_get_buf(winid) 45 | local marks = {} -- a mapping of character to line for buffer marks 46 | local items = concat( 47 | fn.getmarklist(bufnr), 48 | fn.getmarklist() 49 | ) 50 | for _, item in ipairs(items) do 51 | if item.pos ~= nil 52 | and item.mark ~= nil 53 | and fn.strchars(item.mark, 1) == 2 then 54 | local char = fn.strcharpart(item.mark, 1, 1) 55 | -- Marks are (1, 0)-indexed (so we only have to check the first 56 | -- value for 0). Using nvim_buf_get_mark is a more reliable way to 57 | -- check for global marks versus the existing approach (see commit 58 | -- 53c14b5 and its WARN comments for details). 59 | local should_show = api.nvim_buf_get_mark(bufnr, char)[1] ~= 0 60 | if should_show then 61 | marks[char] = item.pos[2] 62 | end 63 | end 64 | end 65 | for _, char in ipairs(vim.g.scrollview_marks_characters) do 66 | local value = nil 67 | if marks[char] ~= nil then 68 | value = {marks[char]} 69 | end 70 | local name = names[char] 71 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 72 | vim.b[bufnr][name] = value 73 | end 74 | end 75 | end) 76 | 77 | api.nvim_create_autocmd('CmdlineLeave', { 78 | callback = function() 79 | if not scrollview.is_sign_group_active(group) then return end 80 | if to_bool(vim.v.event.abort) then 81 | return 82 | end 83 | if fn.expand('') ~= ':' then 84 | return 85 | end 86 | -- Refresh scrollview after the following commands, which could change the marks. 87 | -- :[range]ma[rk] {a-zA-Z'} 88 | -- :[range]k{a-zA-Z'} 89 | -- :delm[arks] {marks} 90 | -- :kee[pmarks] {command} 91 | -- WARN: [range] is not handled. 92 | -- WARN: Only text at the beginning of the command is considered. 93 | -- WARN: CmdlineLeave is not executed for command mappings (). 94 | -- WARN: CmdlineLeave is not executed for commands executed from Lua 95 | -- (e.g., vim.cmd('help')). 96 | local cmdline = fn.getcmdline() 97 | if vim.startswith(cmdline, 'ma') 98 | or vim.startswith(cmdline, 'k') 99 | or vim.startswith(cmdline, 'delm') 100 | or vim.startswith(cmdline, 'kee') then 101 | scrollview.refresh() 102 | end 103 | end 104 | }) 105 | end 106 | 107 | return M 108 | -------------------------------------------------------------------------------- /lua/scrollview/signs/quickfix.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | function M.init(enable) 8 | if api.nvim_create_autocmd == nil then 9 | return 10 | end 11 | 12 | local group = 'quickfix' 13 | scrollview.register_sign_group(group) 14 | local registration = scrollview.register_sign_spec({ 15 | group = group, 16 | highlight = 'ScrollViewQuickFix', 17 | priority = vim.g.scrollview_quickfix_priority, 18 | symbol = vim.g.scrollview_quickfix_symbol, 19 | }) 20 | local name = registration.name 21 | scrollview.set_sign_group_state(group, enable) 22 | 23 | scrollview.set_sign_group_callback(group, function() 24 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 25 | local bufnr = api.nvim_win_get_buf(winid) 26 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 27 | vim.b[bufnr][name] = nil 28 | end 29 | local buflines = {} -- maps buffers to a list of quickfix lines 30 | for _, item in ipairs(fn.getqflist()) do 31 | if buflines[item.bufnr] == nil then 32 | buflines[item.bufnr] = {} 33 | end 34 | table.insert(buflines[item.bufnr], item.lnum) 35 | end 36 | for bufnr, lines in pairs(buflines) do 37 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 38 | vim.b[bufnr][name] = lines 39 | end 40 | end) 41 | 42 | -- WARN: QuickFixCmdPost won't fire for some cases where the quickfix list 43 | -- can be updated (e.g., setqflist). 44 | api.nvim_create_autocmd('QuickFixCmdPost', { 45 | callback = function() 46 | if not scrollview.is_sign_group_active(group) then return end 47 | scrollview.refresh() 48 | end 49 | }) 50 | end 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /lua/scrollview/signs/search.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | local utils = require('scrollview.utils') 5 | local to_bool = utils.to_bool 6 | 7 | local M = {} 8 | 9 | function M.init(enable) 10 | if api.nvim_create_autocmd == nil then 11 | return 12 | end 13 | 14 | local group = 'search' 15 | scrollview.register_sign_group(group) 16 | local registration = scrollview.register_sign_spec({ 17 | group = group, 18 | highlight = 'ScrollViewSearch', 19 | priority = vim.g.scrollview_search_priority, 20 | symbol = vim.g.scrollview_search_symbol, 21 | }) 22 | local name = registration.name 23 | scrollview.set_sign_group_state(group, enable) 24 | 25 | scrollview.set_sign_group_callback(group, function() 26 | local pattern = fn.getreg('/') 27 | -- Track visited buffers, to prevent duplicate computation when multiple 28 | -- windows are showing the same buffer. 29 | local visited = {} 30 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 31 | local bufnr = api.nvim_win_get_buf(winid) 32 | if not visited[bufnr] then 33 | local bufvars = vim.b[bufnr] 34 | local lines = {} 35 | if to_bool(vim.v.hlsearch) then 36 | local cache_hit = false 37 | local changedtick = bufvars.changedtick 38 | if bufvars.scrollview_search_pattern_cached == pattern then 39 | local changedtick_cached = 40 | bufvars.scrollview_search_changedtick_cached 41 | cache_hit = changedtick_cached == changedtick 42 | end 43 | if cache_hit then 44 | lines = bufvars.scrollview_search_cached 45 | else 46 | lines = scrollview.with_win_workspace(winid, function() 47 | local result = {} 48 | -- Use a pcall since searchcount() and :global throw an 49 | -- exception (E383, E866) when the pattern is invalid (e.g., 50 | -- "\@a"). 51 | pcall(function() 52 | -- searchcount() can return {} (e.g., when launching Neovim 53 | -- with -i NONE). 54 | local searchcount_total = fn.searchcount().total or 0 55 | if searchcount_total > 0 then 56 | result = fn.split( 57 | fn.execute('keepjumps global//echo line(".")')) 58 | end 59 | end) 60 | return result 61 | end) 62 | for idx, line in ipairs(lines) do 63 | lines[idx] = tonumber(line) 64 | end 65 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 66 | bufvars.scrollview_search_pattern_cached = pattern 67 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 68 | bufvars.scrollview_search_changedtick_cached = changedtick 69 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 70 | bufvars.scrollview_search_cached = lines 71 | end 72 | end 73 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 74 | bufvars[name] = lines 75 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 76 | bufvars.scrollview_search_pattern = pattern 77 | visited[bufnr] = true 78 | end 79 | end 80 | end) 81 | 82 | api.nvim_create_autocmd('OptionSet', { 83 | pattern = 'hlsearch', 84 | callback = function() 85 | if not scrollview.is_sign_group_active(group) then return end 86 | scrollview.refresh() 87 | end 88 | }) 89 | 90 | api.nvim_create_autocmd('CmdlineLeave', { 91 | callback = function() 92 | if not scrollview.is_sign_group_active(group) then return end 93 | if to_bool(vim.v.event.abort) then 94 | return 95 | end 96 | local afile = fn.expand('') 97 | -- Handle the case where a search is executed. 98 | local refresh = afile == '/' or afile == '?' 99 | -- Handle the case where :nohls may have been executed (this won't work 100 | -- for e.g., nohls in a mapping). 101 | -- WARN: CmdlineLeave is not executed for command mappings (). 102 | -- WARN: CmdlineLeave is not executed for commands executed from Lua 103 | -- (e.g., vim.cmd('help')). 104 | if afile == ':' and string.find(fn.getcmdline(), 'nohls') then 105 | refresh = true 106 | end 107 | if refresh then 108 | scrollview.refresh() 109 | end 110 | end 111 | }) 112 | 113 | -- It's possible that nohlsearch was executed from a mapping, and 114 | -- wouldn't be handled by the CmdlineLeave callback above. Use a CursorMoved 115 | -- event to check if search signs are shown when they shouldn't be, and 116 | -- update accordingly. Also handle the case where 'n', 'N', '*', '#', 'g*', 117 | -- or 'g#' are pressed (although these won't be properly handled when there 118 | -- is only one search result and the cursor is already on it, since the 119 | -- cursor wouldn't move; creating scrollview refresh mappings for those keys 120 | -- could handle that scenario). NOTE: If there are scenarios where search 121 | -- signs become out of sync (i.e., shown when they shouldn't be), this same 122 | -- approach could be used with a timer. 123 | api.nvim_create_autocmd('CursorMoved', { 124 | callback = function() 125 | if not scrollview.is_sign_group_active(group) then return end 126 | -- Use defer_fn since vim.v.hlsearch may not have been properly set yet. 127 | vim.defer_fn(function() 128 | local refresh = false 129 | if to_bool(vim.v.hlsearch) then 130 | -- Refresh bars if (1) v:hlsearch is on, (2) search signs aren't 131 | -- currently shown, and (3) searchcount().total > 0. Also refresh 132 | -- bars if v:hlsearch is on and the shown search signs correspond to 133 | -- a different pattern than the current one. 134 | -- Track visited buffers, to prevent duplicate computation when 135 | -- multiple windows are showing the same buffer. 136 | local pattern = fn.getreg('/') 137 | local visited = {} 138 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 139 | local bufnr = api.nvim_win_get_buf(winid) 140 | if not visited[bufnr] then 141 | visited[bufnr] = true 142 | refresh = api.nvim_win_call(winid, function() 143 | if pattern ~= vim.b.scrollview_search_pattern then 144 | return true 145 | end 146 | local lines = vim.b[name] 147 | if lines == nil or vim.tbl_isempty(lines) then 148 | -- Use a pcall since searchcount() throws an exception (E383, 149 | -- E866) when the pattern is invalid (e.g., "\@a"). 150 | local searchcount_total = 0 151 | pcall(function() 152 | -- searchcount() can return {} (e.g., when launching Neovim 153 | -- with -i NONE). 154 | searchcount_total = fn.searchcount().total or 0 155 | end) 156 | if searchcount_total > 0 then 157 | return true 158 | end 159 | end 160 | return false 161 | end) 162 | if refresh then 163 | break 164 | end 165 | end 166 | end 167 | else 168 | -- Refresh bars if v:hlsearch is off and search signs are currently 169 | -- shown. 170 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 171 | local bufnr = api.nvim_win_get_buf(winid) 172 | local lines = vim.b[bufnr][name] 173 | if lines ~= nil and not vim.tbl_isempty(lines) then 174 | refresh = true 175 | break 176 | end 177 | end 178 | end 179 | if refresh then 180 | scrollview.refresh() 181 | end 182 | end, 0) 183 | end 184 | }) 185 | 186 | -- The InsertEnter case handles when insert mode is entered at the same time 187 | -- as v:hlsearch is turned off. The InsertLeave case updates search signs 188 | -- after leaving insert mode, when newly added text might correspond to new 189 | -- signs. 190 | api.nvim_create_autocmd({'InsertEnter', 'InsertLeave'}, { 191 | callback = function() 192 | if not scrollview.is_sign_group_active(group) then return end 193 | scrollview.refresh() 194 | end 195 | }) 196 | end 197 | 198 | return M 199 | -------------------------------------------------------------------------------- /lua/scrollview/signs/spell.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | local utils = require('scrollview.utils') 5 | local to_bool = utils.to_bool 6 | 7 | local M = {} 8 | 9 | function M.init(enable) 10 | if api.nvim_create_autocmd == nil or vim.keymap == nil then 11 | return 12 | end 13 | 14 | local group = 'spell' 15 | scrollview.register_sign_group(group) 16 | local registration = scrollview.register_sign_spec({ 17 | group = group, 18 | highlight = 'ScrollViewSpell', 19 | priority = vim.g.scrollview_spell_priority, 20 | symbol = vim.g.scrollview_spell_symbol, 21 | type = 'w', 22 | }) 23 | local name = registration.name 24 | scrollview.set_sign_group_state(group, enable) 25 | 26 | local invalidate_cache = function() 27 | for _, winid in ipairs(api.nvim_list_wins()) do 28 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 29 | vim.w[winid].scrollview_spell_changedtick_cached = nil 30 | end 31 | end 32 | 33 | -- Invalidate cache and refresh scrollbars after certain spell key sequences. 34 | local seqs = {'zg', 'zG', 'zq', 'zW', 'zuw', 'zug', 'zuW', 'zuG'} 35 | for _, seq in ipairs(seqs) do 36 | scrollview.register_key_sequence_callback(seq, 'nv', function() 37 | invalidate_cache() 38 | scrollview.refresh() -- asynchronous 39 | end) 40 | end 41 | 42 | scrollview.set_sign_group_callback(group, function() 43 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 44 | local bufnr = api.nvim_win_get_buf(winid) 45 | local spell = api.nvim_win_get_option(winid, 'spell') 46 | local winvars = vim.w[winid] 47 | local lines = {} 48 | if spell then 49 | local changedtick = vim.b[bufnr].changedtick 50 | local changedtick_cached = winvars.scrollview_spell_changedtick_cached 51 | local bufnr_cached = winvars.scrollview_spell_bufnr_cached 52 | local cache_hit = changedtick_cached == changedtick 53 | and bufnr_cached == bufnr 54 | if cache_hit then 55 | lines = winvars.scrollview_spell_cached 56 | else 57 | local line_count = api.nvim_buf_line_count(bufnr) 58 | scrollview.with_win_workspace(winid, function() 59 | for line = 1, line_count do 60 | fn.cursor(line, 1) 61 | local spellbadword = fn.spellbadword() 62 | if spellbadword[1] ~= '' then table.insert(lines, line) end 63 | end 64 | end) 65 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 66 | winvars.scrollview_spell_changedtick_cached = changedtick 67 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 68 | winvars.scrollview_spell_bufnr_cached = bufnr 69 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 70 | winvars.scrollview_spell_cached = lines 71 | end 72 | end 73 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 74 | winvars[name] = lines 75 | end 76 | end) 77 | 78 | api.nvim_create_autocmd('OptionSet', { 79 | pattern = {'dictionary', 'spell'}, 80 | callback = function(args) 81 | if not scrollview.is_sign_group_active(group) then return end 82 | if args.match == 'dictionary' then 83 | invalidate_cache() 84 | end 85 | scrollview.refresh() 86 | end 87 | }) 88 | 89 | api.nvim_create_autocmd('CmdlineLeave', { 90 | callback = function() 91 | if not scrollview.is_sign_group_active(group) then return end 92 | if to_bool(vim.v.event.abort) then 93 | return 94 | end 95 | if fn.expand('') ~= ':' then 96 | return 97 | end 98 | -- Invalidate cache and refresh scrollbars after certain spell commands. 99 | -- :[count]spe[llgood] 100 | -- :spe[llgood]! 101 | -- :[count]spellw[rong] 102 | -- :spellw[rong]! 103 | -- :[count]spellra[re] 104 | -- :spellr[are]! 105 | -- :[count]spellu[ndo] 106 | -- :spellu[ndo]! 107 | -- WARN: Only text at the beginning of the command is considered. 108 | -- WARN: CmdlineLeave is not executed for command mappings (). 109 | -- WARN: CmdlineLeave is not executed for commands executed from Lua 110 | -- (e.g., vim.cmd('help')). 111 | local cmdline = fn.getcmdline() 112 | if string.match(cmdline, '^%d*spe') ~= nil then 113 | invalidate_cache() 114 | scrollview.refresh() 115 | end 116 | end 117 | }) 118 | 119 | api.nvim_create_autocmd('TextChangedI', { 120 | callback = function() 121 | if not scrollview.is_sign_group_active(group) then return end 122 | local winid = api.nvim_get_current_win() 123 | local bufnr = api.nvim_get_current_buf() 124 | local line = fn.line('.') 125 | local str = fn.getbufline(bufnr, line)[1] 126 | local spellbadword = fn.spellbadword(str) 127 | local expect_sign = spellbadword[1] ~= '' 128 | -- Wait until leaving insert mode before showing spell signs. This way, 129 | -- signs won't show while entering a word (which will temporarily be 130 | -- misspelled). Such a word won't be highlighted by Neovim, but it will 131 | -- be returned by spellbadword(). The approach here could still cause a 132 | -- synchronization issue, since the word would be highlighted when 133 | -- starting to type the next word, and there would be no sign until 134 | -- leaving insert mode. But I couldn't find a preferable solution, since 135 | -- there doesn't appear to be any functions for retrieving just the 136 | -- highlighted misspelled words. 137 | if expect_sign then 138 | api.nvim_create_autocmd('InsertLeave', { 139 | callback = function() 140 | scrollview.refresh() 141 | end, 142 | once = true, 143 | }) 144 | return 145 | end 146 | local idx = -1 147 | local lines = vim.w[winid][name] 148 | if lines ~= nil then 149 | idx = utils.binary_search(lines, line) 150 | if lines[idx] ~= line then 151 | idx = -1 152 | end 153 | end 154 | local has_sign = idx ~= -1 155 | if expect_sign ~= has_sign then 156 | scrollview.refresh() 157 | end 158 | end 159 | }) 160 | end 161 | 162 | return M 163 | -------------------------------------------------------------------------------- /lua/scrollview/signs/textwidth.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | local utils = require('scrollview.utils') 5 | 6 | local M = {} 7 | 8 | function M.init(enable) 9 | if api.nvim_create_autocmd == nil then 10 | return 11 | end 12 | 13 | local group = 'textwidth' 14 | scrollview.register_sign_group(group) 15 | local registration = scrollview.register_sign_spec({ 16 | group = group, 17 | highlight = 'ScrollViewTextWidth', 18 | priority = vim.g.scrollview_textwidth_priority, 19 | symbol = vim.g.scrollview_textwidth_symbol, 20 | }) 21 | local name = registration.name 22 | scrollview.set_sign_group_state(group, enable) 23 | 24 | scrollview.set_sign_group_callback(group, function() 25 | -- Track visited buffers, to prevent duplicate computation when multiple 26 | -- windows are showing the same buffer. 27 | local visited = {} 28 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 29 | local bufnr = api.nvim_win_get_buf(winid) 30 | local textwidth = api.nvim_buf_get_option(bufnr, 'textwidth') 31 | if not visited[bufnr] then 32 | local bufvars = vim.b[bufnr] 33 | local lines = {} 34 | local cache_hit = false 35 | local changedtick = bufvars.changedtick 36 | if bufvars.scrollview_textwidth_option_cached == textwidth then 37 | local changedtick_cached = 38 | bufvars.scrollview_textwidth_changedtick_cached 39 | cache_hit = changedtick_cached == changedtick 40 | end 41 | if cache_hit then 42 | lines = bufvars.scrollview_textwidth_cached 43 | else 44 | local line_count = api.nvim_buf_line_count(bufnr) 45 | if textwidth > 0 then 46 | api.nvim_win_call(winid, function() 47 | for line = 1, line_count do 48 | local str = fn.getbufline(bufnr, line)[1] 49 | local line_length = fn.strchars(str, 1) 50 | if line_length > textwidth then 51 | table.insert(lines, line) 52 | end 53 | end 54 | end) 55 | end 56 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 57 | bufvars.scrollview_textwidth_option_cached = textwidth 58 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 59 | bufvars.scrollview_textwidth_changedtick_cached = changedtick 60 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 61 | bufvars.scrollview_textwidth_cached = lines 62 | end 63 | -- luacheck: ignore 122 (setting read-only field b.?.? of global vim) 64 | bufvars[name] = lines 65 | visited[bufnr] = true 66 | end 67 | end 68 | end) 69 | 70 | api.nvim_create_autocmd('OptionSet', { 71 | pattern = 'textwidth', 72 | callback = function() 73 | if not scrollview.is_sign_group_active(group) then return end 74 | scrollview.refresh() 75 | end 76 | }) 77 | 78 | api.nvim_create_autocmd('TextChangedI', { 79 | callback = function() 80 | if not scrollview.is_sign_group_active(group) then return end 81 | local bufnr = api.nvim_get_current_buf() 82 | local textwidth = api.nvim_buf_get_option(bufnr, 'textwidth') 83 | local line = fn.line('.') 84 | local str = fn.getbufline(bufnr, line)[1] 85 | local line_length = fn.strchars(str, 1) 86 | local expect_sign = textwidth > 0 and line_length > textwidth 87 | local idx = -1 88 | local lines = vim.b[bufnr][name] 89 | if lines ~= nil then 90 | idx = utils.binary_search(lines, line) 91 | if lines[idx] ~= line then 92 | idx = -1 93 | end 94 | end 95 | local has_sign = idx ~= -1 96 | if expect_sign ~= has_sign then 97 | scrollview.refresh() 98 | end 99 | end 100 | }) 101 | end 102 | 103 | return M 104 | -------------------------------------------------------------------------------- /lua/scrollview/signs/trail.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local scrollview = require('scrollview') 4 | 5 | local M = {} 6 | 7 | function M.init(enable) 8 | if api.nvim_create_autocmd == nil then 9 | return 10 | end 11 | 12 | local group = 'trail' 13 | scrollview.register_sign_group(group) 14 | local registration = scrollview.register_sign_spec({ 15 | group = group, 16 | highlight = 'ScrollViewTrail', 17 | priority = vim.g.scrollview_trail_priority, 18 | symbol = vim.g.scrollview_trail_symbol, 19 | }) 20 | local name = registration.name 21 | scrollview.set_sign_group_state(group, enable) 22 | 23 | scrollview.set_sign_group_callback(group, function() 24 | -- Track visited buffers, to prevent duplicate computation when multiple 25 | -- windows are showing the same buffer. 26 | local visited = {} 27 | for _, winid in ipairs(scrollview.get_sign_eligible_windows()) do 28 | local bufnr = api.nvim_win_get_buf(winid) 29 | -- Don't update when in insert mode. This way, pressing 'o' to start a 30 | -- new line won't trigger a new sign when there is indentation. 31 | local mode = api.nvim_win_call(winid, fn.mode) 32 | if not visited[bufnr] and mode ~= 'i' then 33 | local bufvars = vim.b[bufnr] 34 | local lines = {} 35 | local changedtick = bufvars.changedtick 36 | local changedtick_cached = bufvars.scrollview_trail_changedtick_cached 37 | local cache_hit = changedtick_cached == changedtick 38 | if cache_hit then 39 | lines = bufvars.scrollview_trail_cached 40 | else 41 | local line_count = api.nvim_buf_line_count(bufnr) 42 | for line = 1, line_count do 43 | local str = fn.getbufline(bufnr, line)[1] 44 | if string.match(str, "%s$") then 45 | table.insert(lines, line) 46 | end 47 | end 48 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 49 | bufvars.scrollview_trail_changedtick_cached = changedtick 50 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 51 | bufvars.scrollview_trail_cached = lines 52 | end 53 | -- luacheck: ignore 122 (setting read-only field w.?.? of global vim) 54 | bufvars[name] = lines 55 | visited[bufnr] = true 56 | end 57 | end 58 | end) 59 | 60 | api.nvim_create_autocmd('InsertLeave', { 61 | callback = function() 62 | if not scrollview.is_sign_group_active(group) then return end 63 | scrollview.refresh() 64 | end 65 | }) 66 | end 67 | 68 | return M 69 | -------------------------------------------------------------------------------- /lua/scrollview/utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- Returns the index of x in l if present, or the index for insertion 4 | -- otherwise. Assumes that l is sorted. 5 | function M.binary_search(l, x) 6 | local lo = 1 7 | local hi = #l 8 | while lo <= hi do 9 | local mid = math.floor((hi - lo) / 2 + lo) 10 | if l[mid] == x then 11 | if mid == 1 or l[mid - 1] ~= x then 12 | return mid 13 | end 14 | -- Keep searching for the leftmost match. 15 | hi = mid - 1 16 | elseif l[mid] < x then 17 | lo = mid + 1 18 | else 19 | hi = mid - 1 20 | end 21 | end 22 | return lo 23 | end 24 | 25 | -- Concatenate two array-like tables. 26 | function M.concat(a, b) 27 | local result = {} 28 | for _, x in ipairs(a) do 29 | table.insert(result, x) 30 | end 31 | for _, x in ipairs(b) do 32 | table.insert(result, x) 33 | end 34 | return result 35 | end 36 | 37 | -- Create a shallow copy of a map-like table. 38 | function M.copy(table) 39 | local result = {} 40 | for key, val in pairs(table) do 41 | result[key] = val 42 | end 43 | return result 44 | end 45 | 46 | -- Takes a list of lists. Each sublist is comprised of a highlight group name 47 | -- and a corresponding string to echo. 48 | function M.echo(echo_list) 49 | vim.cmd('redraw') 50 | for _, item in ipairs(echo_list) do 51 | local hlgroup, string = unpack(item) 52 | vim.g.scrollview_echo_string = string 53 | vim.cmd('echohl ' .. hlgroup .. ' | echon g:scrollview_echo_string') 54 | vim.g.scrollview_echo_string = vim.NIL 55 | end 56 | vim.cmd('echohl None') 57 | end 58 | 59 | -- Given array-like tables a and b, add all elements from b to a. 60 | function M.extend(a, b) 61 | for _, item in ipairs(b) do 62 | table.insert(a, item) 63 | end 64 | end 65 | 66 | -- Convert a list-like table to a map-like table. The output's keys are the 67 | -- elements of the input, and the corresponding values are the indices where 68 | -- the element is in the list. 69 | function M.mapify(l) 70 | local result = {} 71 | for idx, item in ipairs(l) do 72 | if result[item] == nil then 73 | result[item] = {} 74 | end 75 | table.insert(result[item], idx) 76 | end 77 | return result 78 | end 79 | 80 | -- For sorted list l with no duplicates, return the previous item before the 81 | -- specified item (wraps around). 82 | function M.preceding(l, item, count, wrapscan) 83 | assert(count >= 0) 84 | if count == 0 then 85 | -- This special-case handling is necessary. Without it, if item is not in 86 | -- the list, another item would be returned. 87 | return item 88 | end 89 | if vim.tbl_isempty(l) then 90 | return nil 91 | end 92 | local idx = M.binary_search(l, item) - count 93 | if idx < 1 then 94 | idx = wrapscan and (idx - 1) % #l + 1 or #l 95 | end 96 | return l[idx] 97 | end 98 | 99 | -- Return a new list with duplicate elements removed from a sorted array-like 100 | -- table. 101 | function M.remove_duplicates(l) 102 | local result = {} 103 | for _, x in ipairs(l) do 104 | if vim.tbl_isempty(result) or result[#result] ~= x then 105 | table.insert(result, x) 106 | end 107 | end 108 | return result 109 | end 110 | 111 | -- Round to the nearest integer. 112 | -- WARN: .5 rounds to the right on the number line, including for negatives 113 | -- (which would not result in rounding up in magnitude). 114 | -- (e.g., round(3.5) == 3, round(-3.5) == -3 != -4) 115 | function M.round(x) 116 | return math.floor(x + 0.5) 117 | end 118 | 119 | -- A non-destructive sort function. 120 | function M.sorted(l) 121 | local result = M.copy(l) 122 | table.sort(result) 123 | return result 124 | end 125 | 126 | -- For sorted list l with no duplicates, return the th item after the 127 | -- specified item. 128 | function M.subsequent(l, item, count, wrapscan) 129 | assert(count >= 0) 130 | if count == 0 then 131 | -- This special-case handling is necessary. Without it, if item is not in 132 | -- the list, another item would be returned. 133 | return item 134 | end 135 | if vim.tbl_isempty(l) then 136 | return nil 137 | end 138 | local idx = M.binary_search(l, item) 139 | if idx <= #l and l[idx] == item then 140 | idx = idx + 1 -- use the next item 141 | end 142 | idx = idx + count - 1 143 | if idx > #l then 144 | idx = wrapscan and (idx - 1) % #l + 1 or #l 145 | end 146 | return l[idx] 147 | end 148 | 149 | -- Replace termcodes. 150 | function M.t(str) 151 | return vim.api.nvim_replace_termcodes(str, true, true, true) 152 | end 153 | 154 | -- Get value from a map-like table, using the specified default. 155 | function M.tbl_get(table, key, default) 156 | local result = table[key] 157 | if result == nil then 158 | result = default 159 | end 160 | return result 161 | end 162 | 163 | -- Returns true for boolean true and any non-zero number, otherwise returns 164 | -- false. 165 | function M.to_bool(x) 166 | if type(x) == 'boolean' then 167 | return x 168 | elseif type(x) == 'number' then 169 | return x ~= 0 170 | end 171 | return false 172 | end 173 | 174 | return M 175 | -------------------------------------------------------------------------------- /plugin/scrollview.vim: -------------------------------------------------------------------------------- 1 | " The plugin should not be reloaded. #110 2 | if get(g:, 'loaded_scrollview', v:false) 3 | finish 4 | endif 5 | let g:loaded_scrollview = v:true 6 | 7 | if !has('nvim-0.6') 8 | " Logging error with echomsg or echoerr interrupts Neovim's startup by 9 | " blocking. Fail silently. 10 | finish 11 | endif 12 | 13 | " === Highlights === 14 | 15 | " Highlights are specified here instead of in autoload/scrollview.vim. Since 16 | " that file is loaded asynchronously, calling ':highlight ...' would clear the 17 | " intro screen. #102 18 | 19 | " The default highlight groups are specified below. 20 | " Change the defaults by defining or linking an alternative highlight group. 21 | " E.g., the following will use the Pmenu highlight. 22 | " :highlight link ScrollView Pmenu 23 | " E.g., the following will use custom highlight colors. 24 | " :highlight ScrollView ctermbg=159 guibg=LightCyan 25 | highlight default link ScrollView Visual 26 | highlight default link ScrollViewChangeListPrevious SpecialKey 27 | highlight default link ScrollViewChangeListCurrent SpecialKey 28 | highlight default link ScrollViewChangeListNext SpecialKey 29 | highlight default link ScrollViewConflictsMiddle DiffAdd 30 | highlight default link ScrollViewConflictsTop DiffAdd 31 | highlight default link ScrollViewConflictsMiddle DiffAdd 32 | highlight default link ScrollViewConflictsBottom DiffAdd 33 | highlight default link ScrollViewCursor WarningMsg 34 | " Set the diagnostic highlights to the corresponding Neovim sign text 35 | " highlight if defined, or the default otherwise. 36 | let s:diagnostics_highlight_data = [ 37 | \ ['ScrollViewDiagnosticsError', 'DiagnosticError', 'DiagnosticSignError'], 38 | \ ['ScrollViewDiagnosticsHint', 'DiagnosticHint', 'DiagnosticSignHint'], 39 | \ ['ScrollViewDiagnosticsInfo', 'DiagnosticInfo', 'DiagnosticSignInfo'], 40 | \ ['ScrollViewDiagnosticsWarn', 'DiagnosticWarn', 'DiagnosticSignWarn'], 41 | \ ] 42 | for [s:key, s:fallback, s:sign] in s:diagnostics_highlight_data 43 | if has('nvim-0.10') 44 | let s:highlight = s:sign 45 | else 46 | try 47 | let s:highlight = sign_getdefined(s:sign)[0].texthl 48 | catch 49 | let s:highlight = s:fallback 50 | endtry 51 | endif 52 | execute 'highlight default link ' .. s:key .. ' ' .. s:highlight 53 | endfor 54 | highlight default link ScrollViewFolds Directory 55 | if has('nvim-0.9.2') 56 | highlight default link ScrollViewHover CurSearch 57 | else 58 | highlight default link ScrollViewHover WildMenu 59 | endif 60 | highlight default link ScrollViewIndentSpaces LineNr 61 | highlight default link ScrollViewIndentTabs LineNr 62 | highlight default link ScrollViewKeywordsFix ColorColumn 63 | highlight default link ScrollViewKeywordsHack ColorColumn 64 | highlight default link ScrollViewKeywordsTodo ColorColumn 65 | highlight default link ScrollViewKeywordsWarn ColorColumn 66 | highlight default link ScrollViewKeywordsXxx ColorColumn 67 | highlight default link ScrollViewLatestChange SpecialKey 68 | highlight default link ScrollViewLocList LineNr 69 | highlight default link ScrollViewMarks Identifier 70 | highlight default link ScrollViewQuickFix Constant 71 | if has('nvim-0.9.2') 72 | highlight default link ScrollViewRestricted CurSearch 73 | else 74 | highlight default link ScrollViewRestricted MatchParen 75 | endif 76 | highlight default link ScrollViewSearch NonText 77 | highlight default link ScrollViewSpell Statement 78 | highlight default link ScrollViewTextWidth Question 79 | 80 | " === Initialization === 81 | 82 | " Initialize scrollview asynchronously. Asynchronous initialization is used to 83 | " prevent issues when setting configuration variables is deferred (#99). This 84 | " was originally used to avoid an issue that prevents diff mode from 85 | " functioning properly when it's launched at startup (i.e., with nvim 86 | " -d). The issue was reported on Jan 8, 2021, in Neovim Issue #13720. As of 87 | " Neovim 0.9.0, the issue is resolved (Neovim PR #21829, Jan 16, 2023). 88 | " WARN: scrollview events are omitted from the output of --startuptime. 89 | call timer_start(0, {-> execute('call scrollview#Initialize()', '')}) 90 | -------------------------------------------------------------------------------- /tests/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import glob 4 | import os 5 | from string import Template 6 | import subprocess 7 | import sys 8 | import tempfile 9 | 10 | test_dir = os.path.dirname(os.path.realpath(__file__)) 11 | project_dir = os.path.join(test_dir, os.path.pardir) 12 | test_scripts = sorted(glob.glob(os.path.join(test_dir, 'test_*.vim'))) 13 | errors = [] 14 | template = Template(""" 15 | try 16 | source ${file} 17 | catch 18 | call assert_report(v:throwpoint . ': ' . v:exception) 19 | endtry 20 | verbose echo join(v:errors, "\\n") 21 | quitall! 22 | """) 23 | for test_script in test_scripts: 24 | with tempfile.TemporaryDirectory() as tmp: 25 | runner_script = os.path.join(tmp, 'runner.vim') 26 | with open(runner_script, 'w') as f: 27 | f.write(template.substitute( 28 | project_dir=project_dir, file=test_script)) 29 | args = [ 30 | 'nvim', 31 | '-n', # no swap file 32 | '-e', # start in Ex mode 33 | '-s', # silent mode 34 | '-S', runner_script, # source the test runner script 35 | ] 36 | result = subprocess.run(args, capture_output=True) 37 | lines = result.stderr.decode('ascii').splitlines() 38 | lines = [line.strip() for line in lines] 39 | lines = [line for line in lines if line] 40 | for line in lines: 41 | print(line, file=sys.stderr) 42 | errors.extend(lines) 43 | sys.exit(min(len(errors), 255)) 44 | -------------------------------------------------------------------------------- /tests/test_linewise_simple_consistency.vim: -------------------------------------------------------------------------------- 1 | " Test the consistency of linewise and simple computations. These should only 2 | " match without folds. 3 | 4 | " Load a file with many lines. 5 | help eval.txt 6 | 7 | let s:lua_module = luaeval('require("scrollview")') 8 | 9 | let s:line_count = nvim_buf_line_count(0) 10 | 11 | let s:vtopline_lookup_simple = 12 | \ s:lua_module.simple_topline_lookup(win_getid(winnr())) 13 | let s:vtopline_lookup_linewise = 14 | \ s:lua_module.virtual_topline_lookup_linewise() 15 | call assert_equal(s:vtopline_lookup_simple, s:vtopline_lookup_linewise) 16 | 17 | " Create folds. 18 | set foldmethod=indent 19 | normal! zM 20 | 21 | let s:vtopline_lookup_simple = 22 | \ s:lua_module.simple_topline_lookup(win_getid(winnr())) 23 | call assert_equal(s:vtopline_lookup_simple, s:vtopline_lookup_linewise) 24 | let s:vtopline_lookup_linewise = 25 | \ s:lua_module.virtual_topline_lookup_linewise() 26 | call assert_notequal(s:vtopline_lookup_simple, s:vtopline_lookup_linewise) 27 | -------------------------------------------------------------------------------- /tests/test_linewise_spanwise_consistency.vim: -------------------------------------------------------------------------------- 1 | " Test the consistency of linewise and spanwise computations. 2 | 3 | " Load a file with many lines. 4 | help eval.txt 5 | 6 | let s:lua_module = luaeval('require("scrollview")') 7 | 8 | let s:line_count = nvim_buf_line_count(0) 9 | 10 | let s:vline_count_spanwise = 11 | \ s:lua_module.virtual_line_count_spanwise(1, s:line_count) 12 | let s:vline_count_linewise = 13 | \ s:lua_module.virtual_line_count_linewise(1, s:line_count) 14 | call assert_equal(s:line_count, s:vline_count_spanwise) 15 | call assert_equal(s:vline_count_spanwise, s:vline_count_linewise) 16 | let s:vtopline_lookup_spanwise = 17 | \ s:lua_module.virtual_topline_lookup_spanwise() 18 | let s:vtopline_lookup_linewise = 19 | \ s:lua_module.virtual_topline_lookup_linewise() 20 | call assert_equal(s:vtopline_lookup_spanwise, s:vtopline_lookup_linewise) 21 | 22 | " Create folds. 23 | set foldmethod=indent 24 | normal! zM 25 | 26 | let s:vline_count_spanwise = 27 | \ s:lua_module.virtual_line_count_spanwise(1, s:line_count) 28 | let s:vline_count_linewise = 29 | \ s:lua_module.virtual_line_count_linewise(1, s:line_count) 30 | call assert_true(s:vline_count_spanwise <# s:line_count) 31 | call assert_equal(s:vline_count_spanwise, s:vline_count_linewise) 32 | let s:vtopline_lookup_spanwise = 33 | \ s:lua_module.virtual_topline_lookup_spanwise() 34 | call assert_notequal(s:vtopline_lookup_spanwise, s:vtopline_lookup_linewise) 35 | let s:vtopline_lookup_linewise = 36 | \ s:lua_module.virtual_topline_lookup_linewise() 37 | call assert_equal(s:vtopline_lookup_spanwise, s:vtopline_lookup_linewise) 38 | --------------------------------------------------------------------------------