📸

【Neovim】編集中のMarkdownファイルにスクリーンショットをお手軽挿入するLuaスクリプト

2023/11/04に公開

はじめに

NeovimでMarkdownの文書を書いていると、Neovimのエディタから出ずにさくっとスクリーンショットで画像を挿入したい瞬間がよくありませんか?

私の場合、以下のような形式でテキストファイルとその配下に画像フォルダを用意したドキュメント管理をよくします。

/docs
├── document1.md
├── document2.md
└── .img
    └── screenshot_yyyymmdd_hhMMSS.png

document1.mdでは、以下のようなイメージ

この機能があーしてこうして、画面のイメージは以下です。

![img1](.img/screenshot_20231104_174348.png)

これを実際にやろうとすると、以下のような手順が必要です。

①Neovimを一旦放り出して
②スクリーンショットを撮影して
③スクリーンショットを保存したファイルをコピーして
④.imgフォルダにペーストしてリネームして
⑤Neovimに戻って、画像リンクを貼る

それはもう面倒で、ストレスフルでした。。
なので、お手軽に実施できるようにさくっとスクリプトを書いてみたため紹介します。

環境

  • OS: Linux
  • エディタ: Neovim
  • 備考: 設定ファイルはluaで記述しています

やりたいこと

MarkdownをNeovimで編集中に、ショートカットキーの入力、スクリーンショット領域の選択でいい感じに撮影・挿入したいです。

操作

操作としては、以下の2Stepでバンっと完結させたい。

①特定のショートカットキーを入力する
②スクリーンショット撮影のインタラクティブウィンドウが表示され、選択した領域を撮影する
③撮影されたスクリーンショットは自動で.imgフォルダに保存され、かつMarkdownにも画像リンクが自動で挿入される

保存先

フォルダ構成は以下のようにしたい。
.imgディレクトリがなかった場合には、自動で作成しておいてほしい。

/docs
├── document1.md
├── document2.md
└── .img
    └── screenshot_yyyymmdd_hhMMSS.png

ファイル名

挿入される形式としては、以下のようにしたい。

![img1](.img/screenshot_20231104_174348.png)

また、画像の参照名もちょっといい感じにしたい。

  • カーソル以下の単語がある場合にはその単語としたい
  • カーソル以下になにもない場合には、img1, img2, img3...のように連番にしたい

といった感じのことをやりたかったので、luaファイルに書いてみます。

書いたスクリプト

init.luaの呼び出しは以下のようにしました。

-- setup screenshot
local api = vim.api

require('screenshot').setup()
api.nvim_set_keymap('i', '<c-s>', "<esc>:lua require('screenshot').take_and_insert()<CR>", { noremap = true })

スクリーンショットの部分は、以下のようにしました。
~/.config/nvim/lua/screenshot.luaに以下を記載しています。

local M = {}

-- モジュールの初期設定関数
-- 現状では何も設定していませんが、将来的な拡張のために記載
M.setup = function()
end

-- カーソルの下にある単語を取得する関数
local function get_word_under_cursor()
  return vim.fn.expand("<cword>")
end

-- Markdownファイル内で次に使うべき画像の番号を決定する関数
local function get_next_img_number()
  local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
  local highest_num = 0
  for _, line in ipairs(lines) do
    for num in string.gmatch(line, "%!%[img(%d+)%]") do
      num = tonumber(num)
      if num and num > highest_num then
        highest_num = num
      end
    end
  end
  return highest_num + 1
end

-- スクリーンショットを撮影してMarkdownファイルに挿入するメイン関数
M.take_and_insert = function()
 -- スクリーンショットを保存するディレクトリのパスを組み立て
  local screenshot_dir = vim.fn.expand("%:p:h") .. "/.img"
 -- スクリーンショットのファイル名
  local date_str = os.date("%Y%m%d_%H%M%S")
  local filename = "screenshot_" .. date_str .. ".png"
  local filepath = screenshot_dir .. "/" .. filename
  -- カーソル下の単語を取得
  local cursorword = get_word_under_cursor()
 -- 次に使用する画像の番号を取得
  local img_num = get_next_img_number()
 -- 画像の参照名を取得(カーソル下の単語があればそれを使用、なければ連番)
  local description_name = cursorword ~= "" and cursorword or ("img" .. img_num)
  
  -- 保存先ディレクトリが存在しない場合は作成
  if vim.fn.isdirectory(screenshot_dir) == 0 then
    local mkdir_result = vim.fn.mkdir(screenshot_dir, "p")
    if mkdir_result == 0 then
      print("Error: Could not create the screenshot directory: " .. screenshot_dir)
      return
    else
      print("Screenshot directory created: " .. screenshot_dir)
    end
  end

  -- OSに応じたスクリーンショットコマンドを選択して実行
  local screenshot_cmd = ""
  if vim.fn.has("mac") == 1 then
    screenshot_cmd = "screencapture -i " .. vim.fn.shellescape(filepath)
  elseif vim.fn.has("unix") == 1 then
    screenshot_cmd = "scrot -s " .. vim.fn.shellescape(filepath)
  elseif vim.fn.has("win32") == 1 or vim.fn.has("win64") == 1 then
    print("Error: Taking screenshots is not supported on Windows through this plugin.")
    return
  end
  
  -- スクリーンショットコマンドを実行
  if screenshot_cmd ~= "" then
    local shell_result = vim.fn.system(screenshot_cmd)
    if vim.v.shell_error ~= 0 then
      print("Error: Failed to take a screenshot.")
      return
    end
  end
 
 -- Markdownに画像リンクを挿入
  local relative_filepath = string.format(".img/%s", filename)
  local link_text = string.format("![%s](%s)", description_name, relative_filepath)
  vim.api.nvim_put({link_text}, "l", true, true)
end

return M

ちょこっと解説

このLuaスクリプトは、Neovimの拡張性を使ってMarkdown編集のスクリーンショット挿入のストレスを減らすためのものです。個人的には満足してます。主に3つの関数から構成されてます。

  1. get_word_under_cursor()
    画像の参照名にするために、カーソルの下の単語を取得しています。
vim.fn.expand("<cword>")
  1. get_next_img_number()
    編集中のMarkdownファイルの中で、挿入されているスクリーンショットの連番を振るようにしてます。

  2. take_and_insert()
    本体部分。フォルダ作成やファイル名取得をした上で、スクリーンショットを撮影して挿入している。

スクリーンショットをインタラクティブに取る部分として以下で書いてますが、ぶっちゃけLinuxしか動作確認をしていない。Windowsに至ってはやる気がわきませんでした。

  if vim.fn.has("mac") == 1 then
    screenshot_cmd = "screencapture -i " .. vim.fn.shellescape(filepath)
  elseif vim.fn.has("unix") == 1 then
    screenshot_cmd = "scrot -s " .. vim.fn.shellescape(filepath)
  elseif vim.fn.has("win32") == 1 or vim.fn.has("win64") == 1 then
    print("Error: Taking screenshots is not supported on Windows through this plugin.")
    return
  end

まとめ

NeovimでMarkdwonを書いているときに、スクリーンショットをお手軽に挿入できるようにLuaスクリプトを書いてみました。
個人的にはやりたいことができたので満足してます。

今後やりたいこととして、プラグインにしてみたいですね。(すでにどっかにありそうな気もしますが)

Discussion