1
1
local ts_utils = require (' nvim-treesitter.ts_utils' )
2
2
local tree_utils = require (' orgmode.utils.treesitter' )
3
3
local Date = require (' orgmode.objects.date' )
4
+ local Range = require (' orgmode.parser.range' )
4
5
local config = require (' orgmode.config' )
5
6
local query = vim .treesitter .query
6
7
8
+ --- @class Headline
9
+ --- @field headline userdata
7
10
local Headline = {}
8
11
9
12
--- @param headline_node userdata tree sitter headline node
@@ -14,6 +17,7 @@ function Headline:new(headline_node)
14
17
return data
15
18
end
16
19
20
+ --- @return userdata stars node
17
21
function Headline :stars ()
18
22
return self .headline :field (' stars' )[1 ]
19
23
end
@@ -28,6 +32,7 @@ function Headline:priority()
28
32
return self :parse (' %[#(%w+)%]' )
29
33
end
30
34
35
+ --- @return userdata , string
31
36
function Headline :tags ()
32
37
local node = self .headline :field (' tags' )[1 ]
33
38
local text = ' '
@@ -37,6 +42,7 @@ function Headline:tags()
37
42
return node , text
38
43
end
39
44
45
+ --- @param tags string
40
46
function Headline :set_tags (tags )
41
47
local predecessor = nil
42
48
for _ , node in ipairs (ts_utils .get_named_children (self .headline )) do
@@ -76,6 +82,7 @@ function Headline:align_tags()
76
82
end
77
83
end
78
84
85
+ --- @param priority string
79
86
function Headline :set_priority (priority )
80
87
local current_priority = self :priority ()
81
88
if current_priority then
@@ -96,6 +103,7 @@ function Headline:set_priority(priority)
96
103
tree_utils .set_node_text (stars , (' %s [#%s]' ):format (text , priority ))
97
104
end
98
105
106
+ --- @param keyword string
99
107
function Headline :set_todo (keyword )
100
108
local current_todo = self :todo ()
101
109
if current_todo then
@@ -128,14 +136,15 @@ function Headline:todo()
128
136
local text = query .get_node_text (todo_node , 0 )
129
137
for _ , word in ipairs (keywords ) do
130
138
-- there may be multiple substitutions necessary
131
- escaped_word = vim .pesc (word )
139
+ local escaped_word = vim .pesc (word )
132
140
local todo = text :match (escaped_word )
133
141
if todo then
134
142
return todo_node , word , vim .tbl_contains (done_keywords , word )
135
143
end
136
144
end
137
145
end
138
146
147
+ --- @return userdata
139
148
function Headline :plan ()
140
149
local section = self .headline :parent ()
141
150
for _ , node in ipairs (ts_utils .get_named_children (section )) do
@@ -145,6 +154,7 @@ function Headline:plan()
145
154
end
146
155
end
147
156
157
+ --- @return Table<string , userdata>
148
158
function Headline :dates ()
149
159
local plan = self :plan ()
150
160
local dates = {}
@@ -160,6 +170,7 @@ function Headline:dates()
160
170
return dates
161
171
end
162
172
173
+ --- @return userdata[]
163
174
function Headline :repeater_dates ()
164
175
return vim .tbl_filter (function (entry )
165
176
local timestamp = entry :field (' timestamp' )[1 ]
@@ -171,37 +182,44 @@ function Headline:repeater_dates()
171
182
end , self :dates ())
172
183
end
173
184
185
+ --- @return Date | nil
186
+ function Headline :deadline ()
187
+ return self :_get_date (' DEADLINE' )
188
+ end
189
+
190
+ --- @return Date | nil
191
+ function Headline :scheduled ()
192
+ return self :_get_date (' SCHEDULED' )
193
+ end
194
+
195
+ --- @param date Date
196
+ function Headline :set_deadline_date (date )
197
+ return self :_add_date (' DEADLINE' , date , true )
198
+ end
199
+
200
+ --- @param date Date
201
+ function Headline :set_scheduled_date (date )
202
+ return self :_add_date (' SCHEDULED' , date , true )
203
+ end
204
+
174
205
function Headline :add_closed_date ()
175
206
local dates = self :dates ()
176
207
if dates [' CLOSED' ] then
177
208
return
178
209
end
179
- local closed_text = ' CLOSED: ' .. Date .now ():to_wrapped_string (false )
180
- if vim .tbl_isempty (dates ) then
181
- local indent = config :get_indent (self :level () + 1 )
182
- local start_line = self .headline :start ()
183
- return vim .api .nvim_call_function (' append' , {
184
- start_line + 1 ,
185
- string.format (' %s%s' , indent , closed_text ),
186
- })
187
- end
188
- local keys = vim .tbl_keys (dates )
189
- local last_child = dates [' DEADLINE' ] or dates [' SCHEDULED' ] or dates [keys [# keys ]]
190
- local ptext = query .get_node_text (last_child , 0 )
191
- local text = ptext .. ' ' .. closed_text
192
- tree_utils .set_node_text (last_child , text )
210
+ return self :_add_date (' CLOSED' , Date .now (), false )
193
211
end
194
212
195
213
function Headline :remove_closed_date ()
196
- local dates = self :dates ( )
197
- if vim . tbl_count ( dates ) == 0 or not dates [ ' CLOSED ' ] then
198
- return
199
- end
200
- local line_nr = dates [ ' CLOSED ' ]: start () + 1
201
- tree_utils . set_node_text ( dates [ ' CLOSED ' ], ' ' , true )
202
- if vim . trim ( vim . fn . getline ( line_nr )) == ' ' then
203
- return vim . api . nvim_call_function ( ' deletebufline ' , { vim . api . nvim_get_current_buf (), line_nr } )
204
- end
214
+ return self :_remove_date ( ' CLOSED ' )
215
+ end
216
+
217
+ function Headline : remove_deadline_date ()
218
+ return self : _remove_date ( ' DEADLINE ' )
219
+ end
220
+
221
+ function Headline : remove_scheduled_date ( )
222
+ return self : _remove_date ( ' SCHEDULED ' )
205
223
end
206
224
207
225
function Headline :cookie ()
@@ -251,4 +269,70 @@ function Headline:parse(pattern)
251
269
return matching_nodes [1 ], match
252
270
end
253
271
272
+ --- @param type string | " DEADLINE" | " SCHEDULED" | " CLOSED"
273
+ --- @return Date | nil
274
+ function Headline :_get_date (type )
275
+ local dates = self :dates ()
276
+ local date_node = dates [type ]
277
+ if not date_node then
278
+ return nil
279
+ end
280
+ local timestamp_node = date_node :field (' timestamp' )[1 ]
281
+ if not timestamp_node then
282
+ return nil
283
+ end
284
+ local parsed_date = Date .from_org_date (query .get_node_text (timestamp_node , 0 ), {
285
+ range = Range .from_node (timestamp_node ),
286
+ })
287
+ return parsed_date and parsed_date [1 ] or nil
288
+ end
289
+
290
+ --- @param type string | " DEADLINE" | " SCHEDULED" | " CLOSED"
291
+ --- @param date Date
292
+ --- @param active ? boolean
293
+ --- @private
294
+ function Headline :_add_date (type , date , active )
295
+ local dates = self :dates ()
296
+ local text = type .. ' : ' .. date :to_wrapped_string (active )
297
+ if vim .tbl_isempty (dates ) then
298
+ local indent = config :get_indent (self :level () + 1 )
299
+ local start_line = self .headline :start ()
300
+ return vim .api .nvim_call_function (' append' , {
301
+ start_line + 1 ,
302
+ string.format (' %s%s' , indent , text ),
303
+ })
304
+ end
305
+ if dates [type ] then
306
+ return tree_utils .set_node_text (dates [type ], text , true )
307
+ end
308
+
309
+ local keys = vim .tbl_keys (dates )
310
+ local other_types = vim .tbl_filter (function (t )
311
+ return t ~= type
312
+ end , { ' DEADLINE' , ' SCHEDULED' , ' CLOSED' })
313
+ local last_child = dates [keys [# keys ]]
314
+ for _ , date_type in ipairs (other_types ) do
315
+ if dates [date_type ] then
316
+ last_child = dates [date_type ]
317
+ break
318
+ end
319
+ end
320
+ local ptext = query .get_node_text (last_child , 0 )
321
+ tree_utils .set_node_text (last_child , ptext .. ' ' .. text )
322
+ end
323
+
324
+ --- @param type string | " DEADLINE" | " SCHEDULED" | " CLOSED"
325
+ --- @private
326
+ function Headline :_remove_date (type )
327
+ local dates = self :dates ()
328
+ if vim .tbl_count (dates ) == 0 or not dates [type ] then
329
+ return
330
+ end
331
+ local line_nr = dates [type ]:start () + 1
332
+ tree_utils .set_node_text (dates [type ], ' ' , true )
333
+ if vim .trim (vim .fn .getline (line_nr )) == ' ' then
334
+ return vim .api .nvim_call_function (' deletebufline' , { vim .api .nvim_get_current_buf (), line_nr })
335
+ end
336
+ end
337
+
254
338
return Headline
0 commit comments