251 lines
6.4 KiB
Lua
251 lines
6.4 KiB
Lua
---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
|
|
---TODO: Add reducer as buf_select predicate
|
|
|
|
---@type mod_buf_select
|
|
local buf_select = require('tsql.buf_select')
|
|
---@type mod_token_select
|
|
local token_select = require('tsql.token_select')
|
|
|
|
---@module 'tsql'
|
|
local M = {}
|
|
|
|
---@class mod_sink_by
|
|
M.sink_by = {}
|
|
|
|
---@class mod_format
|
|
---@alias Format fun(nodes: QNode[]): string
|
|
M.format = {}
|
|
|
|
|
|
---@class Sink
|
|
---@field sink fun(self, nodes: QNode[])
|
|
M.Sink = {}
|
|
M.Sink.__index = M.Sink
|
|
|
|
---@type Ctor<fun(nodes: QNode[]), Sink>
|
|
function M.sink_by.pure_fn(func)
|
|
local self = setmetatable({
|
|
sink = function(_, nodes)
|
|
return func(nodes)
|
|
end
|
|
}, M.Sink)
|
|
self.__index = M.Sink
|
|
return self
|
|
end
|
|
|
|
---@return Sink
|
|
function M.sink_by.highlight()
|
|
-- 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
|
|
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 },
|
|
{ inclusive = false }
|
|
)
|
|
end
|
|
end)
|
|
end
|
|
|
|
---@type Format
|
|
---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
|
|
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,
|
|
{}
|
|
)
|
|
table.insert(texts, table.concat(text, '\n'))
|
|
end
|
|
return table.concat(texts, '\n\n')
|
|
end
|
|
|
|
---@type Format
|
|
---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 = ' ' })
|
|
end
|
|
|
|
M.format.debug = M.format.dump
|
|
M.format.default = M.format.display
|
|
|
|
---@param format Format | nil
|
|
---@return Sink
|
|
function M.sink_by.print(format)
|
|
if format == nil then
|
|
format = M.format.default
|
|
end
|
|
return M.sink_by.pure_fn(
|
|
function(nodes)
|
|
print(format(nodes))
|
|
end
|
|
)
|
|
end
|
|
|
|
---@param format Format
|
|
---@return Sink
|
|
function M.sink_by.nvim_yank_buf(format)
|
|
if format == nil then
|
|
format = M.format.default
|
|
end
|
|
return M.sink.pure_fn(
|
|
function(nodes)
|
|
local text = format(nodes)
|
|
vim.fn.setreg('"', text)
|
|
end
|
|
)
|
|
end
|
|
|
|
---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
|
|
|
|
---@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)
|
|
return setmetatable({
|
|
buf_match = buf_match,
|
|
codeql = codeql,
|
|
sink = sink
|
|
}, M.Tsql)
|
|
end
|
|
|
|
---NOTE: requires nvim runtime
|
|
function M.Tsql:q_nodes()
|
|
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())
|
|
end
|
|
|
|
---NOTE: This is now exiting the functional core and entering
|
|
--- imperative shell
|
|
--- @param store StaticStore
|
|
function M.Tsql:do_nvim(store)
|
|
self.sink:sink(self:q_nodes())
|
|
-- FIXME: this should be limited to only highlight sink
|
|
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)
|
|
o.__index = self
|
|
return o
|
|
end
|
|
|
|
function M.clear_highlights()
|
|
return M._delete_all_highlights(M.config, M.store)
|
|
end
|
|
|
|
---@param config Config | nil
|
|
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")
|
|
|
|
vim.api.nvim_create_user_command("Noh", function(_) M.clear_highlights() end,
|
|
{ nargs = 0, desc = "Clear tsql highlights" })
|
|
vim.api.nvim_create_user_command("Tdsl", function(cmd)
|
|
M.s(cmd.args):do_nvim(M.store)
|
|
end, { nargs = "?", desc = "tsql DSL invocation" })
|
|
|
|
M.store = M.Store:new()
|
|
end
|
|
|
|
return M
|