├── .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 |
--------------------------------------------------------------------------------