Skip to content

Commit 7f0829b

Browse files
committed
feat: make VirtualIndent react to changes in vim.b.org_indent_mode
1 parent ab045e3 commit 7f0829b

File tree

2 files changed

+83
-27
lines changed

2 files changed

+83
-27
lines changed

lua/orgmode/org/indent.lua

+6-2
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,12 @@ end
311311
local function setup()
312312
local v = vim.version()
313313

314-
if config.org_startup_indented and not vim.version.lt({ v.major, v.minor, v.patch }, { 0, 10, 0 }) then
315-
VirtualIndent:new():attach()
314+
if not vim.version.lt({ v.major, v.minor, v.patch }, { 0, 10, 0 }) then
315+
if config.org_startup_indented then
316+
VirtualIndent:new():attach()
317+
else
318+
VirtualIndent:new():start_watch_org_indent()
319+
end
316320
end
317321
end
318322

lua/orgmode/ui/virtual_indent.lua

+77-25
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,54 @@
11
---@class VirtualIndent
22
---@field private _ns_id number extmarks namespace id
3+
---@field private _headline_lib any Treesitter headline library
4+
---@field private _bufnr integer Buffer VirtualIndent is attached to
5+
---@field private _attached boolean Whether or not VirtualIndent is attached for its buffer
6+
---@field private _bufnrs {integer: boolean} Buffers with VirtualIndent attached
7+
---@field private _timer uv_timer_t Timer used for tracking `org_indent_mode`
38
local VirtualIndent = {
4-
enabled = false,
5-
lib = {},
9+
_ns_id = vim.api.nvim_create_namespace('orgmode.ui.indent'),
10+
_bufnrs = {},
611
}
712

8-
function VirtualIndent:new()
9-
if self.enabled then
10-
return self
13+
--- Creates a new instance of VirtualIndent for a given buffer or returns the existing instance if
14+
--- one exists
15+
---@param bufnr? integer Buffer to use for VirtualIndent when attached
16+
---@return VirtualIndent
17+
function VirtualIndent:new(bufnr)
18+
bufnr = bufnr or vim.api.nvim_get_current_buf()
19+
20+
local curr_instance = VirtualIndent._bufnrs[bufnr]
21+
if curr_instance then
22+
return curr_instance
1123
end
12-
self._ns_id = vim.api.nvim_create_namespace('orgmode.ui.indent')
13-
self.lib.headline = require('orgmode.treesitter.headline')
14-
self.enabled = true
15-
return self
24+
25+
local new = {}
26+
setmetatable(new, self)
27+
self.__index = self
28+
29+
new._bufnr = bufnr
30+
new._headline_lib = require('orgmode.treesitter.headline')
31+
new._attached = false
32+
VirtualIndent._bufnrs[new._bufnr] = new
33+
new._timer = vim.uv.new_timer()
34+
return new
1635
end
1736

18-
function VirtualIndent:_delete_old_extmarks(buffer, start_line, end_line)
37+
function VirtualIndent:_delete_old_extmarks(start_line, end_line)
1938
local old_extmarks = vim.api.nvim_buf_get_extmarks(
20-
buffer,
39+
self._bufnr,
2140
self._ns_id,
2241
{ start_line, 0 },
2342
{ end_line, 0 },
2443
{ type = 'virt_text' }
2544
)
2645
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])
2847
end
2948
end
3049

3150
function VirtualIndent:_get_indent_size(line)
32-
local headline = self.lib.headline.from_cursor({ line + 1, 1 })
51+
local headline = self._headline_lib.from_cursor({ line + 1, 1 })
3352

3453
if headline then
3554
local headline_line, _, _ = headline.headline:start()
@@ -42,13 +61,12 @@ function VirtualIndent:_get_indent_size(line)
4261
return 0
4362
end
4463

