I have log file of multi threaded application. Each line has a well know format (e.g. 3rd field is thread id). while one of fields is thread id. I hope that I'm not trying to reinvent the wheel :)
Any way, to easy reading of the file, I thought of two options that could help:
Highlight all lines with the same thread id as current line.
If some keystroke is pressed, all lines with other thread id are folded, pressing again the keystroke unfold the lines.
A skeleton for both items is welcomed.
Highlighting by pattern
Here is a function to highlight (and another to clear) all lines that contain a given pattern, with an accent highlight on the pattern itself. The "last search" register #/ is also set to the requested pattern so n/N, in normal mode, jumps forwards/backwards through matching lines. <Leader>l (equivalent to \l on most installs) is a shortcut to highlight lines that contain the WORD under your cursor.
highlight def link xHiLine Special
highlight def link xHiPatt String
function! ClearHighlight()
syn clear xHiLine
syn clear xHiPatt
let #/ = ''
endfunction
function! HighlightPattern(patt)
call ClearHighlight()
if a:patt != ''
echo "Highlighting pattern: ".a:patt
exec "syn match xHiPatt \"".a:patt."\" contained"
exec "syn match xHiLine \".*".a:patt.".*\" contains=xHiPatt"
let #/ = a:patt
endif
endfunction
map <Leader>l :call HighlightPattern(expand("<cWORD>"))<CR>
map <Leader>c :call ClearHighlight()<CR>
Folding by pattern
For an example of folding based on patterns, check out the Show-Hide Vim plug-in. It provides two commands, SHOW and HIDE, and a few shortcut maps. For example, :SHOW thread=1234 will fold all lines except those that contain thread=1234, while zs in normal mode will show lines containing the word under your cursor. [You may want to create an alternate map, such as zS, to use <cWORD> instead of <cword>.]
Building patterns
If neither <cword> nor <cWORD> extract a sufficiently unique filter pattern (or to avoid moving the cursor to the proper field), create another function like the one below and call it from a map.
function! GetField(num)
let toks = split(getline('.'))
if len(toks) >= a:num
return toks[a:num-1]
endif
return ''
endfunction
map <Leader>hl :call HighlightPattern(GetField(3))<CR>
map <Leader>fl :exec "SHOW ".GetField(3)<CR>
What you are basically looking for is an external mechanisem to be built on top of your log file.
Chainsaw is doing exactly that for log4j based logs:
http://logging.apache.org/chainsaw/index.html
Not sure what is your logging application, but you should probbaly look at that direction.
Related
I have been trying to implement a function in vimscript to switch between two views of the same buffer. The functionality I want to have is like this:
I press a key and the screen position and cursor are moved to my previously saved location. If there is not yet another saved location, it creates one at the current view and cursor location. I have a vimscript function that does exactly this functionality i described:
fun! SwitchFileMarker(reset)
if a:reset == 1
if exists("b:switch_file_window")
unlet b:switch_file_window
endif
endif
if exists("b:switch_file_window")
let cursor_location = getpos("'0")
let top_location = getpos("'9")
:execute "normal! m0Hm9`0"
call setpos('.', top_location)
:execute "normal! zt"
call setpos('.', cursor_location)
:execute "normal! zv"
else
" save the location
:execute "normal! m0Hm9`0"
endif
let b:switch_file_window = 1
endfun
nmap <leader>b :call SwitchFileMarker(0)<CR>
nmap <leader>B :call SwitchFileMarker(1)<CR>
The only problem with this function is I want it to save the folds of the current view and load the folds of the saved view when the function is called. I can achieve this by using :mkview and :loadview, but the problem with that is if the number of lines in the file changes, the folds are lost. The :mkview function seems to remember the folds at a specific line number, and If ive added several lines above that fold location while editing the file, when i use :loadview, the fold is lost. using the marks, as done in the function i show works to save my cursor position (but not folds) because the marks keep track of the changing line number. The funcionality that I am trying to get is essentially like having two views of a buffer but in the same window, rather than two windows. If i have a two windows editing the same buffer, I can add lines in one window and the folds are not lost in the other, so this is exactly the functionality I want, just in one window instead of two. Any suggestions how this can be done?
Use marks!
To set a mark, you can use m<letter> or :mark <letter>.
To go to a marked line, use '<letter>. To go to the exact position, prefer `<letter>.
If the letter is lowercase, it is buffer-local. If it is uppercase, it is global.
Edit: OP wants to save folds, which this can’t do. But it’s helpful to know anyway, so I’m leaving the answer.
Often when I'm using f to search for characters in the current line, I'll run into more occurrences of the character than I expected so highlighting each search match would be nice.
In the example below, let's say I'm starting at the beginning of the line and am trying to get to e in vowels. It would be helpful to highlight each of those occurrences so that I could get some context on the number of times to press ; after the initial search
# Here is a comment with a lot of vowels and I have passed it now
How does Vim's current implementation of f know how to wait for only a single character instead of a newline?
I would prefer to overwrite the builtin f functionality so I'm using a remap similar to this, but one of the problems is that it expects me to press enter at the end.
nnoremap f :call HighlightFSearches(input(''))<CR>
Currently have some issues with implementing my function HighlightFSearches as well, but one problem at a time.
Also, not really looking for a plugin and yes I know I can just do a search instead of using f but my brain seems to prefer going with f first in a lot of cases.
Update
Here's my final solution with much thanks to #filbranden below!
function! HighlightFSearches(cmd)
" Get extra character for the command.
let char = nr2char(getchar())
if char ==# ''
" Skip special keys: arrows, backspace...
return ''
endif
" Highlight 'char' on the current line.
let match_str = 'match Visual "\%' . line('.') . 'l' . char . '"'
execute match_str
" Finally, execute the original command with char appended to it
return a:cmd.char
endfunction
" highlight searches using 'f'
nnoremap <expr> f highlighting#HighlightFSearches('f')
nnoremap f<bs> <nop>
vnoremap <expr> f highlighting#HighlightFSearches('f')
vnoremap f<bs> <nop>
" highlight searches using 'F'
nnoremap <expr> F highlighting#HighlightFSearches('F')
nnoremap F<bs> <nop>
vnoremap <expr> F highlighting#HighlightFSearches('F')
vnoremap F<bs> <nop>
Note that I chose the Highlight Group used for visual selects. You could choose a different one or make your own too
The short answer is that you should use getchar() to get a single character from the user.
The long answer is that this gets somewhat complicated pretty quickly, since you need to deal with special keys and corner cases while handling getchar().
Note that getchar() may return a number (for a normal keypress, which you can convert to a character with nr2char()), or a string, starting with a special 0x80 byte for special keys (backspace, arrows, etc.)
A simplistic approach (but somewhat effective) is that running nr2char() on the strings returned for the special keys will return an empty string, so we can use that to skip those.
The next advice is that you can use <expr> in your mappings to return the new command as a string. That, together with non-recursive mappings, allow you to return the actual f command itself at the end of the function, so that part of emulating it is taken care of!
Finally, one more trick you might want to use is to create a "dummy" mapping for f followed by an invalid character. The fact that such a 2-character mapping exists makes it so that your f mapping won't trigger until a second character has been entered, and this will prevent Vim from moving the cursor to the last line while waiting for a character, making the f emulation more seamless.
Putting it all together:
function! HighlightFSearches(cmd)
" Get extra character for the command.
let char = nr2char(getchar())
if char ==# ''
" Skip special keys: arrows, backspace...
return ''
endif
" Here you'll want to highlight "char"
" on the current line.
" Finally, execute the original command.
return a:cmd.char
endfunction
nnoremap <expr> f HighlightFSearches('f')
nnoremap f<bs> <nop>
The function is written in a way that you can easily reuse it for F, t and T.
For highlighting the matches, you can either use :match (or :2match, :3match) or maybe you could set #/ and let 'hlsearch' do the highlighting...
You'll probably want to anchor the regexp on the current line, so only those matches are highlighted, see :help /\%l for what you can use for that.
Finally, you'll probably want to clear the highlighting if you move to a different line. Take a look at the CursorMoved event of autocmd for that purpose.
There are quite a few details to iron out, but hopefully this will clarify how to emulate the command part of getting the character to search for.
The short and sweet answer is to substitute input() for getchar()
Just another vim source code comment question here. I have this mapping for my python source code files:
map <C-C> <Home>i#<Esc>
imap <C-C> <Home>#<Esc> i
On Ctrl-C it puts # in the beginning if the line to comment it out. This improves productivity a lot. But when I want to uncomment lines, I have to do this manually, meaning going to the first character of each commented line and remove it. This is very annoying. At the first glance, I can just bind Home-x to some key, but I can occasionally remove an innocent space or something else in case I misshit and do this on line that has no # character at the beginning of it. I first try to do some replacement with :%s// for a single line, but that has an unwanted affect - it triggers a search and highlights 'pattern' in other lines. In case of a single # character it is a fail.
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
I have created a simple function to Toggle comment in the line:
function! ToggleComment()
let l:pos = col('.')
if getline('.') =~ '\v(\s+|\t+)?#'
exec 'normal! _"_x'
let l:pos -= 1
else
exec 'normal! I#'
let l:pos += 1
endif
call cursor(line("."), l:pos)
endfunction
nnoremap <Leader>t :call ToggleComment()<CR>
inoremap <Leader>t <C-o>:call ToggleComment()<CR>
I recommend Tim Pope's plugin vim-commentary because is way more complete. But of course our idea gives you guys a glimpse how far we can get with vimscript.
Another approach, which does not need to save windowview and toggles comments in other languages can be seen here
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
A solution would be (assuming your cursor is anywhere to the right of # when using the map):
map <c-c> mmF#x`m
A more general solution would be to use a substitution and histdel() to delete the last search pattern:
function! DelComment()
s/^\( *\)#/\1/
call histdel("search", -1)
let #/ = histget("search", -1)
endfunction
After executing the function (by selecting it and typing :#") you can map it to <c-c>:
map <silent> <c-c> mm:silent! call DelComment()<cr>`m
I like using marks around functions to retain the cursor position after executing the map. Feel free to remove mm and `m in the above map.
I've enjoyed using # to highlight and search for a word (variable) but I would love if I could hit a single command that would simply tell me how many occurrences are in the current file. I've found the following
:%s/dns_name_change_flag/&/gn
But that's too much typing. Is there anyway to maybe map the above one-liner to use the word under cursor?
I don't know how to do this without execute. The following maps F5 to count occurrences of the word under the cursor using <cword> and word boundary patterns (\\< and \\>):
:map <f5> :execute ":%s#\\<" . expand("<cword>") . "\\>\#&#gn"<CR>
:map <F2> "zyiw:exe "%s/".#z."//gn"<CR>
add this line (without the ":") to your .vimrc and F2 will be mapped every time you start vim.
It yanks the 'inner word' to the z register and then performs a search in the whole buffer outputting the number of appearances.
This approach differs to the one given by Thor in that way, that it also counts appearances of the word that are not a word themselves, but only part of a word. For example: looking for 'an' will also count 'and'.
This might be helpful too:
"A quick way to list all occurrences of the word under the cursor it to type [I (which displays each line containing the current keyword, in this file and in included files when using a language such as C)."
source: http://vim.wikia.com/wiki/Word_count
Map Execute to Grep
You can map a sequence to an external grep command. For example:
:nmap fg :execute '!fgrep --count <cword> %'<CR>
This maps a normal-mode command to fg. The command will run fgrep on the current file in an external process, counting the instances of the word under the cursor.
Minor Caveat
This operates on the current file, not the current buffer. You need to make sure the file is written to disk (e.g. :write) before the word count will be accurate.
I have written a plugin for that: SearchPosition; it provides mappings for the current search pattern and current word / selection, and also lists where the matches occur:
1 match after cursor in this line, 8 following, 2 in previous lines;
total 10 for /\<SearchPosition\>/
:nmap <Leader>* *<C-o>:%s///gn<CR><C-o>
I have a function that does not change search register #/
fun! CountWordFunction()
try
let l:win_view = winsaveview()
exec "%s/" . expand("<cword>") . "//gn"
finally
call winrestview(l:win_view)
endtry
endfun
command! -nargs=0 CountWord :call CountWordFunction()
nnoremap <F3> :CountWord<CR>
If you alredy have searched for the word you can just type
:%~n
I'm using vim to maintain a weblog of what I do through the day (what commands I used to generate output, etc..), and sometimes I need to copy-paste strings that have special html characters in them. Is there a way to make an "html-paste" mode that will (for instance) convert < to <?
There are a few small functions here if someone feels like modifying them to accept a range, then provide a mapping which passes the [ and ] marks to act on the last pasted text.
Actually, after looking a bit, you don't need to modify the functions from the vim tip at all. If a function doesn't explicitly pass the range option, the function is called once for each line of the given range. This means that all you need to do is call the function with a range.
A couple useful examples are below. The first calls HtmlEscape() for each line in the last pasted text, the second does the same but for each line in a visually selected block. Add this to your .vimrc:
nnoremap <Leader>h :'[,']call HtmlEscape()<CR>
vnoremap <Leader>h :call HtmlEscape()<CR>
function HtmlEscape()
silent s/&/\&/eg
silent s/</\</eg
silent s/>/\>/eg
endfunction
Obviously if you want more things replaced you'd have to add them; there are many on the linked wiki page.
For simple XML encoding I use Tim Pope's unimpaired.vim.
[x followed by a motion
visually select the text then [x
[xx to encode the current line
To encode the just pasted text:
`[[x`]
Explanation:
`[ and `] are marks set by the boundaries of a change or a yank. `[ at the beginning and `] at the end.
First move to the start of the just pasted text with `[
Next execute [x to encode the following motion
By using `] the motion will be all the text between the current cursor location to the end of the pasted text.
Optionally you can wrap this up into a mapping
nmap <leader>x `[[x`]
More information:
:h mark-motion
:h `[
You can use a mapping that will convert register value and then paste it:
python import xmlrpclib, vim
function! EscapeHTML(str)
try " Force any error to be an exception "
let d={}
python vim.eval("extend(d, {'xml': '"+xmlrpclib.escape(vim.eval("a:str")).replace("'", "''")+"'})")
return d.xml
endtry
endfunction
function! s:PasteHTML()
return "\"=EscapeHTML(getreg(".string(v:register)."))\np"
endfunction
nnoremap <expr> ,p <SID>PasteHTML()
Requires vim with python support and python installed. xmlrpclib packages comes with python. If you don't want python, replace EscapeHTML function. With this script ,p will work just as p except for converting its input.