@@ -25,6 +25,7 @@ local neorg = require("neorg.core")
2525local lib , modules , utils = neorg .lib , neorg .modules , neorg .utils
2626
2727local module = modules .create (" core.summary" )
28+ local ts
2829
2930module .setup = function ()
3031 return {
@@ -43,94 +44,34 @@ module.load = function()
4344 },
4445 })
4546 end )
46- local ts = module .required [" core.integrations.treesitter" ]
4747
48- -- declare query on load so that it's parsed once, on first use
49- local heading_query
48+ ts = module .required [" core.integrations.treesitter" ]
5049
51- local get_first_heading_title = function (bufnr )
52- local document_root = ts .get_document_root (bufnr )
53- if not heading_query then
54- -- allow second level headings, just in case
55- local heading_query_string = [[
56- [
57- (heading1
58- title: (paragraph_segment) @next-segment
59- )
60- (heading2
61- title: (paragraph_segment) @next-segment
62- )
63- ]
64- ]]
65- heading_query = utils .ts_parse_query (" norg" , heading_query_string )
66- end
67- -- search up to 20 lines (a doc could potentially have metadata without metadata.title)
68- local _ , heading = heading_query :iter_captures (document_root , bufnr )()
69- if not heading then
70- return nil
71- end
72- local start_line , _ = heading :start ()
73- local lines = vim .api .nvim_buf_get_lines (bufnr , start_line , start_line + 1 , false )
74- if # lines > 0 then
75- local title = lines [1 ]:gsub (" ^%s*%*+%s*" , " " ) -- strip out '*' prefix (handle '* title', ' **title', etc)
76- if title ~= " " then -- exclude an empty heading like `*` (although the query should have excluded)
77- return title
78- end
79- end
80- end
50+ module .config .public .strategy = lib .match (module .config .public .strategy )(module .public .strategies ) or
51+ module .config .public .strategy
52+ end
8153
82- -- Return true if catagories_path is or is a subcategory of an entry in included_categories
83- local is_included_category = function ( included_categories , category_path )
84- local found_match = false
85- for _ , included in ipairs ( included_categories ) do
86- local included_path = vim . split ( included , " . " , { plain = true })
87- for i , path in ipairs ( included_path ) do
88- if path == category_path [ i ] and i == # included_path then
89- found_match = true
90- break
91- elseif path ~= category_path [ i ] then
92- break
93- end
94- end
95- end
96- return found_match
97- end
54+ module . private = {
55+ heading_query = nil
56+ }
57+
58+ module . config . public = {
59+ -- The strategy to use to generate a summary.
60+ --
61+ -- Possible options are:
62+ -- - "default" - read the metadata to categorize and annotate files. Files
63+ -- without metadata will use the top level heading as the title. If no headings are present, the filename will be used.
64+ -- - "by_path" - Similar to "default" but uses the capitalized name of the folder containing a *.norg file as category.
65+ -- - A custom function with the signature:
66+ -- `fun(files: PathlibPath[], ws_root: PathlibPath, heading_level: number?, include_categories: string[]?): string[]?`.
67+ -- Returning a list of lines that make up the summary
68+ strategy = " default " ,
69+ }
9870
99- -- Insert a categorized record for the given file into the categories table
100- local insert_categorized = function (categories , category_path , norgname , metadata )
101- local leaf_categories = categories
102- for i , path in ipairs (category_path ) do
103- local titled_path = lib .title (path )
104- if i == # category_path then
105- -- There are no more sub catergories so insert the record
106- table.insert (leaf_categories [titled_path ], {
107- title = tostring (metadata .title ),
108- norgname = norgname ,
109- description = metadata .description ,
110- })
111- break
112- end
113- local sub_categories = vim .defaulttable ()
114- if leaf_categories [titled_path ] then
115- -- This category already been added so find it's sub_categories table
116- for _ , item in ipairs (leaf_categories [titled_path ]) do
117- if item .sub_categories then
118- leaf_categories = item .sub_categories
119- goto continue
120- end
121- end
122- end
123- -- This is a new sub category
124- table.insert (leaf_categories [titled_path ], {
125- title = titled_path ,
126- sub_categories = sub_categories ,
127- })
128- leaf_categories = sub_categories
129- :: continue::
130- end
131- end
13271
133- module .config .public .strategy = lib .match (module .config .public .strategy )({
72+ --- @class core.summary
73+ module .public = {
74+ strategies = {
13475 default = function ()
13576 return function (files , ws_root , heading_level , include_categories )
13677 local categories = vim .defaulttable ()
@@ -160,7 +101,7 @@ module.load = function()
160101 end
161102
162103 if not metadata .title then
163- metadata .title = get_first_heading_title (bufnr )
104+ metadata .title = module . public . get_first_heading_title (bufnr )
164105 if not metadata .title then
165106 metadata .title = vim .fs .basename (norgname )
166107 end
@@ -174,11 +115,11 @@ module.load = function()
174115 local category_path = vim .split (category , " ." , { plain = true })
175116
176117 if include_categories then
177- if is_included_category (include_categories , category_path ) then
178- insert_categorized (categories , category_path , norgname , metadata )
118+ if module . public . is_included_category (include_categories , category_path ) then
119+ module . public . insert_categorized (categories , category_path , norgname , metadata )
179120 end
180121 else
181- insert_categorized (categories , category_path , norgname , metadata )
122+ module . public . insert_categorized (categories , category_path , norgname , metadata )
182123 end
183124 end
184125 end )
@@ -207,9 +148,9 @@ module.load = function()
207148 lib .title (datapoint .title ),
208149 " ]" ,
209150 })
210- .. (
211- datapoint .description and (table.concat ({ " - " , datapoint .description })) or " "
212- )
151+ .. (
152+ datapoint .description and (table.concat ({ " - " , datapoint .description })) or " "
153+ )
213154 )
214155 end
215156 end
@@ -250,7 +191,7 @@ module.load = function()
250191 norgname = string.sub (norgname , ws_root :len () + 1 )
251192
252193 if not metadata .title then
253- metadata .title = get_first_heading_title (bufnr ) or vim .fs .basename (norgname )
194+ metadata .title = module . public . get_first_heading_title (bufnr ) or vim .fs .basename (norgname )
254195 end
255196
256197 if metadata .description == vim .NIL then
@@ -282,39 +223,103 @@ module.load = function()
282223 lib .title (datapoint .title ),
283224 " ]" ,
284225 })
285- .. (datapoint .description and (table.concat ({ " - " , datapoint .description })) or " " )
226+ .. (datapoint .description and (table.concat ({ " - " , datapoint .description })) or " " )
286227 )
287228 end
288229 end
289230
290231 return result
291232 end
292233 end ,
293- }) or module .config .public .strategy
294- end
234+ },
295235
296- module .config .public = {
297- -- The strategy to use to generate a summary.
298- --
299- -- Possible options are:
300- -- - "default" - read the metadata to categorize and annotate files. Files
301- -- without metadata will use the top level heading as the title. If no headings are present, the filename will be used.
302- -- - "by_path" - Similar to "default" but uses the capitalized name of the folder containing a *.norg file as category.
303- -- - A custom function with the signature:
304- -- `fun(files: PathlibPath[], ws_root: PathlibPath, heading_level: number?, include_categories: string[]?): string[]?`.
305- -- Returning a list of lines that make up the summary
306- strategy = " default" ,
307- }
236+ get_first_heading_title = function (bufnr )
237+ local document_root = ts .get_document_root (bufnr )
238+ if not module .private .heading_query then
239+ -- allow second level headings, just in case
240+ local heading_query_string = [[
241+ [
242+ (heading1
243+ title: (paragraph_segment) @next-segment
244+ )
245+ (heading2
246+ title: (paragraph_segment) @next-segment
247+ )
248+ ]
249+ ]]
250+ module .private .heading_query = utils .ts_parse_query (" norg" , heading_query_string ) --[[ @as vim.treesitter.Query]]
251+ end
252+ -- search up to 20 lines (a doc could potentially have metadata without metadata.title)
253+ local _ , heading = module .private .heading_query :iter_captures (document_root , bufnr )()
254+ if not heading then
255+ return nil
256+ end
257+ local start_line , _ = heading :start ()
258+ local lines = vim .api .nvim_buf_get_lines (bufnr , start_line , start_line + 1 , false )
259+ if # lines > 0 then
260+ local title = lines [1 ]:gsub (" ^%s*%*+%s*" , " " ) -- strip out '*' prefix (handle '* title', ' **title', etc)
261+ if title ~= " " then -- exclude an empty heading like `*` (although the query should have excluded)
262+ return title
263+ end
264+ end
265+ end ,
266+
267+ -- Return true if catagories_path is or is a subcategory of an entry in included_categories
268+ is_included_category = function (included_categories , category_path )
269+ local found_match = false
270+ for _ , included in ipairs (included_categories ) do
271+ local included_path = vim .split (included , " ." , { plain = true })
272+ for i , path in ipairs (included_path ) do
273+ if path == category_path [i ] and i == # included_path then
274+ found_match = true
275+ break
276+ elseif path ~= category_path [i ] then
277+ break
278+ end
279+ end
280+ end
281+ return found_match
282+ end ,
283+
284+ -- Insert a categorized record for the given file into the categories table
285+ insert_categorized = function (categories , category_path , norgname , metadata )
286+ local leaf_categories = categories
287+ for i , path in ipairs (category_path ) do
288+ local titled_path = lib .title (path )
289+ if i == # category_path then
290+ -- There are no more sub categories so insert the record
291+ table.insert (leaf_categories [titled_path ], {
292+ title = tostring (metadata .title ),
293+ norgname = norgname ,
294+ description = metadata .description ,
295+ })
296+ break
297+ end
298+ local sub_categories = vim .defaulttable ()
299+ if leaf_categories [titled_path ] then
300+ -- This category already been added so find it's sub_categories table
301+ for _ , item in ipairs (leaf_categories [titled_path ]) do
302+ if item .sub_categories then
303+ leaf_categories = item .sub_categories
304+ goto continue
305+ end
306+ end
307+ end
308+ -- This is a new sub category
309+ table.insert (leaf_categories [titled_path ], {
310+ title = titled_path ,
311+ sub_categories = sub_categories ,
312+ })
313+ leaf_categories = sub_categories
314+ :: continue::
315+ end
316+ end ,
308317
309- --- @class core.summary
310- module .public = {
311318 --- @param buf integer ? the buffer to insert the summary to
312319 --- @param cursor_pos integer[] ? a tuple of row , col of the cursor positon (see nvim_win_get_cursor ())
313320 --- @param include_categories string[] ? table of strings (ignores case ) for categories that you wish to include in the summary.
314- -- if excluded then all categories are written into the summary.
321+ --- if excluded then all categories are written into the summary.
315322 generate_workspace_summary = function (buf , cursor_pos , include_categories )
316- local ts = module .required [" core.integrations.treesitter" ]
317-
318323 local buffer = buf or 0
319324 local cursor_position = cursor_pos or vim .api .nvim_win_get_cursor (0 )
320325
0 commit comments