From f5ec5806eded34883fa42e6edb472b84fd94dd26 Mon Sep 17 00:00:00 2001 From: Gabriel Ebner Date: Sat, 30 Oct 2021 18:30:42 +0200 Subject: [PATCH] nvim: add osc52 support --- Makefile | 2 +- config/nvim/lua/osc52.lua | 213 ++++++++++++++++++++++++++++++++++++++ vimrc | 2 + 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 config/nvim/lua/osc52.lua diff --git a/Makefile b/Makefile index ffd6a27..3973ad5 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ FILES = bashrc bash_profile vimrc zshrc gitconfig screenrc commonshrc liquidpromptrc \ neomuttrc pentadactylrc mailcap latexmkrc ctags \ i3/config i3status.conf config/dunst/dunstrc msmtprc spacemacs emacs.d mbsyncrc authinfo \ - config/qutebrowser config/nvim/init.vim config/nvim/ginit.vim ideavimrc \ + config/qutebrowser config/nvim/init.vim config/nvim/ginit.vim config/nvim/lua ideavimrc \ config/khal config/vdirsyncer/config config/khard \ config/rofi config/rofi-pass gdbinit notmuch-config \ tridactylrc config/sway/config config/mpv/mpv.conf \ diff --git a/config/nvim/lua/osc52.lua b/config/nvim/lua/osc52.lua new file mode 100644 index 0000000..ccd26ed --- /dev/null +++ b/config/nvim/lua/osc52.lua @@ -0,0 +1,213 @@ +local M = {} + +-- Code inspired by https://github.com/fcpg/vim-osc52/blob/master/plugin/osc52.vim + +local base64 = {} +local max_osc52_sequence = 100000 + +function M.get_OSC52(str, tgt) + return "\x1b]52;" .. tgt .. ";" .. base64.encode(str) .. "\x07" +end + +-- This is for `tmux` sessions which filters OSC 52 locally. +function M.get_OSC52_tmux(str, tgt) + return "\x1bPtmux;\x1b\x1b]52;" .. tgt .. ";" .. base64.encode(str) .. "\x07\x1b\\" +end + +-- base64s the entire source, wraps it in a single OSC52, and then +-- breaks the result in small chunks which are each wrapped in a DCS sequence. +-- +-- This is appropriate when running on `screen`. Screen doesn't support OSC 52, +-- but will pass the contents of a DCS sequence to the outer terminal unmolested. +-- It imposes a small max length to DCS sequences, so we send in chunks. +function M.get_OSC52_DCS(str, tgt) + local b64 = base64.encode(str) + + -- Insert pair every 76 characters. + -- TODO: insert "\x1b\\\x1bP" every 76 characters + + -- Now wrap the whole thing in .... + b64 = "\x1bP\x1b]52;" .. tgt .. ";" .. b64 .. "\x07\x1b\x5c" + + return b64 +end + +M.terminal = vim.api.nvim_eval'$TERM' + +function M.copy(str, tgt) + tgt = tgt or 'c' + + if #str > max_osc52_sequence then error'string to long to copy via OSC52' end + + local osc52 + if M.terminal:match'^tmux' ~= nil then + osc52 = M.get_OSC52_tmux(str, tgt) + elseif M.terminal:match'^screen' ~= nil then + osc52 = M.get_OSC52_DCS(str, tgt) + else + osc52 = M.get_OSC52(str, tgt) + end + + io.stdout:write(osc52) +end + +function _G.osc52_clipboard_handler(lines, regtype, reg) + M.copy(table.concat(lines, '\n'), reg == '*' and 's' or 'c') +end + +function M.enable() + vim.cmd[[ + let g:clipboard = { + \ 'name': 'osc52', + \ 'copy': { + \ '+': { lines, regtype -> v:lua.osc52_clipboard_handler(lines, regtype, '+') }, + \ '*': { lines, regtype -> v:lua.osc52_clipboard_handler(lines, regtype, '*') }, + \ }, + \ 'paste': { + \ '+': { -> '' }, + \ '*': { -> '' }, + \ }, + \} + ]] +end + +if M.terminal ~= 'dumb' then + M.enable() +end + +-- base 64 library from: https://github.com/iskolbin/lbase64/blob/master/base64.lua + +if false then _G.bit32 = {} end -- shut up type error + +local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode +if not extract then + if _G.bit then -- LuaJIT + local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band + extract = function( v, from, width ) + return band( shr( v, from ), shl( 1, width ) - 1 ) + end + elseif _G._VERSION == "Lua 5.1" then + extract = function( v, from, width ) + local w = 0 + local flag = 2^from + for i = 0, width-1 do + local flag2 = flag + flag + if v % flag2 >= flag then + w = w + 2^i + end + flag = flag2 + end + return w + end + else -- Lua 5.3+ + extract = load[[return function( v, from, width ) + return ( v >> from ) & ((1 << width) - 1) + end]]() + end +end + +function base64.makeencoder( s62, s63, spad ) + local encoder = {} + for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J', + 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', + 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2', + '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do + encoder[b64code] = char:byte() + end + return encoder +end + +function base64.makedecoder( s62, s63, spad ) + local decoder = {} + for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do + decoder[charcode] = b64code + end + return decoder +end + +local DEFAULT_ENCODER = base64.makeencoder() +local DEFAULT_DECODER = base64.makedecoder() + +local char, concat = string.char, table.concat + +function base64.encode( str, encoder, usecaching ) + encoder = encoder or DEFAULT_ENCODER + local t, k, n = {}, 1, #str + local lastn = n % 3 + local cache = {} + for i = 1, n-lastn, 3 do + local a, b, c = str:byte( i, i+2 ) + local v = a*0x10000 + b*0x100 + c + local s + if usecaching then + s = cache[v] + if not s then + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + cache[v] = s + end + else + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + end + t[k] = s + k = k + 1 + end + if lastn == 2 then + local a, b = str:byte( n-1, n ) + local v = a*0x10000 + b*0x100 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64]) + elseif lastn == 1 then + local v = str:byte( n )*0x10000 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64]) + end + return concat( t ) +end + +function base64.decode( b64, decoder, usecaching ) + decoder = decoder or DEFAULT_DECODER + local pattern = '[^%w%+%/%=]' + if decoder then + local s62, s63 + for charcode, b64code in pairs( decoder ) do + if b64code == 62 then s62 = charcode + elseif b64code == 63 then s63 = charcode + end + end + pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) ) + end + b64 = b64:gsub( pattern, '' ) + local cache = usecaching and {} + local t, k = {}, 1 + local n = #b64 + local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0 + for i = 1, padding > 0 and n-4 or n, 4 do + local a, b, c, d = b64:byte( i, i+3 ) + local s + if usecaching then + local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d + s = cache[v0] + if not s then + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + cache[v0] = s + end + else + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + end + t[k] = s + k = k + 1 + end + if padding == 1 then + local a, b, c = b64:byte( n-3, n-1 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + t[k] = char( extract(v,16,8), extract(v,8,8)) + elseif padding == 2 then + local a, b = b64:byte( n-3, n-2 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + t[k] = char( extract(v,16,8)) + end + return concat( t ) +end + +return M diff --git a/vimrc b/vimrc index 767b20e..6812064 100644 --- a/vimrc +++ b/vimrc @@ -317,6 +317,8 @@ require('telescope').setup{ }, } +require'osc52' + EOF hi LspReferenceRead cterm=bold ctermbg=red guibg=LightYellow