1
1
local config = require (' orgmode.config' )
2
- local buf_blocks = {}
2
+ local ts_utils = require (' nvim-treesitter.ts_utils' )
3
+ local query = nil
3
4
4
5
local valid_pre_marker_chars = { ' ' , ' (' , ' -' , " '" , ' "' , ' {' }
5
6
local valid_post_marker_chars = { ' ' , ' )' , ' -' , ' }' , ' "' , " '" , ' :' , ' ;' , ' !' , ' \\ ' , ' [' , ' ,' , ' .' , ' ?' }
@@ -31,119 +32,258 @@ local markers = {
31
32
},
32
33
}
33
34
34
- local function apply_markup_to_line (namespace , bufnr , line_index , line )
35
- local hl = function (from , to , opts )
36
- local options = vim .tbl_extend (' force' , { ephemeral = true , end_col = to }, opts or {})
37
- vim .api .nvim_buf_set_extmark (bufnr , namespace , line_index , from , options )
35
+ local function get_node_text (node , source , offset_col_start , offset_col_end )
36
+ local start_row , start_col = node :start ()
37
+ local end_row , end_col = node :end_ ()
38
+ start_col = start_col + (offset_col_start or 0 )
39
+ end_col = end_col + (offset_col_end or 0 )
40
+
41
+ local lines
42
+ local eof_row = vim .api .nvim_buf_line_count (source )
43
+ if start_row >= eof_row then
44
+ return nil
45
+ end
46
+
47
+ if end_col == 0 then
48
+ lines = vim .api .nvim_buf_get_lines (source , start_row , end_row , true )
49
+ end_col = - 1
50
+ else
51
+ lines = vim .api .nvim_buf_get_lines (source , start_row , end_row + 1 , true )
52
+ end
53
+
54
+ if # lines > 0 then
55
+ if # lines == 1 then
56
+ lines [1 ] = string.sub (lines [1 ], start_col + 1 , end_col )
57
+ else
58
+ lines [1 ] = string.sub (lines [1 ], start_col + 1 )
59
+ lines [# lines ] = string.sub (lines [# lines ], 1 , end_col )
60
+ end
61
+ end
62
+
63
+ return table.concat (lines , ' \n ' )
64
+ end
65
+
66
+ local get_tree = ts_utils .memoize_by_buf_tick (function (bufnr )
67
+ local tree = vim .treesitter .get_parser (bufnr , ' org' ):parse ()
68
+ if not tree or not # tree then
69
+ return nil
70
+ end
71
+ return tree [1 ]:root ()
72
+ end )
73
+
74
+ local function get_predicate_nodes (match )
75
+ local counter = 1
76
+ local start_node = nil
77
+ local end_node = nil
78
+ for i , node in pairs (match ) do
79
+ if counter == 1 then
80
+ start_node = node
81
+ end
82
+ if counter == 2 then
83
+ end_node = node
84
+ end
85
+ counter = counter + 1
86
+ end
87
+ if not start_node or not end_node then
88
+ return false
89
+ end
90
+ return start_node , end_node
91
+ end
92
+
93
+ local function is_valid_markup_range (match , _ , source , _ )
94
+ local start_node , end_node = get_predicate_nodes (match )
95
+ if not start_node or not end_node then
96
+ return
97
+ end
98
+
99
+ -- Ignore conflicts with hyperlink
100
+ if start_node :type () == ' [' or end_node :type () == ' ]' then
101
+ return true
102
+ end
103
+
104
+ local start_line = start_node :range ()
105
+ local end_line = start_node :range ()
106
+
107
+ if start_line ~= end_line then
108
+ return false
109
+ end
110
+
111
+ local start_text = get_node_text (start_node , source , - 1 )
112
+ local end_text = get_node_text (end_node , source , 0 , 1 )
113
+
114
+ local is_valid_start = start_text :len () < 2 or vim .tbl_contains (valid_pre_marker_chars , start_text :sub (1 , 1 ))
115
+ local is_valid_end = end_text :len () < 2 or vim .tbl_contains (valid_post_marker_chars , end_text :sub (2 , 2 ))
116
+ return is_valid_start and is_valid_end
117
+ end
118
+
119
+ local function is_valid_hyperlink_range (match , _ , source , _ )
120
+ local start_node , end_node = get_predicate_nodes (match )
121
+ if not start_node or not end_node then
122
+ return
123
+ end
124
+ -- Ignore conflicts with markup
125
+ if start_node :type () ~= ' [' or end_node :type () ~= ' ]' then
126
+ return true
127
+ end
128
+
129
+ local start_line = start_node :range ()
130
+ local end_line = start_node :range ()
131
+
132
+ if start_line ~= end_line then
133
+ return false
134
+ end
135
+
136
+ local start_text = get_node_text (start_node , source , 0 , 1 )
137
+ local end_text = get_node_text (end_node , source , - 1 )
138
+
139
+ local is_valid_start = start_text == ' [['
140
+ local is_valid_end = end_text == ' ]]'
141
+ return is_valid_start and is_valid_end
142
+ end
143
+
144
+ local function load_deps ()
145
+ -- Already defined
146
+ if query then
147
+ return
148
+ end
149
+ query = vim .treesitter .get_query (' org' , ' markup' )
150
+ vim .treesitter .query .add_predicate (' org-is-valid-markup-range?' , is_valid_markup_range )
151
+ vim .treesitter .query .add_predicate (' org-is-valid-hyperlink-range?' , is_valid_hyperlink_range )
152
+ end
153
+
154
+ --- @param bufnr ? number
155
+ --- @param first_line ? number
156
+ --- @param last_line ? number
157
+ --- @return table[]
158
+ local function get_matches (bufnr , first_line , last_line )
159
+ bufnr = bufnr or 0
160
+ local root = get_tree (bufnr )
161
+ if not root then
162
+ return
38
163
end
39
164
40
- local hide_markers = config .org_hide_emphasis_markers
41
- local l = line
42
- local stars = l :match (' ^%*+%s+' )
43
- local offset = 0
44
- if stars then
45
- l = l :sub (stars :len () + 1 )
46
- offset = stars :len ()
47
- end
48
- local chars = vim .split (l , ' ' , true )
49
165
local ranges = {}
166
+ local taken_locations = {}
167
+
168
+ for _ , match , _ in query :iter_matches (root , bufnr , first_line , last_line ) do
169
+ for _ , node in pairs (match ) do
170
+ local char = node :type ()
171
+ local range = ts_utils .node_to_lsp_range (node )
172
+ local linenr = tostring (range .start .line )
173
+ taken_locations [linenr ] = taken_locations [linenr ] or {}
174
+ if not taken_locations [linenr ][range .start .character ] then
175
+ table.insert (ranges , {
176
+ type = char ,
177
+ range = range ,
178
+ })
179
+ taken_locations [linenr ][range .start .character ] = true
180
+ end
181
+ end
182
+ end
183
+
184
+ table.sort (ranges , function (a , b )
185
+ if a .range .start .line == b .range .start .line then
186
+ return a .range .start .character < b .range .start .character
187
+ end
188
+ return a .range .start .line < b .range .start .line
189
+ end )
190
+
50
191
local seek = {}
51
192
local seek_link = {}
52
- local link_ranges = {}
53
-
54
- for i , char in ipairs (chars ) do
55
- -- Markup parsing
56
- if markers [char ] then
57
- if seek [char ] then
58
- local next_char = chars [i + 1 ]
59
- if next_char == nil or vim .tbl_contains (valid_post_marker_chars , next_char ) then
60
- local to = i + offset
61
- table.insert (ranges , { type = char , from = seek [char ], to = to })
62
- -- Cleanup all unclosed markers in between
63
- for c , pos in pairs (seek ) do
64
- if c ~= char and pos < to and pos > seek [char ] then
65
- seek [c ] = nil
66
- end
193
+ local result = {}
194
+ local link_result = {}
195
+
196
+ for _ , item in ipairs (ranges ) do
197
+ if markers [item .type ] then
198
+ if seek [item .type ] then
199
+ local from = seek [item .type ]
200
+ table.insert (result , {
201
+ type = item .type ,
202
+ from = from .range ,
203
+ to = item .range ,
204
+ })
205
+
206
+ seek [item .type ] = nil
207
+
208
+ for t , pos in pairs (seek ) do
209
+ if
210
+ pos .range .start .line == from .range .start .line
211
+ and pos .range .start .character > from .range [' end' ].character
212
+ and pos .range .start .character < item .range .start .character
213
+ then
214
+ seek [t ] = nil
67
215
end
68
- seek [char ] = nil
69
216
end
70
217
else
71
- local prev_char = chars [i - 1 ]
72
- if prev_char == nil or vim .tbl_contains (valid_pre_marker_chars , prev_char ) then
73
- seek [char ] = i + offset
74
- end
218
+ seek [item .type ] = item
75
219
end
76
220
end
77
221
78
- -- Links parsing
79
- if char == ' [' and chars [i - 1 ] == ' [' then
80
- seek_link [char ] = i - 1 + offset
222
+ if item .type == ' [' then
223
+ seek_link = item
81
224
end
82
225
83
- if char == ' ]' and chars [i + 1 ] == ' ]' and seek_link [' [' ] then
84
- table.insert (link_ranges , { from = seek_link [' [' ], to = i + 1 + offset })
226
+ if item .type == ' ]' and seek_link then
227
+ table.insert (link_result , {
228
+ from = seek_link .range ,
229
+ to = item .range ,
230
+ })
231
+ seek_link = nil
85
232
end
86
233
end
87
234
235
+ return result , link_result
236
+ end
237
+
238
+ local function apply (namespace , bufnr , _ , first_line , last_line , _ )
239
+ local ranges , link_ranges = get_matches (bufnr , first_line , last_line )
240
+ local hide_markers = config .org_hide_emphasis_markers
241
+
88
242
for _ , range in ipairs (ranges ) do
89
- hl (range .from - 1 , range .to , {
243
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , range .from .start .line , range .from .start .character , {
244
+ ephemeral = true ,
245
+ end_col = range .to [' end' ].character ,
90
246
hl_group = markers [range .type ].hl_name ,
91
- priority = 110 + range .from ,
247
+ priority = 110 + range .from . start . character ,
92
248
})
249
+
93
250
if hide_markers then
94
- hl (range .from - 1 , range .from , { conceal = ' ' })
95
- hl (range .to - 1 , range .to , { conceal = ' ' })
251
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , range .from .start .line , range .from .start .character , {
252
+ end_col = range .from [' end' ].character ,
253
+ ephemeral = true ,
254
+ conceal = ' ' ,
255
+ })
256
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , range .to .start .line , range .to .start .character , {
257
+ end_col = range .to [' end' ].character ,
258
+ ephemeral = true ,
259
+ conceal = ' ' ,
260
+ })
96
261
end
97
262
end
98
263
99
264
for _ , link_range in ipairs (link_ranges ) do
100
- local link = line :sub (link_range .from , link_range .to )
265
+ local line = vim .api .nvim_buf_get_lines (bufnr , link_range .from .start .line , link_range .from .start .line + 1 , false )[1 ]
266
+ local link = line :sub (link_range .from .start .character + 1 , link_range .to [' end' ].character )
101
267
local alias = link :find (' %]%[' ) or 1
102
- hl (link_range .from - 1 , link_range .to , {
268
+
269
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , link_range .from .start .line , link_range .from .start .character , {
270
+ ephemeral = true ,
271
+ end_col = link_range .to [' end' ].character ,
103
272
hl_group = ' org_hyperlink' ,
104
- priority = 200 + link_range . from ,
273
+ priority = 110 ,
105
274
})
106
- hl (link_range .from - 1 , link_range .from + alias , { conceal = ' ' })
107
- hl (link_range .to - 2 , link_range .to , { conceal = ' ' })
108
- end
109
- end
110
275
111
- local function apply (namespace , bufnr , changed_lines , first_line , _ , tick_changed )
112
- if not buf_blocks [bufnr ] or tick_changed then
113
- buf_blocks = {}
114
- local all_lines = vim .api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
115
- local seek_blocks = {}
116
- for i , line in ipairs (all_lines ) do
117
- local lower_line = line :lower ()
118
- if lower_line :match (' ^%s*#%+begin_' ) then
119
- if not seek_blocks .from then
120
- seek_blocks .from = i
121
- end
122
- end
123
- if lower_line :match (' ^%s*#%+end_' ) then
124
- if not seek_blocks .to then
125
- seek_blocks .to = i
126
- end
127
- if seek_blocks .from and seek_blocks .to then
128
- table.insert (buf_blocks , { seek_blocks .from , seek_blocks .to })
129
- seek_blocks = {}
130
- end
131
- end
132
- end
133
- end
276
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , link_range .from .start .line , link_range .from .start .character , {
277
+ ephemeral = true ,
278
+ end_col = link_range .from .start .character + 1 + alias ,
279
+ conceal = ' ' ,
280
+ })
134
281
135
- for i , line in ipairs (changed_lines ) do
136
- local line_nr = first_line + i
137
- local apply_to_line = true
138
- for _ , block in ipairs (buf_blocks ) do
139
- if line_nr >= block [1 ] and line_nr <= block [2 ] then
140
- apply_to_line = false
141
- break
142
- end
143
- end
144
- if apply_to_line then
145
- apply_markup_to_line (namespace , bufnr , line_nr - 1 , line )
146
- end
282
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , link_range .from .start .line , link_range .to [' end' ].character - 2 , {
283
+ ephemeral = true ,
284
+ end_col = link_range .to [' end' ].character ,
285
+ conceal = ' ' ,
286
+ })
147
287
end
148
288
end
149
289
@@ -152,6 +292,7 @@ local function setup()
152
292
vim .cmd (marker .hl_cmd )
153
293
end
154
294
vim .cmd (' hi def link org_hyperlink Underlined' )
295
+ load_deps ()
155
296
end
156
297
157
298
return {
0 commit comments