Vim vmap, send selected text as parameter to function - function

In brief, having written some functions to save typing I'm trying to set up a vmap[ping] that will allow me to select something I typed, and pass this selection to a function (since typing the function call on the command line, typing the parameters (with quotes), and escaping backslashes etc... counteracts most of the time saved by calling functions)
For (a simple) example, supposing I had the following function
func Test(iStr)
if a:iStr[0] =~ [a-zA-z]
echo "hello"
else
echo "hello world"
endif
endfunc
I'd like to be able to visual select some text and then with some key mapping, F2 say, it will call Test(iStr) with the selection serving as the parameter iStr
I believe, that with more work (i.e. some way to specify that what I've selected should be inside Test()), the following
vmap <F2> :call Test()
is what I'm after. Thing is I've tried a number of variants (guesswork plus a little bit of dodgy inference from :help map) and I'm not getting anything useful. Update, I've tried using a helper function Test2() with just
call Test(<C-W>)
as its body...somehow I think I need to grab hold of the word under my cursor (somehow) and then I'm done - since I can pass that to Test(...) from within Test2
As for an actual example of a function I want to call, the following (incomplete yet) function (and helper functions) would allow me to convert an expression of the form, say,
f_k^{(j)}g
to
f_1^{(j)}g, f_2^{(j)}g, \dots, f_{n-1}^{(j)}g, f_n^{(j)}g
In terms of a procedure I want to
a) type the repeated term in vim
b) visually select it
c) hit some mapping key that will call SumOrSequence(iExpression, iIndex)
d) provide "k" as a parameter
e) press enter
f) see the change made by SumOrSequence(...)
The code for SumOrSequence(...) is as follows:
func SumOrSequence(iExpression, iIndex)
"need to check validity of these - maybe set a default
let default = Interrogate("do with defaults? yes [y] (2,1,n,0,\",\"), yes but specify last term [d[a-Z]], no [n]")
if default == "y"
let leftTerms = 2
let rightTerms = 1
let lastTermIndex = "n"
let firstTermIndex = 0
let operator = ","
let dotType = "\\dots"
elseif default =~ 'd[a-zA-Z]'
let leftTerms = 2
let rightTerms = 1
let lastTermIndex = default[1]
let firstTermIndex = 0
let operator = Interrogate("what separates terms? add [+], subtract [-], times [*], comma [,], ampersand [&]?")
let dotType = "\\cdots"
else "so n or anything else
let leftTerms = InterrogateNumber("how many terms before dots? ")
let rightTerms = InterrogateNumber("how many terms after dots? ")
let lastTermIndex = Interrogate("what is last term index?")
let firstTermIndex = Interrogate("what is first term index?")
let operator = Interrogate("what separates terms? add [+], subtract [-], times [*], comma [,], ampersand [&]?") "need to check only any of these provided
let dotType = "\\cdots"
endif
if operator == ","
let dotType = "\\dots"
endif
if operator == "*"
let operator = "\\times"
endif
let leftCount = 0
let oExpression = ""
while leftCount < leftTerms
if leftCount > 0
let oExpression .= operator . " "
endif
let oExpression .= ReplaceIndex(a:iExpression, a:iIndex, leftCount,1)
let leftCount += 1
endwhile
let oExpression .= operator . " " . dotType . " "
let rightCount = rightTerms-1
while rightCount > 0
"here we are going to be counting backwards from some number denoting number of terms - may need to know if we actually have a number!
echo "decrement: " . HandleDecrement(lastTermIndex, rightCount)
let oExpression .= operator . " " . ReplaceIndex(a:iExpression, a:iIndex, HandleDecrement(lastTermIndex, rightCount),1)
let rightCount -= 1
endwhile
let oExpression .= operator . " " . ReplaceIndex(a:iExpression, a:iIndex, lastTermIndex,0)
echo oExpression
endfunc
func ReplaceIndex(iExpression, iIndex, iReplacement, iInsertBraces)
"the game we play here is to search for iIndex in such form that it is not part of any other string
"We should expect this to be the case if the character to the left or right of the index is not in [A-z] (or just to the right if a greek char)
let oExpression = ""
let strEndPosition = strlen(a:iExpression) - 1
let currPosition = 0
let indexLen = strlen(a:iIndex)
while currPosition <= strEndPosition
let indexCounter = 0
let foundIndex = 1
while indexCounter < indexLen
if a:iExpression[currPosition + indexCounter] == a:iIndex[indexCounter]
if a:iExpression[currPosition + indexLen] =~ '[a-zA-Z]'
let foundIndex = 0
let indexCounter = indexLen
elseif a:iExpression[currPosition -1] =~ '[a-zA-Z]' && a:iExpression[currPosition] != "\\"
let foundIndex = 0
let indexCounter = indexLen
else
let indexCounter+=1
endif
else
let indexCounter = indexLen
let foundIndex = 0
endif
endwhile
if foundIndex == 0
let oExpression .= a:iExpression[currPosition]
let currPosition+=1
else
if a:iInsertBraces == 1
let oExpression .= "{" . a:iReplacement . "}"
else
let oExpression .= a:iReplacement
endif
let currPosition+=indexLen
endif
endwhile
echo "oExpression: " . oExpression
return oExpression
endfunc
func HandleIncrement(iExpression, iIncrement)
"and what about negative numbers for iExpression!??? not handling these yet :[
let oExpression = ""
if !(a:iExpression[0] =~ '[0-9]') || a:iExpression < 10 && strlen(a:iExpression) > 1
let oExpression = a:iExpression . " + " . a:iIncrement
else
let oExpression = a:iExpression + a:iIncrement
endif
echo oExpression
return oExpression
endfunc
func HandleDecrement(iExpression, iIncrement)
"TODO and what about negative numbers for iExpression!??? not handling these yet :[
let oExpression = ""
if !(a:iExpression[0] =~ '[0-9]') || a:iExpression < 10 && strlen(a:iExpression) > 1
let oExpression = a:iExpression . " - " . a:iIncrement
else
let oExpression = a:iExpression - a:iIncrement
endif
echo oExpression
return oExpression
endfunc
func Interrogate(iQuestion)
call inputsave()
let answer = input(a:iQuestion)
call inputrestore()
return answer
endfunc
func InterrogateNumber(iQuestion)
call inputsave()
let answer = input(a:iQuestion)
call inputrestore()
"TODO what if negative number??
if !(answer[0] =~ '[0-9]')
let answer = InterrogateNumber(a:iQuestion . " you didn't enter a numerical value ")
endif
return answer
endfunc
As regards the mapping bit, I know it looks like I haven't done too much work but assuming I have lots more digging ahead of me to find the answer myself, can anyone help?
Update. Ok, I have something that works in a clumsy sort of way, i.e. if I define the following helperfunction:
func SumOrSequenceHelper()
let oIndex = Interrogate("index variable? ")
"go to last thing visually selected (I think!), yank it (putting it in the " register), then fetch it via oParam. Then pass this off to SumOrSequence
execute "normal! gvy"
let oExpression = getreg('"')
call SumOrSequence(oExpression, oIndex)
endfunc
vnoremap <F6> :call SumOrSequenceHelper()
then all is well, and I can just do an execute command to replace what I selected with what I got from SumOrSequence(...)
Would be grateful for any improvements but for all intents and purposes this one is solved :]

You could use a helper function like this:
func! GetSelectedText()
normal gv"xy
let result = getreg("x")
normal gv
return result
endfunc
vnoremap <F6> :call MyFunc(GetSelectedText())<cr>
There is also :com -range, which can register a custom command that operates on a selection, but the interface is line oriented.

Use the selection register to call the function with whatever you have visually selected.
vnoremap <F6> :call Test(#*)<CR>
If you just want to grab hold of the word under the cursor in Normal mode you can use this, which yanks to the selection register then uses the value. (You could yank to any named register instead , e.g. the a register with "ay and #a.)
noremap <S-F6> "*yaw:call Test(#*)<CR>
By the way these don't work with your Test function, but just calling it doesn't seem to work either?
:call Test("fred")
Error detected while processing function Test:
line 1:
E121: Undefined variable: a
E15: Invalid expression: a:iStr[0] =~ [a-zA-z]
They work with this test function:
function Test(iStr)
echo a:iStr
endfunction

Related

How to the display the number of instances of a string in vim statusline

A while ago I got to find a function to use in .vimrc to show if there ocurrances of " TODO " in the current buffer and show TD in the statusline. This is the function:
...
hi If_TODO_COLOR ctermbg=0 ctermfg=175 cterm=bold
set statusline+=%#If_TODO_COLOR#%{If_TODO()}
...
function! If_TODO()
let todos = join(getline(1, '$'), "\n")
if todos =~? " TODO "
return " TD "
else
return ""
endif
endfunction
My question is how can I modify the function to also return how many times the string has appeared in the buffer - somthing like TD (6).
You could :help filter() the lines to get a list of lines containing TODO:
let lines = getline(1, '$')
let todos = filter(lines, 'v:val =~? " TODO "')
return len(todos) > 0 ? 'TD' : ''
The same thing, expressed in a slightly more "modern" way:
return getline(1, '$')
\ ->filter('v:val =~? " TODO "')
\ ->len() > 0 ? 'TD' : ''
See :help method.

Meaning of ":" and "?"

I found semicolons & question marks in a Return statement of an AutoIt script:
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
Func A()
;do somethingA
EndFunc
Func B($a,$b,$c)
;do somethingB
EndFunc
Func C($a,$b,$c,$d)
;do somethingC
EndFunc
Func CallFunc( $f, $a = Default, $b = Default, $c = Default, $c = Default )
Return FuncName($f) = "A" ? $f() : FuncName($f) = "B" ? $f($a,$b,$c) : $f($a,$b,$c,$d)
EndFunc
Example()
Func Example()
CallFunc( A )
CallFunc( B, 1, 2, 3 )
CallFunc( C, 1, 2, 3, 4 )
EndFunc
I know FuncName() returns the name of a function stored in a variable, but I don't know what question-marks & semicolons mean in this Return statement:
Return FuncName($f) = "A" ? $f() : FuncName($f) = "B" ? $f($a,$b,$c) : $f($a,$b,$c,$d)
I don't know question-marks & semicolons meanings
As per Documentation - Keywords - Ternary operator:
Conditionally chooses one of two responses based on the result of an expression.
For example;
Return $g_bBlockInput ? $MOE_BLOCKDEFPROC : $MOE_RUNDEFPROC
is functionally equivalent to:
If $g_bBlockInput Then
Return $MOE_BLOCKDEFPROC
Else
Return $MOE_RUNDEFPROC
EndIf
So
Return FuncName($f) = "A" ? $f() : FuncName($f) = "B" ? $f($a,$b,$c) : $f($a,$b,$c,$d)
equals:
If FuncName($f) = "A" Then
Return $f()
Else
If FuncName($f) = "B" Then
Return $f($a,$b,$c)
Else
Return $f($a,$b,$c,$d)
EndIf
EndIf
Whatever that code's purpose seems a case for Switch...Case...EndSwitch instead. Popular use of ternary operator includes conditional assignment. Example:
Global Const $g_bState = True
Global Const $g_sState = $g_bState ? 'ON' : 'OFF'
ConsoleWrite('$g_bState = ' & $g_sState & #CRLF)

Functions don't operate correctly (AutoIt)

So i am trying to make a function out of many functions (i believe it is called recursion, read a post about it earlier on this forum)
When i try to make a number of things into 1 big function so i can call upon it later it doesn't seem to be working but when i take away the "func _hello()" and the "endfunc" from the end, everything seems to be working fine. Can someone please explain this to me. I know the problem is occurring because of the the "Conversion" function but i can't seem to understand why this is happening. Please help, language used here is AutoIt
;;;****Program adds spaces *****
;;;***** the input variable here is $New*****
Global $final
Global $Hexadec
Func _hello()
$DataToBeDecrypted = "55fdaf fdafd"
$2space = $DataToBeDecrypted
$New = $2space
$AddingSpace = StringSplit($New, "")
$Final = ""
If Conversion($AddingSpace[0]) Then
For $Spacing = 1 to $AddingSpace[0] Step 2
$Final = $Final & $AddingSpace[$Spacing] & $AddingSpace[$Spacing+1] & " "
Next
MsgBox(0, "Adding space to the message so it can be converted back to Hex", $Final)
Else
MsgBox(0, "Result", "String does not contain an even number of characters.")
EndIf
Func Conversion($Hexadec)
Return Mod($Hexadec, 2) = 0
EndFunc
;;;***The final value is stored in the $final variable****
;***** Hexadecimals to ASCII*****
;;***Input variable is $HexadecimaltoASCII2******
$HexadecimalToASCII2 =$final
$HexadecimalsToASCII = ChrH($HexadecimalToASCII2)
$Ascii2Hex = Sub($HexadecimalsToASCII)
$v5ar = Chr($HexadecimalsToASCII);char
MsgBox(0,"Hex to ASCII",$HexadecimalsToASCII)
Func ChrH($v8)
Local $v5=""
$A1 = StringSplit($v8, " ")
For $count = 1 To $A1[0]
$v5 &= Chr(Dec($A1[$count]))
Next
Return $v5
endFunc
Func Sub($v8)
Local $v9=""
For $count = 1 To StringLen($v8)
If StringLen(Hex(Asc(StringMid($v8, $count, 1)),2)) = 1 Then
$v9 &= "0" & Hex(Asc(StringMid($v8, $count, 1)))
Else
$v9 &= Hex(Asc(StringMid($v8, $count, 1)),2)
EndIf
If $count <> StringLen($v8) Then $v9 &= " "
Next
Return $v9
endFunc
;*****HEXADECIMAL to ASCII*****
EndFunc
It seems like you never call your Hello() function. To execute a function you have to call it.
Try adding Hello() at the top of the file and it should work.

Vim: Show current json path

I'm editing a very large, nested JSON doc (rightscale api incase you're interested) in vim and would love to know the current json-path (like xpath for json) Something like:
Given the JSON:
{
"leve1": {
"level2": {
"level3": {
"name": "goes here"
}
}
}
}
With my cursor between "name": and "goes here" i'd like a command (or status line) that shows me:
level1/level2/level3/name
or similar.
Does anything like this exist?
I recently wrote a plugin for this called vim-jsonpath. It currently provides the following commands (that of course can be mapped):
:JsonPath: Echoes the path to the identifier under the cursor.
:JsonPath path.to.prop: Searches the active buffer for the given path, placing the cursor on it if found.
I've written two mappings that use the fold information (so they should work with any structure, not just JSON). For your example, they output
{ / "leve1": { / "level2": { / "level3": {
and (long version):
1 {
2 "leve1": {
3 "level2": {
4 "level3": {
Here is the scriptlet. It depends on my ingo-library plugin.
" [count]z? Print all lines (with numbers) that start a fold where
" the current line is contained in (for [count] upper
" levels). When a line consists of just a symbol like "{",
" the preceding non-empty line is printed, too.
" [count]z/ Like z?, but use a short output format with all line
" contents concatenated, and without line numbers and
" symbols.
if ! exists('g:PrintFoldHierarchySymbolLinePattern')
let g:PrintFoldHierarchySymbolLinePattern = '^\s*{\s*$'
endif
function! s:PrintFoldHierarchy( count, isJoin )
if foldclosed('.') != -1
return 0
endif
let l:save_view = winsaveview()
let l:levels = []
let l:lnum = line('.')
while (a:count ? len(l:levels) < a:count : 1)
silent! normal! [z
if line('.') == l:lnum
break
endif
let l:lnum = line('.')
call insert(l:levels, l:lnum)
if getline(l:lnum) =~# g:PrintFoldHierarchySymbolLinePattern
let l:precedingLnum = prevnonblank(l:lnum - 1)
if l:precedingLnum > 0
if a:isJoin
let l:levels[0] = l:precedingLnum
else
call insert(l:levels, l:precedingLnum)
endif
endif
endif
endwhile
call winrestview(l:save_view)
if a:isJoin
echo
let l:isFirst = 1
for l:lnum in l:levels
if l:isFirst
let l:isFirst = 0
else
echohl SpecialKey
echon ' / '
echohl None
endif
echon ingo#str#Trim(getline(l:lnum))
endfor
else
for l:lnum in l:levels
echohl LineNr
echo printf('%' . (ingo#window#dimensions#GetNumberWidth(1) - 1) . 'd ', l:lnum)
echohl None
echon getline(l:lnum)
endfor
endif
return 1
endfunction
nnoremap <silent> z? :<C-u>if ! <SID>PrintFoldHierarchy(v:count, 0)<Bar>execute "normal! \<lt>C-\>\<lt>C-n>\<lt>Esc>"<Bar>endif<CR>
nnoremap <silent> z/ :<C-u>if ! <SID>PrintFoldHierarchy(v:count, 1)<Bar>execute "normal! \<lt>C-\>\<lt>C-n>\<lt>Esc>"<Bar>endif<CR>
You can put that into your ~/.vimrc (or a separate ~/.vim/plugin/PrintFoldHierarchy.vim), and invoke the mappings from normal mode via z? and z/.

Word Count printed in Vim document

I'd like to add to my .vimrc file a function which updates the text in an open document, specifically where it finds the text "Word Count: " it would use vim to insert an accurate word count in the current document.
This is mostly as a programming exercise and to better learn vim, I know there are external programs like wc available to do this work.
Here's an example of a similar function I'm using to count lines of code:
function! CountNonEmpty()
let l = 1
let char_count = 0
while l <= line("$")
if len(substitute(getline(l), '\s', '', 'g')) > 3
let char_count += 1
endif
let l += 1
endwhile
return char_count
endfunction
function! LastModified()
if &modified
let save_cursor = getpos(".")
let n = min([15, line("$")])
keepjumps exe '1,' . n . 's#^\(.\{,10}LOC:\).*#\1' .
\ ' ' . CountNonEmpty() . '#e'
call histdel('search', -1)
call setpos('.', save_cursor)
endif
endfun
autocmd BufWritePre * call LastModified()
Can someone help me figure out how to add to the LastModified function so that it inserts a word count where it finds the text Word Count in the header?
After some more digging I found the answer. This is code from Michael Dunn, another StackOverflow user, posted at Fast word count function in Vim
I'll post how I incorporated it here in case anyone else finds this portion of my .vimrc to be useful:
function! CountNonEmpty()
let l = 1
let char_count = 0
while l <= line("$")
if len(substitute(getline(l), '\s', '', 'g')) > 3
let char_count += 1
endif
let l += 1
endwhile
return char_count
endfunction
function WordCount()
let s:old_status = v:statusmsg
exe "silent normal g\<c-g>"
let s:word_count = str2nr(split(v:statusmsg)[11])
let v:statusmsg = s:old_status
return s:word_count
endfunction
" If buffer modified, update any 'Last modified: ' in the first 20 lines.
" 'Last modified: ' can have up to 10 characters before (they are retained).
" Restores cursor and window position using save_cursor variable.
function! LastModified()
if &modified
let save_cursor = getpos(".")
let n = min([15, line("$")])
keepjumps exe '1,' . n . 's#^\(.\{,10}LOC:\).*#\1' .
\ ' ' . CountNonEmpty() . '#e'
keepjumps exe '1,' . n . 's#^\(.\{,10}Word Count:\).*#\1' .
\ ' ' . WordCount() . '#e'
call histdel('search', -1)
call setpos('.', save_cursor)
endif
endfun
autocmd BufWritePre * call LastModified()