Skip to content

Commit 1873458

Browse files
refactor(todo_keywords): Extract todo keywords parsing from config class (#697)
1 parent 779a568 commit 1873458

File tree

12 files changed

+350
-187
lines changed

12 files changed

+350
-187
lines changed

lua/orgmode/colors/highlights.lua

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,22 +154,40 @@ function M.define_todo_keyword_faces()
154154
local opts = {
155155
underline = {
156156
type = vim.o.termguicolors and 'gui' or 'cterm',
157-
valid = 'on',
157+
is_valid = function(value)
158+
return value == 'on'
159+
end,
158160
result = 'underline',
159161
},
160162
weight = {
161163
type = vim.o.termguicolors and 'gui' or 'cterm',
162-
valid = 'bold',
164+
is_valid = function(value)
165+
return value == 'bold'
166+
end,
163167
},
164168
foreground = {
165169
type = vim.o.termguicolors and 'guifg' or 'ctermfg',
170+
is_valid = function(value)
171+
if vim.o.termguicolors then
172+
return true
173+
end
174+
return value:sub(1, 1) ~= '#'
175+
end,
166176
},
167177
background = {
168178
type = vim.o.termguicolors and 'guibg' or 'ctermbg',
179+
is_valid = function(value)
180+
if vim.o.termguicolors then
181+
return true
182+
end
183+
return value:sub(1, 1) ~= '#'
184+
end,
169185
},
170186
slant = {
171187
type = vim.o.termguicolors and 'gui' or 'cterm',
172-
valid = 'italic',
188+
is_valid = function(value)
189+
return value == 'italic'
190+
end,
173191
},
174192
}
175193

@@ -185,7 +203,7 @@ function M.define_todo_keyword_faces()
185203
local opt_value = vim.trim(faces[2])
186204
opt_value = opt_value:gsub('^"*', ''):gsub('"*$', '')
187205
local opt = opts[opt_name]
188-
if opt and (not opt.valid or opt.valid == opt_value) then
206+
if opt and opt.is_valid(opt_value) then
189207
if not hl_opts[opt.type] then
190208
hl_opts[opt.type] = {}
191209
end

lua/orgmode/config/_meta.lua

Lines changed: 0 additions & 12 deletions
This file was deleted.

lua/orgmode/config/init.lua

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ local fs = require('orgmode.utils.fs')
44
local defaults = require('orgmode.config.defaults')
55
---@type table<string, OrgMapEntry>
66
local mappings = require('orgmode.config.mappings')
7+
local TodoKeywords = require('orgmode.objects.todo_keywords')
78

89
---@class OrgConfig:OrgDefaultConfig
910
---@field opts table
10-
---@field todo_keywords table
11+
---@field todo_keywords OrgTodoKeywords
1112
local Config = {}
1213

1314
---@param opts? table
@@ -183,48 +184,13 @@ end
183184
---@return OrgTodoKeywords
184185
function Config:get_todo_keywords()
185186
if self.todo_keywords then
186-
return vim.deepcopy(self.todo_keywords)
187+
return self.todo_keywords
187188
end
188-
local parse_todo = function(val)
189-
local value, shortcut = val:match('(.*)%((.)[^%)]*%)$')
190-
if value and shortcut then
191-
return { value = value, shortcut = shortcut, custom_shortcut = true }
192-
end
193-
return { value = val, shortcut = val:sub(1, 1):lower(), custom_shortcut = false }
194-
end
195-
local types = { TODO = {}, DONE = {}, ALL = {}, KEYS = {}, FAST_ACCESS = {}, has_fast_access = false }
196-
local type = 'TODO'
197-
local has_separator = vim.tbl_contains(self.opts.org_todo_keywords, '|')
198-
local index = 1
199-
for i, word in ipairs(self.opts.org_todo_keywords) do
200-
if word == '|' then
201-
type = 'DONE'
202-
else
203-
if not has_separator and i == #self.opts.org_todo_keywords then
204-
type = 'DONE'
205-
end
206-
local data = parse_todo(word)
207-
if data.custom_shortcut then
208-
types.has_fast_access = true
209-
end
210-
table.insert(types[type], data.value)
211-
table.insert(types.ALL, data.value)
212-
types.KEYS[data.value] = {
213-
type = type,
214-
shortcut = data.shortcut,
215-
len = data.value:len(),
216-
index = index,
217-
}
218-
table.insert(types.FAST_ACCESS, {
219-
value = data.value,
220-
type = type,
221-
shortcut = data.shortcut,
222-
})
223-
index = index + 1
224-
end
225-
end
226-
self.todo_keywords = types
227-
return types
189+
self.todo_keywords = TodoKeywords:new({
190+
org_todo_keywords = self.opts.org_todo_keywords,
191+
org_todo_keyword_faces = self.opts.org_todo_keyword_faces,
192+
})
193+
return self.todo_keywords
228194
end
229195

230196
--- Setup mappings for a given category and buffer
@@ -340,7 +306,7 @@ function Config:get_priorities()
340306
end
341307

342308
function Config:setup_ts_predicates()
343-
local todo_keywords = self:get_todo_keywords().KEYS
309+
local todo_keywords = self:get_todo_keywords():keys()
344310
local valid_priorities = self:get_priorities()
345311

346312
vim.treesitter.query.add_predicate('org-is-todo-keyword?', function(match, _, source, predicate)

lua/orgmode/files/headline.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,22 +306,22 @@ memoize('get_todo')
306306
--- and it's type (todo or done)
307307
--- @return string | nil, TSNode | nil, string | nil
308308
function Headline:get_todo()
309-
local todo_keywords = config:get_todo_keywords()
310-
local keywords_info = todo_keywords.KEYS
311-
312309
-- A valid keyword can only be the first child
313310
local first_item_node = self:_get_child_node('item')
314311
local todo_node = first_item_node and first_item_node:named_child(0)
315312
if not todo_node then
316313
return nil, nil, nil
317314
end
318315

316+
local todo_keywords = config:get_todo_keywords()
317+
319318
local text = self.file:get_node_text(todo_node)
320-
if not keywords_info[text] then
319+
local keyword_by_value = todo_keywords:find(text)
320+
if not keyword_by_value then
321321
return nil, nil, nil
322322
end
323323

324-
return text, todo_node, keywords_info[text].type
324+
return text, todo_node, keyword_by_value.type
325325
end
326326

327327
---@return boolean
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
local utils = require('orgmode.utils')
2+
local TodoKeyword = require('orgmode.objects.todo_keywords.todo_keyword')
3+
4+
---@class OrgTodoKeywords
5+
---@field org_todo_keywords string[]
6+
---@field org_todo_keyword_faces table<string, string>
7+
---@field todo_keywords OrgTodoKeyword[]
8+
local TodoKeywords = {}
9+
TodoKeywords.__index = TodoKeywords
10+
11+
---@param opts { org_todo_keywords: string[], org_todo_keyword_faces: table<string, string> }
12+
---@return OrgTodoKeywords
13+
function TodoKeywords:new(opts)
14+
local this = setmetatable({
15+
org_todo_keywords = opts.org_todo_keywords,
16+
org_todo_keyword_faces = opts.org_todo_keyword_faces,
17+
}, self)
18+
this:_parse()
19+
return this
20+
end
21+
22+
---Return a lookup table of all todo keywords by value.
23+
---@return table<string, OrgTodoKeyword>
24+
function TodoKeywords:keys()
25+
local result = {}
26+
for _, keyword in ipairs(self.todo_keywords) do
27+
result[keyword.value] = keyword
28+
end
29+
return result
30+
end
31+
32+
---@return boolean
33+
function TodoKeywords:has_fast_access()
34+
return utils.find(self.todo_keywords, function(todo_keyword)
35+
return todo_keyword.has_fast_access
36+
end) and true or false
37+
end
38+
39+
---@param keyword string
40+
---@return OrgTodoKeyword | nil
41+
function TodoKeywords:find(keyword)
42+
return utils.find(self.todo_keywords, function(todo_keyword)
43+
return todo_keyword.value == keyword
44+
end)
45+
end
46+
47+
---@param type OrgTodoKeywordType
48+
---@return OrgTodoKeyword
49+
function TodoKeywords:first_by_type(type)
50+
for _, keyword in ipairs(self.todo_keywords) do
51+
if type == keyword.type then
52+
return keyword
53+
end
54+
end
55+
return self.todo_keywords[#self.todo_keywords]
56+
end
57+
58+
---@return OrgTodoKeyword[]
59+
function TodoKeywords:all()
60+
return self.todo_keywords
61+
end
62+
63+
---@return OrgTodoKeyword
64+
function TodoKeywords:first()
65+
return self.todo_keywords[1]
66+
end
67+
68+
---@return OrgTodoKeyword
69+
function TodoKeywords:last()
70+
return self.todo_keywords[#self.todo_keywords]
71+
end
72+
73+
---@return string[]
74+
function TodoKeywords:all_values()
75+
return vim.tbl_map(function(todo_keyword)
76+
return todo_keyword.value
77+
end, self.todo_keywords)
78+
end
79+
80+
---@private
81+
function TodoKeywords:_parse()
82+
local todo, done = self:_split_todo_and_done()
83+
local list = {}
84+
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')
91+
table.insert(list, todo_keyword)
92+
end
93+
94+
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')
101+
table.insert(list, todo_keyword)
102+
end
103+
104+
self.todo_keywords = list
105+
end
106+
107+
---@private
108+
---@param keyword string
109+
---@param type OrgTodoKeywordType
110+
---@return string
111+
function TodoKeywords:_get_hl(keyword, type)
112+
if not self.org_todo_keyword_faces[keyword] then
113+
return type == 'TODO' and '@org.keyword.todo' or '@org.keyword.done'
114+
end
115+
return ('@org.keyword.face.%s'):format(keyword:gsub('%-', ''))
116+
end
117+
118+
---@private
119+
---@return string[], string[]
120+
function TodoKeywords:_split_todo_and_done()
121+
local keywords = self.org_todo_keywords
122+
local has_separator = vim.tbl_contains(keywords, '|')
123+
if not has_separator then
124+
return { unpack(keywords, 1, #keywords - 1) }, { keywords[#keywords] }
125+
end
126+
127+
local type = 'TODO'
128+
local by_type = {
129+
TODO = {},
130+
DONE = {},
131+
}
132+
for _, keyword in ipairs(keywords) do
133+
if keyword == '|' then
134+
type = 'DONE'
135+
else
136+
table.insert(by_type[type], keyword)
137+
end
138+
end
139+
140+
return by_type.TODO, by_type.DONE
141+
end
142+
143+
return TodoKeywords
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---@alias OrgTodoKeywordType 'TODO' | 'DONE'
2+
3+
---@class OrgTodoKeyword
4+
---@field keyword string
5+
---@field index number
6+
---@field type OrgTodoKeywordType
7+
---@field value string
8+
---@field shortcut string
9+
---@field hl string
10+
---@field has_fast_access boolean
11+
local TodoKeyword = {}
12+
TodoKeyword.__index = TodoKeyword
13+
14+
---@param opts { type: OrgTodoKeywordType, keyword: string, index: number }
15+
---@return OrgTodoKeyword
16+
function TodoKeyword:new(opts)
17+
local this = setmetatable({
18+
keyword = opts.keyword,
19+
type = opts.type,
20+
index = opts.index,
21+
has_fast_access = false,
22+
}, self)
23+
this:parse()
24+
return this
25+
end
26+
27+
function TodoKeyword:empty()
28+
return setmetatable({
29+
keyword = '',
30+
value = '',
31+
type = '',
32+
index = 1,
33+
has_fast_access = false,
34+
hl = '',
35+
}, self)
36+
end
37+
38+
function TodoKeyword:is_empty()
39+
return self.keyword == ''
40+
end
41+
42+
function TodoKeyword:parse()
43+
self.value = self.keyword
44+
self.shortcut = self.keyword:sub(1, 1):lower()
45+
46+
local value, shortcut = self.keyword:match('(.*)%((.)[^%)]*%)$')
47+
if value and shortcut then
48+
self.value = value
49+
self.shortcut = shortcut
50+
self.has_fast_access = true
51+
end
52+
end
53+
54+
return TodoKeyword

0 commit comments

Comments
 (0)