Skip to content

Commit dc7a9e6

Browse files
committed
Start implementation of :CrazywallDry
1 parent e9680be commit dc7a9e6

File tree

2 files changed

+283
-45
lines changed

2 files changed

+283
-45
lines changed

lua/crazywall/init.lua

Lines changed: 269 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ local Context = require("core.context")
33
local streams = require("core.streams")
44
local fold = require("core.fold")
55
local utils = require("core.utils")
6+
local validate = require("crazywall.validate")
7+
local default_config = require("core.defaults.config")
68
local M = {}
79

810
local configs = {}
11+
local current_config_name = "DEFAULT"
912

1013
local output_options = { "both", "planonly", "textonly" }
1114
local on_unsaved_options = { "warn", "write" }
@@ -15,7 +18,7 @@ local err_buf = nil
1518

1619
---@param path string?
1720
---@return number?
18-
local function find_buffer(path)
21+
local find_buffer = function(path)
1922
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
2023
local bufname = vim.api.nvim_buf_get_name(buf_id)
2124
if bufname == path then
@@ -25,6 +28,90 @@ local function find_buffer(path)
2528
return nil
2629
end
2730

31+
---@param path string
32+
---@param text string
33+
---@return string? errmsg
34+
local write = function(path, text)
35+
local dir = vim.fn.fnamemodify(path, ":p:h")
36+
if vim.fn.isdirectory(dir) == 0 then
37+
vim.fn.mkdir(dir, "p")
38+
end
39+
40+
local file = io.open(path, "w")
41+
if file then
42+
local _, err = file:write(text)
43+
file:close()
44+
if err then
45+
return err
46+
end
47+
return
48+
end
49+
return "Unable to write to " .. path
50+
end
51+
52+
---@return string
53+
local get_plan_path = function()
54+
return string.format(
55+
"%s/crazywall/plan-%s.txt",
56+
vim.fn.stdpath("data"),
57+
os.time()
58+
)
59+
end
60+
61+
---@param lines string[]
62+
---@return number? buf_id
63+
---@return string? errmsg
64+
local display_floating_window = function(lines)
65+
local plan_path_prefix =
66+
string.format("%s/crazywall/plan-", vim.fn.stdpath("data"))
67+
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
68+
local buf_name = vim.api.nvim_buf_get_name(buf_id)
69+
if utils.str.starts_with(buf_name, plan_path_prefix) then
70+
vim.api.nvim_buf_delete(buf_id, { force = true })
71+
end
72+
end
73+
local buf_id = vim.api.nvim_create_buf(false, true)
74+
if not buf_id then
75+
return nil, "Failed to create buffer"
76+
end
77+
vim.api.nvim_buf_set_name(buf_id, get_plan_path())
78+
vim.api.nvim_buf_set_option(buf_id, "buftype", "")
79+
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
80+
local editor_height = vim.api.nvim_get_option("lines")
81+
local editor_width = vim.api.nvim_get_option("columns")
82+
local win_height = math.min(math.floor(editor_height * 0.75), #lines)
83+
local longest_line_length = (function()
84+
local res = 0
85+
for _, line in ipairs(lines) do
86+
res = math.max(res, #line)
87+
end
88+
return res
89+
end)()
90+
local win_width =
91+
math.min(math.floor(editor_width * 0.75), longest_line_length)
92+
local row = math.floor((editor_height - win_height) / 2)
93+
local col = math.floor((editor_width - win_width) / 2)
94+
95+
local win = vim.api.nvim_open_win(buf_id, true, {
96+
relative = "editor",
97+
width = win_width,
98+
height = win_height,
99+
row = row,
100+
col = col,
101+
border = "rounded",
102+
style = "minimal",
103+
})
104+
if not win then
105+
return nil, "Failed to open floating window."
106+
end
107+
vim.api.nvim_buf_set_option(buf_id, "modifiable", false)
108+
109+
local default_bg = vim.api.nvim_get_hl(0, { name= 'Normal'}).background
110+
vim.api.nvim_set_hl(0, 'NormalFloat', { bg = default_bg })
111+
112+
return buf_id
113+
end
114+
28115
---@param err_text string?
29116
local display_err = function(err_text)
30117
local err = err_text and utils.str.split_lines_to_list(err_text or "") or {}
@@ -33,6 +120,8 @@ local display_err = function(err_text)
33120
end
34121
local total_height = vim.api.nvim_get_option("lines") -- Total height of the Neovim window
35122
local split_height = math.floor(total_height / 3)
123+
split_height = math.min(#err, split_height)
124+
split_height = math.max(split_height, 3)
36125
vim.cmd("botright new")
37126
local win_id = vim.api.nvim_get_current_win()
38127
err_buf = vim.api.nvim_get_current_buf()
@@ -43,32 +132,91 @@ local display_err = function(err_text)
43132
vim.api.nvim_buf_set_option(err_buf, "modifiable", false)
44133
end
45134

135+
---@param buf_id integer?
136+
---@param ctx Context
137+
---@return Plan? plan
138+
---@return string? errmsg
139+
local do_fold = function(buf_id, ctx)
140+
local root, err = fold.parse(ctx)
141+
if not root then
142+
return nil, err
143+
end
144+
145+
_, err = fold.prepare(root, ctx)
146+
if err then
147+
return nil, err
148+
end
149+
150+
local plan
151+
plan, err = fold.execute(root, ctx, true)
152+
if not plan then
153+
return nil, err
154+
end
155+
156+
if ctx.is_dry_run then
157+
return plan
158+
end
159+
160+
plan, err = fold.execute(root, ctx, false)
161+
if err then
162+
error(err)
163+
end
164+
165+
if buf_id then
166+
vim.api.nvim_buf_call(buf_id, function()
167+
vim.cmd("edit")
168+
end)
169+
end
170+
171+
return plan
172+
end
173+
174+
---@param buf_id integer?
175+
---@param write_on_unsaved boolean
176+
---@return boolean
177+
local handle_unsaved = function(buf_id, write_on_unsaved)
178+
if not buf_id then
179+
return true
180+
end
181+
if not vim.api.nvim_buf_get_option(buf_id, "modified") then
182+
return true
183+
end
184+
if write_on_unsaved then
185+
vim.api.nvim_buf_call(buf_id, function()
186+
vim.cmd("write")
187+
end)
188+
return true
189+
end
190+
vim.api.nvim_err_writeln("crazywall: Buffer has unsaved changes.")
191+
return false
192+
end
193+
46194
vim.api.nvim_create_user_command("CrazywallQuick", function(opts)
47-
local on_unsaved = opts.fargs[2] or "warn"
48-
local src_path = opts.fargs[3] or vim.fn.expand("%")
49-
local dest_path = opts.fargs[4] or vim.fn.expand("%")
195+
local on_unsaved = opts.fargs[1] or "warn"
196+
local err = validate.string_in_list(on_unsaved, on_unsaved_options)
197+
if err then
198+
vim.api.nvim_err_writeln(err)
199+
return
200+
end
50201

51-
local buf_id = find_buffer(src_path)
202+
local src_path = opts.fargs[2] or vim.fn.expand("%")
203+
local dest_path = opts.fargs[3] or vim.fn.expand("%")
52204

53-
if
54-
buf_id
55-
and on_unsaved == "warn"
56-
and vim.api.nvim_buf_get_option(buf_id, "modified")
57-
then
58-
vim.api.nvim_err_writeln("crazywall: Buffer has unsaved changes.")
205+
local buf_id = find_buffer(src_path)
206+
if not handle_unsaved(buf_id, on_unsaved == "write") then
59207
return
60-
else
61-
if buf_id and vim.api.nvim_buf_get_option(buf_id, "modified") then
62-
vim.api.nvim_buf_call(buf_id, function()
63-
vim.cmd("write")
64-
end)
65-
end
66208
end
67209

68-
local config, err = Config:new({})
210+
if configs[current_config_name] == nil then
211+
return vim.api.nvim_err_writeln(
212+
"crazywall: Could not find config " .. current_config_name .. "."
213+
)
214+
end
215+
216+
local config
217+
config, err = Config:new(configs[current_config_name])
69218
if not config then
70-
display_err(err)
71-
return
219+
return display_err(err)
72220
end
73221

74222
local ctx
@@ -87,52 +235,120 @@ vim.api.nvim_create_user_command("CrazywallQuick", function(opts)
87235
)
88236

89237
if not ctx then
90-
display_err(err)
91-
return
238+
return display_err(err)
92239
end
93240

94-
local root
95-
root, err = fold.parse(ctx)
96-
if not root then
97-
display_err(err)
98-
return
241+
local plan
242+
plan, err = do_fold(buf_id, ctx)
243+
if not plan then
244+
return display_err(err)
99245
end
100246

101-
_, err = fold.prepare(root, ctx)
247+
local plan_path = get_plan_path()
248+
err = write(plan_path, tostring(plan))
249+
if err then
250+
return display_err(err)
251+
end
252+
253+
print("crazywall: Logs written to " .. plan_path)
254+
end, {
255+
nargs = "*",
256+
complete = function(_, line)
257+
local args = vim.split(line, " ")
258+
if #args == 2 then
259+
return on_unsaved_options
260+
end
261+
if #args == 3 then
262+
return { vim.fn.expand("%") }
263+
end
264+
if #args == 4 then
265+
return { vim.fn.expand("%") }
266+
end
267+
return {}
268+
end,
269+
desc = "Folds a file with crazywall, skipping the confirmation window.",
270+
})
271+
272+
vim.api.nvim_create_user_command("CrazywallDry", function(opts)
273+
local output_style = opts.fargs[1] or "both"
274+
local on_unsaved = opts.fargs[2] or "warn"
275+
local err = validate.string_in_list(on_unsaved, on_unsaved_options)
276+
or validate.string_in_list(output_style, output_options)
102277
if err then
103-
display_err(err)
278+
vim.api.nvim_err_writeln(err)
104279
return
105280
end
106281

107-
local plan
108-
plan, err = fold.execute(root, ctx, true)
109-
if not plan then
110-
display_err(err)
282+
local src_path = opts.fargs[3] or vim.fn.expand("%")
283+
local dest_path = opts.fargs[4] or vim.fn.expand("%")
284+
285+
local buf_id = find_buffer(src_path)
286+
if not handle_unsaved(buf_id, on_unsaved == "write") then
111287
return
112288
end
113289

114-
plan, err = fold.execute(root, ctx, false)
290+
if configs[current_config_name] == nil then
291+
return vim.api.nvim_err_writeln(
292+
"crazywall: Could not find config " .. current_config_name .. "."
293+
)
294+
end
295+
296+
local config
297+
config, err = Config:new(configs[current_config_name])
298+
if not config then
299+
return display_err(err)
300+
end
301+
302+
local ctx
303+
ctx, err = Context:new(
304+
config,
305+
src_path,
306+
dest_path,
307+
vim.fn.readfile(src_path),
308+
nil,
309+
true,
310+
false,
311+
(output_style == "plan-only" or output_style == "both")
312+
and streams.STDOUT
313+
or streams.NONE,
314+
(output_style == "text-only" or output_style == "both")
315+
and streams.STDOUT
316+
or streams.NONE,
317+
true,
318+
false
319+
)
320+
321+
if not ctx then
322+
return display_err(err)
323+
end
324+
325+
local plan
326+
plan, err = do_fold(buf_id, ctx)
115327
if err then
116-
error(err)
328+
return display_err(err)
117329
end
118330

119-
if buf_id then
120-
vim.api.nvim_buf_call(buf_id, function()
121-
vim.cmd("edit")
122-
end)
331+
_, err =
332+
display_floating_window(utils.str.split_lines_to_list(tostring(plan)))
333+
if err then
334+
return nil, err
123335
end
124-
print("Done!")
336+
337+
vim.cmd('setlocal nowrap')
125338
end, {
126-
nargs = "*", -- Expect exactly 2 arguments
339+
nargs = "*",
127340
complete = function(_, line)
128341
local args = vim.split(line, " ")
129342
if #args == 2 then
130343
return output_options
131-
elseif #args == 3 then
344+
end
345+
if #args == 3 then
132346
return on_unsaved_options
133-
elseif #args == 4 then
347+
end
348+
if #args == 4 then
134349
return { vim.fn.expand("%") }
135-
elseif #args == 5 then
350+
end
351+
if #args == 5 then
136352
return { vim.fn.expand("%") }
137353
end
138354
return {}
@@ -142,7 +358,16 @@ end, {
142358

143359
M.setup = function(opts)
144360
opts = opts or {}
361+
local keys = { "configs", "default_config_name" }
362+
for key in pairs(opts) do
363+
local err = validate.string_in_list(key, keys)
364+
if err then
365+
error(err)
366+
end
367+
end
145368
configs = opts.configs or configs
369+
configs.DEFAULT = configs.DEFAULT or default_config
370+
current_config_name = opts.default_config_name or current_config_name
146371
end
147372

148373
M.foo = 42

0 commit comments

Comments
 (0)