mini.indentscope documentation
Generated from the main
branch of ‘mini.nvim’
mini.indentscope Visualize and work with indent scope
MIT License Copyright (c) 2022 Evgeni Chasnovski
Module
Indent scope (or just “scope”) is a maximum set of consecutive lines which contains certain reference line (cursor line by default) and every member has indent not less than certain reference indent (“indent at cursor” by default: minimum between cursor column and indent of cursor line).
Features:
Visualize scope with animated vertical line. It is very fast and done automatically in a non-blocking way (other operations can be performed, like moving cursor). You can customize debounce delay and animation rule.
Customization of scope computation options can be done on global level (in MiniIndentscope.config), for a certain buffer (using
vim.b.miniindentscope_config
buffer variable), or within a call (usingopts
variable in MiniIndentscope.get_scope()).Customizable notion of a border: which adjacent lines with strictly lower indent are recognized as such. This is useful for a certain filetypes (for example, Python or plain text).
Customizable way of line to be considered “border first”. This is useful if you want to place cursor on function header and get scope of its body.
There are textobjects and motions to operate on scope. Support count and dot-repeat (in operator pending mode).
Setup
This module needs a setup with require('mini.indentscope').setup({})
(replace {}
with your config
table). It will create global Lua table MiniIndentscope
which you can use for scripting or manually (with :lua MiniIndentscope.*
).
See MiniIndentscope.config for available config settings.
You can override runtime config settings locally to buffer inside vim.b.miniindentscope_config
which should have same structure as MiniIndentscope.config
. See mini.nvim-buffer-local-config for more details.
Comparisons
lukas-reineke/indent-blankline.nvim:
Its main functionality is about showing static guides of indent levels.
Implementation of ‘mini.indentscope’ is similar to ‘indent-blankline.nvim’ (using extmarks on first column to be shown even on blank lines). They can be used simultaneously, but it will lead to one of the visualizations being on top (hiding) of another.
Highlight groups
MiniIndentscopeSymbol
- symbol showing on every line of scope if its indent is multiple of ‘shiftwidth’.MiniIndentscopeSymbolOff
- symbol showing on every line of scope if its indent is not multiple of ‘shiftwidth’. Default: links toMiniIndentscopeSymbol
.
To change any highlight group, set it directly with nvim_set_hl().
Disabling
To disable autodrawing, set vim.g.miniindentscope_disable
(globally) or vim.b.miniindentscope_disable
(for a buffer) to true
. Considering high number of different scenarios and customization intentions, writing exact rules for disabling module’s functionality is left to user. See mini.nvim-disabling-recipes for common recipes.
Drawing
Draw of scope indicator is done as iterative animation. It has the following design:
Draw indicator on origin line (where cursor is at) immediately. Indicator is visualized as
MiniIndentscope.config.symbol
placed to the right of scope’s border indent. This creates a line from top to bottom scope edges.Draw upward and downward concurrently per one line. Progression by one line in both direction is considered to be one step of animation.
Before each step wait certain amount of time, which is decided by “animation function”. It takes next and total step numbers (both are one or bigger) and returns number of milliseconds to wait before drawing next step. Comparing to a more popular “easing functions” in animation (input: duration since animation start; output: percent of animation done), it is a discrete inverse version of its derivative. Such interface proved to be more appropriate for kind of task at hand.
Special cases
When scope to be drawn intersects (same indent, ranges overlap) currently visible one (at process or finished drawing), drawing is done immediately without animation. With most common example being typing new text, this feels more natural.
Scope for the whole buffer is not drawn as it is isually redundant. Technically, it can be thought as drawn at column 0 (because border indent is -1) which is not visible.
setup()
MiniIndentscope.setup
({config})
Module setup
Parameters
{config} (table|nil)
Module config table. See MiniIndentscope.config.
Usage
require('mini.indentscope').setup() -- use default config
-- OR
require('mini.indentscope').setup({}) -- replace {} with your config table
config
MiniIndentscope.config
Defaults
MiniIndentscope.config = {
-- Draw options
draw = {
-- Delay (in ms) between event and start of drawing scope indicator
delay = 100,
-- Animation rule for scope's first drawing. A function which, given
-- next and total step numbers, returns wait time (in ms). See
-- |MiniIndentscope.gen_animation| for builtin options. To disable
-- animation, use `require('mini.indentscope').gen_animation.none()`.
animation = --<function: implements constant 20ms between steps>,
-- Whether to auto draw scope: return `true` to draw, `false` otherwise.
-- Default draws only fully computed scope (see `options.n_lines`).
predicate = function(scope) return not scope.body.is_incomplete end,
-- Symbol priority. Increase to display on top of more symbols.
priority = 2,
},
-- Module mappings. Use `''` (empty string) to disable one.
mappings = {
-- Textobjects
object_scope = 'ii',
object_scope_with_border = 'ai',
-- Motions (jump to respective border line; if not present - body line)
goto_top = '[i',
goto_bottom = ']i',
},
-- Options which control scope computation
options = {
-- Type of scope's border: which line(s) with smaller indent to
-- categorize as border. Can be one of: 'both', 'top', 'bottom', 'none'.
border = 'both',
-- Whether to use cursor column when computing reference indent.
-- Useful to see incremental scopes with horizontal cursor movements.
indent_at_cursor = true,
-- Maximum number of lines above or below within which scope is computed
n_lines = 10000,
-- Whether to first check input line to be a border of adjacent scope.
-- Use it if you want to place cursor on function header to get scope of
-- its body.
try_as_border = false,
},
-- Which character to use for drawing scope indicator
symbol = '╎',
}
Options
Border
Field border
controls which line(s) with smaller indent to categorize as border. This matters for textobjects and motions. It also controls how empty lines are treated: they are included in scope only if followed by a border. Another way of looking at it is that indent of blank line is computed based on value of border
option. Here is an illustration of how border
works in presence of empty lines:
|both|bottom|top|none|
1|function foo() | 0 | 0 | 0 | 0 |
2| | 4 | 0 | 4 | 0 |
3| print('Hello world') | 4 | 4 | 4 | 4 |
4| | 4 | 4 | 2 | 2 |
5| end | 2 | 2 | 2 | 2 |
Numbers inside a table are indent values of a line computed with certain value of border
. So, for example, a scope with reference line 3 and right-most column has body range depending on value of border
option:
border
is “both”: range is 2-4, border is 1 and 5 with indent 2.border
is “top”: range is 2-3, border is 1 with indent 0.border
is “bottom”: range is 3-4, border is 5 with indent 0.border
is “none”: range is 3-3, border is empty with indentnil
.
Indent at cursor
Field indent_at_cursor
controls if cursor position should affect computation of scope. If true
, reference indent is a minimum of reference line’s indent and cursor column. In main example, here how scope’s body range differs depending on cursor column and indent_at_cursor
value (assuming cursor is on line 3 and it is whole buffer):
Column\Option true|false
1 and 2 2-5 | 2-4
3 and more 2-4 | 2-4
Number of lines
Field n_lines
defines MiniIndentscope.get_scope() behavior for how many lines above/below to check before iteration is stopped. Scope that reached computation limit has <is_incomplete> field set to true
. It will also not be auto drawn with default config.draw.predicate
.
Lower values will result in better overall performance in exchange for more frequent incomplete scope computation. Set to math.huge
for no restriction.
Try as border
Field try_as_border
controls how to act when input line can be recognized as a border of some neighbor indent scope. In main example, when input line is 1 and can be recognized as border for inner scope, value try_as_border=true
means that inner scope will be returned. Similar, for input line 5 inner scope will be returned if it is recognized as border.
get_scope()
MiniIndentscope.get_scope
({line}, {col}, {opts})
Compute indent scope
Indent scope (or just “scope”) is a maximum set of consecutive lines which contains certain reference line (cursor line by default) and every member has indent not less than certain reference indent (“indent at column” by default). Here “indent at column” means minimum between input column value and indent of reference line. When using cursor column, this allows for a useful interactive view of nested indent scopes by making horizontal movements within line.
Options controlling actual computation is taken from these places in order:
Argument
opts
. Use it to ensure independence from other sources.Buffer local variable
vim.b.miniindentscope_config
(options
field). Useful to define local behavior (for example, for a certain filetype).Global options from MiniIndentscope.config.
Algorithm overview
Compute reference “indent at column”. Reference line is an input
line
which might be modified to one of its neighbors iftry_as_border
option istrue
: if it can be viewed as border of some neighbor scope, it will.Process upwards and downwards from reference line searching for line with indent strictly less than reference one. This is like casting rays up and down from reference line and reference indent until meeting “a wall” (character to the right of indent or buffer edge). Latest line before meeting a wall is a respective end of scope body. It always exists because reference line is a such one. Casting ray is forced to stop if it goes over
opts.n_lines
lines.Based on top and bottom lines with strictly lower indent, construct scopes’s border. The way it is computed is decided based on
border
option (see MiniIndentscope.config for more information).Compute border indent as maximum indent of border lines (or reference indent minus one in case of no border). This is used during drawing visual indicator.
Indent computation
For every line indent is intended to be computed unambiguously:
For “normal” lines indent is an output of indent().
Indent is
-1
for imaginary lines 0 and past last line.For blank and empty lines indent is computed based on previous (prevnonblank()) and next (nextnonblank()) non-blank lines. The way it is computed is decided based on
border
in order to not include blank lines at edge of scope’s body if there is no border there. See MiniIndentscope.config for a details example.
Parameters
{line} (number|nil)
Input line number (starts from 1). Can be modified to a neighbor if try_as_border
is true
. Default: cursor line.
{col} (number|nil)
Column number (starts from 1). Default: if indent_at_cursor
option is true
- cursor column from curswant
of getcurpos() (allows for more natural behavior on empty lines); math.huge
otherwise in order to not incorporate cursor in computation.
{opts} (table|nil)
Options to override global or buffer local ones (see MiniIndentscope.config).
Return
(table)
Table with scope information:
<body> - table with <top> (top line of scope, inclusive), <bottom> (bottom line of scope, inclusive), and <indent> (minimum indent within scope) keys. Line numbers start at 1. Can also have <is_incomplete> key set to
true
if computation was stopped due toopts.n_lines
restriction.<border> - table with <top> (line of top border, might be
nil
), <bottom> (line of bottom border, might benil
), and <indent> (indent of border) keys. Line numbers start at 1.<buf_id> - identifier of current buffer.
<reference> - table with <line> (reference line), <column> (reference column), and <indent> (“indent at column”) keys.
draw()
MiniIndentscope.draw
({scope}, {opts})
Draw scope manually
Scope is visualized as a vertical line within scope’s body range at column equal to border indent plus one (or body indent if border is absent). Numbering starts from one.
Parameters
{scope} (table|nil)
Scope. Default: output of MiniIndentscope.get_scope() with default arguments.
{opts} (table|nil)
Options. Currently supported:
<animation_fun> - animation function for drawing. See MiniIndentscope-drawing and MiniIndentscope.gen_animation.
<priority> - priority number for visualization. See
priority
option for nvim_buf_set_extmark().
undraw()
MiniIndentscope.undraw
()
Undraw currently visible scope manually
gen_animation
MiniIndentscope.gen_animation
Generate builtin animation function
This is a builtin source to generate animation function for usage in MiniIndentscope.config.draw.animation
. Most of them are variations of common easing functions, which provide certain type of progression for revealing scope visual indicator.
Each field corresponds to one family of progression which can be customized further by supplying appropriate arguments.
Examples:
Don’t use animation:
MiniIndentscope.gen_animation.none()
Use quadratic “out” easing with total duration of 1000 ms:
gen_animation.quadratic({ easing = 'out', duration = 1000, unit = 'total' })
See also
MiniIndentscope-drawing for more information about how drawing is done.
gen_animation.none()
MiniIndentscope.gen_animation.none
()
Generate no animation
Show indicator immediately. Same as animation function always returning 0.
gen_animation.linear()
MiniIndentscope.gen_animation.linear
({opts})
Generate linear progression
Parameters
{opts} (table|nil)
Options that control progression. Possible keys:
<easing>
(string)
- a subtype of progression. One of “in” (accelerating from zero speed), “out” (decelerating to zero speed), “in-out” (default; accelerating halfway, decelerating after).<duration>
(number)
- duration (in ms) of a unit. Default: 20.<unit>
(string)
- which unit’s durationopts.duration
controls. One of “step” (default; ensures average duration of step to beopts.duration
) or “total” (ensures fixed total duration regardless of scope’s range).
Return
(function)
Animation function (see MiniIndentscope-drawing).
gen_animation.quadratic()
MiniIndentscope.gen_animation.quadratic
({opts})
Generate quadratic progression
Parameters
{opts} (table|nil)
Options that control progression. Possible keys:
<easing>
(string)
- a subtype of progression. One of “in” (accelerating from zero speed), “out” (decelerating to zero speed), “in-out” (default; accelerating halfway, decelerating after).<duration>
(number)
- duration (in ms) of a unit. Default: 20.<unit>
(string)
- which unit’s durationopts.duration
controls. One of “step” (default; ensures average duration of step to beopts.duration
) or “total” (ensures fixed total duration regardless of scope’s range).
Return
(function)
Animation function (see MiniIndentscope-drawing).
gen_animation.cubic()
MiniIndentscope.gen_animation.cubic
({opts})
Generate cubic progression
Parameters
{opts} (table|nil)
Options that control progression. Possible keys:
<easing>
(string)
- a subtype of progression. One of “in” (accelerating from zero speed), “out” (decelerating to zero speed), “in-out” (default; accelerating halfway, decelerating after).<duration>
(number)
- duration (in ms) of a unit. Default: 20.<unit>
(string)
- which unit’s durationopts.duration
controls. One of “step” (default; ensures average duration of step to beopts.duration
) or “total” (ensures fixed total duration regardless of scope’s range).
Return
(function)
Animation function (see MiniIndentscope-drawing).
gen_animation.quartic()
MiniIndentscope.gen_animation.quartic
({opts})
Generate quartic progression
Parameters
{opts} (table|nil)
Options that control progression. Possible keys:
<easing>
(string)
- a subtype of progression. One of “in” (accelerating from zero speed), “out” (decelerating to zero speed), “in-out” (default; accelerating halfway, decelerating after).<duration>
(number)
- duration (in ms) of a unit. Default: 20.<unit>
(string)
- which unit’s durationopts.duration
controls. One of “step” (default; ensures average duration of step to beopts.duration
) or “total” (ensures fixed total duration regardless of scope’s range).
Return
(function)
Animation function (see MiniIndentscope-drawing).
gen_animation.exponential()
MiniIndentscope.gen_animation.exponential
({opts})
Generate exponential progression
Parameters
{opts} (table|nil)
Options that control progression. Possible keys:
<easing>
(string)
- a subtype of progression. One of “in” (accelerating from zero speed), “out” (decelerating to zero speed), “in-out” (default; accelerating halfway, decelerating after).<duration>
(number)
- duration (in ms) of a unit. Default: 20.<unit>
(string)
- which unit’s durationopts.duration
controls. One of “step” (default; ensures average duration of step to beopts.duration
) or “total” (ensures fixed total duration regardless of scope’s range).
Return
(function)
Animation function (see MiniIndentscope-drawing).
move_cursor()
MiniIndentscope.move_cursor
({side}, {use_border}, {scope})
Move cursor within scope
Cursor is placed on a first non-blank character of target line.
Parameters
{side} (string)
One of “top” or “bottom”.
{use_border} (boolean|nil)
Whether to move to border or within scope’s body. If particular border is absent, body is used.
{scope} (table|nil)
Scope to use. Default: output of MiniIndentscope.get_scope().
operator()
MiniIndentscope.operator
({side}, {add_to_jumplist})
Function for motion mappings
Move to a certain side of border. Respects count and dot-repeat (in operator-pending mode). Doesn’t move cursor for scope that is not shown (drawing indent less that zero).
Parameters
{side} (string)
One of “top” or “bottom”.
{add_to_jumplist} (boolean|nil)
Whether to add movement to jump list. It is true
only for Normal mode mappings.
textobject()
MiniIndentscope.textobject
({use_border})
Function for textobject mappings
Respects count and dot-repeat (in operator-pending mode). Doesn’t work for scope that is not shown (drawing indent less that zero).
Parameters
{use_border} (boolean|nil)
Whether to include border in textobject. When true
and try_as_border
option is false
, allows “chaining” calls for incremental selection.