Vim: dynamic syntax-highlighting - vim

I want to dynamically (i.e., depending on the content of the current file) adapt syntax highlighting. While this might be useful in general, my specific setting is as follows:
The kind of files I consider may contain (arbitrary many) blocks of the form (VAR ...), where such "VAR-blocks" contain a space-separated list of identifiers that should be considered as variables (while identifiers that are not in a VAR-block are considered to be fixed function symbols or constants). Furthermore, there is already a file trs.vim that takes care of syntax highlighting for such files. In trs.vim a syntax group trsKeyword is declared. Now my goal is to highlight all variables using this group.
Consider the following example (lets call it add.trs):
(VAR x y)(RULES
add(z, y) -> y
add(s(x), y) -> s(add(x, y))
)
When opening add.trs in vim, I want that x and y are printed as keywords (whereas for example z is not, despite having no arguments).

I already achieved the desired result (but wanted to share it on SO). So here it is (the following snippets should be combined in a file vars.vim).
First I define a pattern that will recognize VAR-blocks:
" pattern that matches VAR-blocks of *.trs files
let varblock = '(VAR\s*\(.*\))'
Then I check the current file (maybe there is a better way than using % for that?) for VAR-blocks and ignore the case where no match was found.
" create list of variables from all VAR-blocks
try
silent exe "vimgrep /" . varblock . "/j %"
catch /^Vim\%((\a\+)\)\=:E480/ " no match
" do nothing
endtry
The found matches are obtained by getqfilst(), which I copy. Then I replace every element in this list by the first subgroup that matched the varblock pattern (which will be a string containing space-separated identifiers). I join all such strings into a single one and then split it at spaces to get a list of identifiers:
let vars = split(join(map(copy(getqflist()), 'matchlist(v:val.text, varblock)[1]')))
Finally I create a pattern matching these identifiers and register it to the trsKeyword group:
" activate syntax highlighting for variables
exe "syn match trsKeyword \"\\<\\(" . join(vars, '\|') . "\\)\\>\""
" echo "variables: " . join(vars, ", ")
To make it work together with trs.vim I created the directory ~/.vim/syntax/trs and put trs.vim as well as vars.vim inside. Then in my ~/.vimrc I added
" TRS files
au BufNewFile,BufRead,BufWrite *.trs setf trs
which activates syntax highlighting for files ending with .trs (and also updates the list of variables whenever a buffer is saved).

Related

vim folds for asciidoc: include the line _previous_ to the section-title line

Summary. How (and if?) can I get vim folds to automatically associate the asciidoc [id=''] lines found immediately prior to a section-title line with the fold created for said section?
Details. asciidoc content enables section-title identification (that, among other things, maps to rendered-HTML-anchor-tag names and also enables intra-document cross-reference), otherwise known (I think?) as a block identifier. However, the [id=''] line blurb must be place prior to the section header line, even though it's part of the section. eg:
[id='under_construction', reftext='Under Construction']
## DISCLAIMER: This Document is Under Construction
This makes vim-folding of an asciidoc file much harder to manage, as folded-section moves "lose" the previous line (and all the section id's get shuffled), because in the eyes of the vim fold, the previous line belongs to the previous section.
I'm not sure how vim foldings asciidoc, but I assume that new section starts with ## (or ==, judging from a brief look at the link you provided), and [id=...] provides additional information on the section.
So you could have a look at Steve Losh's markdown folding and :h fold-expr.
So here is the modified code for markdown folding:
function! Fold_askiidoc(lnum)
let l1 = getline(a:lnum)
if l1 =~ '^\s*$'
" assume sections are separated by blank lines
return '0'
endif
" check next line
let l2 = getline(a:lnum+1)
if l2 =~ '^#'
" next line starts with hashes (or '=', or any symbol)
" number of hashes specifies indent level
return '>'.matchend(l2, '^#\+')
else
" otherwise keep previous foldlevel
return '='
endif
endfunction
setlocal foldexpr=Fold_test(v:lnum)
setlocal foldmethod=expr
It checks each line, if it's blank, suppose it's the end of a section. If the next line starts with #, it means the fold starts on the current line. The amount of #'s specify folding level.
It folds any non-blank line before a section title, if you want it only for specific lines, like id=[''], you would have to add additional string comparison.
You can save it to $HOME/.vim/after/ftplugin/asciidoc.vim. I'm not sure about file type, if it exists or you have to create it separately. From there it should be loaded automatically each time you open specific file. Or you can just put function in your vimrc and specify
setlocal foldexpr=Fold_test(v:lnum)
setlocal foldmethod=expr
as auto-comand for this file type.

Looking for a way to correctly order ctags matches

I have a codebase that has ctags configured correctly. When I do, :tjump keyword it shows me a list of potential matches for the keyword.
However these matches aren't ordered correctly. I'm looking for a way to correctly order the matches so that the best match is at the top of the list. ie:- the first jump when I directly use Ctrl-] should go to the correct place.
For the GetFile navigation with gf I have found includeexpr which allows me to run custom logic to determine the file to jump to.
Does Vim have a similar function for altering the tags results?
Another approach I am considering is to grab the list of tags from :tjump, do sorting, and override the mapping for Ctrl-].
For this approach, is there a function to get the list of matches from :tjump?
Any other ideas to ensure that the the correct match is at the top are also welcome!
Thanks.
It's often not clear what the "correct" match is. Currently Vim uses the following logic (from :help tag-priority):
When there are multiple matches for a tag, this priority is used:
1. "FSC" A full matching static tag for the current file.
2. "F C" A full matching global tag for the current file.
3. "F " A full matching global tag for another file.
4. "FS " A full matching static tag for another file.
5. " SC" An ignore-case matching static tag for the current file.
6. " C" An ignore-case matching global tag for the current file.
7. " " An ignore-case matching global tag for another file.
8. " S " An ignore-case matching static tag for another file.
If you want to implement your own custom logic, there's nothing (that I know of) similar to the includeexpr that can help you.
You could create multiple tags and order them in the tags setting in such a way that encodes your preference. It's hard to say what that would be, though, and very likely to require some experimenting.
Another, more complicated thing you could do is override the <c-]> key (and maybe others, like <c-w>]) to do something different. Something like:
nnoremap <c-]> :call <SID>JumpToTag()<cr>
function! s:JumpToTag()
" try to find a word under the cursor
let current_word = expand("<cword>")
" check if there is one
if current_word == ''
echomsg "No word under the cursor"
return
endif
" find all tags for the given word
let tags = taglist('^'.current_word.'$')
" if no tags are found, bail out
if empty(tags)
echomsg "No tags found for: ".current_word
return
endif
" take the first tag, or implement some more complicated logic here
let selected_tag = tags[0]
" edit the relevant file, jump to the tag's position
exe 'edit '.selected_tag.filename
exe selected_tag.cmd
endfunction
You can use the taglist() function to locate the tags for the word under the cursor. Then, instead of let selected_tag = tags[0], you can implement your own logic, like filtering out test files, or sorting by certain criteria.
Unfortunately, this doesn't maintain the :tnext and :tprevious commands, since you're manually editing files. You could replace it with the quickfix or the location list, using the setqflist() function with the tags ordered the way you like and then navigate using :cnext and :cprev. But that's a whole lot of more scripting :). If you decide to go down this rabbit hole, you might want to take a look at the source of my tagfinder plugin for inspiration.
Based on your comment to #AndrewRadev's answer:
I often create a "mktags" script that builds ctags and then filters out of the tags file the ones I want to omit. For example (for sh, ksh, bash, zsh):
ctags "$#"
egrep -v "RE-for-tags-to-delete" tags > tags.$$
mv tags.$$ tags

vim - how to match linked sequences in syntax file

I want to create a syntax file in vim for a custom file type I have. Part of the syntax is this line
entry: 1,02:15:00,03:15:00,56,Some String
I would like to match every part separately to assign different colors. entry should be a part, 1 should be a part, 02:15:00 should be a part, 03:15:00 should be a different part, 56 yet another part, Some String a different part, and all ,s as a part. Each of these should be named differently so I can color them with different colors as needed.
I was able to match them with contains, but similar values (first time and second time) get the same name. I also tried using nextgroup to chain them one after another, but this left me with , having many names I need to color separately (and there are many commas in the original file not like the simple example I shown here).
Is there a way to do such syntax highlighting in a proper way?
You can link all comma names to one with
" Adjust the below to make commas have another color.
" It is common that instead of defining colors directly you link
" new highlighting groups to some standard one.
hi def link MySyntaxComma Delimiter
hi def link MySyntaxCommaAfterNumber MySyntaxComma
hi def link MySyntaxCommaAfterFirstTime MySyntaxComma
hi def link MySyntaxCommaAfterSecondTime MySyntaxComma
hi def link MySyntaxCommaAfterSecondNumber MySyntaxComma
You can also use a loop with :execute to hide the repeating rules:
syntax match MySyntaxEntryStart /^entry:\s*/ nextgroup=MySyntaxNumber1
let s:rules=[['Number1', '\d\+'], ['Time1', '\d\d:\d\d:\d\d'], ['Time2', '\d\d:\d\d:\d\d'], ['Number2', '\d\+'], ['String', '.*']]
while !empty(s:rules)
let [s:name, s:reg]=remove(s:rules, 0)
let s:cmd='syntax match MySyntax'.s:name.' /'.s:reg.'/ contained'
if !empty(s:rules)
let s:cmd.=' nextgroup=MySyntaxCommaAfter'.s:name
execute 'syntax match MySyntaxCommaAfter'.s:name.' /,/ contained nextgroup=MySyntax'.s:rules[0][0]
execute 'hi def link MySyntaxCommaAfter'.s:name.' MySyntaxComma'
endif
execute s:cmd
endwhile
unlet s:rules s:cmd s:name s:reg

Notepad++ like "multi editing" in Vim?

I’m switching from Notepad++ to Vim as my main text editor.
In Notepad++, you can have multiple cursors by holding down Ctrl and clicking anywhere in the text, so that if you type, the text appears in multiple locations.
Is it possible in Vim? Something like insert after selecting multiple rows in Visual mode, but with the possibility to have cursors anywhere in the text.
It’s a feature I rarely use, and it’s also quite easily avoidable; I’m just curious, since it’s the only one I could’t find a replacement for in Vim yet.
There is not a built-in feature of that kind.
Let me suggest a function that repeats command (for example . repeating last
change command) at the positions of given marks. Both marks and command are
specified as string arguments. Marks specified in the way ranges in regular
expressions or scanf-format specifier are defined. For example, za-dx
means marks z, a, b, c, d, x.
function! MarksRepeat(marks, command)
let pos = 0
let len = strlen(a:marks)
let alpha = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
let beta = '1234567899bcdefghijklmnopqrstuvwxyzzBCDEFGHIJKLMNOPQRSTUVWXYZZ'
while pos < len
if a:marks[pos + 1] != '-'
exe 'norm `' . a:marks[pos] . a:command
let pos += 1
elseif a:marks[pos] <= a:marks[pos+2]
let mark = a:marks[pos]
let stop = a:marks[pos+2]
if mark =~ '[0-9a-zA-Z]' && stop =~ '[0-9a-zA-Z]'
while 1
exe 'norm `' . mark . a:command
if mark == stop
break
endif
let mark = tr(mark, alpha, beta)
endwhile
endif
let pos += 3
endif
endwhile
endfunction
In your case, the function could be used as follows.
Mark all places for simultaneous insertions (except one) using Vim
marks (by means of m command).
Actually insert text in the one place that has not been marked.
Run the function:
:call MarksRepeat(‹marks›, '.')
You could insert the text in one place, in a single operation, then use . to repeat that insertion at each other place you want the text.
It's the converse of what you asked for, because you wanted to mark the locations before entering the text, but it gives you the same result in the same number of keystrokes :).
Check multi select vim plugin: http://www.vim.org/scripts/script.php?script_id=953
ib's response and the multi select vim plugin are interesting, but the following is a suggestion that does not require a special function or plugin.
Temporarily set foldmethod=manual, then mark the blocks you want to operate on with zf.
Finally, use the ex command :folddoclosed to do ex commands on the folded blocks.
For example: :folddoclosed norm Iinsert some text at the front
Note, you can use :folddoclosed on any folded groups of lines, so you could use other foldmethods... but usually it makes sense to manually create the folds.
You can also use visual markers, followed by :norm which gives you :'<,'>norm... But visual markers only let you select a continuous range of lines. Using folds and :folddoclosed you can operate on multiple ranges of lines at once.
Another tip... to save time having to type out :folddoclosed, I will type :fo<shifttab><shifttab><shifttab>

Custom syntax highlighting in vim for links

I have a custom file format for graphs which looks like this:
node1.link1 : node2
node1.attribute1 : an_attribute_for_node1
node2.my_attribute1 : an_attribute_for_node2
(there is nothing special about the attributes names, an attribute is a link iff one can find its value at the left of a dot. So node2 is a link, because there is a line somewhere in the file that begins with node2.<something>).
I would like to highlight the attribute values if they are links (so I would like to highlight node2, but not attribute_for_node1).
Obviously, this kind of syntax highlighting cannot be based only on line wide regexp, because one needs to read the entire file to do the correct highlighting.
I already have a python parser for this kind of files (which gives a dict of dict string -> (string -> string)), but I don't know if python can interact with syntax highlighting in vim 7.
EDIT
As a clarification, the dictionary produced for this example is:
d = {
'node1': {'link1' : 'node2', 'attribute1' : 'an_attribute_for_node1'},
'node2': {'attribute2': 'an_attribute_for_node2'}
}
By definition, l is a link for node n if and only if:
d[n][l] in d
Names are meaningless, the format is only structure dependant, and there is no language keywords.
I would like to highlight node2 in the first line, because it is the name for a node.
I hope it is clearer now.
Someone has an idea ?
This should be very straightforward, but it's a little difficult to work out exactly what your dict looks like (what is 'string'? node1? attribute1? something else?). I have a plugin that I wrote called the ctags highlighter which does a fairly similar thing: it uses ctags to generate a list of keywords and then uses python to turn this into a simple vim script that highlights those keywords appropriately.
Basically, what you need to do is make your parser (or another python module that uses your parser) to generate a list of keywords (node1, node2 etc) and output them in this form (use as many per line as you like, but don't make the lines too long):
syn keyword GraphNode node1 node2
syn keyword GraphNode node3
Write this to a file and create an autocommand that does something like:
autocmd BufRead,BufNewFile *.myextension if filereadable('nodelist.vim') | source nodelist.vim | endif
Then do:
hi GraphNode guifg=blue
or whatever. If you want more details, post a little more information about your parser or have a look at the code in my plugin.
For more information, see
:help :autocmd
:help syn-keyword
:help BufEnter
:help BufNewFile
:help filereadable()
:help :source
:help :highlight
Edit
I'm still not completely sure I know what you want, but if I am understanding correctly, something like this should work:
Let's assume that your python parser is called mypyparser.py, it takes one argument (the current filename) and the dictionary it creates is called MyPyDict. You'll obviously have to modify the script to match the actual use of your parser. Add this script somewhere in your runtimepath (e.g. in .vimrc or in ~/.vim/ftplugin/myfiletype.vim) and then open your file and type :HighlightNodes.
" Create a command for ease of use
command! HighlightNodes call HighlightNodes()
function! HighlightNodes()
" Run the parser to create MyPyDict
exe 'pyfile mypyparser.py ' . expand('%:p')
" Next block is python code (indent gone to keep python happy)
py <<EOF
# Import the Vim interface
import vim
# Iterate through the keys in the dictionary and highlight them in Vim
for key in MyPyDict.keys():
vim.command('syn keyword GraphNode ' + key)
endfor
EOF
" Make sure that the GraphNode is highlighted in some colour or other
hi link GraphNode Keyword
endfunction

Categories

Resources