Skip to content

Commit 1783928

Browse files
authored
feat(export): copy to clipboard (#1627)
1 parent 399832e commit 1783928

File tree

1 file changed

+91
-22
lines changed

1 file changed

+91
-22
lines changed

lua/neorg/modules/core/export/module.lua

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ It takes 3 arguments:
2424
- `filetype` - the filetype to export to
2525
- `output-dir` (optional) - a custom output directory to use. If not provided will fall back to `config.public.export_dir`
2626
(see [configuration](#configuration)).
27+
28+
And if you just want to export a snippet from a single document, you can use `:Neorg export
29+
to-clipboard <filetype>`. Filetype is required, at the time of writing the only option is "markdown".
30+
This copies the range given to the command to the `+` register.
2731
--]]
2832

2933
local neorg = require("neorg.core")
@@ -39,7 +43,12 @@ module.setup = function()
3943
}
4044
end
4145

46+
---@type core.integrations.treesitter
47+
local ts
48+
4249
module.load = function()
50+
ts = module.required["core.integrations.treesitter"]
51+
4352
modules.await("core.neorgcmd", function(neorgcmd)
4453
neorgcmd.add_commands_from_table({
4554
export = {
@@ -57,6 +66,10 @@ module.load = function()
5766
max_args = 3,
5867
name = "export.directory",
5968
},
69+
["to-clipboard"] = {
70+
args = 1,
71+
name = "export.to-clipboard",
72+
},
6073
},
6174
},
6275
})
@@ -84,39 +97,56 @@ module.public = {
8497
return modules.get_module("core.export." .. ftype), modules.get_module_config("core.export." .. ftype)
8598
end,
8699

100+
---export part of a buffer
101+
---@param buffer number
102+
---@param start_row number 1 indexed
103+
---@param end_row number 1 indexed, inclusive
104+
---@param filetype string
105+
---@return string? content, string? extension exported content as a string, and the extension
106+
---used for the export
107+
export_range = function(buffer, start_row, end_row, filetype)
108+
local converter = module.private.get_converter_checked(filetype)
109+
if not converter then return end
110+
local content = vim.iter(vim.api.nvim_buf_get_lines(buffer, start_row - 1, end_row, false)):join("\n")
111+
local root = ts.get_document_root(content)
112+
if not root then
113+
return
114+
end
115+
116+
return module.public.export_from_root(root, converter, content)
117+
end,
118+
87119
--- Takes a buffer and exports it to a specific file
88120
---@param buffer number #The buffer ID to read the contents from
89121
---@param filetype string #A Neovim filetype to specify which language to export to
90122
---@return string?, string? #The entire buffer parsed, converted and returned as a string, as well as the extension used for the export.
91123
export = function(buffer, filetype)
92-
local converter, converter_config = module.public.get_converter(filetype)
93-
124+
local converter, converter_config = module.private.get_converter_checked(filetype)
94125
if not converter or not converter_config then
95-
log.error("Unable to export file - did not find exporter for filetype '" .. filetype .. "'.")
96126
return
97127
end
98128

99-
-- Each converter must have a `extension` field in its public config
100-
-- This is done to do a backwards lookup, e.g. `markdown` uses the `.md` file extension.
101-
if not converter_config.extension then
102-
log.error(
103-
"Unable to export file - exporter for filetype '"
104-
.. filetype
105-
.. "' did not return a preferred extension. The exporter is unable to infer extensions."
106-
)
107-
return
108-
end
109-
110-
local document_root = module.required["core.integrations.treesitter"].get_document_root(buffer)
129+
local document_root = ts.get_document_root(buffer)
111130

112131
if not document_root then
113132
return
114133
end
115134

135+
local content = module.public.export_from_root(document_root, converter, buffer)
136+
137+
return content, converter_config.extension
138+
end,
139+
140+
---Do the work of exporting the given TS node via the given converter
141+
---@param root TSNode
142+
---@param converter table
143+
---@param source number | string
144+
---@return string
145+
export_from_root = function(root, converter, source)
116146
-- Initialize the state. The state is a table that exists throughout the entire duration
117147
-- of the export, and can be used to e.g. retain indent levels and/or keep references.
118148
local state = converter.export.init_state and converter.export.init_state() or {}
119-
local ts_utils = module.required["core.integrations.treesitter"].get_ts_utils()
149+
local ts_utils = ts.get_ts_utils()
120150

121151
--- Descends down a node and its children
122152
---@param start table #The TS node to begin at
@@ -144,7 +174,7 @@ module.public = {
144174
-- `keep_descending` - if true will continue to recurse down the current node's children despite the current
145175
-- node already being parsed
146176
-- `state` - a modified version of the state that then gets merged into the main state table
147-
local result = exporter(vim.treesitter.get_node_text(node, buffer), node, state, ts_utils)
177+
local result = exporter(vim.treesitter.get_node_text(node, source), node, state, ts_utils)
148178

149179
if type(result) == "table" then
150180
state = result.state and vim.tbl_extend("force", state, result.state) or state
@@ -155,8 +185,8 @@ module.public = {
155185

156186
if result.keep_descending then
157187
if state.parse_as then
158-
node = module.required["core.integrations.treesitter"].get_document_root(
159-
"\n" .. vim.treesitter.get_node_text(node, buffer),
188+
node = ts.get_document_root(
189+
"\n" .. vim.treesitter.get_node_text(node, source),
160190
state.parse_as
161191
)
162192
state.parse_as = nil
@@ -174,7 +204,7 @@ module.public = {
174204
elseif exporter == true then
175205
table.insert(
176206
output,
177-
module.required["core.integrations.treesitter"].get_node_text(node, buffer)
207+
ts.get_node_text(node, source)
178208
)
179209
else
180210
table.insert(output, exporter)
@@ -208,13 +238,42 @@ module.public = {
208238
or (not vim.tbl_isempty(output) and table.concat(output))
209239
end
210240

211-
local output = descend(document_root)
241+
local output = descend(root)
212242

213243
-- Every converter can also come with a `cleanup` function that performs some final tweaks to the output string
214-
return converter.export.cleanup and converter.export.cleanup(output) or output, converter_config.extension
244+
return converter.export.cleanup and converter.export.cleanup(output) or output
215245
end,
216246
}
217247

248+
module.private = {
249+
---get the converter for the given filetype
250+
---@param filetype string
251+
---@return table?, table?
252+
get_converter_checked = function(filetype)
253+
local converter, converter_config = module.public.get_converter(filetype)
254+
255+
if not converter or not converter_config then
256+
log.error("Unable to export file - did not find exporter for filetype '" .. filetype .. "'.")
257+
return
258+
end
259+
260+
-- Each converter must have a `extension` field in its public config
261+
-- This is done to do a backwards lookup, e.g. `markdown` uses the `.md` file extension.
262+
if not converter_config.extension then
263+
log.error(
264+
"Unable to export file - exporter for filetype '"
265+
.. filetype
266+
.. "' did not return a preferred extension. The exporter is unable to infer extensions."
267+
)
268+
return
269+
end
270+
271+
return converter, converter_config
272+
end,
273+
274+
}
275+
276+
---@param event neorg.event
218277
module.on_event = function(event)
219278
if event.type == "core.neorgcmd.events.export.to-file" then
220279
-- Syntax: Neorg export to-file file.extension forced-filetype?
@@ -234,6 +293,15 @@ module.on_event = function(event)
234293

235294
vim.schedule(lib.wrap(utils.notify, "Successfully exported 1 file!"))
236295
end)
296+
elseif event.type == "core.neorgcmd.events.export.to-clipboard" then
297+
-- Syntax: Neorg export to-clipboard filetype
298+
-- Example: Neorg export to-clipboard markdown
299+
300+
local filetype = event.content[1]
301+
local data = event.content.data
302+
local exported = module.public.export_range(event.buffer, data.line1, data.line2, filetype)
303+
304+
vim.fn.setreg("+", exported, "l")
237305
elseif event.type == "core.neorgcmd.events.export.directory" then
238306
local path = event.content[3] and vim.fn.expand(event.content[3])
239307
or module.config.public.export_dir
@@ -328,6 +396,7 @@ end
328396
module.events.subscribed = {
329397
["core.neorgcmd"] = {
330398
["export.to-file"] = true,
399+
["export.to-clipboard"] = true,
331400
["export.directory"] = true,
332401
},
333402
}

0 commit comments

Comments
 (0)