OSC 52 による Neovim とクリップボードの連携
はじめに
Neovim 0.10 から OSC 52 が公式にサポートされました。追加でソフトを入れずともコンテナの中でも、ssh でもホスト側のクリップボードと連携ができるので OSC 52 を重宝している人も多いと思います。
私自身、以前は ojroques/nvim-osc52 を利用して OSC 52 を活用していました。公式機能が提供されたのを機に移行を試みましたが、これまでクリップボード設定を深く理解せずに使っていたせいでスムーズに移行できなかったため、改めて設定を見直すことにしました。
この記事では、OSC 52 を活用した Neovim とクリップボードの連携方法について、実験を交えながら解説します。
OSC 52 を題材にしていますが、他の clipboard tool でも共通していることがほとんどだと思います。
想定読者
- よくわからないけれど
vim.opt.clipboard = 'unnamed,unnamedplus'
を設定したらクリップボード連携できたのでそのまま放置している人 - 公式 doc の OSC 52 の設定を使ったら paste 時に Neovim の動作が止まった人
-
+
register?*
register? なにそれ?な人
動作確認環境
- OS: Ubuntu 24.10 (Ubuntu Sway Remix)
- Terminal: alacritty
実験
まずはまっさらな環境で公式 doc に書かれている OSC 52 の設定だけを入れてみます。
export NVIM_APPNAME=nvim_osc52
mkdir -p ~/.config/$NVIM_APPNAME
vim.g.clipboard = {
name = 'OSC 52',
copy = {
['+'] = require('vim.ui.clipboard.osc52').copy('+'),
['*'] = require('vim.ui.clipboard.osc52').copy('*'),
},
paste = {
['+'] = require('vim.ui.clipboard.osc52').paste('+'),
['*'] = require('vim.ui.clipboard.osc52').paste('*'),
},
}
Neovim を起動して OSC 52 になっていることを確認
:che clipboard
provider.clipboard: require("provider.clipboard.health").check()
Clipboard (optional) ~
- OK Clipboard tool found: OSC 52
この状態で適当な文章を書いて yy
をして、Neovim 外の適当なところで ctrl-v
してみます。
・・・連携されていません 🤔
よくわからないけれど、Neovim クリップボード連携でよく見る vim.opt.clipboard = 'unnamed,unnamedplus'
も入れてみます。
今度は連携されました。
ただ、yy
-> p
とすると10秒ほど Neovim の操作ができなくなり、下記のような message が表示され、timeout したら yank したところが paste されました。
Waiting for OSC 52 response from the terminal. Press Ctrl-C to interrupt...
Timed out waiting for a clipboard response from the terminal
途中で Ctrl-C
をすれば10秒待つことなく paste されますが paste のたびにそんなことをするのは不便極まりないです。
message の文言で調べたら同様のことに悩んでいる人がおり、解決策として出されていた paste 用の関数を別で用意すると問題は起こらなくなりました。
vim.o.clipboard = "unnamedplus"
local function paste()
return {
vim.fn.split(vim.fn.getreg(""), "\n"),
vim.fn.getregtype(""),
}
end
vim.g.clipboard = {
name = "OSC 52",
copy = {
["+"] = require("vim.ui.clipboard.osc52").copy("+"),
["*"] = require("vim.ui.clipboard.osc52").copy("*"),
},
paste = {
["+"] = paste,
["*"] = paste,
},
}
ref:
普段使いのコピペに関してはよくわからないもののとりあえず問題は起きなくなりましたが、ojroques/nvim-osc52 を使っていたときはファイルパスを clipboard に送るなどもしていたのでそのへんのやり方もわからないと完全なる移行ができません。
どうやら clipboard 連携についてちゃんと向き合わなければならない時が来たようです。
selection registers
clipboard の設定で肝となっているところは *
, +
でこれらは selection register と呼ばれるものだそうです。今度は Neovim における register とは?という疑問がわきますが公式 Doc には text を保存する場所と書かれていました。
A register is a place where Vim stores text.
https://neovim.io/doc/user/usr_07.html#_using-registers
doc を読む限りは、*
, +
register に text を詰めると clipboard と連携できるようです。*
は Linux などで単に文を選択したときに使われる clipboard で、+
は ctrl-c
したときに使われる clipboard にそれぞれ連携されるようです。
register を使うときは "{register}
と使うとのこと。
clipboard の設定を初期に戻して実験し直してみます。
vim.g.clipboard = {
name = 'OSC 52',
copy = {
['+'] = require('vim.ui.clipboard.osc52').copy('+'),
['*'] = require('vim.ui.clipboard.osc52').copy('*'),
},
paste = {
['+'] = require('vim.ui.clipboard.osc52').paste('+'),
['*'] = require('vim.ui.clipboard.osc52').paste('*'),
},
}
"*yy
, "+yy
をしてからミドルクリック, ctrl-v を Neovim 外の適当なところで試してみます。
今度は連携がちゃんとできました!でも yy
だけでは連携されません。
- https://neovim.io/doc/user/change.html#quote
- https://neovim.io/doc/user/change.html#registers
- https://neovim.io/doc/user/provider.html#primary-selection
clipboard='unnamed,unnamedplus'
次に clipboard='unnamed,unnamedplus'
について調べてみます。
まずは doc の記述を見てみましょう。
unnamed
When included, Vim will use the clipboard register "*" for all yank, delete, change and put operations which would normally go to the unnamed register. When a register is explicitly specified, it will always be used regardless of whether "unnamed" is in 'clipboard' or not.unnamedplus
A variant of the "unnamed" flag which uses the clipboard register "+" (quoteplus) instead of register "*" for all yank, delete, change and put operations which would normally go to the unnamed register. When "unnamed" is also included to the option, yank and delete operations (but not put) will additionally copy the text into register
https://neovim.io/doc/user/options.html#clipboard-unnamed
unnamed を指定すると yank や delete したときに *
register を使用するようになる、unnamedplus を指定すると *
の変わりに +
を使用する、ただし unnamed も指定されていたら *
も使用するとのこと。
またどちらにも書いてある "which would normally go to the unnamed register" という部分も重要で、これが実験の節で紹介した改良版 paste 関数に関わってきます。
unnamed register の説明を見てみます
- Unnamed register "" quote_quote quotequote
Vim fills this register with text deleted with the "d", "c", "s", "x" commands or copied with the yank "y" command, regardless of whether or not a specific register was used (e.g. "xdd).
https://neovim.io/doc/user/change.html#quote_quote
yank や削除したときは問答無用で unnamed register が使われるとのことです。
まとめると、clipboard='unnamed,unnamedplus'
という設定をすると何でもかんでも unnamed register に入れていた挙動を *
, +
register に対してもやるということを設定しているようです。
また selection registers の節でも書きましたが *
は Linux などで選択したときに使われるクリップボードとの連携なので、Windows などではそもそも設定する必要がないということがわかります。私の場合は Neovim で yank した内容を ctrl-v で paste できればよく、ミドルクリックでは必要ない(むしろ連携してほしくない)ので clipboard=unnamedplus
だけを設定しておけばいいこともわかりました。クリップボード設定に対してだいぶ解像度が上がってきました。
ちなみに、今回は OSC 52 を明示的に有効にするために vim.g.clipboard
に設定を書いてますが、clipboard-tool のところに書いてあるソフトがある場合は自動的にそれが使われるので vim.g.clipboard
を書く必要はありません。大抵の場合は vim.opt.clipboard = 'unnamedplus'
を設定するだけで事足りると思います。
timeout する理由
OSC 52 の恩恵にあずかるためには terminal emulator も OSC 52 に対応している必要があります。alacritty や Windows Terminal など、最近の terminal emulator は OSC 52 に対応していることが多いとおもいます。
問題はクリップボードから読み出すことをデフォルトで許可しているかどうかということです。alacritty の場合は copy は許可していますが、paste はデフォルトでは許可されていません。そのため、デフォルト設定のままだと terminal が情報を返さないので timeout してしまいます。alacritty の場合は以下のように paste も許可する設定を入れると timeout 問題が起きなくなります。
[terminal]
osc52 = "CopyPaste"
terminal の設定を変えれば p
押したときに paste できるようにはなりますが、そもそもシステムの paste 機能 (ex. ctrl-shift-v
) があるから必要ないでしょうと Neovim の doc には書いてあり、その通りだと私は納得したので設定はしてません。デフォルトで paste が無効になっているのは security 上の問題も考えてのことなので paste を有効にすることに固執する必要はないのかなと思います。
Note that not all terminal emulators support reading from the system clipboard (and even for those that do, users should be aware of the security implications), so using OSC 52 for pasting may not be possible (and not necessary, because you can paste instead using your system paste function). Users may need to configure their terminal emulator to allow reading from the clipboard.
https://neovim.io/doc/user/provider.html#clipboard-osc52
paste に関しては OSC 52 を使わないことにしたので設定から paste の項目を抜いてみます。
vim.g.clipboard = {
name = "OSC 52",
copy = {
["+"] = require("vim.ui.clipboard.osc52").copy("+"),
["*"] = require("vim.ui.clipboard.osc52").copy("*"),
},
}
この状態で yy
すると clipboard: No provider.
と出てしまいました。health check してみると確かに認識されていません。doc にこの辺の説明が無いように思えるのですが、どうやら copy
, paste
をともに設定しないといけないようです。
doc によると、copy, paste には関数も設定することができ、paste の場合は返り値は {lines, regtype}
とすればいいようです。
g:clipboard can also use functions (see lambda) instead of strings.
...
The "paste" function returns the clipboard as a [lines, regtype] list, where lines is a list of lines and regtype is a register type conforming to setreg().
https://neovim.io/doc/user/provider.html#clipboard-tool
ここで、timeout 回避のために紹介されていた paste 関数を見てみます。
local function paste()
return {
vim.fn.split(vim.fn.getreg(""), "\n"),
vim.fn.getregtype(""),
}
end
vim.fn.getreg("")
で unnamed register から text を取得して改行コードで分割。getregtype("")
で unnamed register の register type を取得しています。
つまり、+
, *
の代わりに unnamed resiter から text を取得してくるようにしているわけです。
これでようやく clipborad の設定項目がなにをやっているのか理解できました。
require('osc52').copy(text)
の置き換え
ojroques/nvim-osc52 を使って require('osc52').copy(text)
のように書いていたところを置き換えます。
私が実現したかったことは OSC 52 か否かは関係なく単に指定した文字をクリップボードに連携させることでした。今までのことを踏まえるとクリップボードに任意の文字を送るには +
register に text を送れば良いということはわかります。
任意の register に text を入れるようにするには setreg を使えばいいらしいので次のように書けば解決です。
vim.fn.setreg('+', text)
-- 開いているファイルの絶対パスをコピーする例
vim.fn.setreg('+', vim.fn.expand('%'))
おわりに
これまで雰囲気で設定していたクリップボード周りの解像度がかなり上がりました。これからはクリップボードまわりで不具合が起きても自力で修復できそうです。
調査する際に ChatGPT を多用しましたが、なんて検索していいのかすらわからないものを調べる際は ChatGPT などの AI 支援は有用だと改めて実感しました。
Discussion