Last active
September 28, 2025 21:52
-
-
Save gitmpr/8ab8729469e59d8c6952030530474a5a to your computer and use it in GitHub Desktop.
OSC 52 clipboard .vimrc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| " 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