How to use execut in vim indentexpr - vim

I tried to use execut in vim indentexpr with under code
function! AddSpace(lnum,str)
while len(getline(a:lnum)) < 80
execut a:lnum . "," . a:lnum . "s/".a:str."/ ".a:str
endwhile
endfunction
function! GetIndent()
if getline(v:lnum) =~ ';'
call AddSpace(v:lnum,";")
endif
...
return ...
endfunction
setlocal indentexpr=GetIndent()
and gg=G.It doesn't work...
Vim just fail into a dead loop...
However,other indent rules works before the loop.
But hen I call it by
call AddSpace(3,";")
It works fine.
Maybe "execut" dosen't work in indentexpr?
Is there still any way to insert space into the file with "execut"?
Or is there better way to finish the insert without cursor moving?
Thanks for your help!

'indentexpr' is not meant to actually modify the text directly. In fact, it is explicitly forbidden to modify the text while evaluating the expression. Error messages are suppressed by default, so you don't get an indication something is wrong, but the expression just stops when it encounters the text modification.
Your AddSpace function should return the number of spaces to add, rather than actually adding the spaces. Note you don't need the while loop, you can just use subtraction to find the number of spaces needed.
See :help 'indentexpr' for details.

Related

Vimscript function to select block then sort

I'm learning Vimscript and I'm trying to select a block of text and sort it via a function. Here's what I got right now:
function! SortFirstBlock()
call setpos(".", [0, 1, 1, 0])
execute "normal! vip:sort<cr>"
endfunction
How I read this is to go to the first position in the buffer via setpos, then with execute, run vip which visually selects a block of text, then :sort to sort that block.
My cursor goes to the first position, but doesn't highlight the block and doesn't sort. What is the proper way to do this?
Almost there, all that's missing is a backslash before <CR>:
function! SortFirstBlock() abort
call cursor(1, 1)
execute "normal! vip:sort\<CR>"
endfunction
<> tokens must be inside a double-quoted string and escaped. The documentation for all such special tokens is at :h expr-string.
Two unrelated suggestions, code review style:
You can use cursor() as a shorthand for setpos(".", ...).
Always use abort to make a function stop as soon as it encounters an error. If you don't use abort Vim will try to continue executing even after an error and you'll get a long and confusing stacktrace.
What's wrong with your function:
setpos() is too verbose, cursor() is simpler,
execute is not necessary because you don't concatenate anything,
you should separate macros and ex commands:
normal! vip
sort
normal! <Esc>
but visual mode is totally unnecessary here anyway.
You should use :sort over the desired range:
function! SortFirstBlock()
1,/^$/-sort
endfunction
Well, I would simply use :1,/^$/-sort<CR> and don't bother with a function, actually… but your example may be part of a larger whole.
I’ve upvoted glts’ answer but I thought I’d offer an alternative solution.
Instead of selecting before sorting, you can simply sort from the position you just moved to (the start of the file) to the line just before the end of the current paragraph:
function! SortFirstBlock()
call setpos(".", [0, 1, 1, 0])
,'}-1sort
endfunction
Nothing before the comma means the start of the range is the current line.
The '} is a predefined mark which indicates the end of the paragraph. The
-1 moves the end-point of the range to be sorted back by one line so that
the blank line at the end of the paragraph isn’t included in the sort. This
range covers the same lines that would be included with a vip in Normal
mode.

Returning to Normal Mode from Insert Mode after "execute normal"

