@@ -2,18 +2,26 @@ local utils = require('orgmode.utils')
22local TodoKeyword = require (' orgmode.objects.todo_keywords.todo_keyword' )
33
44--- @class OrgTodoKeywords
5- --- @field org_todo_keywords string[]
5+ --- @field org_todo_keywords string[][] | string[]
66--- @field org_todo_keyword_faces table<string , string>
77--- @field todo_keywords OrgTodoKeyword[]
8+ --- @field sequences OrgTodoKeyword[][] Array of todo keyword sequences
89local TodoKeywords = {}
910TodoKeywords .__index = TodoKeywords
1011
11- --- @param opts { org_todo_keywords : string[] , org_todo_keyword_faces : table<string , string> }
12+ --- @param opts { org_todo_keywords : string[][] | string[] , org_todo_keyword_faces : table<string , string> }
1213--- @return OrgTodoKeywords
1314function TodoKeywords :new (opts )
15+ -- Normalize input to always be sequences (string[][])
16+ local normalized_keywords = opts .org_todo_keywords
17+ if type (normalized_keywords [1 ]) ~= ' table' then
18+ normalized_keywords = { normalized_keywords }
19+ end
20+
1421 local this = setmetatable ({
15- org_todo_keywords = opts . org_todo_keywords ,
22+ org_todo_keywords = normalized_keywords ,
1623 org_todo_keyword_faces = opts .org_todo_keyword_faces ,
24+ sequences = {},
1725 }, self )
1826 this :_parse ()
1927 return this
@@ -44,6 +52,19 @@ function TodoKeywords:find(keyword)
4452 end )
4553end
4654
55+ --- @param keyword string
56+ --- @return number | nil sequence index this keyword belongs to
57+ function TodoKeywords :find_sequence_index (keyword )
58+ for seq_idx , seq in ipairs (self .sequences ) do
59+ for _ , todo_keyword in ipairs (seq ) do
60+ if todo_keyword .value == keyword then
61+ return seq_idx
62+ end
63+ end
64+ end
65+ return nil
66+ end
67+
4768--- @param type OrgTodoKeywordType
4869--- @return OrgTodoKeyword
4970function TodoKeywords :first_by_type (type )
@@ -60,6 +81,12 @@ function TodoKeywords:all()
6081 return self .todo_keywords
6182end
6283
84+ --- @param sequence_idx ? number
85+ --- @return OrgTodoKeyword[]
86+ function TodoKeywords :sequence (sequence_idx )
87+ return self .sequences [sequence_idx or 1 ] or {}
88+ end
89+
6390--- @return OrgTodoKeyword
6491function TodoKeywords :first ()
6592 return self .todo_keywords [1 ]
79106
80107--- @private
81108function TodoKeywords :_parse ()
82- local todo , done = self :_split_todo_and_done ()
109+ self .todo_keywords = {}
110+ self .sequences = {}
111+ local used_shortcuts = {}
112+
113+ for seq_idx , sequence in ipairs (self .org_todo_keywords ) do
114+ local keyword_offset = # self .todo_keywords
115+ local keywords , seq_keywords = self :_parse_sequence (sequence , seq_idx , used_shortcuts , keyword_offset )
116+
117+ -- Add keywords to the main list and the sequence
118+ for _ , keyword in ipairs (keywords ) do
119+ table.insert (self .todo_keywords , keyword )
120+ end
121+ table.insert (self .sequences , seq_keywords )
122+ end
123+ end
124+
125+ --- @private
126+ --- @param keyword string
127+ --- @param status_type string ' TODO' or ' DONE'
128+ --- @param index number
129+ --- @param seq_idx number
130+ --- @param used_shortcuts table<string , boolean>
131+ --- @return OrgTodoKeyword
132+ function TodoKeywords :_create_keyword (keyword , status_type , index , seq_idx , used_shortcuts )
133+ local todo_keyword = TodoKeyword :new ({
134+ type = status_type ,
135+ keyword = keyword ,
136+ index = index ,
137+ sequence_index = seq_idx ,
138+ })
139+
140+ -- Track used shortcuts to avoid conflicts
141+ if todo_keyword .has_fast_access then
142+ used_shortcuts [todo_keyword .shortcut ] = true
143+ elseif not used_shortcuts [todo_keyword .shortcut ] and # self .org_todo_keywords > 1 then
144+ -- Auto-assign shortcuts when we have multiple sequences
145+ todo_keyword .has_fast_access = true
146+ used_shortcuts [todo_keyword .shortcut ] = true
147+ end
148+
149+ todo_keyword .hl = self :_get_hl (todo_keyword .value , status_type )
150+ return todo_keyword
151+ end
152+
153+ --- @private
154+ --- @param keywords string[]
155+ --- @param seq_idx number
156+ --- @param used_shortcuts table<string , boolean>
157+ --- @param keyword_offset number
158+ --- @return OrgTodoKeyword[] keywords for the sequence
159+ --- @return OrgTodoKeyword[] seq_keywords keywords in this sequence
160+ function TodoKeywords :_parse_sequence (keywords , seq_idx , used_shortcuts , keyword_offset )
161+ keyword_offset = keyword_offset or 0
162+ local todo , done = self :_split_todo_and_done (keywords )
83163 local list = {}
164+ local seq_keywords = {}
165+
166+ -- Process TODO keywords
84167 for i , keyword in ipairs (todo ) do
85- local todo_keyword = TodoKeyword :new ({
86- type = ' TODO' ,
87- keyword = keyword ,
88- index = i ,
89- })
90- todo_keyword .hl = self :_get_hl (todo_keyword .value , ' TODO' )
168+ local todo_keyword = self :_create_keyword (keyword , ' TODO' , keyword_offset + i , seq_idx , used_shortcuts )
91169 table.insert (list , todo_keyword )
170+ table.insert (seq_keywords , todo_keyword )
92171 end
93172
173+ -- Process DONE keywords
94174 for i , keyword in ipairs (done ) do
95- local todo_keyword = TodoKeyword :new ({
96- type = ' DONE' ,
97- keyword = keyword ,
98- index = # todo + i ,
99- })
100- todo_keyword .hl = self :_get_hl (todo_keyword .value , ' DONE' )
175+ local todo_keyword = self :_create_keyword (keyword , ' DONE' , keyword_offset + # todo + i , seq_idx , used_shortcuts )
101176 table.insert (list , todo_keyword )
177+ table.insert (seq_keywords , todo_keyword )
102178 end
103179
104- self . todo_keywords = list
180+ return list , seq_keywords
105181end
106182
107183--- @private
@@ -116,9 +192,9 @@ function TodoKeywords:_get_hl(keyword, type)
116192end
117193
118194--- @private
195+ --- @param keywords string[]
119196--- @return string[] , string[]
120- function TodoKeywords :_split_todo_and_done ()
121- local keywords = self .org_todo_keywords
197+ function TodoKeywords :_split_todo_and_done (keywords )
122198 local has_separator = vim .tbl_contains (keywords , ' |' )
123199 if not has_separator then
124200 return { unpack (keywords , 1 , # keywords - 1 ) }, { keywords [# keywords ] }
0 commit comments