Repository: michaelrommel/nvim-silicon Branch: main Commit: 7f66bda8f60c Files: 8 Total size: 36.1 KB Directory structure: gitextract_arkdobxl/ ├── .gitignore ├── LICENSE ├── README.md ├── docs/ │ └── wsl2-clipboard.md ├── helper/ │ └── wslclipimg └── lua/ ├── nvim-silicon/ │ ├── init.lua │ └── utils.lua └── silicon/ └── init.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store tags test/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 Michael Rommel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # nvim-silicon Plugin to create code images using the external `silicon` tool. This differs from `silicon.nvim` as that plugin uses a rust binding to call directly into the silicon rust library. The plugin has been mentioned in a recent YouTube video by "Dreams of Code", titled ["Create beautiful code screenshots in Neovim. Without damaging your wrists."](https://youtu.be/ig_HLrssAYE?si=R2OXs7EgcLZ8dj6r) Thank you, Elliott, for the kind words! ## Features Right now, the plugin supports most options, that the original `silicon` tool offers. For watermarking an image, you can possibly use a background image with a watermark at the top/bottom edge. This implementation supports selected line ranges, also highlighting of a line and removing superfluous indents and adding consistent padding or a separator between the numbers and the code. Also now it is possible to configure an output file AND the clipboard as destinations, the code will then call silicon possibly twice. For WSL2 users, a helper script is provided that copies the code screenshot to the Windows clipboard, see explanation below and in the separate document in the `docs` folder. Example code image: ![Example code image](https://raw.githubusercontent.com/michaelrommel/nvim-silicon/main/assets/2024-03-01T20-33-20_code.png) The typeface used in this example is called "Victor Mono" from Rune Bjørnerås. You can find it [here](https://rubjo.github.io/victor-mono/). Please consider using this typeface and sponsor Rune as well. Not visible in the screenshot, my go-to terminal emulator is [Wezterm](https://wezfurlong.org/wezterm/index.html). Please show him some love, too. ### Ranges If a range is visually selected it does not matter, whether it is block, line or normally selected. The range is then taken as complete lines: from the line in which the selection starts up to the line in which the selection ends. If no selection is made, the whole file is taken as input. If you only want to select a single line, then you would have to manually select it with `Shift-V`. ### Highlighting You can mark a single line as to be highlighted using the standard vim `mark` command with the mark `h`, the default key combination would be `mh`. ### Colours and background image You can define either your own solid background colour or provide the path to a background image, setting both is not supported by `silicon` itself. The default colour setting for the shadow colour has also now been removed to let you consistently decide, which colour you want on both accounts. ### Gobbling and padding With the `gobble` parameter set to true, the longest common set of leading whitespace in each line is removed, making it easy to share screenshots of code fragments deep down in a nested structure. However, after removing all that whitespace, you now have the option to insert arbitrary characters between the line number rendered by `silicon` and the code fragment. Since you can use any string, you can - apart from padding blanks - also insert vertical bars etc. ```lua num_separator = "\u{258f} ", ``` ### Language options The underlying `silicon` command uses the rust `syntect` create for syntax detection and highlighting along with some heuristics. This plugin used the `vim.bo.filetype` as `--language` argument but users reported that some filetypes are not recognized, e.g. fortran. Therefore - in order not to break existing configs - now the following methods are used: - if the users set the `language` option in their config, this is used verbatim - if none is set, first the argument `--language ` is used as before, but if the `silicon` execution errors out, then - the file's extension is used as `--language ` argument in a second attempt This change hopefully does not break s.b. config but improves the chances of getting an image. ### silicon's own config files `silicon` has the option of using an own config file, usually located at `${HOME}/.config/silicon/config`, but you can find out the location on your system with `silicon --config-file`. There common options can be defined, but the problem is, that command line arguments that `nvim-silicon` supplies and the same arguments in the config file lead to errors. Now in order to have both worlds, there is now a `disable_defaults` option. This will then only set the command argument. Nothing is added, so if a mandatory option like output destination selection or language is not given either in the config file or the options table, there likely is an error to be expected. So now you can split your arguments between the silicon config file and the neovim lua opts table, depending for instance on how you synchronize your configs across computers. Note that still conflicting arguments in both locations, like `background` and `background_image` still have to be avoided. Examples: `~/.config/silicon/config`: ```text --output="./code.png" --language="javascript" --background="#00ff00" --pad-horiz=10 --pad-vert=5 ``` with `nvim-silicon.lua` for the `lazy` package manager: ```lua -- create code images local opts = { "michaelrommel/nvim-silicon", lazy = true, cmd = "Silicon", opts = { disable_defaults = true } } return opts ``` will render any file with `javascript` syntax highlighting in a file named `./code.png`. ### Integrating with the Windows Subsystem for Linux There are now two new options `wslclipboard` and `wslclipboardcopy`, which allow to send code images to the Windows clipboard, even though `nvim` runs inside the WSL, and without installing an X server on Windows and `xclip` on Linux. The complete rabbit hole journey of this endeavor deserved it's own [description](./docs/wsl2-clipboard.md) ### Lua Keybindings There was the wish to be able to call directly lua functions for triggering the code images. There are now two entry points available - `.shoot()`: creates a code image with the default settings - `.file()`: saves the generated code image only into a file - `.clip()`: puts the generated code image only onto the clipboard They can be used with `which-key`, for example, like this: ```lua local wk = require("which-key") wk.add({ mode = { "v" }, { "s", group = "Silicon" }, { "sc", function() require("nvim-silicon").clip() end, desc = "Copy code screenshot to clipboard" }, { "sf", function() require("nvim-silicon").file() end, desc = "Save code screenshot as file" }, { "ss", function() require("nvim-silicon").shoot() end, desc = "Create code screenshot" }, }) ``` Calling the `.shoot()` function behaves normal, just like calling the `:Silicon` vim command would behave, see the explanation of enabling both file and clipboard destinations below. On the other hand the `.file()` and `.clip()` functions were thought of as additional, overriding functions, that would en-/disable the `--to-clipboard` and en-/disable the `--output` settings, respectively, for this one invocation. Because here we manipulate the options table, these new functions may not work well with using a `silicon`-config file as described above. We do not know the settings in that file and the overriding mechanisms may cause conflicting command line and config file settings. ### Setting multiple destinations There was a request to have the option to generate an image file but also put the image onto the clipboard. Since the `silicon` command only supports an either/or setting, this means we need two calls to `silicon`. Up to now, the options were 1:1 converted into `silicon` arguments and if you had both `--to-clipboard` and `--output` set, `silicon` would throw an error. Now the plugin interprets this combination as "I want both!" and calls `silicon` twice, one time with the `--output` option and one time with the `--to-clipboard` option. In case the WSL integration is used, the `wslclipboardcopy` option steers, whether to keep or delete the possibly superfluous file. ## Setup With the `lazy.nvim` package manager: ```lua { "michaelrommel/nvim-silicon", lazy = true, cmd = "Silicon", main = "nvim-silicon", opts = { -- Configuration here, or leave empty to use defaults line_offset = function(args) return args.line1 end, } }, ``` **Please note:** When I created this plugin, I hadn't been fully aware of the namespaces that all plugins create. So I named the lua directory differently than the plugin name. In order to avoid name clashes with other modules, I have decided now to move from `require("silicon)` to `require("nvim-silicon)`. If you use the old name, a small message will be appended to the output of a successful call. (In a future version a deprecation warning will then show and when you look at `:messages` you should be able to find the place where the deprecated `require()` statements are and convert them. Most likely in the package manager or a key mappings configuration file.) The `setup` function accepts the following table (shown with the builtin defaults, I have selected the defaults in a way, that they should work out of the box on most systems, please customize to your preference. In particular I removed the default output and font settings in order to enable using only the clipboard and make it work for users, who do not have Victor Mono NerdFont installed): ```lua { -- disable_defaults will disable all builtin default settings apart -- from the base arguments, that are needed to call silicon at all, see -- mandatory_options below, also those options can be overridden -- all of the settings could be overridden in the lua setup call, -- but this clashes with the use of an external silicon --config=file, -- see issue #9 disable_defaults = false, -- turn on debug messages debug = false, -- most of them could be overridden with other -- the font settings with size and fallback font -- Example: font = "VictorMono NF=34;Noto Emoji", font = nil -- the theme to use, depends on themes available to silicon theme = "gruvbox-dark", -- the background color outside the rendered os window -- (in hexcode string e.g "#076678") background = nil, -- a path to a background image background_image = nil, -- the paddings to either side pad_horiz = 100, pad_vert = 80, -- whether to have the os window rendered with rounded corners no_round_corner = false, -- whether to put the close, minimize, maximise traffic light -- controls on the border no_window_controls = false, -- whether to turn off the line numbers no_line_number = false, -- with which number the line numbering shall start line_offset = 1, -- here a function is used to return the actual source code line number -- line_offset = function(args) -- return args.line1 -- end, -- the distance between lines of code line_pad = 0, -- the rendering of tab characters as so many space characters tab_width = 4, -- with which language the syntax highlighting shall be done, should be -- a function that returns either a language name or an extension like "js" -- it is set to nil, so you can override it, if you do not set it, we try the -- filetype first, and if that fails, the extension language = nil -- language = function() -- return vim.bo.filetype -- end, -- language = function() -- return vim.fn.fnamemodify( -- vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf()), -- ":e" -- ) -- end, -- if the shadow below the os window should have be blurred shadow_blur_radius = 16, -- the offset of the shadow in x and y directions shadow_offset_x = 8, shadow_offset_y = 8, -- the color of the shadow (in hexcode string e.g "#100808") shadow_color = nil, -- whether to strip of superfluous leading whitespace gobble = true, -- a string to pad each line with after gobbling removed larger indents, num_separator = nil, -- here a bar glyph is used to draw a vertial line and some space -- num_separator = "\u{258f} ", -- whether to put the image onto the clipboard, may produce an error, -- if run on WSL2 to_clipboard = false, -- a string or function returning a string that defines the title -- showing in the image, only works in silicon versions greater than v0.5.1 window_title = nil, -- here a function is used to get the name of the current buffer -- window_title = function() -- return vim.fn.fnamemodify( -- vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf()), -- ":t" -- ) -- end, -- how to deal with the clipboard on WSL2 -- possible values are: never, always, auto wslclipboard = nil, -- what to do with the temporary screenshot image file when using the Windows -- clipboard from WSL2, possible values are: keep, delete wslclipboardcopy = nil, -- the silicon command, put an absolute location here, if the -- command is not in your ${PATH} command = "silicon", -- a string or function that defines the path to the output image output = nil -- here a function is used to create a file in the current directory -- output = function() -- return "./" .. os.date("!%Y-%m-%dT%H-%M-%SZ") .. "_code.png" -- end, } ``` The mandatory options, that are used, even when the option `disable_defaults` is set to true are: ```lua -- without that silicon cannot run. But you can override the command -- option in your lua config M.mandatory_options = { command = 'silicon', } ``` ================================================ FILE: docs/wsl2-clipboard.md ================================================ # WSL2 and the Windows Clipboard ## Motivation When working inside the WSL2 it is sometimes preferable to get a code snapshot directly in the Windows clipboard to paste it in Teams or Mail or share it in another way. The `silicon` tool itself can directly copy a generated image to the clipboard on all platforms (Linux, Windows, Mac) but it can only do so when run on that platform itself, not in a subsystem like the WSL. Based on your feedback and my own desire to come up with a solution, I researched several ways to achieve this and here I outline my solution and the reasons behind several decisions. It may help others when facing similar problems. ## Navigating the solution space ### No-Go: direct copy to the Windows Clipboard Since WSL runs Linux, the `silicon` version on that platform tries to finde the `xclip` tool in order to copy the generated image to the X Clipboard. Which means running an X server on the Windows side and exposing that to the WSL. Which is pretty bloated and cumbersome to set up and probably not something a vi user wants to have. So I have pretty much decided that this is not the direction I would like to take. ### Second choice: External 3rd Party Programs There are numerous tools available including Windows' own `clip.exe` or `wslclip` programs that can put text onto the clipboard, but very few, that can actually handle images. In my search, I found two tools: 1. [NirCmd](http://www.nirsoft.net/utils/nircmd.html) 2. [image-clipboard](https://github.com/bamontelucas/image-clipboard) Both tools work as intended and can take an image file as input and put it onto the Windows clipboard. However that requires the user to download and install a binary program on their systems, which is always coming with an inherent risk, it is another step to perform and may be outright forbidden in your place of work. ### Preferred: Powershell Script Since powershell provides a lot of commandlets and libraries as standard, there are native ways to put an image file on the Windows clipboard. The hurdles to overcome here are mainly two: 1. the Script ExecutionPolicy that might be set 2. the path semantics on Windows and Linux The first issue arises, if the script that is called from neovim is part of the lua package installation and resides on the Linux side. Then - depending on the policy - loading a WSL-path script into the Windows powershell.exe, the WSL is seen as "remote" and might be blocked if the Policy is set to "RemoteSigned". It can be tackled by calling the `powershell.exe` interpreter with a script path that is local to the Windows side, but this is also then another step to copy/move that powershell script to Windows and adapt a Linux script with the new path to the installation. It for sure can be done, but to have to do it on every machine, where you sync your dotfiles to is a hassle, that I'd like to avoid. The second issue is, that the generated image file from `silicon` most likely resides on the Linux side and the `powershell.exe` interpreter needs to see it from windows. Since the Windows perceived path contains the name of the distribution/installation of the WSL instance, it should not be a fixed value. ## Solution The solution I finally arrived at does also come with inconveniences, albeit hopefully some, that most users can live with, because they might already be fulfilled as part of a minimal installation. ### Prerequisites You need to have the `wslpath` tool available and in your path. It is by default a symlink from `/usr/bin/wslpath` to `/init` in a WSL2 installation, there should not be a need for any installation. Also the `powershell.exe` interpreter should be in your path, which is typically taken over from the Windows System Environment Variables setting of your Windows installation (or you would need to adapt the helper script accordingly, see below). ### How it works The idea is to provide a new configuration option `wslclipboard` that steers, how `nvim-silicon` should interact with the clipboard if the clipboard has been selected as destination. Several values are available: - `never`: never try to make special provisions to copy to the Windows clipboard. This essentially means, that the usual Linux way used by `silicon` (via `xclip`) is always used. `nil` is regarded as `never`. - `always`: unconditionally use the provided mechanism to first create a file based screenshot on Linux and then push this image onto the Windows clipboard - `auto`: detect that nvim is running under WSL by looking for the string "WSL" in the output of the `uname -r` command, e.g. "5.15.146.1-microsoft-standard-WSL2". If WSL is detected, then use the provided mechanism, otherwise keep the `silicon` standard. Since we cannot access the Windows clipboard directly, we have to construct an image file first. This will be put in the location specified by the `output` opts key. If this is `nil` because you always only wanted the images to be placed on the clipboard or you called the new `.clip()` function, a temporary file will be created in `/tmp/_code.png`. There is a second option `wslclipboardcopy` that defines, whether to keep these temporary files or not, the values are `keep` or `delete`. `nil` is regarded as `keep`. Whenever the Windows clipboard shall be used, first this (temporary) file is created in the usual manner. Then a script `wslclipimg` is called that resides in the `helper` directory of the plugin installation with the filename of that image file as parameter. This Linux bash script contains the powershell code needed to read that file and put the contents on the Windows clipboard. The original idea of using an EncodedCommand was misleading, because of a [bug in Powershell](https://github.com/PowerShell/PowerShell/issues/5912). Now the helper script passes the script as text, explicitly setting the ExecutionPolicy to Bypass. The path to the helper script is determined automatically based on the installation path of the plugin. If that breaks, please turn on `debug` in your config and let me know the output, so that it can be improved. ```bash #! /usr/bin/env bash # we need to get the path to the WSL located file as it would be accessed # by the windows side. wslpath should be a symlink to /init on a standard WSL2 # installation IMG=$(wslpath -w "$1") SCRIPT=$(cat << EOF Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.Windows.Forms [Windows.Forms.Clipboard]::SetImage(\$([System.Drawing.Image]::Fromfile(\$(Get-Item "${IMG}")))); EOF ) # powershell.exe should be on your path, otherwise specify the complete path to the # interpreter, like /mnt/c/Windows/system32/WindowsPowerShell/v1.0/powershell.exe echo "${SCRIPT}" | powershell.exe -NoProfile -NoLogo -InputFormat text -OutputFormat text -NonInteractive -ExecutionPolicy Bypass -Command - ``` If you have suggestions, how this could be even more simplified, please let me know. ================================================ FILE: helper/wslclipimg ================================================ #! /usr/bin/env bash # we need to get the path to the WSL located file as it would be accessed # by the windows side. wslpath should be a symlink to /init on a standard WSL2 # installation IMG=$(wslpath -w "$1") SCRIPT=$( cat < 0 then local hl if args.range == 0 or (args.line1 and marks >= begin_line and marks <= finish_line) then hl = marks - begin_line table.insert(cmdline, "--highlight-lines") table.insert(cmdline, hl) end end local lines = vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(0), begin_line, finish_line, false) if options.gobble then lines = M.utils.gobble(lines) end if options.num_separator then lines = M.utils.separate(lines, options.num_separator) end if options.debug then print("lines to shoot: " .. M.utils.dump(lines)) end return lines, cmdline end M.cmd = function(args, options) local lines = nil local cmdline = nil -- build the commandline based on supplied options local base_cmdline = M.get_arguments(args, options) -- parse buffer into lines, based on arguments from neovim, reshapes cmdline lines, base_cmdline = M.format_lines(base_cmdline, args, options) local ret = {} local code -- if a language was supplied by the user, take that as argument directly if options.language then if type(options.language) == "function" then ret.language = options.language() else ret.language = options.language end cmdline = vim.tbl_extend("error", base_cmdline, {}) table.insert(cmdline, '--language') table.insert(cmdline, ret.language) if options.debug then print("cmd cmdline: " .. M.utils.dump(cmdline)) end code = vim.fn.system(cmdline, lines) code = string.gsub(code, "\n", "") ret.code = code else if options.disable_defaults then -- run silicon as is, no supplement of anything if options.debug then print("base_cmdline: " .. M.utils.dump(base_cmdline)) end code = vim.fn.system(base_cmdline, lines) code = string.gsub(code, "\n", "") ret.language = nil ret.code = code else -- try first the language parameter derived from the buffer's filetype cmdline = vim.tbl_extend("error", base_cmdline, {}) ret.language = vim.bo.filetype table.insert(cmdline, '--language') table.insert(cmdline, ret.language) if options.debug then print("1. lang cmdline: " .. M.utils.dump(cmdline)) end code = vim.fn.system(cmdline, lines) code = string.gsub(code, "\n", "") ret.code = code if options.debug then print("returncode: " .. M.utils.dump(code)) end if code ~= "" then vim.notify( "silicon call with filetype error: " .. code .. ", trying extension...", vim.log.levels.WARN, { title = "nvim-silicon" } ) -- seems to have gone wrong, new try with extension cmdline = vim.tbl_extend("error", base_cmdline, {}) ret.language = vim.fn.fnamemodify( vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf()), ":e" ) table.insert(cmdline, '--language') table.insert(cmdline, ret.language) if options.debug then print("2. lang cmdline: " .. M.utils.dump(cmdline)) end code = vim.fn.system(cmdline, lines) code = string.gsub(code, "\n", "") ret.code = code end end end -- last, final attempt being evaluated if code ~= "" then vim.notify( "silicon returned with: " .. code, vim.log.levels.WARN, { title = "nvim-silicon" } ) else if not M.message then M.message = "" end if options.to_clipboard then vim.notify( "silicon put the image on the clipboard." .. M.message, vim.log.levels.INFO, { title = "nvim-silicon" } ) else local get_location = function() local location = nil if not M.filename then location = "the location specified in your config file" elseif string.sub(tostring(M.filename), 1, 1) == "~" then location = M.filename elseif string.sub(tostring(M.filename), 1, 2) == "./" then location = vim.fn.getcwd() .. string.sub(tostring(M.filename), 2) else -- location = vim.fn.getcwd() .. "/" .. M.filename location = M.filename end return location end ret.location = get_location() vim.notify( "silicon generated an image at " .. ret.location .. "." .. M.message, vim.log.levels.INFO, { title = "nvim-silicon" } ) end end return ret end M.start = function(args, opts) local options -- stores the error code in case silicon returns something local code -- stores the return value of the call to create a file local ret = nil if opts.debug then print("Global options: " .. M.utils.dump(M.options)) print("Local options: " .. M.utils.dump(opts)) end -- make a deep copy of the original options options = vim.tbl_deep_extend( "force", opts, {} ) if (not opts.output) and (not opts.to_clipboard) and (not opts.disable_defaults) then -- the user has not supplied any valid destination and not disabled defaults -- so add the default output file destination function that we used before if opts.debug then print("setting default output function") end -- temporary marker to create a temporary output, note this assignment changes -- the supplied opts table for subsequent calls, so make sure not to submit the -- global options table to the function. Needs a refactor w/r to opts/options opts.output = true -- the actual output destination options.output = function() return "./" .. os.date("!%Y-%m-%dT%H-%M-%SZ") .. "_code.png" end end -- if wished for, let's create the file first if opts.output then options.to_clipboard = false ret = M.cmd(args, options) end local function shell_exists(name) local f = io.open(name, "r") if f ~= nil then io.close(f) return true else return false end end if opts.to_clipboard then -- check whether wsl detection shall be done if (opts.wslclipboard == "auto" and M.utils.is_wsl()) or (opts.wslclipboard == "always") then -- we want to use the WSL integration local cmdline = {} -- check which shell to use here if (shell_exists("/bin/bash")) then table.insert(cmdline, "/bin/bash") elseif (shell_exists("/usr/bin/bash")) then table.insert(cmdline, "/usr/bin/bash") elseif (shell_exists("/usr/local/bin/bash")) then table.insert(cmdline, "/usr/local/bin/bash") else table.insert(cmdline, "/bin/sh") end table.insert(cmdline, M.helper) if ret and ret.location then -- we have already a file, need to send it to the windows side table.insert(cmdline, ret.location) else -- we need to create a temporary file options.output = "/tmp/" .. os.date("!%Y-%m-%dT%H-%M-%SZ") .. "_code.png" options.to_clipboard = false ret = M.cmd(args, options) if ret and ret.location then -- now we have a file, need to send it to the windows side table.insert(cmdline, ret.location) else -- notify user that the tmp image generation failed vim.notify( "silicon returned with: " .. ret.code, vim.log.levels.WARN, { title = "nvim-silicon" } ) return end end if opts.debug then print("start cmdline: " .. M.utils.dump(cmdline)) end code = vim.fn.system(cmdline) code = string.gsub(code, "\n", "") if code ~= "" then vim.notify( "wslclipimg returned with: " .. code, vim.log.levels.WARN, { title = "nvim-silicon" } ) else vim.notify( "wslclipimg put the image at " .. ret.location .. " onto the clipboard", vim.log.levels.INFO, { title = "nvim-silicon" } ) end -- file based outp[ut was not desired, so we created a tmp file -- we need to check opts and not options here if (not opts.output) and (opts.wslclipboardcopy == "delete") then -- we should clean that tmp file now local _, err = os.remove(ret.location) if err then vim.notify( "wslclipimg could not delete the tmp file: " .. err, vim.log.levels.WARN, { title = "nvim-silicon" } ) end end else -- we want the standard way of putting an image onto the clipboard if ret and ret.code == "" and ret.language then -- we already know which language works options.language = ret.language end options.output = nil options.to_clipboard = true ret = M.cmd(args, options) end end end M.shoot = function(opts) local options -- we get overridden options, if we are called from -- .clip() or .file() if opts then -- make a deep copy of the original options options = vim.tbl_deep_extend( "force", opts, {} ) else -- make a deep copy of the original options options = vim.tbl_deep_extend( "force", M.options, {} ) end local args = nil local mode = vim.api.nvim_get_mode().mode if mode == "n" then args = { line1 = 0, line2 = -1, range = 0 } elseif mode == "v" or mode == "V" or mode == "\22" then local line1 = vim.fn.getpos("v")[2] local line2 = vim.api.nvim_win_get_cursor(0)[1] if line1 > line2 then local linetmp = line1 line1 = line2 line2 = linetmp end args = { line1 = line1, line2 = line2, range = 1 } end if M.options.debug then print("mode is: " .. mode) print("args: " .. M.utils.dump(args)) end M.start(args, options) end M.file = function() local options -- make a deep copy of the original options options = vim.tbl_deep_extend( "force", M.options, {} ) options.to_clipboard = false -- if not options.output then -- print("You triggered the .file() function, but forgot to set an output path") -- options.output = "/tmp/" .. os.date("!%Y-%m-%dT%H-%M-%SZ") .. "_code.png" -- end M.shoot(options) end M.clip = function() local options -- make a deep copy of the original options options = vim.tbl_deep_extend( "force", M.options, {} ) options.to_clipboard = true options.output = nil M.shoot(options) end M.usercmd = function(args) local options -- make a deep copy of the original options options = vim.tbl_deep_extend( "force", M.options, {} ) M.start(args, options) end M.setup = function(opts) -- populate the global options table M.options = M.parse_options(opts) -- find my own path M.helper = M.get_helper_path() if M.options.debug then print("helper is at: " .. M.helper) end -- define commands for neovim vim.api.nvim_create_user_command("Silicon", function(args) M.usercmd(args) end, { desc = "convert range to code image representation", force = false, range = true }) end return M ================================================ FILE: lua/nvim-silicon/utils.lua ================================================ local M = {} M.gobble = function(lines) local shortest_whitespace = nil local whitespace = "" for _, v in pairs(lines) do _, _, whitespace = string.find(v, "^(%s*)") if type(whitespace) ~= "nil" then if shortest_whitespace == nil or (#whitespace < #shortest_whitespace and v ~= "") then shortest_whitespace = whitespace end end end if #shortest_whitespace > 0 then local newlines = {} for _, v in pairs(lines) do local newline = string.gsub(v, "^" .. shortest_whitespace, "", 1) table.insert(newlines, newline) end return newlines else return lines end end M.separate = function(lines, num_separator) local newlines = {} for _, v in pairs(lines) do local newline = string.gsub(v, "^", num_separator, 1) table.insert(newlines, newline) end return newlines end M.dump = function(t) local conv = { ["nil"] = function() return "nil" end, ["number"] = function(n) return tostring(n) end, ["string"] = function(s) return '"' .. s .. '"' end, ["boolean"] = function(b) return tostring(b) end, ["function"] = function(_) return "function()" end, } if type(t) == "table" then local s = "{" for k, v in pairs(t) do if type(v) == "table" then s = s .. (s == "{" and " " or ", ") .. (k .. " = " .. M.dump(v)) else s = s .. (s == "{" and " " or ", ") .. k .. " = " .. conv[type(v)](v) end end return s .. " }" else return conv[type(t)](t) end end M.is_wsl = function() local output = vim.fn.systemlist "uname -r" return not not string.find(output[1] or "", "WSL") end return M ================================================ FILE: lua/silicon/init.lua ================================================ local M = {} -- In the next release use the deprecation function to -- alert users. For now a message addendum should suffice. -- vim.deprecate( -- "require(\"silicon\")", -- "require(\"nvim-silicon\")", -- "v1.2", -- "nvim-silicon", -- true -- ) M = require('nvim-silicon') M.message = " Please use 'require(\"nvim-silicon\")' now, see README!" return M