2023-06-04 06:38:45 +00:00
---TODO: how does this work with changing texts? (extmarks, but can look at `TextChanged` and `TextChangedI` events, along with some buf events to re-run when needs to)
-- TODO: Better `ts_query` construction experience
-- TODO: injection tree-sitter query
---TODO: add reducer as formatter
2023-05-30 16:35:27 +00:00
---TODO: Add reducer as buf_select predicate
2023-05-30 04:39:15 +00:00
---@type mod_buf_select
local buf_select = require ( ' tsql.buf_select ' )
2023-05-30 07:29:49 +00:00
---@type mod_token_select
local token_select = require ( ' tsql.token_select ' )
2023-05-30 04:39:15 +00:00
---@module 'tsql'
local M = { }
2023-05-30 07:29:49 +00:00
---@class mod_sink_by
2023-05-30 04:39:15 +00:00
M.sink_by = { }
2023-05-30 07:29:49 +00:00
---@class mod_format
---@alias Format fun(nodes: QNode[]): string
2023-05-30 04:39:15 +00:00
M.format = { }
2023-06-01 15:20:36 +00:00
2023-05-30 07:29:49 +00:00
---@class Sink
---@field sink fun(self, nodes: QNode[])
M.Sink = { }
M.Sink . __index = M.Sink
2023-05-30 04:39:15 +00:00
2023-06-03 20:53:06 +00:00
---@type Ctor<fun(nodes: QNode[]), Sink>
2023-06-01 15:20:36 +00:00
function M . sink_by . pure_fn ( func )
2023-06-03 20:53:06 +00:00
local self = setmetatable ( {
sink = function ( _ , nodes )
return func ( nodes )
end
2023-06-01 15:20:36 +00:00
} , M.Sink )
2023-06-03 20:53:06 +00:00
self.__index = M.Sink
return self
2023-06-01 15:20:36 +00:00
end
2023-05-30 07:29:49 +00:00
---@return Sink
2023-05-30 04:39:15 +00:00
function M . sink_by . highlight ( )
2023-06-04 06:38:45 +00:00
-- NOTE: magic! Neovim has extmarks, so in a very general case, you don't need to
-- re-run this sink to have the highlights be up-to-date
2023-06-03 20:53:06 +00:00
return M.sink_by . pure_fn ( function ( nodes )
for _ , node in ipairs ( nodes ) do
vim.highlight . range (
node.buf . bufnr ,
M.config . nvim_ns ,
M.config . nvim_hl_group ,
{ node.start . row_0 , node.start . col_0 } ,
{ node.end_ex_col . row_0 , node.end_ex_col . col_0 } ,
2023-06-04 06:38:45 +00:00
{ inclusive = false }
2023-06-03 20:53:06 +00:00
)
2023-05-30 07:29:49 +00:00
end
2023-06-03 20:53:06 +00:00
end )
2023-05-30 04:39:15 +00:00
end
---@type Format
2023-05-30 07:29:49 +00:00
---Something that can be represented in string in a concise/DSL format.
---In the context of QNode, it should just be the text content. If it's multiline,
---just join by newline
---
---`vim.api.nvim_buf_get_text(
--- bufnr: number,
--- start_row: number,
--- start_col: number,
--- end_row: number,
--- end_col_exclusive: number,
--- opt: {}
---) -> string[]` return value is array of lines, empty array for unloaded buffer
function M . format . display ( nodes )
local texts = { }
for _ , node in ipairs ( nodes ) do
2023-06-03 20:53:06 +00:00
local text = vim.api . nvim_buf_get_text (
node.buf . bufnr ,
node.start . row_0 ,
node.start . col_0 ,
node.end_ex_col . row_0 ,
node.end_ex_col . col_0 ,
{ }
)
2023-05-30 07:29:49 +00:00
table.insert ( texts , table.concat ( text , ' \n ' ) )
end
return table.concat ( texts , ' \n \n ' )
2023-05-30 04:39:15 +00:00
end
---@type Format
2023-05-30 07:29:49 +00:00
---Something like a JSON if natively possible, or RON for Rust for clarity
---Basically return a string that is pretty-printed that represents
---a Lua table onto string
function M . format . dump ( nodes )
return vim.inspect ( nodes , { newline = ' \n ' , indent = ' ' } )
2023-05-30 04:39:15 +00:00
end
2023-06-01 15:20:36 +00:00
M.format . debug = M.format . dump
2023-06-03 20:53:06 +00:00
M.format . default = M.format . display
2023-06-01 15:20:36 +00:00
---@param format Format | nil
2023-05-30 07:29:49 +00:00
---@return Sink
2023-05-30 04:39:15 +00:00
function M . sink_by . print ( format )
2023-06-01 15:20:36 +00:00
if format == nil then
format = M.format . default
end
2023-06-03 22:34:25 +00:00
return M.sink_by . pure_fn (
function ( nodes )
2023-05-30 07:29:49 +00:00
print ( format ( nodes ) )
end
2023-06-03 22:34:25 +00:00
)
2023-05-30 04:39:15 +00:00
end
---@param format Format
2023-05-30 07:29:49 +00:00
---@return Sink
2023-05-30 04:39:15 +00:00
function M . sink_by . nvim_yank_buf ( format )
2023-06-01 15:20:36 +00:00
if format == nil then
format = M.format . default
end
2023-06-03 22:34:25 +00:00
return M.sink . pure_fn (
function ( nodes )
2023-05-30 07:29:49 +00:00
local text = format ( nodes )
vim.fn . setreg ( ' " ' , text )
end
2023-06-03 22:34:25 +00:00
)
2023-05-30 04:39:15 +00:00
end
2023-05-30 07:29:49 +00:00
---NOTE: re-export with implementation
M.buf_match = buf_select.buf_match
M.BufMatch = buf_select.BufMatch
M.QBuf = buf_select.QBuf
M.nvim_get_qbufs = buf_select.nvim_get_qbufs
M.ts_query = token_select.ts_query
M.TSQuery = token_select.TSQuery
---@class Tsql
---@field buf_match BufMatch
---@field codeql TSQuery
---@field sink Sink
M.Tsql = { }
M.Tsql . __index = M.Tsql
2023-05-30 04:39:15 +00:00
---@return Tsql
---@param external_dsl string
function M . s ( external_dsl )
-- TODO: implement
end
---@return Tsql
---@param buf_match BufMatch
---@param codeql TSQuery
---@param sink Sink
function M . t ( buf_match , codeql , sink )
2023-05-30 07:29:49 +00:00
return setmetatable ( {
buf_match = buf_match ,
codeql = codeql ,
sink = sink
} , M.Tsql )
2023-05-30 04:39:15 +00:00
end
2023-05-30 16:35:27 +00:00
---NOTE: requires nvim runtime
function M . Tsql : q_nodes ( )
2023-06-03 22:34:25 +00:00
return self.codeql : find_nodes ( self : qbufs ( ) )
end
function M . Tsql : qbufs ( )
return self.buf_match . filter_on ( self.buf_match , M.nvim_get_qbufs ( ) )
2023-05-30 16:35:27 +00:00
end
2023-05-30 04:39:15 +00:00
---NOTE: This is now exiting the functional core and entering
--- imperative shell
2023-05-30 16:35:27 +00:00
--- @param store StaticStore
function M . Tsql : do_nvim ( store )
self.sink : sink ( self : q_nodes ( ) )
2023-06-04 06:38:45 +00:00
-- FIXME: this should be limited to only highlight sink
2023-05-30 16:35:27 +00:00
store : add_highlight ( self )
end
---@param config RtConfig
---@param store StaticStore
function M . _delete_all_highlights ( config , store )
---@type table<integer, QBuf>
local bufs = { }
for _ , highlight_q in pairs ( store.highlighting ) do
for _ , qnode in ipairs ( highlight_q : q_nodes ( ) ) do
table.insert ( bufs , qnode.buf . bufnr , qnode.buf )
end
end
for _ , buf in pairs ( bufs ) do
vim.api . nvim_buf_clear_namespace ( buf.bufnr , config.nvim_ns , 0 , - 1 )
end
store : clear_highlights ( )
end
---NOTE: Collocated with `M.setup`
---@class Config
---@field nvim_hl_group string
local Config = { }
---@type Config
M.config_default = {
nvim_hl_group = " Search "
}
---@class RtConfig: Config
---@field nvim_ns number
M.RtConfig = { }
---@type RtConfig
M.config = { }
---@class StaticStore
---@field highlighting Tsql[]
---This is needed to undo the highlighting done by this plugin and potentially
---subscribe to the changing buffers to re-highlight if necessary
M.Store = { }
M.Store . __index = M.Store
---@param tsql Tsql
function M . Store : add_highlight ( tsql )
table.insert ( self.highlighting , tsql )
end
function M . Store : clear_highlights ( )
self.highlighting = { }
end
---@return StaticStore
function M . Store : new ( )
local o = { highlighting = { } }
setmetatable ( o , self )
2023-06-03 22:34:25 +00:00
o.__index = self
2023-05-30 16:35:27 +00:00
return o
end
function M . clear_highlights ( )
return M._delete_all_highlights ( M.config , M.store )
end
2023-06-03 03:00:27 +00:00
---@param config Config | nil
2023-05-30 16:35:27 +00:00
function M . setup ( config )
M.config = vim.tbl_deep_extend ( " force " , M.config , config or M.config_default )
M.config . nvim_ns = vim.api . nvim_create_namespace ( " tsql " )
2023-06-03 03:09:26 +00:00
vim.api . nvim_create_user_command ( " Noh " , function ( _ ) M.clear_highlights ( ) end ,
{ nargs = 0 , desc = " Clear tsql highlights " } )
2023-06-01 15:20:36 +00:00
vim.api . nvim_create_user_command ( " Tdsl " , function ( cmd )
M.s ( cmd.args ) : do_nvim ( M.store )
2023-06-03 03:09:26 +00:00
end , { nargs = " ? " , desc = " tsql DSL invocation " } )
2023-05-30 16:35:27 +00:00
M.store = M.Store : new ( )
2023-05-30 04:39:15 +00:00
end
return M