From 8823b207feeda8e1a843d4f82ca290f0d5a9a705 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Thu, 6 Jul 2023 23:00:24 +0200 Subject: [PATCH 01/12] feat: Delimit tangled code blocks with headings An extention to the idea discussed in #958. Add heading text as delimiter as a comment if: 1. There is in-fact a current heading. Headings are optional. 2. Current heading hasn't been used as a delimiter before. 3. `commentstring` is available for the filetype in question. Otherwise just use a newline. That last one (3) requires making a temporary scratch buffer to trigger ftplugin, which retrieves `commentstring` for all supported filetypes in vim. --- lua/neorg/modules/core/tangle/module.lua | 35 ++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index f5f297588..1e80b6435 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -245,6 +245,8 @@ module.public = { }) local query = utils.ts_parse_query("norg", query_str) + local previous_headings = {} + local commentstrings = {} for id, node in query:iter_captures(document_root, buffer, 0, -1) do local capture = query.captures[id] @@ -253,7 +255,8 @@ module.public = { local parsed_tag = module.required["core.integrations.treesitter"].get_tag_info(node) if parsed_tag then - local file_to_tangle_to = options.languages[parsed_tag.parameters[1]] + local language = parsed_tag.parameters[1] + local file_to_tangle_to = options.languages[language] local content = parsed_tag.content if parsed_tag.parameters[1] == "norg" then @@ -278,8 +281,36 @@ module.public = { if file_to_tangle_to then if tangles[file_to_tangle_to] then - -- insert a blank line between blocks table.insert(content, 1, "") + + -- get current heading + local heading_string + local heading = module.required["core.integrations.treesitter"].find_parent(node:parent(), "heading%d+") + if heading and heading:named_child(1) then + local srow, scol, erow, ecol = heading:named_child(1):range() + heading_string = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})[1] + end + + -- don't reuse the same header more than once + if heading_string and previous_headings[language] ~= heading then + + -- Get commentstring from vim scratch buffer + if not commentstrings[language] then + local cur_buf = vim.api.nvim_get_current_buf() + local tmp_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(tmp_buf) + vim.bo.filetype = language + commentstrings[language] = vim.bo.commentstring + vim.api.nvim_set_current_buf(cur_buf) + vim.api.nvim_buf_delete(tmp_buf, {force = true}) + end + + if commentstrings[language] ~= "" then + table.insert(content, 1, commentstrings[language]:format(heading_string)) + table.insert(content, 1, "") + previous_headings[language] = heading + end + end else tangles[file_to_tangle_to] = {} end From 7fc920af06ac062b2e1576a1f305076fac2e0311 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Fri, 7 Jul 2023 06:42:59 +0200 Subject: [PATCH 02/12] Tangle at the begining of the file Heading as comment followed by an extra newline above the first block above the first codeblock assuming it is under a heading. Nothing otherwise. As before, an extra newline above the heading as a comment after that. This give the flow: ``` -- Heading 1 print("Codeblock 1") -- Heading 2 print("Codeblock 2") ``` --- lua/neorg/modules/core/tangle/module.lua | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 1e80b6435..ede32a924 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -282,37 +282,37 @@ module.public = { if file_to_tangle_to then if tangles[file_to_tangle_to] then table.insert(content, 1, "") + else + tangles[file_to_tangle_to] = {} + end - -- get current heading - local heading_string - local heading = module.required["core.integrations.treesitter"].find_parent(node:parent(), "heading%d+") - if heading and heading:named_child(1) then - local srow, scol, erow, ecol = heading:named_child(1):range() - heading_string = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})[1] + -- get current heading + local heading_string + local heading = module.required["core.integrations.treesitter"].find_parent(node:parent(), "heading%d+") + if heading and heading:named_child(1) then + local srow, scol, erow, ecol = heading:named_child(1):range() + heading_string = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})[1] + end + + -- don't reuse the same header more than once + if heading_string and previous_headings[language] ~= heading then + + -- Get commentstring from vim scratch buffer + if not commentstrings[language] then + local cur_buf = vim.api.nvim_get_current_buf() + local tmp_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(tmp_buf) + vim.bo.filetype = language + commentstrings[language] = vim.bo.commentstring + vim.api.nvim_set_current_buf(cur_buf) + vim.api.nvim_buf_delete(tmp_buf, {force = true}) end - -- don't reuse the same header more than once - if heading_string and previous_headings[language] ~= heading then - - -- Get commentstring from vim scratch buffer - if not commentstrings[language] then - local cur_buf = vim.api.nvim_get_current_buf() - local tmp_buf = vim.api.nvim_create_buf(false, true) - vim.api.nvim_set_current_buf(tmp_buf) - vim.bo.filetype = language - commentstrings[language] = vim.bo.commentstring - vim.api.nvim_set_current_buf(cur_buf) - vim.api.nvim_buf_delete(tmp_buf, {force = true}) - end - - if commentstrings[language] ~= "" then - table.insert(content, 1, commentstrings[language]:format(heading_string)) - table.insert(content, 1, "") - previous_headings[language] = heading - end + if commentstrings[language] ~= "" then + table.insert(content, 1, commentstrings[language]:format(heading_string)) + table.insert(content, 1, "") + previous_headings[language] = heading end - else - tangles[file_to_tangle_to] = {} end vim.list_extend(tangles[file_to_tangle_to], content) From 5c6ec32310e8d55ea850c54a264b2f78b6f887e9 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Fri, 7 Jul 2023 10:21:25 +0200 Subject: [PATCH 03/12] remove redundant jump to parent node --- lua/neorg/modules/core/tangle/module.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index ede32a924..3fa7c3d36 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -288,7 +288,7 @@ module.public = { -- get current heading local heading_string - local heading = module.required["core.integrations.treesitter"].find_parent(node:parent(), "heading%d+") + local heading = module.required["core.integrations.treesitter"].find_parent(node, "heading%d+") if heading and heading:named_child(1) then local srow, scol, erow, ecol = heading:named_child(1):range() heading_string = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})[1] From e7257fc71260f3062166f033de3311bba59f843a Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Fri, 7 Jul 2023 11:42:08 +0200 Subject: [PATCH 04/12] bugfix: newline for first heading incorrectly placed --- lua/neorg/modules/core/tangle/module.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 3fa7c3d36..60ff40f8c 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -280,11 +280,6 @@ module.public = { end if file_to_tangle_to then - if tangles[file_to_tangle_to] then - table.insert(content, 1, "") - else - tangles[file_to_tangle_to] = {} - end -- get current heading local heading_string @@ -309,12 +304,18 @@ module.public = { end if commentstrings[language] ~= "" then - table.insert(content, 1, commentstrings[language]:format(heading_string)) table.insert(content, 1, "") + table.insert(content, 1, commentstrings[language]:format(heading_string)) previous_headings[language] = heading end end + if not tangles[file_to_tangle_to] then + tangles[file_to_tangle_to] = {} + else + table.insert(content, 1, "") + end + vim.list_extend(tangles[file_to_tangle_to], content) end From b4496ada4bf7553f13f5b7eb62adf4d1176e4e58 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Fri, 7 Jul 2023 15:33:31 +0200 Subject: [PATCH 05/12] applying stylua --- lua/neorg/modules/core/tangle/module.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 60ff40f8c..262c3c217 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -280,7 +280,6 @@ module.public = { end if file_to_tangle_to then - -- get current heading local heading_string local heading = module.required["core.integrations.treesitter"].find_parent(node, "heading%d+") @@ -291,7 +290,6 @@ module.public = { -- don't reuse the same header more than once if heading_string and previous_headings[language] ~= heading then - -- Get commentstring from vim scratch buffer if not commentstrings[language] then local cur_buf = vim.api.nvim_get_current_buf() @@ -300,13 +298,13 @@ module.public = { vim.bo.filetype = language commentstrings[language] = vim.bo.commentstring vim.api.nvim_set_current_buf(cur_buf) - vim.api.nvim_buf_delete(tmp_buf, {force = true}) + vim.api.nvim_buf_delete(tmp_buf, { force = true }) end if commentstrings[language] ~= "" then - table.insert(content, 1, "") - table.insert(content, 1, commentstrings[language]:format(heading_string)) - previous_headings[language] = heading + table.insert(content, 1, "") + table.insert(content, 1, commentstrings[language]:format(heading_string)) + previous_headings[language] = heading end end From cd06222f80105a00b135ee408475d15676c539f1 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Wed, 2 Aug 2023 15:59:29 +0200 Subject: [PATCH 06/12] better file detection --- lua/neorg/modules/core/tangle/module.lua | 91 ++++++++++++++---------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 262c3c217..18fa973f8 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -181,43 +181,24 @@ module.load = function() }) end + module.public = { tangle = function(buffer) - local parsed_document_metadata = module.required["core.integrations.treesitter"].get_document_metadata(buffer) - - if vim.tbl_isempty(parsed_document_metadata) or not parsed_document_metadata.tangle then - parsed_document_metadata = { - tangle = {}, - } - end + local parsed_document_metadata = module.required["core.integrations.treesitter"].get_document_metadata(buffer) or {} + local tangle_settings = parsed_document_metadata.tangle or {} + local scope = tangle_settings.scope or "all" -- "all" | "tagged" | "main" - local document_root = module.required["core.integrations.treesitter"].get_document_root(buffer) + local treesitter = module.required["core.integrations.treesitter"] + local document_root = treesitter.get_document_root(buffer) - local options = { - languages = {}, - scope = parsed_document_metadata.tangle.scope or "all", -- "all" | "tagged" | "main" - } - - if type(parsed_document_metadata.tangle) == "table" then - if vim.tbl_islist(parsed_document_metadata.tangle) then - for _, file in ipairs(parsed_document_metadata.tangle) do - options.languages[vim.filetype.match({ filename = file })] = file - end - elseif parsed_document_metadata.tangle.languages then - for language, file in pairs(parsed_document_metadata.tangle.languages) do - options.languages[language] = file - end - end - elseif type(parsed_document_metadata.tangle) == "string" then - options.languages[vim.filetype.match({ filename = parsed_document_metadata.tangle })] = - parsed_document_metadata.tangle - end + local filetype_to_filenames = {} + local filename_to_languages = {} local tangles = { -- filename = { content } } - local query_str = lib.match(options.scope)({ + local query_str = lib.match(scope)({ _ = [[ (ranged_verbatim_tag name: (tag_name) @_name @@ -252,11 +233,11 @@ module.public = { local capture = query.captures[id] if capture == "tag" then - local parsed_tag = module.required["core.integrations.treesitter"].get_tag_info(node) + local parsed_tag = treesitter.get_tag_info(node) if parsed_tag then - local language = parsed_tag.parameters[1] - local file_to_tangle_to = options.languages[language] + local declared_filetype = parsed_tag.parameters[1] + local file_to_tangle_to local content = parsed_tag.content if parsed_tag.parameters[1] == "norg" then @@ -271,25 +252,63 @@ module.public = { if attribute.name == "tangle.none" then goto skip_tag elseif attribute.name == "tangle" and attribute.parameters[1] then - if options.scope == "main" then + if scope == "main" then goto skip_tag end - file_to_tangle_to = table.concat(attribute.parameters) end end + if not file_to_tangle_to then + if declared_filetype and filetype_to_filenames[declared_filetype] then + file_to_tangle_to = filetype_to_filenames[declared_filetype] + elseif type(tangle_settings) == "string" then + file_to_tangle_to = tangle_settings + elseif type(tangle_settings) == "table" then + if not declared_filetype then + goto skip_tag + end + if vim.tbl_islist(tangle_settings) then + for idx, filename in ipairs(tangle_settings) do + if declared_filetype == vim.filetype.match({ filename=filename, contents=content }) then + tangle_settings[idx] = nil + break + end + end + else + file_to_tangle_to = tangle_settings[declared_filetype] + tangle_settings[declared_filetype] = nil + end + if file_to_tangle_to then + filetype_to_filenames[declared_filetype] = file_to_tangle_to + end + end + end + if file_to_tangle_to then + + local language + if filename_to_languages[file_to_tangle_to] then + language = filename_to_languages[file_to_tangle_to] + else + language = vim.filetype.match({ filename=file_to_tangle_to, contents=content }) + if not language and declared_filetype then + language = vim.filetype.match({ filename="___." .. declared_filetype, contents=content }) + end + filename_to_languages[file_to_tangle_to] = language + end + -- get current heading local heading_string - local heading = module.required["core.integrations.treesitter"].find_parent(node, "heading%d+") + local heading = treesitter.find_parent(node, "heading%d+") if heading and heading:named_child(1) then local srow, scol, erow, ecol = heading:named_child(1):range() heading_string = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})[1] end -- don't reuse the same header more than once - if heading_string and previous_headings[language] ~= heading then + if heading_string and language and previous_headings[language] ~= heading then + -- Get commentstring from vim scratch buffer if not commentstrings[language] then local cur_buf = vim.api.nvim_get_current_buf() @@ -300,7 +319,6 @@ module.public = { vim.api.nvim_set_current_buf(cur_buf) vim.api.nvim_buf_delete(tmp_buf, { force = true }) end - if commentstrings[language] ~= "" then table.insert(content, 1, "") table.insert(content, 1, commentstrings[language]:format(heading_string)) @@ -329,6 +347,7 @@ module.public = { module.on_event = function(event) if event.type == "core.neorgcmd.events.core.tangle.current-file" then local tangles = module.public.tangle(event.buffer) + assert(0, vim.inspect(tangles)) if not tangles or vim.tbl_isempty(tangles) then utils.notify("Nothing to tangle!", vim.log.levels.WARN) From 86c9277cde9e47bf1ed0420b948a5799727cd42a Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Wed, 2 Aug 2023 20:00:38 +0200 Subject: [PATCH 07/12] support arguments --- lua/neorg/modules/core/tangle/module.lua | 103 +++++++++++++++-------- 1 file changed, 66 insertions(+), 37 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 18fa973f8..90b630f38 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -68,11 +68,20 @@ tangle: { lua: ./output.lua haskell: my-haskell-file } + delimiter: heading scope: all } @end ``` +The `delimiter` option determines how to delimit codeblocks that exports to the same file. +The following alternatives are allowed: + +* `heading` -- Try to determine the filetype of the code block and insert the current heading as a comment as a delimiter. + If filetype determination fails, `newline` will be used instead. +* `newline` -- Use an extra newline between blocks. +* `none` --Do not add delimiter. This implies that the code blocks are inserted into the the tangle target as-is. + The `scope` option is discussed in a [later section](#tangling-scopes), what we want to focus on is the `languages` object. It's a simple language-filepath mapping, but it's especially useful when the output file's language type cannot be inferred from the name. So far we've been using `init.lua`, `output.hs` - but what if we wanted to export all `haskell` code blocks into `my-file-without-an-extension`? @@ -182,16 +191,45 @@ module.load = function() end +local function clean_norg_content(content) + for i, line in ipairs(content) do + -- remove escape char + local new_line, _ = line:gsub("\\(.?)", "%1") + content[i] = new_line or "" + end + return content +end + +local function get_comment_string(language) + local cur_buf = vim.api.nvim_get_current_buf() + local tmp_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(tmp_buf) + vim.bo.filetype = language + local commentstring = vim.bo.commentstring + vim.api.nvim_set_current_buf(cur_buf) + vim.api.nvim_buf_delete(tmp_buf, { force = true }) + return commentstring +end + + module.public = { tangle = function(buffer) - local parsed_document_metadata = module.required["core.integrations.treesitter"].get_document_metadata(buffer) or {} + local treesitter = module.required["core.integrations.treesitter"] + local parsed_document_metadata = treesitter.get_document_metadata(buffer) or {} local tangle_settings = parsed_document_metadata.tangle or {} local scope = tangle_settings.scope or "all" -- "all" | "tagged" | "main" + local delimiter = tangle_settings.delimiter or "newline" -- "newline" | "heading" | "none" + local filetype_to_filenames = tangle_settings.languages or tangle_settings + local filenames_only + if vim.tbl_islist(filetype_to_filenames) then + filenames_only = filetype_to_filenames + filetype_to_filenames = {} + elseif type(filetype_to_filenames) == string then + filetype_to_filenames = {_ = filetype_to_filenames} + end - local treesitter = module.required["core.integrations.treesitter"] local document_root = treesitter.get_document_root(buffer) - local filetype_to_filenames = {} local filename_to_languages = {} local tangles = { @@ -241,11 +279,7 @@ module.public = { local content = parsed_tag.content if parsed_tag.parameters[1] == "norg" then - for i, line in ipairs(content) do - -- remove escape char - local new_line, _ = line:gsub("\\(.?)", "%1") - content[i] = new_line or "" - end + content = clean_norg_content(content) end for _, attribute in ipairs(parsed_tag.attributes) do @@ -259,39 +293,41 @@ module.public = { end end + -- determine tangle file target if not file_to_tangle_to then if declared_filetype and filetype_to_filenames[declared_filetype] then file_to_tangle_to = filetype_to_filenames[declared_filetype] - elseif type(tangle_settings) == "string" then - file_to_tangle_to = tangle_settings - elseif type(tangle_settings) == "table" then - if not declared_filetype then - goto skip_tag - end - if vim.tbl_islist(tangle_settings) then - for idx, filename in ipairs(tangle_settings) do + else + if filenames_only then + for _, filename in ipairs(filenames_only) do if declared_filetype == vim.filetype.match({ filename=filename, contents=content }) then - tangle_settings[idx] = nil + file_to_tangle_to = filename break end end else - file_to_tangle_to = tangle_settings[declared_filetype] - tangle_settings[declared_filetype] = nil + if not declared_filetype then + goto skip_tag + end + file_to_tangle_to = filetype_to_filenames[declared_filetype] end if file_to_tangle_to then filetype_to_filenames[declared_filetype] = file_to_tangle_to + else + file_to_tangle_to = filetype_to_filenames["_"] end end end + if not file_to_tangle_to then + goto skip_tag + end - if file_to_tangle_to then - + if delimiter == "heading" then local language if filename_to_languages[file_to_tangle_to] then language = filename_to_languages[file_to_tangle_to] else - language = vim.filetype.match({ filename=file_to_tangle_to, contents=content }) + language = vim.filetype.match({filename = file_to_tangle_to, contents = content}) if not language and declared_filetype then language = vim.filetype.match({ filename="___." .. declared_filetype, contents=content }) end @@ -311,13 +347,7 @@ module.public = { -- Get commentstring from vim scratch buffer if not commentstrings[language] then - local cur_buf = vim.api.nvim_get_current_buf() - local tmp_buf = vim.api.nvim_create_buf(false, true) - vim.api.nvim_set_current_buf(tmp_buf) - vim.bo.filetype = language - commentstrings[language] = vim.bo.commentstring - vim.api.nvim_set_current_buf(cur_buf) - vim.api.nvim_buf_delete(tmp_buf, { force = true }) + commentstrings[language] = get_comment_string(language) end if commentstrings[language] ~= "" then table.insert(content, 1, "") @@ -325,16 +355,16 @@ module.public = { previous_headings[language] = heading end end + end - if not tangles[file_to_tangle_to] then - tangles[file_to_tangle_to] = {} - else - table.insert(content, 1, "") - end - - vim.list_extend(tangles[file_to_tangle_to], content) + if not tangles[file_to_tangle_to] then + tangles[file_to_tangle_to] = {} + elseif delimiter ~= "none" then + table.insert(content, 1, "") end + vim.list_extend(tangles[file_to_tangle_to], content) + ::skip_tag:: end end @@ -347,7 +377,6 @@ module.public = { module.on_event = function(event) if event.type == "core.neorgcmd.events.core.tangle.current-file" then local tangles = module.public.tangle(event.buffer) - assert(0, vim.inspect(tangles)) if not tangles or vim.tbl_isempty(tangles) then utils.notify("Nothing to tangle!", vim.log.levels.WARN) From 8b37ae48f824de8f0a65b03b4a73be8668e8b6a8 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Wed, 2 Aug 2023 20:47:10 +0200 Subject: [PATCH 08/12] cleanup --- lua/neorg/modules/core/tangle/module.lua | 82 +++++++++++------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 90b630f38..90dd0e6b5 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -74,18 +74,18 @@ tangle: { @end ``` +The `language` option determines which filetype should go into which file. +It's a simple language-filepath mapping, but it's especially useful when the output file's language type cannot be inferred from the name or shebang. + The `delimiter` option determines how to delimit codeblocks that exports to the same file. The following alternatives are allowed: * `heading` -- Try to determine the filetype of the code block and insert the current heading as a comment as a delimiter. - If filetype determination fails, `newline` will be used instead. + If filetype detection fails, `newline` will be used instead. * `newline` -- Use an extra newline between blocks. -* `none` --Do not add delimiter. This implies that the code blocks are inserted into the the tangle target as-is. +* `none` -- Do not add delimiter. This implies that the code blocks are inserted into the the tangle target as-is. -The `scope` option is discussed in a [later section](#tangling-scopes), what we want to focus on is the `languages` object. -It's a simple language-filepath mapping, but it's especially useful when the output file's language type cannot be inferred from the name. -So far we've been using `init.lua`, `output.hs` - but what if we wanted to export all `haskell` code blocks into `my-file-without-an-extension`? -The only way to do that is through the `languages` object, where we explicitly define the language to tangle. Neat! +The `scope` option is discussed below. #### Tangling Scopes What you've seen so far is the tangler operating in `all` mode. This means it captures all code blocks of a certain type unless that code block is tagged @@ -191,15 +191,6 @@ module.load = function() end -local function clean_norg_content(content) - for i, line in ipairs(content) do - -- remove escape char - local new_line, _ = line:gsub("\\(.?)", "%1") - content[i] = new_line or "" - end - return content -end - local function get_comment_string(language) local cur_buf = vim.api.nvim_get_current_buf() local tmp_buf = vim.api.nvim_create_buf(false, true) @@ -217,26 +208,25 @@ module.public = { local treesitter = module.required["core.integrations.treesitter"] local parsed_document_metadata = treesitter.get_document_metadata(buffer) or {} local tangle_settings = parsed_document_metadata.tangle or {} - local scope = tangle_settings.scope or "all" -- "all" | "tagged" | "main" - local delimiter = tangle_settings.delimiter or "newline" -- "newline" | "heading" | "none" - local filetype_to_filenames = tangle_settings.languages or tangle_settings - local filenames_only - if vim.tbl_islist(filetype_to_filenames) then - filenames_only = filetype_to_filenames - filetype_to_filenames = {} - elseif type(filetype_to_filenames) == string then - filetype_to_filenames = {_ = filetype_to_filenames} + local options = { + languages = tangle_settings.languages or tangle_settings, + scope = tangle_settings.scope or "all", -- "all" | "tagged" | "main" + delimiter = tangle_settings.delimiter or "heading", -- "newline" | "heading" | "none" + } + if vim.tbl_islist(options.languages) then + options.filenames_only = options.languages + options.languages = {} + elseif type(options.languages) == string then + options.languages = {_ = options.languages} end local document_root = treesitter.get_document_root(buffer) - local filename_to_languages = {} - local tangles = { -- filename = { content } } - local query_str = lib.match(scope)({ + local query_str = lib.match(options.scope)({ _ = [[ (ranged_verbatim_tag name: (tag_name) @_name @@ -275,18 +265,22 @@ module.public = { if parsed_tag then local declared_filetype = parsed_tag.parameters[1] - local file_to_tangle_to local content = parsed_tag.content if parsed_tag.parameters[1] == "norg" then - content = clean_norg_content(content) + for i, line in ipairs(content) do + -- remove escape char + local new_line, _ = line:gsub("\\(.?)", "%1") + content[i] = new_line or "" + end end + local file_to_tangle_to for _, attribute in ipairs(parsed_tag.attributes) do if attribute.name == "tangle.none" then goto skip_tag elseif attribute.name == "tangle" and attribute.parameters[1] then - if scope == "main" then + if options.scope == "main" then goto skip_tag end file_to_tangle_to = table.concat(attribute.parameters) @@ -295,26 +289,24 @@ module.public = { -- determine tangle file target if not file_to_tangle_to then - if declared_filetype and filetype_to_filenames[declared_filetype] then - file_to_tangle_to = filetype_to_filenames[declared_filetype] + if declared_filetype and options.languages[declared_filetype] then + file_to_tangle_to = options.languages[declared_filetype] else - if filenames_only then - for _, filename in ipairs(filenames_only) do + if options.filenames_only then + for _, filename in ipairs(options.filenames_only) do if declared_filetype == vim.filetype.match({ filename=filename, contents=content }) then file_to_tangle_to = filename break end end - else - if not declared_filetype then - goto skip_tag - end - file_to_tangle_to = filetype_to_filenames[declared_filetype] + elseif declared_filetype then + file_to_tangle_to = options.languages[declared_filetype] + end + if not file_to_tangle_to then + file_to_tangle_to = options.languages["_"] end - if file_to_tangle_to then - filetype_to_filenames[declared_filetype] = file_to_tangle_to - else - file_to_tangle_to = filetype_to_filenames["_"] + if declared_filetype then + options.languages[declared_filetype] = file_to_tangle_to end end end @@ -322,7 +314,7 @@ module.public = { goto skip_tag end - if delimiter == "heading" then + if options.delimiter == "heading" then local language if filename_to_languages[file_to_tangle_to] then language = filename_to_languages[file_to_tangle_to] @@ -359,7 +351,7 @@ module.public = { if not tangles[file_to_tangle_to] then tangles[file_to_tangle_to] = {} - elseif delimiter ~= "none" then + elseif options.delimiter ~= "none" then table.insert(content, 1, "") end From 1f48f64d188933f0ad7a3003314a68110ad532bd Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Wed, 2 Aug 2023 20:55:42 +0200 Subject: [PATCH 09/12] cleanup --- lua/neorg/modules/core/tangle/module.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 90dd0e6b5..3431d8efb 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -76,6 +76,7 @@ tangle: { The `language` option determines which filetype should go into which file. It's a simple language-filepath mapping, but it's especially useful when the output file's language type cannot be inferred from the name or shebang. +It is also possible to use the name `_` as a catch all to direct output to all files not listed. The `delimiter` option determines how to delimit codeblocks that exports to the same file. The following alternatives are allowed: From 20479f232ad4d3428d70302232e8e782d63b3532 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Wed, 2 Aug 2023 21:04:13 +0200 Subject: [PATCH 10/12] cleanup --- lua/neorg/modules/core/tangle/module.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 3431d8efb..eac66b7ec 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -76,15 +76,15 @@ tangle: { The `language` option determines which filetype should go into which file. It's a simple language-filepath mapping, but it's especially useful when the output file's language type cannot be inferred from the name or shebang. -It is also possible to use the name `_` as a catch all to direct output to all files not listed. +It is also possible to use the name `_` as a catch all to direct output for all files not otherwise listed. -The `delimiter` option determines how to delimit codeblocks that exports to the same file. +The `delimiter` option determines how to delimit code blocks that exports to the same file. The following alternatives are allowed: * `heading` -- Try to determine the filetype of the code block and insert the current heading as a comment as a delimiter. If filetype detection fails, `newline` will be used instead. * `newline` -- Use an extra newline between blocks. -* `none` -- Do not add delimiter. This implies that the code blocks are inserted into the the tangle target as-is. +* `none` -- Do not add delimiter. This implies that the code blocks are inserted into the tangle target as-is. The `scope` option is discussed below. From 13749fd376597d65cffa03a4427290b35e113131 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Wed, 2 Aug 2023 21:54:36 +0200 Subject: [PATCH 11/12] cleanup --- lua/neorg/modules/core/tangle/module.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index eac66b7ec..7600f822b 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -212,7 +212,7 @@ module.public = { local options = { languages = tangle_settings.languages or tangle_settings, scope = tangle_settings.scope or "all", -- "all" | "tagged" | "main" - delimiter = tangle_settings.delimiter or "heading", -- "newline" | "heading" | "none" + delimiter = tangle_settings.delimiter or "newline", -- "newline" | "heading" | "none" } if vim.tbl_islist(options.languages) then options.filenames_only = options.languages @@ -300,8 +300,6 @@ module.public = { break end end - elseif declared_filetype then - file_to_tangle_to = options.languages[declared_filetype] end if not file_to_tangle_to then file_to_tangle_to = options.languages["_"] From c8bc9b2809beb7deb5b9ebba9e46920226a62026 Mon Sep 17 00:00:00 2001 From: Jonathan Feinberg Date: Fri, 4 Aug 2023 19:08:09 +0200 Subject: [PATCH 12/12] bugfix: type() returns string --- lua/neorg/modules/core/tangle/module.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neorg/modules/core/tangle/module.lua b/lua/neorg/modules/core/tangle/module.lua index 7600f822b..2be65d0ee 100644 --- a/lua/neorg/modules/core/tangle/module.lua +++ b/lua/neorg/modules/core/tangle/module.lua @@ -217,7 +217,7 @@ module.public = { if vim.tbl_islist(options.languages) then options.filenames_only = options.languages options.languages = {} - elseif type(options.languages) == string then + elseif type(options.languages) == "string" then options.languages = {_ = options.languages} end