1
1
local tree_utils = require (' orgmode.utils.treesitter' )
2
+ local dict_watcher = require (' orgmode.utils.dict_watcher' )
2
3
--- @class OrgVirtualIndent
3
4
--- @field private _ns_id number extmarks namespace id
5
+ --- @field private _bufnr integer Buffer VirtualIndent is attached to
6
+ --- @field private _attached boolean Whether or not VirtualIndent is attached for its buffer
7
+ --- @field private _bufnrs table<integer , OrgVirtualIndent> Buffers with VirtualIndent attached
8
+ --- @field private _watcher_running boolean Whether or not VirtualIndent is reacting to ` vim.b.org_indent_mode`
4
9
local VirtualIndent = {
5
- enabled = false ,
6
- lib = {},
10
+ _ns_id = vim .api .nvim_create_namespace (' orgmode.ui.indent' ),
11
+ _bufnrs = {},
12
+ _watcher_running = false ,
7
13
}
8
14
9
- function VirtualIndent :new ()
10
- if self .enabled then
11
- return self
15
+ --- Creates a new instance of VirtualIndent for a given buffer or returns the existing instance if
16
+ --- one exists
17
+ --- @param bufnr ? integer Buffer to use for VirtualIndent when attached
18
+ --- @return OrgVirtualIndent
19
+ function VirtualIndent :new (bufnr )
20
+ bufnr = bufnr or vim .api .nvim_get_current_buf ()
21
+
22
+ local curr_instance = VirtualIndent ._bufnrs [bufnr ]
23
+ if curr_instance then
24
+ return curr_instance
12
25
end
13
- self ._ns_id = vim .api .nvim_create_namespace (' orgmode.ui.indent' )
14
- self .enabled = true
15
- return self
26
+
27
+ local new = {}
28
+ VirtualIndent ._bufnrs [bufnr ] = new
29
+ setmetatable (new , self )
30
+ self .__index = self
31
+
32
+ new ._bufnr = bufnr
33
+ new ._attached = false
34
+ return new
16
35
end
17
36
18
- function VirtualIndent :_delete_old_extmarks (buffer , start_line , end_line )
37
+ function VirtualIndent :_delete_old_extmarks (start_line , end_line )
19
38
local old_extmarks = vim .api .nvim_buf_get_extmarks (
20
- buffer ,
39
+ self . _bufnr ,
21
40
self ._ns_id ,
22
41
{ start_line , 0 },
23
42
{ end_line , 0 },
24
43
{ type = ' virt_text' }
25
44
)
26
45
for _ , ext in ipairs (old_extmarks ) do
27
- vim .api .nvim_buf_del_extmark (buffer , self ._ns_id , ext [1 ])
46
+ vim .api .nvim_buf_del_extmark (self . _bufnr , self ._ns_id , ext [1 ])
28
47
end
29
48
end
30
49
@@ -43,11 +62,10 @@ function VirtualIndent:_get_indent_size(line)
43
62
return 0
44
63
end
45
64
46
- --- @param bufnr number buffer id
47
65
--- @param start_line number start line number to set the indentation , 0-based inclusive
48
66
--- @param end_line number end line number to set the indentation , 0-based inclusive
49
67
--- @param ignore_ts ? boolean whether or not to skip the treesitter start & end lookup
50
- function VirtualIndent :set_indent (bufnr , start_line , end_line , ignore_ts )
68
+ function VirtualIndent :set_indent (start_line , end_line , ignore_ts )
51
69
ignore_ts = ignore_ts or false
52
70
local headline = tree_utils .closest_headline_node ({ start_line + 1 , 1 })
53
71
if headline and not ignore_ts then
@@ -60,13 +78,13 @@ function VirtualIndent:set_indent(bufnr, start_line, end_line, ignore_ts)
60
78
if start_line > 0 then
61
79
start_line = start_line - 1
62
80
end
63
- self :_delete_old_extmarks (bufnr , start_line , end_line )
81
+ self :_delete_old_extmarks (start_line , end_line )
64
82
for line = start_line , end_line do
65
83
local indent = self :_get_indent_size (line )
66
84
67
85
if indent > 0 then
68
86
-- NOTE: `ephemeral = true` is not implemented for `inline` virt_text_pos :(
69
- pcall (vim .api .nvim_buf_set_extmark , bufnr , self ._ns_id , line , 0 , {
87
+ pcall (vim .api .nvim_buf_set_extmark , self . _bufnr , self ._ns_id , line , 0 , {
70
88
virt_text = { { string.rep (' ' , indent ), ' OrgIndent' } },
71
89
virt_text_pos = ' inline' ,
72
90
right_gravity = false ,
@@ -75,22 +93,56 @@ function VirtualIndent:set_indent(bufnr, start_line, end_line, ignore_ts)
75
93
end
76
94
end
77
95
78
- --- @param bufnr ? number buffer id
79
- function VirtualIndent :attach (bufnr )
80
- bufnr = bufnr or 0
81
- self :set_indent (0 , 0 , vim .api .nvim_buf_line_count (bufnr ) - 1 , true )
96
+ --- Make all VirtualIndent instances react to changes in `org_indent_mode`
97
+ function VirtualIndent :start_watch_org_indent ()
98
+ if not self ._watcher_running then
99
+ self ._watcher_running = true
100
+ dict_watcher .watch_buffer_variable (' org_indent_mode' , function (indent_mode , _ , buf_vars )
101
+ local vindent = VirtualIndent ._bufnrs [buf_vars .org_bufnr ]
102
+ local indent_mode_enabled = indent_mode .new or false
103
+ --- @diagnostic disable-next-line : invisible
104
+ if indent_mode_enabled and not vindent ._attached then
105
+ vindent :attach ()
106
+ --- @diagnostic disable-next-line : invisible
107
+ elseif not indent_mode_enabled and vindent ._attached then
108
+ vindent :detach ()
109
+ end
110
+ end )
111
+ end
112
+ end
113
+
114
+ --- Stops VirtualIndent instances from reacting to changes in `vim.b.org_indent_mode`
115
+ function VirtualIndent :stop_watch_org_indent ()
116
+ self ._watcher_running = false
117
+ dict_watcher .unwatch_buffer_variable (' org_indent_mode' )
118
+ end
119
+
120
+ --- Enables virtual indentation in registered buffer
121
+ function VirtualIndent :attach ()
122
+ self ._attached = true
123
+ self :set_indent (0 , vim .api .nvim_buf_line_count (self ._bufnr ) - 1 , true )
124
+ self :start_watch_org_indent ()
82
125
83
- vim .api .nvim_buf_attach (bufnr , false , {
126
+ vim .api .nvim_buf_attach (self . _bufnr , false , {
84
127
on_lines = function (_ , _ , _ , start_line , _ , end_line )
128
+ if not self ._attached then
129
+ return true
130
+ end
85
131
-- HACK: By calling `set_indent` twice, once synchronously and once in `vim.schedule` we get smooth usage of the
86
132
-- virtual indent in most cases and still properly handle undo redo. Unfortunately this is called *early* when
87
133
-- `undo` or `redo` is used causing the padding to be incorrect for some headlines.
88
- self :set_indent (bufnr , start_line , end_line )
134
+ self :set_indent (start_line , end_line )
89
135
vim .schedule (function ()
90
- self :set_indent (bufnr , start_line , end_line )
136
+ self :set_indent (start_line , end_line )
91
137
end )
92
138
end ,
93
139
})
94
140
end
95
141
142
+ function VirtualIndent :detach ()
143
+ self ._attached = false
144
+ vim .api .nvim_buf_set_var (self ._bufnr , ' org_indent_mode' , false )
145
+ self :_delete_old_extmarks (0 , vim .api .nvim_buf_line_count (self ._bufnr ) - 1 )
146
+ end
147
+
96
148
return VirtualIndent
0 commit comments