|
| 1 | +local logger = require("gp.logger") |
| 2 | + |
| 3 | +---@class gp.Macro_cmd_params |
| 4 | +---@field arg_lead string |
| 5 | +---@field cmd_line string |
| 6 | +---@field cursor_pos number |
| 7 | +---@field cropped_line string |
| 8 | + |
| 9 | +---@class gp.Macro_parser_result |
| 10 | +---@field template string |
| 11 | +---@field artifacts table<string, string> |
| 12 | +---@field state table |
| 13 | + |
| 14 | +--- gp.Macro Interface |
| 15 | +-- @field name string: Name of the macro. |
| 16 | +-- @field description string: Description of the macro. |
| 17 | +-- @field default string: Default value for the macro (optional). |
| 18 | +-- @field max_occurrences number: Maximum number of occurrences for the macro (optional). |
| 19 | +-- @field triggered function: Function that determines if the macro is triggered. |
| 20 | +-- @field completion function: Function that provides completion options. |
| 21 | +-- @field parser function: Function that processes the macro in the template. |
| 22 | + |
| 23 | +---@class gp.Macro |
| 24 | +---@field name string |
| 25 | +---@field description string |
| 26 | +---@field default? string |
| 27 | +---@field max_occurrences? number |
| 28 | +---@field triggered fun(params: gp.Macro_cmd_params, state: table): boolean |
| 29 | +---@field completion fun(params: gp.Macro_cmd_params, state: table): string[] |
| 30 | +---@field parser fun(params: gp.Macro_parser_result): gp.Macro_parser_result |
| 31 | + |
| 32 | +---@param value string # string to hash |
| 33 | +---@return string # returns hash of the string |
| 34 | +local fnv1a_hash = function(value) |
| 35 | + ---@type number |
| 36 | + local hash = 2166136261 |
| 37 | + for i = 1, #value do |
| 38 | + hash = vim.fn.xor(hash, string.byte(value, i)) |
| 39 | + hash = vim.fn["and"]((hash * 16777619), 0xFFFFFFFF) |
| 40 | + end |
| 41 | + return string.format("%08x", hash) -- return as an 8-character hex string |
| 42 | +end |
| 43 | + |
| 44 | +local M = {} |
| 45 | + |
| 46 | +---@param prefix string # prefix for the placeholder |
| 47 | +---@param value string # value to hash |
| 48 | +---@return string # returns placeholder |
| 49 | +M.generate_placeholder = function(prefix, value) |
| 50 | + local hash_value = fnv1a_hash(value) |
| 51 | + local placeholder = "{{" .. prefix .. "." .. hash_value .. "}}" |
| 52 | + return placeholder |
| 53 | +end |
| 54 | + |
| 55 | +---@param macros gp.Macro[] |
| 56 | +---@return fun(template: string, artifacts: table, state: table): gp.Macro_parser_result |
| 57 | +M.build_parser = function(macros) |
| 58 | + ---@param template string |
| 59 | + ---@param artifacts table |
| 60 | + ---@param state table |
| 61 | + ---@return {template: string, artifacts: table, state: table} |
| 62 | + local function parser(template, artifacts, state) |
| 63 | + template = template or "" |
| 64 | + ---@type gp.Macro_parser_result |
| 65 | + local result = { |
| 66 | + template = " " .. template .. " ", |
| 67 | + artifacts = artifacts or {}, |
| 68 | + state = state or {}, |
| 69 | + } |
| 70 | + logger.debug("macro parser input: " .. vim.inspect(result)) |
| 71 | + |
| 72 | + for _, macro in pairs(macros) do |
| 73 | + logger.debug("macro parser current macro: " .. vim.inspect(macro)) |
| 74 | + result = macro.parser(result) |
| 75 | + logger.debug("macro parser result: " .. vim.inspect(result)) |
| 76 | + end |
| 77 | + return result |
| 78 | + end |
| 79 | + |
| 80 | + return parser |
| 81 | +end |
| 82 | + |
| 83 | +---@param macros gp.Macro[] |
| 84 | +---@param state table |
| 85 | +---@return fun(arg_lead: string, cmd_line: string, cursor_pos: number): string[] |
| 86 | +M.build_completion = function(macros, state) |
| 87 | + ---@type table<string, gp.Macro> |
| 88 | + local map = {} |
| 89 | + for _, macro in pairs(macros) do |
| 90 | + map[macro.name] = macro |
| 91 | + state[macro.name .. "_default"] = macro.default |
| 92 | + end |
| 93 | + |
| 94 | + ---@param arg_lead string |
| 95 | + ---@param cmd_line string |
| 96 | + ---@param cursor_pos number |
| 97 | + ---@return string[] |
| 98 | + local function completion(arg_lead, cmd_line, cursor_pos) |
| 99 | + local cropped_line = cmd_line:sub(1, cursor_pos) |
| 100 | + |
| 101 | + ---@type gp.Macro_cmd_params |
| 102 | + local params = { |
| 103 | + arg_lead = arg_lead, |
| 104 | + cmd_line = cmd_line, |
| 105 | + cursor_pos = cursor_pos, |
| 106 | + cropped_line = cropped_line, |
| 107 | + } |
| 108 | + |
| 109 | + local suggestions = {} |
| 110 | + |
| 111 | + logger.debug("macro completion input: " .. vim.inspect({ |
| 112 | + params = params, |
| 113 | + state = state, |
| 114 | + })) |
| 115 | + |
| 116 | + ---@type table<string, number> |
| 117 | + local candidates = {} |
| 118 | + local cand = nil |
| 119 | + for c in cropped_line:gmatch("%s@(%S+)%s") do |
| 120 | + candidates[c] = candidates[c] and candidates[c] + 1 or 1 |
| 121 | + cand = c |
| 122 | + end |
| 123 | + logger.debug("macro completion candidates: " .. vim.inspect(candidates)) |
| 124 | + |
| 125 | + if cand and map[cand] and map[cand].triggered(params, state) then |
| 126 | + suggestions = map[cand].completion(params, state) |
| 127 | + elseif cropped_line:match("%s$") or cropped_line:match("%s@%S*$") then |
| 128 | + for _, c in pairs(macros) do |
| 129 | + if not candidates[c.name] or candidates[c.name] < c.max_occurrences then |
| 130 | + table.insert(suggestions, "@" .. c.name) |
| 131 | + end |
| 132 | + end |
| 133 | + end |
| 134 | + |
| 135 | + logger.debug("macro completion suggestions: " .. vim.inspect(suggestions)) |
| 136 | + return vim.deepcopy(suggestions) |
| 137 | + end |
| 138 | + |
| 139 | + return completion |
| 140 | +end |
| 141 | + |
| 142 | +return M |
0 commit comments