forked from taproot-wizards/bitcoin-script-hints.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinit.lua
201 lines (169 loc) · 5.96 KB
/
init.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
local parsers = require('nvim-treesitter.parsers')
local op_effects = require("bitcoin-script-hints.op-codes")
local M = {}
-- Format as: "[A, B, C], [D, E]"
local function format_state(state)
if state.error then
return "ERROR: " .. state.error
end
local main_str = "[" .. table.concat(state.main, ", ") .. "]"
local alt_str = "[" .. table.concat(state.alt, ", ") .. "]"
return main_str .. ", " .. alt_str
end
-- Show either current state or error:
local function render_hint(bufnr, namespace, row, current_state)
vim.api.nvim_buf_set_extmark(bufnr, namespace, row, 0, {
virt_text = { { " → " .. format_state(current_state), current_state.error and "ErrorMsg" or "Comment" } },
virt_text_pos = "eol",
})
end
-- Parse initial stack state from comment
local function parse_initial_state(comment)
-- Try to match both stacks first
local main_str, alt_str = comment:match("%s*(%[[^%]]*%]),%s*(%[[^%]]*%])")
-- If that fails, try to match just one stack
if not main_str then
main_str = comment:match("%s*(%[[^%]]*%])")
alt_str = "[]" -- Default empty altstack
end
-- String is invalid:
if not main_str then
return nil
end
-- Convert string representations to tables
local function parse_stack(str)
local items = {}
-- Only try to parse items if there's content between the brackets
local content = str:match("%[([^%]]*)%]")
if content and content:len() > 0 then
for item in content:gmatch("[^,%s]+") do
table.insert(items, item)
end
end
return items
end
local state = {
main = parse_stack(main_str),
-- Specifying the alt stack is optional, so we need a fallback:
alt = parse_stack(alt_str or "[]")
}
return state
end
local function handle_branch_operation(op, branch_state, current_state)
if op == "OP_IF" or op == "OP_NOTIF" then
current_state = op_effects[op](current_state)
branch_state.in_if = true
branch_state.executing = not current_state.error and current_state.branch_state.executing
return current_state, branch_state.executing
elseif op == "OP_ELSE" then
if branch_state.in_if then
branch_state.in_if = false
branch_state.in_else = true
branch_state.executing = not branch_state.executing
end
return current_state, branch_state.executing
elseif op == "OP_ENDIF" then
if branch_state.in_if or branch_state.in_else then
branch_state.in_if = false
branch_state.in_else = false
branch_state.executing = true
end
return current_state, branch_state.executing
end
return current_state, branch_state.executing
end
-- for hex values etc...
local function push_raw_value(state, raw_value)
local new_state = vim.deepcopy(state)
table.insert(new_state.main, raw_value)
return new_state
end
local function process_line(line, current_state, branch_state)
local cleaned_line = line:match("^%s*(.-)%s*$"):gsub("//.*", "") -- remove whitespace chars and rust comments
local op = cleaned_line:match("OP_%w+")
local hex_value = cleaned_line:match("0x%x+")
if op then
current_state, should_execute = handle_branch_operation(op, branch_state, current_state)
-- Only execute and show hints for operations in the active branch
if should_execute then
if op_effects[op] then
current_state = op_effects[op](current_state)
return current_state, true
else
-- Handle unknown opcodes
print("Unknown opcode: " .. op)
return current_state, false
end
end
elseif hex_value and branch_state.executing then
current_state = push_raw_value(current_state, hex_value)
return current_state, true
end
return current_state, false
end
local function process_script_content(node, bufnr, namespace)
-- Get start position:
local start_row = node:range()
local content = vim.treesitter.get_node_text(node, bufnr)
local lines = vim.split(content, "\n", { plain = true })
-- Find comment that initialises the state, example:
-- "// [A, B], [C]"
local initial_state
local op_start_line = start_row
for i, line in ipairs(lines) do
if line:match("^%s*//.*%[") then -- Matches any line starting with comment ("//") + "["
initial_state = parse_initial_state(line)
start_row = start_row + i - 1
break
end
end
if initial_state then
local current_state = initial_state
local current_line = op_start_line
-- Track if/else branches:
local branch_state = {
in_if = false,
in_else = false,
executing = true -- whether we're in a branch that should execute
}
-- Process each operation:
for i, line in ipairs(lines) do
local render_line = start_row + i - 2
local new_state, should_render = process_line(line, current_state, branch_state)
current_state = new_state
if should_render then
render_hint(bufnr, namespace, render_line, current_state)
if current_state.error then break end
end
current_line = current_line + 1
end
end
end
function M.setup()
M.namespace = vim.api.nvim_create_namespace('bitcoin-script-hints')
-- Reprocess every time we save the file or change the buffer:
vim.api.nvim_create_autocmd({ "BufEnter", "BufWrite", "InsertLeave" }, {
callback = function()
local bufnr = vim.api.nvim_get_current_buf()
if vim.bo[bufnr].filetype ~= 'rust' then return end
-- Clear existing virtual text
vim.api.nvim_buf_clear_namespace(bufnr, M.namespace, 0, -1)
-- Look for the "script!" macro:
local query = vim.treesitter.query.parse('rust', [[
(macro_invocation
macro: (identifier) @macro (#eq? @macro "script")
(token_tree) @script_content
)
]])
local parser = parsers.get_parser(bufnr)
local tree = parser:parse()[1]
local root = tree:root()
for _, node in query:iter_captures(root, bufnr, 0, -1) do
if node:type() == "token_tree" then
process_script_content(node, bufnr, M.namespace)
end
end
end
})
end
return M