CodeSnap.nvim Windows Support
Fixing a bug in the codesnap.nvim plugin for Windows, and adding a proper error for the next time it breaks.
CodeSnap.nvim on Windows 11 #
CodeSnap is an editor plugin that lets you take perfect cropped screenshots of your code. Delightful little tool. So! I wanted to add it to my Neovim config. I use lazy.nvim so the installation is supposed to be very simple (so simple it’s one line and the recommended install method in CodeSnap’s Docs). The install went fine until the setup code is run (or the plugin is loaded, both are kinda important) and then I got this crazy stack trace of errors:
Side note: getting the error was a whoooole thing. Eventually I found it but it isn’t actually an “error” in Neovim, it is a notification which in my config (LazyVim) is handled by Noice.
Things in quotes are probably from my notes:
“reminder for future jacob<leader>snhopens the notif history which is where you see the giant lua runtime error”
Error 5:16:21 PM msg_show.emsg E492: Not an editor command: CodeSnap
Error 5:16:21 PM notify.error lazy.nvim Failed to source `C:/esers/.../nvim-data/lazy/codesnap.nvim/plugin/codesnap.lua`
[string "?"]:355: nvim_exec2()[1]..C:/Users/.../nvim-data/lazy/codesnap.nvim/plugin/codesnap.lua: Vim(source):E5113: Lua chunk: ...nvim-data/lazy/codesnap.nvim/lua/codesnap/utils/path.lua:23: attempt to concatenate a nil value
stack traceback:
...nvim-data/lazy/codesnap.nvim/lua/codesnap/utils/path.lua:23: in function 'with_dir_name'
...cal/nvim-data/lazy/codesnap.nvim/lua/codesnap/module.lua:15: in main chunk
[C]: in function 'require'
...Local/nvim-data/lazy/codesnap.nvim/lua/codesnap/init.lua:3: in main chunk
[C]: in function 'require'
...a/Local/nvim-data/lazy/codesnap.nvim/plugin/codesnap.lua:1: in main chunk
[C]: in function 'nvim_exec2'
[string "?"]:355: in function 'cmd'
.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:510: in function <.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:509>
[C]: in function 'xpcall'
...ta/Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/util.lua:135: in function 'try'
.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:509: in function 'source'
.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:457: in function 'source_runtime'
.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:425: in function 'packadd'
.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:359: in function '_load'
.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:197: in function 'load'
.../nvim-data/lazy/lazy.nvim/lua/lazy/core/handler/keys.lua:131: in function <.../nvim-data/lazy/lazy.nvim/lua/lazy/core/handler/keys.lua:121>
# stacktrace:
- :355 _in_ **cmd**
Error 5:16:21 PM notify.error lazy.nvim Failed to run `config` for codesnap.nvim
.../Local/nvim-data/lazy/lazy.nvim/lua/lazy/core/loader.lua:387: loop or previous error loading module 'codesnap'
“It’s so broken help.”
I have a computer science degree so I should be able to fix this easily.
“You’ve got this jacob, larp harder and you’ll figure it out.”
I tried a few things like changing the plugin version (the docs were outdated slightly) and modifying the config more but eventually just dug into the code. I haven’t used Lua for more than simple Neovim config things in ~5 years at this point and all the Lua I was writing was in ComputerCraft so I was slow to comprehend the code I was reading. Eventually I found the line that the crash was on and investigated the function call.
“
Lua chunk: ...nvim-data/lazy/codesnap.nvim/lua/codesnap/utils/path.lua:23
I’m going to look at this file now”
Therein we find:
function path_utils.with_dir_name(path)
return path_utils.dir_name() .. path
end
“that sure looks like concatenation. now, where’s this getting called wrongly”
After reading the code a bit I had a theory on it’s purpose:
- Lazy goes to load CodeSnap by running
.../codesnap.nvim/plugin/codesnap.lua- this has
require(codesnap)at the start which then calls:
- this has
/codesnap/init.lua, which calls/codesnap/module.luawhich calls/codesnap/utils/path.lua, specificallypath_utils.with_dir_name()which tries to concatenatepath_utils.dir_name()and it’s parameter. This theory also conveniently matches the stack trace.
Looking at path_utils.dir_name(), its parameter doesn’t seem to be nil so something is up.
“maybe this is sending the wrong value. let’s try changing it holup”
Next up to test my theory I changed the code (gotta love OSS) to let me know if this is in fact the part that’s breaking. I don’t know how to see the output of print and until writing this right now I didn’t think to try making a Noice notif (that would’ve been smart and cool) so instead I wrote to a file to get my output.
I changed it to this:
function path_utils.dir_name()
local sep = path_utils.get_separator()
local pattern = "@?(.*" .. vim.pesc(sep) .. ")"
local retval = debug.getinfo(1).source:match(pattern) -- i think this is getting the current working directory or smth?
-- if that line is returning nil then show it by creating a file I can see
if retval == nil then
local file, err = io.open("THERE_IS_AN_ERROR.txt", "w")
if not file then
print("Could not open file: " .. err)
return
end
file:write("cooked unc")
file:close()
end
return retval
end
With this I found that it was indeed this function creating a nil value.
“my guess is that no path info it’s given is matching the debug getinfo(1). though it should return a table? I don’t know lua so I guess I’m learning.”
And I sure was learning :]
Next up was to test the “no matches” theory so I modified the code to show me the directory which was being printed and the matches.
function path_utils.dir_name()
local out = ""
local sep = path_utils.get_separator()
out = out .. sep .."\n"
local pattern = "@?(.*" .. vim.pesc(sep) .. ")"
out = out .. pattern .."\n"
local retval = debug.getinfo(1)
out = out .. retval .."\n"
retvall = retval.source:match(pattern) -- i think this is getting the current working directory or smth?
out = out .. retval .."\n"
-- if that line is returning nil then show it by creating a file I can see
if retval == nil then
local file, err = io.open("THERE_IS_AN_ERROR.txt", "w")
if not file then
print("Could not open file: " .. err)
return
end
file:write(out)
file:close()
end
return retval
end
“there must be a better way.”
Yup, Noice notifs probably.
After running this it was clear that the problem was a lack of regex matches, so I took a look at the pattern and the test string:
@?(.*\)
@C:/Users/.../nvim-data/lazy/codesnap.nvim/lua/codesnap/utils/path.lua
“oh- we’re checking w/the windows path separator (as we should) but lua is being dope and epic and giving us the path with unix style path separators.”
Exactly past Jacob. So I tested with Unix-style separators and it worked. Now I couldn’t find anything in the Lua docs (I didn’t look hard) about the path separators being guaranteed Unix-style, so I changed the code to first try whatever the OS uses, and on failure fallback to Unix-style. If it fails twice, it should throw a nice readable error now too. The final code looks something like this;
“fixed it.”
Okay here’s my final config too:
-- nvim/lua/plugins/codesnap.lua
return {
"mistricky/codesnap.nvim",
tag = "v2.0.5",
keys = {
{ "<leader>ts", "<cmd>CodeSnap<cr>", mode = "v", desc = "Save selected code snapshot to clipboard" },
{ "<leader>tS", "<cmd>CodeSnapSave<cr>", mode = "v", desc = "Save selected code snapshot in a file" },
},
opts = {
show_line_number = true,
highlight_color = "#ffffff20",
show_workspace = true,
snapshot_config = {
theme = "candy",
window = {
mac_window_bar = false,
shadow = {
radius = 20,
color = "#00000040",
},
margin = {
x = 0,
y = 0,
},
border = {
width = 1,
color = "#ffffff30",
},
title_config = {
color = "#ffffff",
font_family = "Pacifico",
},
},
themes_folders = {},
fonts_folders = {},
line_number_color = "#495162",
command_output_config = {
prompt = "❯",
font_family = "CaskaydiaCove Nerd Font",
prompt_color = "#F78FB3",
command_color = "#98C379",
string_arg_color = "#ff0000",
},
code_config = {
font_family = "CaskaydiaCove Nerd Font",
breadcrumbs = {
enable = true,
separator = "/",
color = "#80848b",
font_family = "CaskaydiaCove Nerd Font",
},
},
watermark = {
content = "",
font_family = "Pacifico",
color = "#ffffff",
},
background = "#00000000",
},
},
}
I checked on another windows machine w/a clean LazyVim install and the error was repeatable so!
“repo has been forked, issue created, pull request requested, and now we wait.”
Thanks for reading. Good luck with your projects o7
-- final modified code (in a box so you can copy)
function path_utils.dir_name()
local fallback_sep = "/"
local sep = path_utils.get_separator()
local pattern = "@?(.*" .. vim.pesc(sep) .. ")"
local src_path = debug.getinfo(1).source
local retval = src_path:match(pattern)
-- if regex has no matches (fails) and you're on non-unix-style system (windows), try again w/Unix-Style path sep
if (retval == nil) and (sep ~= fallback_sep) then
local new_pattern = "@?(.*" .. vim.pesc(fallback_sep) .. ")"
retval = src_path:match(new_pattern)
end
if retval == nil then
error("CodeSnap config cannot determine runtime path.", 2)
end
return retval
end