Skip to content

Commit

Permalink
feat: process cmd (#55)
Browse files Browse the repository at this point in the history
* feat: add process_image function

* feat: use process_iamge function when pasting

* feat: process image before embedding as base64 when pasting from clipboard

* feat(wsl): support image processing

* fix: remove opts parameter

* refactor: clean up process_image function

* feat(base64): apply process command for images from files

* fix: luacheck

* feat(clipboard): process image when pasting from clipboard

* docs: add process_cmd option

* fix: luacheck line too long error
  • Loading branch information
HakonHarnes authored Mar 24, 2024
1 parent 2dc1d02 commit 3924214
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 20 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ The plugin comes with the following defaults:
default = {
dir_path = "assets", -- directory path to save images to, can be relative (cwd or current file) or absolute
file_name = "%Y-%m-%d-%H-%M-%S", -- file name format (see lua.org/pil/22.1.html)
process_cmd = "", -- shell command to process the image before saving or embedding as base64 (e.g. "convert -quality 85 - -")
url_encode_path = false, -- encode spaces and special characters in file path
use_absolute_path = false, -- expands dir_path to an absolute path
relative_to_current_file = false, -- make dir_path relative to current file rather than the cwd
Expand Down Expand Up @@ -182,6 +183,20 @@ dir_path = function()
end,
```

### Processing images

The `process_cmd` option allows you to specify a shell command to process the image before saving or embedding it as base64. The command should read the image data from the standard input and write the processed data to the standard output.

Examples:

```bash
process_cmd = "convert - -quality 85 -" -- compress the image with 85% quality
process_cmd = "convert - -resize 50% -" -- resize the image to 50% of its original size
process_cmd = "convert - -colorspace Gray -" -- convert the image to grayscale
```

Ensure the specified command and its dependencies are installed and accessible in your system's shell environment. The above examples require [ImageMagick](https://imagemagick.org/index.php) to be installed.

### Filetypes

Filetype specific options will override the default (or global) configuration.
Expand Down
34 changes: 23 additions & 11 deletions lua/img-clip/clipboard.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local util = require("img-clip.util")
local config = require("img-clip.config")

local M = {}

Expand Down Expand Up @@ -69,26 +70,28 @@ M.content_is_image = function()
return false
end

---@param file_path string
---@return boolean
M.save_image = function(file_path)
local cmd = M.get_clip_cmd()
local process_cmd = config.get_opt("process_cmd")
if process_cmd ~= "" then
process_cmd = "| " .. process_cmd .. " "
end

-- Linux (X11)
if cmd == "xclip" then
local command = string.format('xclip -selection clipboard -o -t image/png > "%s"', file_path)
local command = string.format('xclip -selection clipboard -o -t image/png %s> "%s"', process_cmd, file_path)
local _, exit_code = util.execute(command)
return exit_code == 0

-- Linux (Wayland)
elseif cmd == "wl-paste" then
local command = string.format('wl-paste --type image/png > "%s"', file_path)
local command = string.format('wl-paste --type image/png %s> "%s"', process_cmd, file_path)
local _, exit_code = util.execute(command)
return exit_code == 0

-- MacOS (pngpaste) which is faster than osascript
elseif cmd == "pngpaste" then
local command = string.format('pngpaste "%s"', file_path)
local command = string.format('pngpaste - %s> "%s"', process_cmd, file_path)
local _, exit_code = util.execute(command)
return exit_code == 0

Expand All @@ -97,7 +100,10 @@ M.save_image = function(file_path)
local command = string.format(
[[osascript -e 'set theFile to (open for access POSIX file "%s" with write permission)' ]]
.. [[-e 'try' -e 'write (the clipboard as «class PNGf») to theFile' -e 'end try' ]]
.. [[-e 'close access theFile']],
.. [[-e 'close access theFile' -e 'do shell script "cat %s %s> %s"']],
file_path,
file_path,
process_cmd,
file_path
)
local _, exit_code = util.execute(command)
Expand Down Expand Up @@ -162,24 +168,29 @@ end

M.get_base64_encoded_image = function()
local cmd = M.get_clip_cmd()
local process_cmd = config.get_opt("process_cmd")
if process_cmd ~= "" then
process_cmd = "| " .. process_cmd .. " "
end

-- Linux (X11)
if cmd == "xclip" then
local output, exit_code = util.execute("xclip -selection clipboard -o -t image/png | base64 | tr -d '\n'")
local output, exit_code =
util.execute("xclip -selection clipboard -o -t image/png " .. process_cmd .. "| base64 | tr -d '\n'")
if exit_code == 0 then
return output
end

-- Linux (Wayland)
elseif cmd == "wl-paste" then
local output, exit_code = util.execute("wl-paste --type image/png | base64 | tr -d '\n'")
local output, exit_code = util.execute("wl-paste --type image/png " .. process_cmd .. "| base64 | tr -d '\n'")
if exit_code == 0 then
return output
end

-- MacOS (pngpaste)
elseif cmd == "pngpaste" then
local output, exit_code = util.execute("pngpaste - | base64 | tr -d '\n'")
local output, exit_code = util.execute("pngpaste - " .. process_cmd .. "| base64 | tr -d '\n'")
if exit_code == 0 then
return output
end
Expand All @@ -189,7 +200,9 @@ M.get_base64_encoded_image = function()
local output, exit_code = util.execute(
[[osascript -e 'set theFile to (open for access POSIX file "/tmp/image.png" with write permission)' ]]
.. [[-e 'try' -e 'write (the clipboard as «class PNGf») to theFile' -e 'end try' -e 'close access theFile'; ]]
.. [[cat /tmp/image.png | base64 | tr -d "\n" ]]
.. [[/tmp/image.png ]]
.. process_cmd
.. [[ | base64 | tr -d "\n" ]]
)
if exit_code == 0 then
return output
Expand All @@ -209,5 +222,4 @@ M.get_base64_encoded_image = function()

return nil
end

return M
1 change: 1 addition & 0 deletions lua/img-clip/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local defaults = {
default = {
dir_path = "assets", -- directory path to save images to, can be relative (cwd or current file) or absolute
file_name = "%Y-%m-%d-%H-%M-%S", -- file name format (see lua.org/pil/22.1.html)
process_cmd = "", -- shell command to process the image before saving or embedding as base64
url_encode_path = false, -- encode spaces and special characters in file path
use_absolute_path = false, -- expands dir_path to an absolute path
relative_to_current_file = false, -- make dir_path relative to current file rather than the cwd
Expand Down
37 changes: 36 additions & 1 deletion lua/img-clip/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,45 @@ M.copy_file = function(src, dest)
return util.execute(string.format("cp '%s' '%s'", src, dest))
end

---@param file_path string
---@param opts? table
---@return string | nil output
---@return number exit_code
M.process_image = function(file_path, opts)
local process_cmd = config.get_opt("process_cmd", opts)
if not process_cmd or process_cmd == "" then
return "", 0
end

if util.has("win32") then
util.warn("Windows does not support image processing yet.")
return "", 0
end

-- create temp file
local tmp_file_path = file_path .. ".tmp"

-- process image
local output, exit_code =
util.execute(string.format("cat '%s' | %s > '%s'", file_path, process_cmd, tmp_file_path), true)
if exit_code == 0 then
M.copy_file(tmp_file_path, file_path)
end

-- remove temp file
util.execute(string.format("rm '%s'", tmp_file_path), true)

return output, exit_code
end

---@param file_path string
---@return string | nil
M.get_base64_encoded_image = function(file_path)
local cmd = clipoard.get_clip_cmd()
local process_cmd = config.get_opt("process_cmd")
if process_cmd ~= "" then
process_cmd = "| " .. process_cmd .. " "
end

-- Windows
if cmd == "powershell.exe" then
Expand All @@ -172,7 +207,7 @@ M.get_base64_encoded_image = function(file_path)

-- Linux/MacOS
else
local command = string.format("base64 '%s' | tr -d '\n'", file_path)
local command = string.format("cat '%s' " .. process_cmd .. "| base64 | tr -d '\n'", file_path)
local output, exit_code = util.execute(command)
if exit_code == 0 then
return output
Expand Down
24 changes: 23 additions & 1 deletion lua/img-clip/paste.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local clipboard = require("img-clip.clipboard")
local markup = require("img-clip.markup")
local config = require("img-clip.config")
local debug = require("img-clip.debug")
local util = require("img-clip.util")
local fs = require("img-clip.fs")

Expand Down Expand Up @@ -80,6 +81,12 @@ M.paste_image_from_url = function(url)
end
end

local output, process_exit_code = fs.process_image(file_path)
if process_exit_code ~= 0 then
util.warn("Could not process image.", true)
util.warn("Output: " .. output, true)
end

if not markup.insert_markup(file_path) then
util.error("Could not insert markup code.")
return false
Expand Down Expand Up @@ -122,6 +129,12 @@ M.paste_image_from_path = function(src_path)
return false
end

local output, exit_code = fs.process_image(file_path)
if exit_code ~= 0 then
util.warn("Could not process image.", true)
util.warn("Output: " .. output, true)
end

if not markup.insert_markup(file_path) then
util.error("Could not insert markup code.")
return false
Expand All @@ -132,7 +145,7 @@ end

M.paste_image_from_clipboard = function()
if config.get_opt("embed_image_as_base64") then
if M.embed_image_as_base64(nil) then
if M.embed_image_as_base64() then
return true
end
end
Expand All @@ -154,6 +167,14 @@ M.paste_image_from_clipboard = function()
return false
end

if util.has("wsl") then
local output, exit_code = fs.process_image(file_path)
if exit_code ~= 0 then
util.warn("Could not process image.", true)
util.warn("Output: " .. output, true)
end
end

if not markup.insert_markup(file_path) then
util.error("Could not insert markup code.")
return false
Expand Down Expand Up @@ -185,6 +206,7 @@ M.embed_image_as_base64 = function(file_path)
-- check if base64 string is too long (max_base64_size is in KB)
local base64_size_kb = math.floor((string.len(base64) * 6) / (8 * 1024))
local max_size_kb = config.get_opt("max_base64_size")
debug.log("Base64 size: " .. base64_size_kb .. " KB")
if base64_size_kb > max_size_kb then
util.warn("Base64 string is too large (" .. base64_size_kb .. " KB). Max allowed size is " .. max_size_kb .. " KB.")
return false
Expand Down
18 changes: 11 additions & 7 deletions lua/img-clip/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ local M = {}
M.verbose = true

---@param input_cmd string
---@param execute_directly boolean
---@return string | nil output
---@return number exit_code
M.execute = function(input_cmd)
M.execute = function(input_cmd, execute_directly)
local shell = vim.o.shell:lower()
local cmd

-- execute command directly if shell is powershell or pwsh
if shell:match("powershell") or shell:match("pwsh") then
-- execute command directly if shell is powershell or pwsh or explicitly requested
if execute_directly or shell:match("powershell") or shell:match("pwsh") then
cmd = input_cmd

-- WSL requires the command to have the format:
Expand All @@ -24,6 +25,7 @@ M.execute = function(input_cmd)
else
cmd = "powershell.exe -NoProfile -Command '" .. input_cmd:gsub("'", '"') .. "'"
end

-- cmd.exe requires the command to have the format:
-- powershell.exe -Command "command 'path/to/file'"
elseif M.has("win32") then
Expand Down Expand Up @@ -55,15 +57,17 @@ M.has = function(feature)
end

---@param msg string
M.warn = function(msg)
if M.verbose then
---@param verbose boolean?
M.warn = function(msg, verbose)
if M.verbose or verbose then
vim.notify(msg, vim.log.levels.WARN, { title = "img-clip" })
end
end

---@param msg string
M.error = function(msg)
if M.verbose then
---@param verbose boolean?
M.error = function(msg, verbose)
if M.verbose or verbose then
vim.notify(msg, vim.log.levels.ERROR, { title = "img-clip" })
end
end
Expand Down
15 changes: 15 additions & 0 deletions vimdoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ The plugin comes with the following defaults:
default = {
debug = false, -- enable debug mode
dir_path = "assets", -- directory path to save images to, can be relative (cwd or current file) or absolute
process_cmd = "", -- shell command to process the image before saving or embedding as base64 (e.g. "convert -quality 85 - -")
file_name = "%Y-%m-%d-%H-%M-%S", -- file name format (see lua.org/pil/22.1.html)
url_encode_path = false, -- encode spaces and special characters in file path
use_absolute_path = false, -- expands dir_path to an absolute path
Expand Down Expand Up @@ -173,6 +174,20 @@ dir_path = function()
end,
```

### Processing images

The `process_cmd` option allows you to specify a shell command to process the image before saving or embedding it as base64. The command should read the image data from the standard input and write the processed data to the standard output.

Examples:

```bash
process_cmd = "convert - -quality 85 -" -- compress the image with 85% quality
process_cmd = "convert - -resize 50% -" -- resize the image to 50% of its original size
process_cmd = "convert - -colorspace Gray -" -- convert the image to grayscale
```

Ensure the specified command and its dependencies are installed and accessible in your system's shell environment. The above examples require [ImageMagick](https://imagemagick.org/index.php) to be installed.

### Filetypes

Filetype specific options will override the default (or global) configuration.
Expand Down

0 comments on commit 3924214

Please sign in to comment.