45-
---@param bufnr number buffer id
4664
---@param start_line number start line number to set the indentation, 0-based inclusive
4765
---@param end_line number end line number to set the indentation, 0-based inclusive
4866
---@param ignore_ts? boolean whether or not to skip the treesitter start & end lookup
49-
function VirtualIndent:set_indent(bufnr, start_line, end_line, ignore_ts)
67+
function VirtualIndent:set_indent(start_line, end_line, ignore_ts)
5068
ignore_ts = ignore_ts or false
51-
local headline = self.lib.headline.from_cursor({ start_line + 1, 1 })
69+
local headline = self._headline_lib.from_cursor({ start_line + 1, 1 })
5270
if headline and not ignore_ts then
5371
local parent = headline.headline:parent()
5472
start_line = parent:start()
@@ -57,13 +75,13 @@ function VirtualIndent:set_indent(bufnr, start_line, end_line, ignore_ts)
5775
if start_line > 0 then
5876
start_line = start_line - 1
5977
end
60-
self:_delete_old_extmarks(bufnr, start_line, end_line)
78+
self:_delete_old_extmarks(start_line, end_line)
6179
for line = start_line, end_line do
6280
local indent = self:_get_indent_size(line)
6381

6482
if indent > 0 then
6583
-- NOTE: `ephemeral = true` is not implemented for `inline` virt_text_pos :(
66-
vim.api.nvim_buf_set_extmark(bufnr, self._ns_id, line, 0, {
84+
vim.api.nvim_buf_set_extmark(self._bufnr, self._ns_id, line, 0, {
6785
virt_text = { { string.rep(' ', indent), 'OrgIndent' } },
6886
virt_text_pos = 'inline',
6987
right_gravity = false,
@@ -72,22 +90,56 @@ function VirtualIndent:set_indent(bufnr, start_line, end_line, ignore_ts)
7290
end
7391
end
7492

75-
---@param bufnr? number buffer id
76-
function VirtualIndent:attach(bufnr)
77-
bufnr = bufnr or 0
78-
self:set_indent(0, 0, vim.api.nvim_buf_line_count(bufnr) - 1, true)
93+
--- Begins a timer to check `vim.b.org_indent_mode` and correctly attach or detatch VirtualIndent as
94+
--- necessary
95+
function VirtualIndent:start_watch_org_indent()
96+
self._timer:start(
97+
50,
98+
50,
99+
vim.schedule_wrap(function()
100+
local success, indent_mode_enabled = pcall(vim.api.nvim_buf_get_var, self._bufnr, 'org_indent_mode')
101+
if success and indent_mode_enabled then
102+
if not self._attached then
103+
self:attach()
104+
end
105+
elseif self._attached then
106+
self:detach()
107+
end
108+
end)
109+
)
110+
end
111+
112+
--- Stops the current VirtualIndent instance from reacting to changes in `vim.b.org_indent_mode`
113+
function VirtualIndent:stop_watch_org_indent()
114+
self._timer:stop()
115+
end
116+
117+
--- Enables virtual indentation in registered buffer
118+
function VirtualIndent:attach()
119+
self._attached = true
120+
self:set_indent(0, vim.api.nvim_buf_line_count(self._bufnr) - 1, true)
121+
self:start_watch_org_indent()
79122

80-
vim.api.nvim_buf_attach(bufnr, false, {
123+
vim.api.nvim_buf_attach(self._bufnr, false, {
81124
on_lines = function(_, _, _, start_line, _, end_line)
125+
if not self._attached then
126+
return true
127+
end
82128
-- HACK: By calling `set_indent` twice, once synchronously and once in `vim.schedule` we get smooth usage of the
83129
-- virtual indent in most cases and still properly handle undo redo. Unfortunately this is called *early* when
84130
-- `undo` or `redo` is used causing the padding to be incorrect for some headlines.
85-
self:set_indent(bufnr, start_line, end_line)
131+
self:set_indent(self._bufnr, start_line, end_line)
86132
vim.schedule(function()
87-
self:set_indent(bufnr, start_line, end_line)
133+
self:set_indent(self._bufnr, start_line, end_line)
88134
end)
89135
end,
90136
})
91137
end
92138

139+
function VirtualIndent:detach()
140+
self._attached = false
141+
vim.api.nvim_buf_set_var(self._bufnr, 'org_indent_mode', false)
142+
self:_delete_old_extmarks(0, vim.api.nvim_buf_line_count(self._bufnr) - 1)
143+
end
144+
93145
return VirtualIndent

0 commit comments

Comments
 (0)