You can perform normal mode commands programmatically in Ex mode, via execute normal, e.g.
:execute "normal" "iNEWTEXT\<Esc>0"
This switches to insert mode (i), writes "NEWTEXT", escapes to normal mode (\< Esc>), then moves to the start of the line (0).
However, using a non-constant string, either a register or variable, the behavior is different. For example, suppose you have the same command above saved on a line in any file (not necessarily a vimscript file):
iNEWTEXT\<Esc>0
You can then copy the text into any register (here, z) via "zy$ and execute the register via #z. This time, though, the output is different:
NEWTEXT\<Esc>0
After entering insert mode, the Escape is no longer treated as a special character, and is instead taken literally. Alternative forms like \e don't work either. Is there a way around this?
EDIT: Using Ingo's answer, I created the the following function. Basically, the use is for having a set of normal/insert commands embedded within the text of the file, and being able to execute them. More commonly, something similar is used for running Ex commands from a line of text, but I couldn't find anything that did this exact thing for normal and insert mode.
So, you'd have text like the following in your file:
jy10j10jpO\<Esc>jEll
When on that line, you could call the function or a remap, and the commands would execute (in this example, copying and pasting 10 lines, and moving 2 columns past the first word). Ingo's alternatives are better for serious usage, namely sourcing commands from another file, having the command in the .vimrc, or a file-type specific option. Macros saved by a session would work just as well, and are more practical than having commands scattered throughout a file. In my case, I was syncing across multiple devices, and didn't want to have another file or clutter my vimrc with this very specific command, but didn't mind cluttering this specific file itself. Think of this like a portable macro.
" Execute current line as Vim normal mode commands.
nnoremap <A-y> :call EvaluateLineAsNormalModeCmd()<CR>
function! EvaluateLineAsNormalModeCmd()
let g:getCurrentLine = getline(".")
"have to :execute twice: once to get the contents of the
"register inserted into a double-quoted string, and then once for
"the :normal to evaluate the string.
execute 'execute "normal" "' . g:getCurrentLine . '"'
endfunction
EDIT2/3: Here are two functions using Christian Brabandt's answer. They work about the same but can put the user in insert mode at the end (whereas, based on my minimal information, 'i' in the other context is considered an incomplete command and not executed, and :startinsert can't be used in that situation). PS: Please don't ask me what all those single and double quotes are doing, as I can't wrap my head around it O_o
function! EvaluateLineAsNormalModeCmd()
normal! 0y$
execute ':call feedkeys("'.#".'", "t")'
endfunction
function! EvaluateLineAsNormalModeCmd()
let g:getCurrentLine = getline(".")
execute ':call feedkeys("'.g:getCurrentLine.'", "t")'
endfunction
If you really need this (the use case is dubious), you have to :execute twice: once to get the contents of the register inserted into a double-quoted string, and then once for the :normal to evaluate the string.
:execute 'execute "normal" "' . #z . '"'
PS: Please give more background; what is your final goal? When a question is only about a small technical step, it's difficult to provide a good answer. If you don't tell us why you want this, it's easy to succumb to the XY problem.
I would rather use the feedkeys() function. E.g. for your sample, this should work:
exe ':call feedkeys("'.#".'", "t")'
(If you yanked your line into the unnamed register, else adjust the register name accordingly). Note, quoting could get ugly.
To understand what is going on, this is what is done:
exe ':call feedkeys(' - First part of the feedkeys() function call
" - Start of Quote for the first argument
. - String concatenation
#" - content of the unnamed register
. - String concatenation
' - Start of second part of the feedkeys function call
" - End of Quote for the first argument
, "t")' - Second argument of feedkeys() function call
You could also do it in 2 steps like this:
exe ':let a="'. #". '"' - Also needs to quote #" correctly.
call feedkeys(a, 't')
which should be easier to understand. The exe call is only to translate the normalized key notation into literal keys.

Comment / uncomment multiple fixed lines in vim

