Skip to content

Instantly share code, notes, and snippets.

@gitmpr
Last active September 28, 2025 21:52
Show Gist options
  • Select an option

  • Save gitmpr/8ab8729469e59d8c6952030530474a5a to your computer and use it in GitHub Desktop.

Select an option

Save gitmpr/8ab8729469e59d8c6952030530474a5a to your computer and use it in GitHub Desktop.
OSC 52 clipboard .vimrc
" Standalone .vimrc with OSC52 clipboard support
" Basic vim configuration
set nocompatible
syntax on
set number
set mouse=a
" Set leader key to space
let mapleader = " "
" disable for easier pasting
" set autoindent
" set smartindent
set tabstop=4
set shiftwidth=4
" set expandtab
set hlsearch
set incsearch
set ignorecase
set smartcase
" OSC52 Clipboard Implementation
" Based on vim-oscyank plugin by ojroques
if !exists('g:loaded_oscyank')
let g:loaded_oscyank = 1
" Configuration variables
let g:oscyank_max_length = get(g:, 'oscyank_max_length', 100000)
let g:oscyank_silent = get(g:, 'oscyank_silent', 0)
let g:oscyank_trim = get(g:, 'oscyank_trim', 0)
" Base64 encoding table (fallback implementation)
let s:b64_table = [
\ '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', '+', '/'
\ ]
function! s:encode_b64_fallback(str, size)
let l:bytes = []
for i in range(len(a:str))
call add(l:bytes, char2nr(a:str[i]))
endfor
let l:b64 = []
for i in range(0, len(l:bytes) - 1, 3)
let l:n = l:bytes[i] * 0x10000
\ + get(l:bytes, i + 1, 0) * 0x100
\ + get(l:bytes, i + 2, 0)
call add(l:b64, s:b64_table[l:n / 0x40000])
call add(l:b64, s:b64_table[(l:n / 0x1000) % 0x40])
call add(l:b64, s:b64_table[(l:n / 0x40) % 0x40])
call add(l:b64, s:b64_table[l:n % 0x40])
endfor
" Add padding
if len(l:bytes) % 3 == 1
let l:b64[-1] = '='
let l:b64[-2] = '='
elseif len(l:bytes) % 3 == 2
let l:b64[-1] = '='
endif
let l:b64_str = join(l:b64, '')
if a:size > 0
let l:chunks = []
for i in range(0, len(l:b64_str) - 1, a:size)
call add(l:chunks, l:b64_str[i:i + a:size - 1])
endfor
return join(l:chunks, "\n")
endif
return l:b64_str
endfunction
function! s:encode_b64(str, size)
" Try system base64 first for better performance
if executable('base64')
let l:b64 = system('echo -n ' . shellescape(a:str) . ' | base64 -w 0')
if v:shell_error == 0
return substitute(l:b64, '\n$', '', '')
endif
endif
" Fallback to VimScript implementation
return s:encode_b64_fallback(a:str, a:size)
endfunction
function! OSCYank(text) abort
" Set flag to indicate OSC52 yank is happening
let g:osc52_yank_active = 1
let l:text = a:text
if g:oscyank_trim
let l:text = substitute(l:text, '\n*$', '', '')
endif
if len(l:text) > g:oscyank_max_length
if !g:oscyank_silent
echohl WarningMsg
echo printf('OSCYank: text too long (%d > %d), not sent', len(l:text), g:oscyank_max_length)
echohl None
endif
let g:osc52_yank_active = 0
return
endif
let l:b64 = s:encode_b64(l:text, 0)
let l:osc52 = printf("\x1b]52;c;%s\x07", l:b64)
if filewritable('/dev/fd/2')
call writefile([l:osc52], '/dev/fd/2', 'b')
elseif exists('*chansend')
call chansend(v:stderr, l:osc52)
else
if executable('tmux') && len($TMUX)
let l:tmux_osc52 = printf("\x1bPtmux;\x1b%s\x1b\\", l:osc52)
call system('tmux send-keys -t ' . shellescape($TMUX_PANE) . ' ' . shellescape(l:tmux_osc52))
else
execute "silent !echo " . shellescape(l:osc52) . " >/dev/tty"
redraw!
endif
endif
if !g:oscyank_silent
echo printf('Copied %d characters to system clipboard via OSC52', len(l:text))
endif
endfunction
function! OSCYankOperator(type) abort
let l:sel_save = &selection
let &selection = "inclusive"
let l:reg_save = getreg('"')
let l:type_save = getregtype('"')
try
if a:type ==# 'line'
silent execute "normal! '[V']y"
elseif a:type ==# 'char'
silent execute "normal! `[v`]y"
elseif a:type ==# 'block'
silent execute "normal! `[\<C-V>`]y"
endif
call OSCYank(getreg('"'))
let l:start = getpos("'[")
let l:end = getpos("']")
let l:end_col = l:end[2]
if l:end_col >= 2147483647
let l:end_col = len(getline(l:end[1]))
endif
call HighlightOSC52Yank(l:start[1], l:start[2], l:end[1], l:end_col)
finally
call setreg('"', l:reg_save, l:type_save)
let &selection = l:sel_save
endtry
endfunction
function! OSCYankVisual() abort
let l:reg_save = getreg('"')
let l:type_save = getregtype('"')
try
" Get text between visual marks
let l:start = getpos("'<")
let l:end = getpos("'>")
let l:start_line = l:start[1]
let l:end_line = l:end[1]
let l:start_col = l:start[2]
let l:end_col = l:end[2]
if l:end_col >= 2147483647
let l:end_col = len(getline(l:end_line))
endif
if l:start_line == l:end_line
" Single line selection
let l:line = getline(l:start_line)
let l:text = l:line[l:start_col-1:l:end_col-1]
else
" Multi-line selection
let l:lines = []
" First line (partial)
let l:first_line = getline(l:start_line)
call add(l:lines, l:first_line[l:start_col-1:])
" Middle lines (complete)
for l:i in range(l:start_line + 1, l:end_line - 1)
call add(l:lines, getline(l:i))
endfor
" Last line (partial)
let l:last_line = getline(l:end_line)
call add(l:lines, l:last_line[:l:end_col-1])
let l:text = join(l:lines, nr2char(10))
endif
call OSCYank(l:text)
call HighlightOSC52Yank(l:start_line, l:start_col, l:end_line, l:end_col)
finally
call setreg('"', l:reg_save, l:type_save)
endtry
endfunction
function! OSCYankReg(reg) abort
return OSCYank(getreg(a:reg))
endfunction
function! OSCYankLine()
call OSCYank(getline('.'))
let l:line = line('.')
let l:end_col = max([1, len(getline(l:line))])
call HighlightOSC52Yank(l:line, 1, l:line, l:end_col)
endfunction
function! OSCYankAll()
call OSCYank(join(getline(1, '$'), "\n"))
let l:end_line = line('$')
let l:end_col = max([1, len(getline(l:end_line))])
call HighlightOSC52Yank(1, 1, l:end_line, l:end_col)
endfunction
" Commands
command! -range OSCYankVisual :<line1>,<line2>call OSCYankVisual()
command! -nargs=1 OSCYankReg call OSCYankReg(<f-args>)
" Copy current line to clipboard
nnoremap <leader>yy :call OSCYankLine()<CR>
" Copy visual selection to clipboard
vnoremap <leader>y :<C-U>call OSCYankVisual()<CR>
" Copy operator - use with motions (e.g., <leader>yiw, <leader>yap)
nnoremap <silent> <leader>y :set opfunc=OSCYankOperator<CR>g@
" Copy entire buffer
nnoremap <leader>Y :call OSCYankAll()<CR>
endif
set backspace=indent,eol,start
set ruler
set showcmd
set wildmenu
set scrolloff=3
set encoding=utf-8
" Highlight trailing whitespace
highlight ExtraWhitespace ctermbg=red guibg=red
match ExtraWhitespace /\s\+$/
" Clear search highlighting
nnoremap <leader>/ :noh<CR>
highlight YankHighlight guibg=#FFA500 ctermbg=214 guifg=#000000 ctermfg=0 " Orange for normal yank
highlight OSC52YankHighlight guibg=#90EE90 ctermbg=120 guifg=#000000 ctermfg=0 " Light green for OSC52
highlight SystemYankHighlight guibg=#00FFFF ctermbg=51 guifg=#000000 ctermfg=0 " Cyan for + register
highlight PrimaryYankHighlight guibg=#00008B ctermbg=18 guifg=#FFFFFF ctermfg=15 " Dark Blue for * register
" Variable to track OSC52 yank operations
let g:osc52_yank_active = 0
" Debounce timer variables for standard yanks
let s:yank_timer = -1
let s:yank_event = {}
" Debounce function called by TextYankPost to improve performance.
function! DebounceHighlight() abort
if s:yank_timer != -1
call timer_stop(s:yank_timer)
endif
let s:yank_event = copy(v:event)
let s:yank_timer = timer_start(1, { -> DoHighlight() })
endfunction
" The actual highlighting logic, called by the debounce timer.
function! DoHighlight() abort
let s:yank_timer = -1
let l:event = s:yank_event
" Get event info from the debounced event.
let l:reg = l:event.regname
let l:regtype = l:event.regtype
let l:regcontents = get(l:event, 'regcontents', [])
let l:is_visual = get(l:event, 'visual', 0)
if empty(l:regcontents)
let l:yanked_text = getreg(l:reg)
let l:regcontents = split(l:yanked_text, '\n')
endif
let l:num_chars = len(join(l:regcontents, "\n"))
" Don't highlight if yanked text is too large.
let l:max_lines = get(g:, 'highlightedyank_max_lines', 1000)
if len(l:regcontents) > l:max_lines
return
endif
" Determine highlight group and message.
let l:higroup = ''
if l:reg == '+'
let l:higroup = 'SystemYankHighlight'
echo printf('Copied %d characters to system clipboard (+)', l:num_chars)
elseif l:reg == '*'
let l:higroup = 'PrimaryYankHighlight'
echo printf('Copied %d characters to primary selection (*)', l:num_chars)
else
let l:higroup = 'YankHighlight'
if l:reg != '"' && l:reg != '0'
echo printf('Yanked %d characters to register %s', l:num_chars, l:reg)
else
echo printf('Yanked %d characters', l:num_chars)
endif
endif
if l:higroup == ''
return
endif
" Determine region, inspired by vim-highlightedyank.
if l:is_visual
let l:start_pos = getpos("'<")
else
let l:start_pos = getpos("'[[")
endif
if l:start_pos[1] == 0
return " Start mark not set.
endif
let l:end_pos = deepcopy(l:start_pos)
if l:regtype == 'v' " Character-wise
let l:line_count = len(l:regcontents)
if l:line_count == 0
return
elseif l:line_count == 1
let l:end_pos[2] += strlen(l:regcontents[0])
if l:end_pos[2] > 1
let l:end_pos[2] -= 1
endif
else
let l:end_pos[1] += l:line_count - 1
let l:end_pos[2] = strlen(l:regcontents[-1])
endif
elseif l:regtype == 'V' " Line-wise
if l:is_visual
let l:end_pos = getpos("'>")
else
let l:end_pos = getpos("']")
endif
let l:end_pos[2] = len(getline(l:end_pos[1]))
elseif l:regtype =~# '\v^\x16' " Block-wise (Ctrl-V)
if l:is_visual
let l:end_pos = getpos("'>")
else
let l:end_pos = getpos("']")
endif
let l:line_count = len(l:regcontents)
if l:line_count > 0
let l:end_pos[1] = l:start_pos[1] + l:line_count - 1
endif
else
return " Unknown or unsupported regtype.
endif
" Build pattern and highlight.
let l:pattern = '\%'.l:start_pos[1].'l\%'.l:start_pos[2].'c\_.*\%'.l:end_pos[1].'l\%'.l:end_pos[2].'c'
let l:id = matchadd(l:higroup, l:pattern)
if l:id > 0
let l:duration = get(g:, 'highlightedyank_highlight_duration', 350)
if l:duration > 0
call timer_start(l:duration, { -> matchdelete(l:id) })
endif
endif
endfunction
" Function to manually highlight OSC52 yanks (since they don't trigger TextYankPost)
function! HighlightOSC52Yank(start_line, start_col, end_line, end_col)
" Don't highlight if yanked text is too large.
let l:max_lines = get(g:, 'highlightedyank_max_lines', 1000)
if (a:end_line - a:start_line + 1) > l:max_lines
let g:osc52_yank_active = 0 " Still need to reset the flag.
return
endif
" Highlight the specified region with OSC52 color.
let l:pattern = '\%'.a:start_line.'l\%'.a:start_col.'c\_.*\%'.a:end_line.'l\%'.a:end_col.'c'
let l:id = matchadd('OSC52YankHighlight', l:pattern)
if l:id > 0
let l:duration = get(g:, 'highlightedyank_highlight_duration', 350)
if l:duration > 0
call timer_start(l:duration, { -> matchdelete(l:id) })
endif
endif
let g:osc52_yank_active = 0
endfunction
" Set up autocommand for yank highlighting (Vim 8.0+ and Neovim)
if exists('##TextYankPost')
augroup highlight_yank
autocmd!
autocmd TextYankPost * call DebounceHighlight()
augroup END
endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment