Skip to content

Instantly share code, notes, and snippets.

@dmitmel
Created December 27, 2021 14:47
Show Gist options
  • Save dmitmel/97f2ef7d1ac675ba7ec7db874f1c87aa to your computer and use it in GitHub Desktop.
Save dmitmel/97f2ef7d1ac675ba7ec7db874f1c87aa to your computer and use it in GitHub Desktop.
--- 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