Skip to content

Instantly share code, notes, and snippets.

@jneen
Created July 14, 2022 02:03
Show Gist options
  • Save jneen/5de9c3dd46c82b5cc3badb70d21fc8b1 to your computer and use it in GitHub Desktop.
Save jneen/5de9c3dd46c82b5cc3badb70d21fc8b1 to your computer and use it in GitHub Desktop.
neovim EunuchNewLine minimal repro

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 brew@HMBRW-A-001-M1-004.local

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)]
" 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:
so eunuch.vim
autocmd BufEnter,TermOpen term://* startinsert
nnoremap : q:A
term /bin/bash
#!/bin/sh
exec nvim --clean -u init.vim
#!/bin/sh
exec nvr --remote -cc split init.vim
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment