Skip to content

Instantly share code, notes, and snippets.

@jneen
Created July 14, 2022 02:03
Show Gist options
  • Select an option

  • Save jneen/5de9c3dd46c82b5cc3badb70d21fc8b1 to your computer and use it in GitHub Desktop.

Select an option

Save jneen/5de9c3dd46c82b5cc3badb70d21fc8b1 to your computer and use it in GitHub Desktop.

Revisions

  1. jneen created this gist Jul 14, 2022.
    41 changes: 41 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    Instructions to reproduce:

    * Run `./run-nvim.sh`
    * In the terminal within nvim, run `./run-nvr.sh`
    * Close the newly opened `init.vim` buffer with `:q` or `^W c` or similar.

    On my system, this results in the following output:

    ```
    [0: jneen@lavender nvim-repro ] -> *main $
    ; ./run-nvr.sh
    [0: jneen@lavender nvim-repro ] -> *main $
    ; 4_EunuchNewLine
    ```

    Version details:

    ```
    [0: jneen@lavender nvim-repro ] -> *main $
    ; nvim --version
    NVIM v0.7.0
    Build type: Release
    LuaJIT 2.1.0-beta3
    Compiled by [email protected]
    Features: +acl +iconv +tui
    See ":help feature-compile"
    system vimrc file: "$VIM/sysinit.vim"
    fall-back for $VIM: "/opt/homebrew/Cellar/neovim/0.7.0/share/nvim"
    Run :checkhealth for more info
    [0: jneen@lavender nvim-repro ] -> *main $
    ; nvr --version
    nvr 2.5.1
    pynvim 0.4.3
    psutil 5.9.0
    Python 3.10.5 (main, Jun 23 2022, 17:14:57) [Clang 13.1.6 (clang-1316.0.21.2.5)]
    ```
    506 changes: 506 additions & 0 deletions eunuch.vim
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,506 @@
    " eunuch.vim - Helpers for UNIX
    " Maintainer: Tim Pope <http://tpo.pe/>
    " Version: 1.3

    if exists('g:loaded_eunuch') || &cp || v:version < 704
    finish
    endif
    let g:loaded_eunuch = 1

    let s:slash_pat = exists('+shellslash') ? '[\/]' : '/'

    function! s:separator() abort
    return !exists('+shellslash') || &shellslash ? '/' : '\'
    endfunction

    function! s:ffn(fn, path) abort
    return get(get(g:, 'io_' . matchstr(a:path, '^\a\a\+\ze:'), {}), a:fn, a:fn)
    endfunction

    function! s:fcall(fn, path, ...) abort
    return call(s:ffn(a:fn, a:path), [a:path] + a:000)
    endfunction

    function! s:AbortOnError(cmd) abort
    try
    exe a:cmd
    catch '^Vim(\w\+):E\d'
    return 'return ' . string('echoerr ' . string(matchstr(v:exception, ':\zsE\d.*')))
    endtry
    return ''
    endfunction

    function! s:MinusOne(...) abort
    return -1
    endfunction

    function! EunuchRename(src, dst) abort
    if a:src !~# '^\a\a\+:' && a:dst !~# '^\a\a\+:'
    return rename(a:src, a:dst)
    endif
    try
    let fn = s:ffn('writefile', a:dst)
    let copy = call(fn, [s:fcall('readfile', a:src, 'b'), a:dst])
    if copy == 0
    let delete = s:fcall('delete', a:src)
    if delete == 0
    return 0
    else
    call s:fcall('delete', a:dst)
    return -1
    endif
    endif
    catch
    return -1
    endtry
    endfunction

    function! s:MkdirCallable(name) abort
    let ns = matchstr(a:name, '^\a\a\+\ze:')
    if !s:fcall('isdirectory', a:name) && s:fcall('filewritable', a:name) !=# 2
    if exists('g:io_' . ns . '.mkdir')
    return [g:io_{ns}.mkdir, [a:name, 'p']]
    elseif empty(ns)
    return ['mkdir', [a:name, 'p']]
    endif
    endif
    return ['s:MinusOne', []]
    endfunction

    function! s:Delete(path) abort
    if has('patch-7.4.1107') && isdirectory(a:path)
    return delete(a:path, 'd')
    else
    return s:fcall('delete', a:path)
    endif
    endfunction

    command! -bar -bang -nargs=? -complete=dir Mkdir
    \ let s:dst = empty(<q-args>) ? expand('%:h') : <q-args> |
    \ if call('call', s:MkdirCallable(s:dst)) == -1 |
    \ echohl WarningMsg |
    \ echo "Directory already exists: " . s:dst |
    \ echohl NONE |
    \ elseif empty(<q-args>) |
    \ silent keepalt execute 'file' fnameescape(@%) |
    \ endif |
    \ unlet s:dst

    function! s:DeleteError(file) abort
    if empty(s:fcall('getftype', a:file))
    return 'Could not find "' . a:file . '" on disk'
    else
    return 'Failed to delete "' . a:file . '"'
    endif
    endfunction

    command! -bar -bang Unlink
    \ if <bang>1 && &undoreload >= 0 && line('$') >= &undoreload |
    \ echoerr "Buffer too big for 'undoreload' (add ! to override)" |
    \ elseif s:Delete(@%) |
    \ echoerr s:DeleteError(@%) |
    \ else |
    \ edit! |
    \ silent exe 'doautocmd <nomodeline> User FileUnlinkPost' |
    \ endif

    command! -bar -bang Remove Unlink<bang>

    command! -bar -bang Delete
    \ if <bang>1 && !(line('$') == 1 && empty(getline(1)) || s:fcall('getftype', @%) !=# 'file') |
    \ echoerr "File not empty (add ! to override)" |
    \ else |
    \ let s:file = expand('%:p') |
    \ execute 'bdelete<bang>' |
    \ if !bufloaded(s:file) && s:Delete(s:file) |
    \ echoerr s:DeleteError(s:sfile) |
    \ endif |
    \ unlet s:file |
    \ endif

    function! s:FileDest(q_args) abort
    let file = expand(a:q_args)
    if file =~# s:slash_pat . '$'
    let file .= expand('%:t')
    elseif s:fcall('isdirectory', file)
    let file .= s:separator() . expand('%:t')
    endif
    return substitute(file, '^\.' . s:slash_pat, '', '')
    endfunction

    command! -bar -nargs=+ -bang -complete=file Copy
    \ let s:dst = s:FileDest(<q-args>) |
    \ call call('call', s:MkdirCallable(fnamemodify(s:dst, ':h'))) |
    \ let s:dst = s:fcall('simplify', s:dst) |
    \ exe expand('<mods>') 'saveas<bang>' fnameescape(remove(s:, 'dst')) |
    \ filetype detect

    function! s:Move(bang, arg) abort
    let dst = s:FileDest(a:arg)
    exe s:AbortOnError('call call("call", s:MkdirCallable(' . string(fnamemodify(dst, ':h')) . '))')
    let dst = s:fcall('simplify', dst)
    if !a:bang && s:fcall('filereadable', dst)
    let confirm = &confirm
    try
    if confirm | set noconfirm | endif
    exe s:AbortOnError('keepalt saveas ' . fnameescape(dst))
    finally
    if confirm | set confirm | endif
    endtry
    endif
    if s:fcall('filereadable', @%) && EunuchRename(@%, dst)
    return 'echoerr ' . string('Failed to rename "'.@%.'" to "'.dst.'"')
    else
    let last_bufnr = bufnr('$')
    exe s:AbortOnError('silent keepalt file ' . fnameescape(dst))
    if bufnr('$') != last_bufnr
    exe bufnr('$') . 'bwipe'
    endif
    setlocal modified
    return 'write!|filetype detect'
    endif
    endfunction

    command! -bar -nargs=+ -bang -complete=file Move exe s:Move(<bang>0, <q-args>)

    " ~/f, $VAR/f, /f, C:/f, url://f, ./f, ../f
    let s:absolute_pat = '^[~$]\|^' . s:slash_pat . '\|^\a\+:\|^\.\.\=\%(' . s:slash_pat . '\|$\)'

    function! s:RenameComplete(A, L, P) abort
    let sep = s:separator()
    if a:A =~# s:absolute_pat
    let prefix = ''
    else
    let prefix = expand('%:h') . sep
    endif
    let files = split(glob(prefix.a:A.'*'), "\n")
    call map(files, 'fnameescape(strpart(v:val, len(prefix))) . (isdirectory(v:val) ? sep : "")')
    return files
    endfunction

    function! s:RenameArg(arg) abort
    if a:arg =~# s:absolute_pat
    return a:arg
    else
    return '%:h/' . a:arg
    endif
    endfunction

    command! -bar -nargs=+ -bang -complete=customlist,s:RenameComplete Duplicate
    \ exe 'Copy<bang>' escape(s:RenameArg(<q-args>), '"|')

    command! -bar -nargs=+ -bang -complete=customlist,s:RenameComplete Rename
    \ exe 'Move<bang>' escape(s:RenameArg(<q-args>), '"|')

    let s:permlookup = ['---','--x','-w-','-wx','r--','r-x','rw-','rwx']
    function! s:Chmod(bang, perm, ...) abort
    let autocmd = 'silent doautocmd <nomodeline> User FileChmodPost'
    let file = a:0 ? expand(join(a:000, ' ')) : @%
    if !a:bang && exists('*setfperm')
    let perm = ''
    if a:perm =~# '^\0*[0-7]\{3\}$'
    let perm = substitute(a:perm[-3:-1], '.', '\=s:permlookup[submatch(0)]', 'g')
    elseif a:perm ==# '+x'
    let perm = substitute(s:fcall('getfperm', file), '\(..\).', '\1x', 'g')
    elseif a:perm ==# '-x'
    let perm = substitute(s:fcall('getfperm', file), '\(..\).', '\1-', 'g')
    endif
    if len(perm) && file =~# '^\a\a\+:' && !s:fcall('setfperm', file, perm)
    return autocmd
    endif
    endif
    if !executable('chmod')
    return 'echoerr "No chmod command in path"'
    endif
    let out = get(split(system('chmod '.(a:bang ? '-R ' : '').a:perm.' '.shellescape(file)), "\n"), 0, '')
    return len(out) ? 'echoerr ' . string(out) : autocmd
    endfunction

    command! -bar -bang -nargs=+ Chmod
    \ exe s:Chmod(<bang>0, <f-args>)

    command! -bang -complete=file -nargs=+ Cfind exe s:Grep(<q-bang>, <q-args>, 'find', '')
    command! -bang -complete=file -nargs=+ Clocate exe s:Grep(<q-bang>, <q-args>, 'locate', '')
    command! -bang -complete=file -nargs=+ Lfind exe s:Grep(<q-bang>, <q-args>, 'find', 'l')
    command! -bang -complete=file -nargs=+ Llocate exe s:Grep(<q-bang>, <q-args>, 'locate', 'l')
    function! s:Grep(bang, args, prg, type) abort
    let grepprg = &l:grepprg
    let grepformat = &l:grepformat
    let shellpipe = &shellpipe
    try
    let &l:grepprg = a:prg
    setlocal grepformat=%f
    if &shellpipe ==# '2>&1| tee' || &shellpipe ==# '|& tee'
    let &shellpipe = "| tee"
    endif
    execute a:type.'grep! '.a:args
    if empty(a:bang) && !empty(getqflist())
    return 'cfirst'
    else
    return ''
    endif
    finally
    let &l:grepprg = grepprg
    let &l:grepformat = grepformat
    let &shellpipe = shellpipe
    endtry
    endfunction

    function! s:SilentSudoCmd(editor) abort
    let cmd = 'env SUDO_EDITOR=' . a:editor . ' VISUAL=' . a:editor . ' sudo -e'
    let local_nvim = has('nvim') && len($DISPLAY . $SECURITYSESSIONID . $TERM_PROGRAM)
    if !local_nvim && (!has('gui_running') || &guioptions =~# '!')
    redraw
    echo
    return ['silent', cmd]
    elseif !empty($SUDO_ASKPASS) ||
    \ filereadable('/etc/sudo.conf') &&
    \ len(filter(readfile('/etc/sudo.conf', '', 50), 'v:val =~# "^Path askpass "'))
    return ['silent', cmd . ' -A']
    else
    return [local_nvim ? 'silent' : '', cmd]
    endif
    endfunction

    augroup eunuch_sudo
    augroup END

    function! s:SudoSetup(file, resolve_symlink) abort
    let file = a:file
    if a:resolve_symlink && getftype(file) ==# 'link'
    let file = resolve(file)
    if file !=# a:file
    silent keepalt exe 'file' fnameescape(file)
    endif
    endif
    let file = substitute(file, s:slash_pat, '/', 'g')
    if file !~# '^\a\+:\|^/'
    let file = substitute(getcwd(), s:slash_pat, '/', 'g') . '/' . file
    endif
    if !filereadable(file) && !exists('#eunuch_sudo#BufReadCmd#'.fnameescape(file))
    execute 'autocmd eunuch_sudo BufReadCmd ' fnameescape(file) 'exe s:SudoReadCmd()'
    endif
    if !filewritable(file) && !exists('#eunuch_sudo#BufWriteCmd#'.fnameescape(file))
    execute 'autocmd eunuch_sudo BufReadPost' fnameescape(file) 'set noreadonly'
    execute 'autocmd eunuch_sudo BufWriteCmd' fnameescape(file) 'exe s:SudoWriteCmd()'
    endif
    endfunction

    let s:error_file = tempname()

    function! s:SudoError() abort
    let error = join(readfile(s:error_file), " | ")
    if error =~# '^sudo' || v:shell_error
    return len(error) ? error : 'Error invoking sudo'
    else
    return error
    endif
    endfunction

    function! s:SudoReadCmd() abort
    if &shellpipe =~ '|&'
    return 'echoerr ' . string('eunuch.vim: no sudo read support for csh')
    endif
    silent %delete_
    silent doautocmd <nomodeline> BufReadPre
    let [silent, cmd] = s:SilentSudoCmd('cat')
    execute silent 'read !' . cmd . ' "%" 2> ' . s:error_file
    let exit_status = v:shell_error
    silent 1delete_
    setlocal nomodified
    if exit_status
    return 'echoerr ' . string(s:SudoError())
    else
    return 'silent doautocmd BufReadPost'
    endif
    endfunction

    function! s:SudoWriteCmd() abort
    silent doautocmd <nomodeline> BufWritePre
    let [silent, cmd] = s:SilentSudoCmd(shellescape('sh -c cat>"$0"'))
    execute silent 'write !' . cmd . ' "%" 2> ' . s:error_file
    let error = s:SudoError()
    if !empty(error)
    return 'echoerr ' . string(error)
    else
    setlocal nomodified
    return 'silent doautocmd <nomodeline> BufWritePost'
    endif
    endfunction

    command! -bar -bang -complete=file -nargs=+ SudoEdit
    \ let s:arg = resolve(expand(<q-args>)) |
    \ call s:SudoSetup(fnamemodify(empty(s:arg) ? @% : s:arg, ':p'), empty(s:arg) && <bang>0) |
    \ if !&modified || !empty(s:arg) || <bang>0 |
    \ exe 'edit<bang>' fnameescape(s:arg) |
    \ endif |
    \ if empty(<q-args>) || expand('%:p') ==# fnamemodify(s:arg, ':p') |
    \ set noreadonly |
    \ endif |
    \ unlet s:arg

    if exists(':SudoWrite') != 2
    command! -bar -bang SudoWrite
    \ call s:SudoSetup(expand('%:p'), <bang>0) |
    \ setlocal noreadonly |
    \ write!
    endif

    command! -bar -nargs=? Wall
    \ if empty(<q-args>) |
    \ call s:Wall() |
    \ else |
    \ call system('wall', <q-args>) |
    \ endif
    if exists(':W') !=# 2
    command! -bar W Wall
    endif
    function! s:Wall() abort
    let tab = tabpagenr()
    let win = winnr()
    let seen = {}
    if !&readonly && &buftype =~# '^\%(acwrite\)\=$' && expand('%') !=# ''
    let seen[bufnr('')] = 1
    write
    endif
    tabdo windo if !&readonly && &buftype =~# '^\%(acwrite\)\=$' && expand('%') !=# '' && !has_key(seen, bufnr('')) | silent write | let seen[bufnr('')] = 1 | endif
    execute 'tabnext '.tab
    execute win.'wincmd w'
    endfunction

    " Adapted from autoload/dist/script.vim.
    let s:interpreters = {
    \ '.': '/bin/sh',
    \ 'sh': '/bin/sh',
    \ 'bash': 'bash',
    \ 'csh': 'csh',
    \ 'tcsh': 'tcsh',
    \ 'zsh': 'zsh',
    \ 'tcl': 'tclsh',
    \ 'expect': 'expect',
    \ 'gnuplot': 'gnuplot',
    \ 'make': 'make -f',
    \ 'pike': 'pike',
    \ 'lua': 'lua',
    \ 'perl': 'perl',
    \ 'php': 'php',
    \ 'python': 'python3',
    \ 'groovy': 'groovy',
    \ 'raku': 'raku',
    \ 'ruby': 'ruby',
    \ 'javascript': 'node',
    \ 'bc': 'bc',
    \ 'sed': 'sed',
    \ 'ocaml': 'ocaml',
    \ 'awk': 'awk',
    \ 'wml': 'wml',
    \ 'scheme': 'scheme',
    \ 'cfengine': 'cfengine',
    \ 'erlang': 'escript',
    \ 'haskell': 'haskell',
    \ 'scala': 'scala',
    \ 'clojure': 'clojure',
    \ 'pascal': 'instantfpc',
    \ 'fennel': 'fennel',
    \ 'routeros': 'rsc',
    \ 'fish': 'fish',
    \ 'forth': 'gforth',
    \ }

    function! s:NormalizeInterpreter(str) abort
    if empty(a:str) || a:str =~# '^[ /]'
    return a:str
    elseif a:str =~# '[ \''"#]'
    return '/usr/bin/env -S ' . a:str
    else
    return '/usr/bin/env ' . a:str
    endif
    endfunction

    function! s:FileTypeInterpreter() abort
    try
    let ft = get(split(&filetype, '\.'), 0, '.')
    let configured = get(g:, 'eunuch_interpreters', {})
    if type(get(configured, ft)) == type(function('tr'))
    return call(configured[ft], [])
    elseif get(configured, ft) is# 1 || get(configured, ft) is# get(v:, 'true', 1)
    return ft ==# '.' ? s:interpreters['.'] : '/usr/bin/env ' . ft
    elseif empty(get(configured, ft, 1))
    return ''
    elseif type(get(configured, ft)) == type('')
    return s:NormalizeInterpreter(get(configured, ft))
    endif
    return s:NormalizeInterpreter(get(s:interpreters, ft, ''))
    endtry
    endfunction

    function! EunuchNewLine(...) abort
    if a:0 && type(a:1) == type('')
    return a:1 . (a:1 =~# "\r" ? "\<C-R>=EunuchNewLine()\r" : "")
    endif
    if !empty(&buftype) || getline(1) !~# '^#!' || line('.') != 2 || getline(2) !~# '^#\=$'
    return ""
    endif
    let b:eunuch_chmod_shebang = 1
    let inject = ''
    let detect = 0
    let ret = empty(getline(2)) ? "" : "\<BS>"
    if getline(1) ==# '#!'
    let inject = s:FileTypeInterpreter()
    let detect = !empty(inject) && empty(&filetype)
    else
    filetype detect
    if getline(1) =~# '^#![^ /].\{-\}[ \''"#]'
    let inject = '/usr/bin/env -S '
    elseif getline(1) =~# '^#![^ /]'
    let inject = '/usr/bin/env '
    endif
    endif
    if len(inject)
    let ret .= "\<Up>\<Right>\<Right>" . inject . "\<Home>\<Down>"
    endif
    if detect
    let ret .= "\<C-\>\<C-O>:filetype detect\r"
    endif
    return ret
    endfunction

    function! s:MapCR() abort
    imap <silent><script> <SID>EunuchNewLine <C-R>=EunuchNewLine()<CR>
    let map = maparg('<CR>', 'i', 0, 1)
    let rhs = substitute(get(map, 'rhs', ''), '\c<sid>', '<SNR>' . get(map, 'sid') . '_', 'g')
    if get(g:, 'eunuch_no_maps') || rhs =~# 'Eunuch' || get(map, 'buffer')
    return
    endif
    if get(map, 'expr')
    exe 'imap <script><silent><expr> <CR> EunuchNewLine(' . rhs . ')'
    elseif rhs =~? '^<cr>' && rhs !~? '<plug>'
    exe 'imap <silent><script> <CR>' rhs . '<SID>EunuchNewLine'
    elseif rhs =~? '^<cr>'
    exe 'imap <silent> <CR>' rhs . '<SID>EunuchNewLine'
    elseif empty(rhs)
    imap <script><silent> <CR> <CR><SID>EunuchNewLine
    endif
    endfunction
    call s:MapCR()

    augroup eunuch
    autocmd!
    autocmd BufNewFile * let b:eunuch_chmod_shebang = 1
    autocmd BufReadPost * if getline(1) !~# '^#!\s*\S' | let b:eunuch_chmod_shebang = 1 | endif
    autocmd BufWritePost,FileWritePost * nested
    \ if exists('b:eunuch_chmod_shebang') && getline(1) =~# '^#!\s*\S' |
    \ call s:Chmod(0, '+x', '<afile>') |
    \ edit |
    \ endif |
    \ unlet! b:eunuch_chmod_shebang
    autocmd InsertLeave * nested if line('.') == 1 && getline(1) ==# @. && @. =~# '^#!\s*\S' |
    \ filetype detect | endif
    autocmd User FileChmodPost,FileUnlinkPost "
    autocmd VimEnter * call s:MapCR() |
    \ if has('patch-8.1.1113') || has('nvim-0.4') |
    \ exe 'autocmd eunuch InsertEnter * ++once call s:MapCR()' |
    \ endif
    augroup END

    " vim:set sw=2 sts=2:
    5 changes: 5 additions & 0 deletions init.vim
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    so eunuch.vim
    autocmd BufEnter,TermOpen term://* startinsert
    nnoremap : q:A
    term /bin/bash
    2 changes: 2 additions & 0 deletions run-nvim.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    #!/bin/sh
    exec nvim --clean -u init.vim
    2 changes: 2 additions & 0 deletions run-nvr.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    #!/bin/sh
    exec nvr --remote -cc split init.vim