In my code I have multiple scattered lines which help me to debug my program and show me what is going on during execution. Is there an easy and fast way to comment and uncomment (toggle) these fixed lines in vim? I thought about marking these lines with a special sign (e.g. //) like this in python:
print "Debug!" # //
and everytime a sepcific shortcut is pressed all lines which end with a "# 'some optional descriptive text' //" are commented or uncommented, respectively.
I've looked at NERD Commenter, but from what I read the lines to be commented / uncommented have to be selected each time?
First, find a pattern that selects the right lines. If you have :set hls, it will help spot the matches. I think something like /#.*\/\/$ is what you want.
Next, comment out the selected lines with
:g/<pattern>/s/^/# /
if # will comment out the line, and un-comment them with
:g/<pattern>/s/^# //
Now, you want a single command to toggle. You can either use a variable to keep track of the toggle state, or you can try to figure out the current state by examining the lines that match. Using a variable seems simpler.
The variable could be global, local to the buffer, or local to the script. I like using script-local variables in order to avoid cluttering the namespace. In this case, using a script-local variable might mean that vim will get confused when you switch buffers, so let's use a buffer-local variable, say b:commentToggle.
The first time the function is called, it notices that the variable is not set, so use search() to look for a line that starts with # (There is a space there!) and ends with the pattern we already have. The n flag means not to move the cursor, and w means to wrap around the end of the file (like searching with 'wrapscan' set). The search() function returns the line number (1-based!) if the pattern is found, 0 if not. See :help search().
This seems to work in a small test:
fun! CommentToggle()
if !exists('b:commentToggle')
let b:commentToggle = !search('^# .*#.*\/\/$', 'nw')
endif
if b:commentToggle == 1
g/#.*\/\/$/s/^/# /
else
g/#.*\/\/$/s/^# //e
endif
let b:commentToggle = !b:commentToggle
endfun
nnoremap <F4> :call CommentToggle()<CR>
If you want to put # in front of the first non-blank, then use ^\s*# in the search() command; s/\ze\S/# / or s/\S/\1# / in the first :g line; and s/^\s*\zs# // in the second :g line. See :help /\zs, :help /\ze, and :help sub-replace-special.

Comment out code using vimscript

Hi Im trying to write my first vim script. I want to write a function that will comment out PHP code by the block or curly brackets.
Here is what I've come up with, but I can't get it to work:
:function Mycom()
:let b = line(".")
:echo "this is "b
// trying to grab the matching bracket, not sure wheather this is ok
:%
//keeps missing and going to end og file
:let e = line(".")
:echo "here is end" e
//here is where i want to comment out the block
:echo b,e s%^%//%
:endfunction
You shouldn’t put a leading : on each line — unless you’re writing the function in the Vim command line, Vim will add the : automatically for you. (It‘d be better to write your script in a file, though; that way it’s easier to modify and test.)
Comments in Vimscript start with " (a double quote), not //.
If you want to execute a normal mode command, like % or dd, you can use normal! % or normal! dd.
echo b,e s%... won’t work. If you want to echo the text, try echo b.','.e.' s%^%//%'.
Also, consider using echom instead of echo. Because echom saves the message in the message history, you can re-read it later using :mess.
P.S. If your cursor is on an open { (I saw you’re trying to use % in your script), you can comment the block using
ctrl-v%I//<esc>

Vim search and highlighting control from a script

I'm writing a script in which I want to control searches programmatically, and get them highlighted. The search() function results are not highlighted (I think), so using that function is not of use to me.
What I want to do is use the 'normal /' command to search for a variable, but that doesn't seem to be straightforward. I can script the command:
execute 'normal /' . my_variable . '\<CR>'
(or other variations as suggested in the vim tip here: http://vim.wikia.com/wiki/Using_normal_command_in_a_script_for_searching )
but it doesn't do anything. I can see the correct search term down in the command line after execution of the script line, but focus is in the document, the search register has not been altered, and the cursor has not done any search. (It seems as though the < CR > isn't getting entered, although no error is thrown -- and yes, I have tried using the literal ^M too.)
I can at least control the search register by doing this:
execute 'let #/ ="' . a:term .'"'
and then the obvious thing seems to be to do a:
normal n
But that 'normal n' doesn't do anything if I run it in a script. Setting the search register does work, if I manually press 'n' after the scrip terminates the search happens (and highlighting appears, since hlsearch is on). I don't even care if the cursor is positioned, I just want the register pattern to be highlighted. But various combinations of 'set hlsearch' in the script don't work either.
I know I could use 'match()', but I want to get it working with regular search highlighting, and I wonder what I'm doing wrong. It must be something simple but I'm not seeing it. Thanks for any help.
run:
let #/ = a:searchStr
from inside your function then run
normal n
from outside your function (inside it does nothing) eg.
command -nargs=* Hs call MySearch() | normal n
or you can use:
set hlsearch
instead of normal n if you don't want the cursor to move
(I cannot work out another way of doing this without having something outside the function.)
If your script is using functions, then this quote from :help function-search-undo is relevant:
The last used search pattern and the redo command "."
will not be changed by the function. This also
implies that the effect of :nohlsearch is undone
when the function returns.
Vim usually tries to reset the search pattern (and a few other things) when a function ends, often you can get around this by adding the n (next search) to the end of a mapping, or using :map <expr> and having your function return the key sequence to be executed.
On closer inspection, it seems \<CR> is not picked up inside single quotes. Try using this instead:
execute 'normal /' . my_variable . "\<CR>"

Resources