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.
Related
I'm writing an SQL database script, and I use iab to upper case special words such as DATABASE or TABLE. I have three questions:
Is there a better tool to use? I prefer to avoid using a plugin as they usually come with functionality that bothers me.
Is there a way to make iab be case insensitive? I mean that it will correct both set and Set into SET?
Is there a way to make iab work when there are special or certain characters following/preceding the word? For example, I want exec and exec( to be changed into EXEC and EXEC( respectively. I can create two entries, but the question is if I can do this in a single line.
Better ways: No, short of a plugin I can't see what else would be as useful as abbreviations in this context. I note that sql_iabbr.vim is at least easy to read, and doesn't seem to contain much in the way of hidden gotchas. It's essentially a whole bunch of this:
inoreabbr <silent> <buffer> select <C-R>=SqlIab_ReplaceConditionally('select', 'SELECT')<CR>
with the following function:
function! SqlIab_ReplaceConditionally(original, replacement)
" only replace outside of comments or strings (which map to constant)
let elesyn = synIDtrans(synID(line("."), col(".") - 1, 0))
if elesyn != hlID('Comment') && elesyn != hlID('Constant')
let word = a:replacement
else
let word = a:original
endif
let g:UndoBuffer = a:original
return word
endfunction
I think it's also worth pointing out Abolish for more powerful abbreviations.
Case insensitive: again, I think the answer is 'No' (based on a reasonably thorough check of the help.
Special/certain characters: sorry, if you need that done in one abbreviation I think you're out of luck again!
So many questions:
Is there a better tool to use? I prefer to avoid using a plugin as they usually come with functionality that bothers me.
Tim Pope's Abolish.vim is the closest plugin which fits your needs. However with the more advanced expansions I feel like it too will fall short. I am not sure about your past plugin experiences, but Tim's plugins are usually well behaving and narrowly focused.
Is there a way to make iab be case insensitive? I mean that it will correct both set and Set into SET?
No, there is no native way that I am aware of. I would recommend using Abolish if you do this regularly and have simpler expansions. Or go all the way and use a snippet plugin.
Is there a way to make iab work when there are special or certain characters following/preceding the word? For example, I want exec and exec( to be changed into EXEC and EXEC( respectively. I can create two entries, but the question is if I can do this in a single line.
This can be done with a fancier expansion. It is in fact very similar to what Rails.vim does with it's abbreviations.
Add the following to your ~/.vim/after/ftplugin/sql.vim:
function! s:selective_expand(root, good, ...)
let [pat, extra; _] = a:0 ? a:000 : ['', '']
let c = nr2char(getchar(0))
if c == "" || c == "\t"
return a:good
elseif c =~ '\s'
return a:good . c
elseif pat != '' && c =~# pat
return a:good . extra
else
return a:root . c
endif
endfunction
function! s:paren_expand(root, good)
return s:selective_expand(a:root, a:good, '[(]', "()\<left>")
endfunction
function! s:sql_expand(root, ...)
let good = a:0 ? a:1 : toupper(a:root)
let good = substitute(good, '[\"|]', '\\&', "g")
let good = substitute(good, '<', '\\<lt>', "g")
let f = 'selective_expand'
let root = a:root
if root =~ '($' && (!a:0 || good !~ '($')
let root = substitute(root, '($', '', '')
let good = substitute(good, '($', '', '')
let f = 'paren_expand'
endif
let root = substitute(root, '[\"|]', '\\&', "g")
let root = substitute(root, '<', '\\<lt>', "g")
let roots = [root]
if root !~# '\u' && root =~# '^\l'
call add(roots, substitute(root, '^\l', '\u&', ''))
endif
for r in roots
execute "iabbr <buffer> " . r . " <c-r>=<SID>" . f . "(\"" . r . "\", \"" . good . "\")<cr>"
endfor
endfunction
command! -nargs=* Sqlabbrev call <SID>sql_expand(<f-args>)
Now you can create abbreviations in your ~/.vim/after/sql.vim file like so:
Sqlabbrev select
Sqlabbrev join INNER\ JOIN
Sqlabbrev exec(
Sqlabbrev Takes 2 arguments similar to iabbrev. However behaves slightly differently:
Abbreviations will always be local to the buffer
If only 1 argument then the expansion will be the same as the first argument but made uppercase
Using <tab>/<c-]> will make the expansion without any following spaces
Using abbreviation followed by space will expand and add space
If the abbreviation ends with ( then expand with ending of () and place cursor inside the parens
You must escape spaces via \ or <space>. Can use keycodes
Will create some case expansions if no uppercase letter is found in abbrevation. e.g. Sqlabbrev select will expand select and Select.
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.
In Vim, one can join two lines by typing capital J.
However, these are usually joined by a space.
I seem to remember there was a way to change the character used for the joining by setting some variable, but I can't seem to find it again.
I'd appreciate it if anyone could remind me, or confirm that it can't be done.
When I want to join just a few lines I use a 3 keys combo (normal mode):
Jr,
being , the joining character.
In case I want to join more lines or even join lines in groups, I use the previous combo with a macro.
For example, to transform 3 lines in a 3 columns CSV table, I record this macro (assigned to letter j of course):
qjJr,Jr,jq
So, using #j joins 3 lines using , and goes to the next line.
10#j converts 10 lines.
There isn't a setting that allows you to do this directly, see:
:help J
in particular, the text below the list of commands.
A couple of ways you could do this:
:nnoremap J gJi.<ESC>
" or
let joinchar = ';'
nnoremap J :s/\n/\=joinchar/<CR>
The latter option allows you to change it on the fly by changing the joinchar option.
Try something like this in your .vimrc:
nnoremap Y Jxi*<Esc>
It'll remap Y to join the lines with a *.
From http://vim.wikia.com/wiki/Remap_join_to_merge_comment_lines
put this in your .vimrc:
function! JoinWithLeader(count, leaderText)
let l:linecount = a:count
" default number of lines to join is 2
if l:linecount < 2
let l:linecount = 2
endif
echo l:linecount . " lines joined"
" clear errmsg so we can determine if the search fails
let v:errmsg = ''
" save off the search register to restore it later because we will clobber
" it with a substitute command
let l:savsearch = #/
while l:linecount > 1
" do a J for each line (no mappings)
normal! J
" remove the comment leader from the current cursor position
silent! execute 'substitute/\%#\s*\%('.a:leaderText.'\)\s*/ /'
" check v:errmsg for status of the substitute command
if v:errmsg=~'Pattern not found'
" just means the line wasn't a comment - do nothing
elseif v:errmsg!=''
echo "Problem with leader pattern for JoinWithLeader()!"
else
" a successful substitute will move the cursor to line beginning,
" so move it back
normal! ``
endif
let l:linecount = l:linecount - 1
endwhile
" restore the #/ register
let #/ = l:savsearch
endfunction
nnoremap <space> :<C-U>call JoinWithLeader(v:count, '"')<CR>
This also allows you to remap J to something else.
It will quicker if you replace the end of line with a comma (or join character)
:%s/$/,
and then joining multiple lines either by providing a range, or by selecting lines in visual mode and using the join command
10J
It's mapping. You can read the tutorial in vim wikia :
Mapping keys in vim
Try the command below in command mode, and try to press . This should work :)
:map <space> J
Suppose that I have a document like this, and I want to search for all occurences of the URL:
Vim resources: [http://example.com/search?q=vim][q]
...
[q]: http://example.com/search?q=vim
I don't want to type it out in full, so I'll place my cursor on the first URL, and run "uyi[ to yank it into the 'u' register. Now to search for it, I'd like to just paste the contents of that register into the search field by running:
/\V<c-r>u<CR>
This results in Vim searching for the string 'http:' - because the '/' character terminates the search field.
I can get around the problem by running this instead:
/\V<c-r>=escape(#u, '\/')<CR><CR>
But it's a lot of typing!
How can I create a mapping for Vim's commandline that simplifies this workflow?
My ideal workflow would go something like this:
press /\V to bring up the search prompt, and use very nomagic mode
hit ctrl-x to trigger the custom mapping (ctrl-x is available)
Vim listens for the next key press... (pressing <Esc> would cancel)
pressing 'u' would escape the contents of the 'u' register, and insert on the command line
Try this:
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
" show some kind of feedback
echo ":".getcmdline()."..."
" get a character from the user
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
return escape(register_content, '\/')
endif
endfunction
By the way, something that might be useful to know (if you don't already) is that you can use ? as the delimiter for :s. Which means that you could write a search-and-replace for an url like so:
:s?http://foo.com?http://bar.com?g
I've accepted Andrew Radev's solution, which solved the hard parts. But here's the version that I've added to my vimrc file, which adds a couple of enhancements:
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
echo "\\".getcmdline()."\""
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
let escaped_register = escape(register_content, '\'.getcmdtype())
return substitute(escaped_register, '\n', '\\n', 'g')
endif
endfunction
This should work:
whether you use / or ? (to search forwards, or backwards)
and when the pasted register includes multiple lines
Also, I changed the prompt. While waiting for a register, the prompt switches to \ - which seems like a suitable cue for 'PasteEscaped'. Also, I've appended a ", which mimics Vim's behavior after pressing <c-r> at the command line.
If you've any further suggestions for improvements, please leave a comment.
How about different workflow? For example, creating your own operator to search target text as is:
" https://gist.github.com/1213642
" Requiement: https://github.com/kana/vim-operator-user
map YourFavoriteKeySequence <Plug>(operator-search-target-text)
call operator#user#define('search-target-text', 'OperatorSerachTargetText')
function! OperatorSerachTargetText(motion_wise)
execute 'normal!' '`['.operator#user#visual_command_from_wise_name(a:motion_wise).'`]"xy'
let #/ = '\V' . escape(substitute(#x, '[\r\n]$', '', ''), '\')
normal! n
endfunction
I like #nelstrom's solution and made a small change to support escaping [ and ].
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
echo "\\".getcmdline()."\""
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
let escaped_register = escape(register_content, '\'.getcmdtype())
let escaped_register2 = substitute(escaped_register,'[','\\[','g')
let escaped_register3 = substitute(escaped_register2,']','\\]','g')
return substitute(escaped_register3, '\n', '\\n', 'g')
endif
endfunction
Typically Vim's global command :g// works on per line basis. Is it possible to make it work on per occurrence basis as there could be more than one occurrence on a line.
Not a direct answer, but you could use something like :rubydo, which will run some ruby scriptlet per line of code. Combining that with gsub in ruby should get you the ability to do just about anything per occurrence of a match. Of course, you will need to do it with ruby code, which may not give you access to everything that you might need without hassle (register appending would be annoying for instance)
:[range]rubyd[o] {cmd} Evaluate Ruby command {cmd} for each line in the
[range], with $_ being set to the text of each line in
turn, without a trailing <EOL>. Setting $_ will change
the text, but note that it is not possible to add or
delete lines using this command.
The default for [range] is the whole file: "1,$".
You can try:
command! -bang -nargs=1 -range OGlobal
\ <line1>,<line2>call s:Global("<bang>", <f-args>)
function! s:Global(bang, param) range
let inverse = a:bang == '!'
" obtain the separator character
let sep = a:param[0]
" obtain all fields in the initial command
let fields = split(a:param, sep)
" todo: handle inverse
let l = a:firstline
while 1
let l = search(fields[0], 'W')
if l == -1 || l > a:lastline
break
endif
exe fields[1]
endwhile
endfunction
Which you can use as :global, except that the command name is different (and that the bang option as not been implemented yet)