├── README.md └── plugin └── incnormal.vim /README.md: -------------------------------------------------------------------------------- 1 | Incremental :normal 2 | ==== 3 | 4 | Experiment with preview of `:normal` commands. Also probably the most insane multi-cursor implementation for nvim. 5 | 6 | To activate, use `:[RANGE]norma ` to place a cursor at the beginning of every line in `[RANGE]`. (As a safe guard, it only activates on this spelling, not `:normal` nor `:norm`) Then type normal-code commands completely _as normal_. 7 | 8 | Demonstrates: 9 | 10 | - Abusing `` and timers to execute commands in command-line mode. 11 | - Abusing `g:Nvim_color_cmdline` and timers to trigger code on cmdline change 12 | - Using `redraw!` to display a temporary buffer state 13 | - Some crashes and weird behavior that needs investigation 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /plugin/incnormal.vim: -------------------------------------------------------------------------------- 1 | let g:s_state_save = [] 2 | let g:s_active = v:false 3 | let g:s_buf = -1 4 | let g:s_win = -1 5 | let g:s_suspended = 0 6 | let g:s_cursors = [] 7 | 8 | " fake exectue in an expr mapping, emulates #4419 9 | func! Fakexecute(cmd,unsandbox) 10 | if a:unsandbox 11 | " 'Note how execute() is used to execute an Ex command. That's ugly though.' 12 | call timer_start(0,{x -> execute(a:cmd)}) 13 | else 14 | exec a:cmd 15 | endif 16 | return '' 17 | endfunc 18 | 19 | function! s:saveCur() 20 | " from matchit 21 | let restore_cursor = virtcol(".") . "|" 22 | normal! g0 23 | let restore_cursor = line(".") . "G" . virtcol(".") . "|zs" . restore_cursor 24 | normal! H 25 | let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor 26 | execute restore_cursor 27 | return restore_cursor 28 | endfunction 29 | 30 | cnoremap (incnormal-suspend) Fakexecute("let g:s_suspended = !g:s_suspended", 0) 31 | cmap (incnormal-suspend) 32 | 33 | map (incnormal-cursor) incnormal#cursor() 34 | map! (incnormal-cursor) incnormal#cursor() 35 | 36 | func! incnormal#cursor() 37 | call add(g:s_cursors, getpos('.')) 38 | return '' 39 | endfunc 40 | 41 | " handy debug helper 42 | cnoremap Fakexecute("let g:copy = deepcopy(g:)", 1) 43 | 44 | func! incnormal#enter() 45 | "let g:lastenter = deepcopy(v:event) 46 | call add(g:s_state_save, get(g:, "Nvim_color_cmdline", 0)) 47 | if v:event.cmdlevel == 1 && v:event.cmdtype == ":" 48 | let g:Nvim_color_cmdline = "incnormal#callback" 49 | let g:s_active = v:true 50 | else 51 | if has_key(g:, "Nvim_color_cmdline") 52 | call remove(g:, "Nvim_color_cmdline") 53 | endif 54 | endif 55 | endfunc 56 | 57 | func! incnormal#leave() 58 | if get(g:,"Nvim_color_cmdline",0) == "incnormal#callback" 59 | call Fakexecute("call incnormal#stop()", 1) 60 | end 61 | 62 | let oldstate = remove(g:s_state_save, -1) 63 | let g:Nvim_color_cmdline = oldstate 64 | if oldstate == 0 65 | call remove(g:, "Nvim_color_cmdline") 66 | elseif oldstate == "incnormal#callback" 67 | let g:s_active = v:true 68 | end 69 | endfunc 70 | 71 | func! incnormal#start() 72 | let oldwin = nvim_get_current_win() 73 | 2new 74 | " TODO: reuse buffer 75 | set buftype=nofile 76 | set nobuflisted 77 | file [incnormal] 78 | let g:s_win = nvim_get_current_win() 79 | let g:s_buf = nvim_get_current_buf() 80 | call nvim_set_current_win(oldwin) 81 | redraw! 82 | endfunc 83 | 84 | func! incnormal#stop() 85 | let g:s_active = v:false 86 | " TODO: save and restore window layout 87 | if g:s_win != -1 88 | let oldwin = nvim_get_current_win() 89 | call nvim_set_current_win(g:s_win) 90 | quit! 91 | call nvim_set_current_win(oldwin) 92 | redraw! 93 | let g:s_win = -1 94 | endif 95 | 96 | endfunc 97 | 98 | let g:s_src_id = nvim_buf_add_highlight(0, 0, "", 0,0,0) 99 | 100 | func! incnormal#doit() 101 | let g:s_cursors = [] 102 | let tick = b:changedtick 103 | let cur = "" 104 | if g:s_suspended 105 | let status = "SUSPENDED" 106 | else 107 | let cur = s:saveCur() 108 | execute g:s_cmdline."\(incnormal-cursor)" 109 | let status = "N" 110 | if b:changedtick != tick 111 | let status = "Y" 112 | endif 113 | endif 114 | call nvim_buf_set_lines(g:s_buf,0,-1,v:true,[status]) 115 | 116 | if len(g:s_cursors) 117 | for c in g:s_cursors 118 | call nvim_buf_add_highlight(0, g:s_src_id, "IncCursor", c[1]-1, c[2]-1, c[2]) 119 | endfor 120 | endif 121 | redraw! 122 | if b:changedtick != tick 123 | undo 124 | endif 125 | if len(g:s_cursors) 126 | call nvim_buf_clear_highlight(0, g:s_src_id, 0, -1) 127 | end 128 | execute cur 129 | endfunc 130 | 131 | let g:s_scheduled = v:false 132 | let g:s_changed = v:false 133 | 134 | func! incnormal#callback(cmdline) 135 | let g:s_changed = v:true 136 | let g:s_cmdline = a:cmdline 137 | if !g:s_scheduled 138 | let g:s_timer = timer_start(0, "incnormal#timer") 139 | let g:s_scheduled = v:true 140 | end 141 | if len(a:cmdline) >= 6 142 | return [[3, 6, "Comment"]] 143 | end 144 | return [] 145 | endfunc 146 | 147 | func! incnormal#checkcmd() 148 | " TODO: handle named marks 149 | " TODO: g/blargh/normal 150 | " TODO: also incsearch for g/blarg 151 | let match = match(g:s_cmdline, '\v^[^a-zA-Z]*norma!? .') 152 | return match >= 0 153 | endfunc 154 | 155 | func! incnormal#timer(timerid) 156 | let g:s_scheduled = v:false 157 | if g:s_active && incnormal#checkcmd() 158 | if g:s_win == -1 159 | call incnormal#start() 160 | end 161 | call incnormal#doit() 162 | end 163 | endfunc 164 | 165 | hi IncCursor cterm=reverse gui=reverse 166 | 167 | augroup IncNormal 168 | au! 169 | au CmdlineEnter * call incnormal#enter() 170 | au CmdlineLeave * call incnormal#leave() 171 | augroup END 172 | --------------------------------------------------------------------------------