Created
December 27, 2021 14:47
-
-
Save dmitmel/97f2ef7d1ac675ba7ec7db874f1c87aa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- This used to be in <https://github.com/dmitmel/dotfiles/blob/master/nvim/lua/dotfiles/lsp/markup.lua>. | |
local M = require('dotfiles.autoload')('dotfiles.lsp.markdown') | |
local utils = require('dotfiles.utils') | |
local ffi = require('ffi') | |
---@type table<string, any> | |
local lib = ffi.load('cmark-gfm') | |
---@type table<string, any> | |
local lib_ext = ffi.load('cmark-gfm-extensions') | |
ffi.cdef(utils.read_file(vim.fn.fnamemodify(utils.script_path(), ':h') .. '/cmark_ffi_defs.h')) | |
function M.cmark_text_to_ast(text) | |
local options = lib.CMARK_OPT_DEFAULT | |
local parser = ffi.gc(lib.cmark_parser_new(options), lib.cmark_parser_free) | |
for _, ext_name in ipairs({ | |
'table', | |
'autolink', | |
'strikethrough', | |
-- 'tagfilter', | |
'tasklist', | |
}) do | |
local ext = lib_ext.cmark_find_syntax_extension(ext_name) | |
if ext == nil then | |
error('extension not available: ' .. ext_name) | |
end | |
lib_ext.cmark_parser_attach_syntax_extension(parser, ext) | |
end | |
lib.cmark_parser_feed(parser, text, #text) | |
local doc = ffi.gc(lib.cmark_parser_finish(parser), lib.cmark_node_free) | |
return M.cmark_node_to_ast(doc) | |
end | |
-- Based on <https://github.com/github/cmark-gfm/blob/0.29.0.gfm.2/src/xml.c#L34-L155>. | |
function M.cmark_node_to_ast(root_node) | |
local ast_root_node = nil | |
local ast_nodes_stack = {} | |
local iter = ffi.gc(lib.cmark_iter_new(root_node), lib.cmark_iter_free) | |
while true do | |
local event = lib.cmark_iter_next(iter) | |
if event == lib.CMARK_EVENT_DONE then | |
break | |
end | |
local node = lib.cmark_iter_get_node(iter) | |
local node_type = lib.cmark_node_get_type(node) | |
if event == lib.CMARK_EVENT_ENTER then | |
local ast_node = { | |
__type = ffi.string(lib.cmark_node_get_type_string(node)), | |
_source_pos = { | |
lib.cmark_node_get_start_line(node), | |
lib.cmark_node_get_start_column(node), | |
lib.cmark_node_get_end_line(node), | |
lib.cmark_node_get_end_column(node), | |
}, | |
} | |
if | |
node_type == lib.CMARK_NODE_TEXT | |
or node_type == lib.CMARK_NODE_CODE | |
or node_type == lib.CMARK_NODE_HTML_BLOCK | |
or node_type == lib.CMARK_NODE_HTML_INLINE | |
or node_type == lib.CMARK_NODE_FOOTNOTE_REFERENCE | |
then | |
ast_node._literal = ffi.string(lib.cmark_node_get_literal(node)) | |
-- | |
elseif node_type == lib.CMARK_NODE_HEADING then | |
ast_node._heading_level = lib.cmark_node_get_heading_level(node) | |
-- | |
elseif node_type == lib.CMARK_NODE_LIST then | |
local list_type = lib.cmark_node_get_list_type(node) | |
if list_type == lib.CMARK_BULLET_LIST then | |
ast_node._list_type = 'bullet' | |
elseif list_type == lib.CMARK_ORDERED_LIST then | |
ast_node._list_type = 'ordered' | |
ast_node._list_start = lib.cmark_node_get_list_start(node) | |
local list_delim = lib.cmark_node_get_list_delim(node) | |
if list_delim == lib.CMARK_PAREN_DELIM then | |
ast_node._list_delim = 'paren' | |
elseif list_delim == lib.CMARK_PERIOD_DELIM then | |
ast_node._list_delim = 'period' | |
end | |
ast_node._list_start = lib.cmark_node_get_list_start(node) | |
end | |
ast_node._list_tight = lib.cmark_node_get_list_tight(node) ~= 0 | |
-- | |
elseif node_type == lib.CMARK_NODE_CODE_BLOCK then | |
ast_node._fence_info = ffi.string(lib.cmark_node_get_fence_info(node)) | |
ast_node._literal = ffi.string(lib.cmark_node_get_literal(node)) | |
local out_len, out_offset, out_char = | |
ffi.new('int[1]'), ffi.new('int[1]'), ffi.new('char[1]') | |
lib.cmark_node_get_fenced(node, out_len, out_offset, out_char) | |
ast_node._fence_length, ast_node._fence_offset = out_len[0], out_offset[0] | |
ast_node._fence_char = string.char(out_char[0]) | |
-- | |
elseif | |
node_type == lib.CMARK_NODE_CUSTOM_BLOCK or node_type == lib.CMARK_NODE_CUSTOM_INLINE | |
then | |
ast_node._on_enter = ffi.string(lib.cmark_node_get_on_enter(node)) | |
ast_node._on_exit = ffi.string(lib.cmark_node_get_on_exit(node)) | |
-- | |
elseif node_type == lib.CMARK_NODE_LINK or node_type == lib.CMARK_NODE_IMAGE then | |
ast_node._title = ffi.string(lib.cmark_node_get_url(node)) | |
ast_node._url = ffi.string(lib.cmark_node_get_url(node)) | |
-- | |
end | |
if ast_root_node == nil then | |
ast_root_node = ast_node | |
else | |
local parent_ast_node = ast_nodes_stack[#ast_nodes_stack] | |
table.insert(parent_ast_node.children, ast_node) | |
end | |
if lib.cmark_node_first_child(node) ~= nil then | |
ast_node.children = {} | |
table.insert(ast_nodes_stack, ast_node) | |
end | |
elseif event == lib.CMARK_EVENT_EXIT then | |
if lib.cmark_node_first_child(node) ~= nil then | |
table.remove(ast_nodes_stack) | |
end | |
end | |
end | |
assert(#ast_nodes_stack == 0) | |
return ast_root_node | |
end | |
return M |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment