Vim E488: Trailing Characters in function - vim

I'm getting a trailing characters error for the 'for loop' in the following lines of code and I've no clue why.
function! s:HashID(str) "{{{
let l:hash_id = 0
for i in split(a:str, '\zs')
l:hash_id += float2nr(pow(2, stridx('abcdefg', i)))
endfor
return l:hash_id
endfunction
What am I goofing up here?

You need let command to assign value to variable.

Related

vim null symbol effecting string ^#

I am using the following function to get the Selected test
let s:drawscript = "somerandom.py"
func! GetSelectedText()
normal gv"xy
let result = getreg("x")
normal gv
return result
endfunc
vnoremap <tab><tab> :<c-u>call Box(GetSelectedText())<CR>
func! Box(text)
let s:b = '"' . a:text . '"'
echom s:b
" exec boxcmd
"echom 'hi'
let c = ["python3", s:drawscript, s:b]
execute ":.!".join(c, " ")
endfunc
I am trying to pass in the text selected to my python file, it works when I only select 1 line, but when I select multiple lines, there are "^#" symbols in the selected text which caused automatic execution which leads to an error. I just wanna pass in the text I have selected into the .py file.
It's a matter of escaping special characters for the shell. While you correctly thought of quoting the text, you missed to escape the line separator. There's the function shellescape() which takes care of this and more, so you can replace
let s:b = '"' . a:text . '"'
by
let s:b = shellescape(a:text, 1)

Yanking all marked lines in vim

Often times when reviewing log files in vim, I'll highlight interesting lines using marks. At some point, I'd like to be able to copy all of the interesting lines (either all marked lines, or a list of marks) to either a register or another file (it doesn't really matter which; the goal is to facilitate writing a summary). I haven't been able to find any built in way to do this; is it possible in vim?
I suppose it's probably a fairly straightforward function; probably looking something like this, but my vimscript abilities are very weak:
for cur_mark in list_of_marks
goto mark
yank current line and append to register
Has anyone ever written anything similar that they can point me to?
Thanks
EDIT: I posted the accepted solution at https://github.com/mikeage/vim-yankmarks
As always, there are few things that are more motivating than asking for help. Here's what I came up with; feedback welcome.
function! Yankmark()
let save_cursor = getpos(".")
let n = 0
" I should really make this a parameter...
let marks_to_yank="abcdefghijklmnopqrstuvwxyz"
let nummarks = strlen(marks_to_yank)
" Clear the a register
let #a=''
while n < nummarks
let c = strpart(marks_to_yank, n, 1)
" Is the mark defined
if getpos("'".c)[2] != 0
" using g' instead of ' doesn't mess with the jumplist
exec "normal g'".c
normal "Ayy
endif
let n = n + 1
endwhile
call setpos('.', save_cursor)
endfunction
Mikeage had a great idea; here's a more refined version of his function turned into a command:
":YankMarks [{marks}] [{register}]
" Yank all marked (with [a-z] / {marks} marks) lines into
" the default register / {register} (in the order of the
" marks).
function! s:YankMarks( ... )
let l:marks = 'abcdefghijklmnopqrstuvwxyz'
let l:register = '"'
if a:0 > 2
echohl ErrorMsg
echomsg 'Too many arguments'
echohl None
return
elseif a:0 == 2
let l:marks = a:1
let l:register = a:2
elseif a:0 == 1
if len(a:1) == 1
let l:register = a:1
else
let l:marks = a:1
endif
endif
let l:lines = ''
let l:yankedMarks = ''
for l:mark in split(l:marks, '\zs')
let l:lnum = line("'" . l:mark)
if l:lnum > 0
let l:yankedMarks .= l:mark
let l:lines .= getline(l:lnum) . "\n"
endif
endfor
call setreg(l:register, l:lines, 'V')
echomsg printf('Yanked %d line%s from mark%s %s',
\ len(l:yankedMarks),
\ len(l:yankedMarks) == 1 ? '' : 's',
\ len(l:yankedMarks) == 1 ? '' : 's',
\ l:yankedMarks
\) . (l:register ==# '"' ? '' : ' into register ' . l:register)
endfunction
command! -bar -nargs=* YankMarks call <SID>YankMarks(<f-args>)
A different way of accomplishing this might be using the :global command. The global command takes the form :g/{pattern}/{cmd}. The command, {cmd}, will be executed on all lines matching {pattern}.
Append lines matching a pattern to a register:
:g/pattern/yank A
Append matching line to a log file:
:g/pattern/w >> file.log
Of course if you want to find line matching a mark you can match it in your pattern. The following pattern matches a line with mark m.
:g/\%'m/w >> file.log
To do something like this. (Note: I am using \v to turn on very magic)
:g/\v(%'a|%'b|%'m)/yank A
Of course if a pattern won't work you can do this by hand. Instead of marking the lines just build up the lines as you go. Just yank a line to an uppercase register to append.
"Ayy
Or do a write append with a range of a single line
:.w >> file.log
For more help see
:h :g
:h :w_a
:h /\%'m
:h /\v
You can do something like:
:redir #a
:silent marks XYZN
:redir END
"ap
That way the output of the :marks command will be redirected to the a register. Note, that it will only lists (in the above case) the X, Y, Z and N marks (as the arguments), and if there was an a register, it will be deleted/overwritten.
Also note, that it might not give the desired output, but gives you a starting point...
I like the solution from Mikeage, though I would probably solve this with the multiselect - Create multiple selections and operate plugin. This also has the benefit that you don't run out of marks.
With the plugin, you can select lines with <Leader>msa or :MSAdd. Finally, yank all lines with:
:let #a=''
:MSExecCmd yank A
If you use an upper-case register name when yanking into a specific register, Vim will append the yanked content instead of overwriting the register's value.
So, for example:
"ayy - yank current line to register a, overwriting
[move]
"Ayy - append this line to register a
[move]
"ap - paste all yanked material
See :help quotea for more details.

How to convert leading spaces to tabs?

Many people use spaces rather than tabs. I use both of them. Tabs at the beginning of line and spaces from the first non-whitespace character. No problem for starting new document and in case I have to modify one better adapt to using format. Still sometimes I need to fix the spaces issue anyway.
According to Search and replace I can just do :%s/spaces_for_tab/tab/g. It is simple and it will work for many cases. Anyway I want to refactor only spaces at the beginning of line.
This is more of a regex issue. To anchor at the beginning of the line, use the caret, e.g.
s/^ /\t/
Or do it using vim's builtin functionality:
:set tabstop=4 "four spaces will make up for one tab
:set noexpandtab "tell vim to keep tabs instead of inserting spaces
:retab "let vim handle your case
By the way, I too prefer tabs for indentation and spaces for alignment. Unfortunately, vim doesn't handle this well (and I don't know what other editors do), so I mostly use :set expandtab (maybe see :set softtabstop).
I've written a simple func for it. Anyway it will work only for 4-space tab.
fu! Fixspaces()
while search('^\t* \{4}') != 0
execute ':%s/^\t*\zs \{4}/\t/g'
endwhile
endfu
You can suggest better solution, if exists, and I will use it with pleasure.
The issue is that this func replaces spaces in strings as well.
David's response is very elegant but it doesn't address leading whitespace that has a mixture of tabs and spaces. For example to convert a line like:
<SPACE><SPACE><TAB>something...
you have to know the position of the tab to determine the number of spaces needed to replace the <TAB> and reach the next tabstop. My solution below, although not as compact as David's, addresses this. It also allows me to select which way to use leading whitespace without depending upon &expandtab. I would appreciate any suggestions to improve my code...
function! TabsToSpaces(...)
let ts = &tabstop
let pos = getpos('.')
while search('^ *\zs\t', "w") != 0
let l:curpos = getcharpos('.')
" The number of spaces needed depends upon the position of the <TAB>
let numsp = 1 + ts - ( curpos[2] % ts )
if numsp == 9
let numsp = 1
endif
silent execute ':s/^ *\zs\t/'.repeat(' ', numsp).'/'
endwhile
if a:0 == 0
echo 'Changed leading tabs to spaces'
endif
call setpos('.', pos)
endfunction
function! SpacesToTabs(...)
let ts = &tabstop
let pos = getpos('.')
" First normalize all tabs to spaces
call TabsToSpaces("quiet")
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
if a:0 == 0
echo 'Changed leading spaces to tabs'
endif
call setpos('.', pos)
endfunction
" Some keystrokes to implement the spaces/tabs functions
nmap <Leader>st :execute SpacesToTabs()<CR>
nmap <Leader>ts :execute TabsToSpaces()<CR>
I took Martin's answer and improved on it a bit if anyone's interested:
function Fixspaces()
let ts = &tabstop
let pos = getpos('.')
if &expandtab
while search('^ *\t') != 0
silent execute ':%s/^ *\zs\t/'.repeat(' ', ts).'/g'
endwhile
echo 'Changed tabs to spaces'
else
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
echo 'Changed spaces to tabs'
endif
call setpos('.', pos)
endfunction
This function does the appropriate thing depending on the values of the expandtab and tabstop settings and also remembers where the cursor is.

faking/simulating typing in Vim

I am writing a very small script in VimL and I am looking to simulate actual typing of a given string.
The problem I am facing is that anything I try places the whole string instantly on the buffer, so the whole operation looks quite atomic and it doesn't portray the natural latency of char-by-char of typing.
I've tried a few variations of the function below, and even though I've added a sleep 50m at different places, I don't get the desired behavior:
function! FakeTyping(string)
let list = split(a:string)
for word in list
for letter in split(word)
execute "normal a" . letter . "\<esc>"
endfor
endfor
endfunction
Is this even possible? and if so, what is it that I am missing?
Maybe this is what you need. You hit Ctrl-MiddleMouse to send the content of clipboard to vim char by char:
nmap <C-MiddleMouse> :call AnimateText(#*)<CR>
fun! AnimateText(text)
let lineno = line('.')
let lines = split(a:text, "\n")
for line in lines
call setline(lineno, '')
let chars = split(line, '.\zs')
let words = ''
for c in chars
let words .= c
call setline(lineno, words)
call cursor(lineno, 0)
normal z.
if c !~ '\s'
sleep 100m
redraw
endif
endfor
let lineno += 1
endfor
endfun

How do I run a vim script that alters the current buffer?

I'm trying to write a beautify.vim script that makes C-like code adhere to a standard that I can easily read.
My file contains only substitution commands that all begin with %s/...
However, when I try to run the script with my file open, in the manner :source beautify.vim, or :runtime beautify.vim, it runs but all the substitute commands state that their pattern wasn't found (patterns were tested by entering them manually and should work).
Is there some way to make vim run the commands in the context of the current buffer?
beautify.vim:
" add spaces before open braces
sil! :%s/\%>1c\s\#<!{/ {/g
" beautify for
sil! :%s/for *( *\([^;]*\) *; *\([^;]*\) *; *\([^;]*\) *)/for (\1; \2; \3)/
" add spaces after commas
sil! :%s/,\s\#!/, /g
In my tests the first :s command should match (it matches when applied manually).
I just recently wrote a similar beautifier script but I implemented it in what I think is a more flexible way; plus, I tried to come up with a mechanism to avoid substituting stuff within strings.
" {{{ regex silly beautifier (avoids strings, works with ranges)
function! Foo_SillyRegexBeautifier(start, end)
let i = a:start
while i <= a:end
let line = getline(i)
" ignore preprocessor directives
if match(line, '^\s*#') == 0
let i += 1
continue
endif
" ignore content of strings, splitting at double quotes characters not
" preceded by escape characters
let chunks = split(line, '\(\([^\\]\|^\)\\\(\\\\\)*\)\#<!"', 1)
let c = 0
for c in range(0, len(chunks), 2)
let chunk = chunks[c]
" add whitespace in couples
let chunk = substitute(chunk, '[?({\[,]', '\0 ', 'g')
let chunk = substitute(chunk, '[?)}\]]', ' \0', 'g')
" continue like this by calling substitute() on chunk and
" reassigning it
" ...
let chunks[c] = chunk
endfor
let line = join(chunks, '"')
" remove spaces at the end of the line
let line = substitute(line, '\s\+$', '', '')
call setline(i, line)
let i += 1
endw
endfunction
" }}}
Then I define a mapping that affects the whole file in normal mode, and only the selected lines in visual mode. This is good when you have some carefully formatted parts of the file that you don't want to touch.
nnoremap ,bf :call Foo_SillyRegexBeautifier(0, line('$'))<CR>
vnoremap ,bf :call Foo_SillyRegexBeautifier(line("'<"), line("'>"))<CR>

Resources