# How to define an operator which is updating while it is taking input?

I have defined an operator mapping which takes a region of text, then asks for an input string and then aligns the region with Tabular using the input string as an argument. It works nicely.

I have implemented it like this, using vim-operator-user to help defining a new operator:

map \aa <Plug>(operator-align)
call operator#user#define('align', 'Align')
function! Align(motion_wiseness)
let expr = input("align: ")
if len(expr) != 0
execute "'[,']Tabularize /".expr
endif
endfunction

function! Getchar()
let c = getchar()
if c =~ '^\d\+\$'
let c = nr2char(c)
endif
return c
endfunction


Then I wondered if I could make it update on the fly while entering the regular expression to align with. The problem with the current approach is that you have to undo and then redo if you are not using the correct expression.

For the interactive attempt, I made this:

map \== <Plug>(operator-align-interactive)
call operator#user#define('align-interactive', 'AlignInteractive')
function! AlignInteractive(motion_wiseness)
let prompt = "Align: "
echon prompt
let expr = ""
let c = Getchar()
" CR has to be checked for separately as it acts as putting the cursor back to zero position
while c != "\<Esc>" && c != "\<CR>"
if c == "\<BS>"
if len(expr) != 0
let expr = expr[0:-2]
echon "\<CR>".substitute(expr, ".", " ", "g")
echon "\<CR>".prompt.expr
endif
else
let expr .= c
echon c
let cmd = "'[,']Tabularize /".expr
execute cmd
endif
let c = Getchar()
endwhile
endfunction


It should work but the aligning is not done before I hit enter, that is after I have finished entering input, effectively meaning that it works the same way as the non-interactive function. I wonder if the problem could be something like the screen / content not being updated during an operator execution, only after.

Any ideas on what the problem could be is appreciated!

You need to undo and redraw if you want to see changes in the buffer right away.

Here's what works:

function! AlignInteractive(motion_wiseness)
let prompt = "Align: "
let undo = 0
let expr = ""
let range = line("'[").','.line("']")
let failed = 0
let accept = 0

echon prompt
let c = Getchar()

while c != "\<Esc>" && c != "\<c-c>"
let undo = len(expr)

if c == "\<CR>"
let accept = 1
break
elseif c == "\<BS>"
if len(expr)
let expr = expr[0:-2]
endif
else
let expr .= c
endif

if undo && !failed
silent! undo
endif

if len(expr)
try
call match('', expr)
let failed = 0
execute range."Tabularize /".expr
catch //
let failed = 1
endtry
endif

redraw

echon prompt
if failed
echohl ErrorMsg
endif
echon expr
echohl None
let c = Getchar()
endwhile

if !accept && len(expr) && !failed
silent! undo
endif
endfunction


## Explanation

The range variable stores the '[ and '] marks. This is for sanity's sake.

A variable called undo is set based on the previous length of expr. This means whenever there is input from the user, the next iteration can safely undo before executing Tabularize.

call match('', expr) tests the expression for pattern errors. If it fails, undo should not be executed. Pattern failures can happen as you're typing atoms such as \zs.

redraw will clear the command line. This is why the full prompt is printed on every iteration.

If the pattern contains an error, it is highlighted with ErrorMsg.

accept is set when <cr> is pressed. If it's false, undo the changes (if any). Anything else that breaks the loop cancels.

## Existing plugin

There is a plugin called vim-easy-align that can do what you're attempting. You could draw inspiration from it's script.

• Thank you so much for the clear explanations! I didn't know that easy-align could do that. The undo functionality was the next thing that I wanted to implement, but I got stuck at the updating. – tusj Jun 30 '16 at 15:31
• No problem 🙂 I just updated the answer to highlight the pattern if it has an error. – Tommy A Jun 30 '16 at 15:40
• There is a tiny bug in the current implementation, due to non-written requirement: if Esc or <C-C> has been typed, the operation should be cancelled. I have fixed the first case by adding: if c == "\<Esc>" && undo silent! undo endif I am not sure how to detect for <C-C> though. – tusj Jun 30 '16 at 17:02
• @tusj I updated the answer. – Tommy A Jun 30 '16 at 18:14