vim - how to match linked sequences in syntax file - vim

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

Related

How to have a Vim multiline syntax highlight clause?

I'm trying to write the syntax highlight module for a tiny text format that includes three different kinds of list elements (starting with -, o and x respectively), and I'd like to highlight entries based on their kind. For single lines it's easy, I just use a syn match myGroup /^\s\+- .\+/ and I'm done.
Problem is, I've been trying to do it so that the next lines without a list marker keep the same colour as the starting list item line, to no success. I've been trying to do it with syntax regions, but I can't seem to understand how they work.
To be more precise, this is the output I'm trying to reach:
If any change is needed in the file format so that it is easier/possible, I have liberty to change it.
Any clue of how can I get it?
You can try something along these lines
syntax region Minus_Region start=/^\s\+-/ end=/;/
hi Minus_Region guifg='Yellow'
syntax region O_Region start=/^\s\+o/ end=/;/
hi O_Region guifg='Green'
syntax region X_Region start=/^\s\+x/ end=/;/
hi X_Region guifg='Gray'
You define region by its start and its end (in this case ;), no matter how many lines are involved.
For more information, see help
:h syn-region
If you want to finish the regions without having a end marking character (in this case ;), you could do it using the match-end (me) option on the end argument of the regions, and having the regions end on the next region-start marker. Example:
syntax region Minus_Region start=/^\s\+- / end=/^\s\+[-ox] /me=s-1
syntax region O_Region start=/^\s\+o / end=/^\s\+[-ox] /me=s-1
syntax region X_Region start=/^\s\+x / end=/^\s\+[-ox] /me=s-1
The me=s-1 part means "The real match ends at one character to the left of the start position of the pattern match".

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

How to format "one line html" as a pretty document in sublime? [duplicate]

This question already has answers here:
How do I reformat HTML code using Sublime Text 2?
(16 answers)
Closed 9 years ago.
I have a html source file containing only one line like the following:
<html><head>test</head><body>wow</body></html>
, and I want to format it as following:
<html>
<head>
test
</head>
<body>
wow
</body>
</html>
, I have used the command: Edit-> Line-> Reindent but it doesn't work.
In Sublime Text try this:
Highlight the last character in the first tag: ">"
Use Find > Quick Add Next (Command+D on Mac or Ctrl+D on PC)
You'll now have all occurrences highlighted in multiple selections, move the caret to the end of the tag and hit return to insert all the new lines you require.
Good luck
I was under the impression that Sublime provided this ability as well. When I found out that it wasn't, I had the idea of using regular expressions. Even though regex are usually considered inappropriate for parsing XML/HTML, I found this approach to be acceptable in this case. Sublime is also said to be highly customizable by plugins, so I think this would be a way.
Sublime Plugins
To be honest, I could have thought of tidy or at least suspect that there must be plugins out there dealing with your issue. Instead I ended up writing my first sublime plugin. I have only tested it with your input and expected output, which it satisfied, but it is most certainly far from working reliably. However, I post it here to share what I've learned and it's still an answer to the problem.
Opening a new buffer (Ctrl+n) and choosing the 'New Plugin...' entry in the Menu 'Tools' generously generates a little 'Hello World!' example plugin (as a Python module), which gives a great template for implementing a sublime_plugin.TextCommand subclass. A TextCommand provides access to an active buffer/currently open file. Like its relatives WindowCommand and ApplicationCommand, it is required to overwrite a run-method.
The official API Reference suggests learning by reading the example sources distributed with the Sublime builds and located in Packages/Default relative to the Sublime config path. Further examples can be found on the website. There's more on the internet.
Processing selected text
To get to a solution for your issue, we primarily need access to a View object which represents an active text buffer. Fortunately, the TextCommand subclass we are about to implement has one, and we can conveniently ask it for the currently selected regions and their selection contents, process selected text conforming our needs and replace the selected text with our preference afterwards.
To sum up the string operations: There are four regular expressions, each of which matches one of the element classes <start-tag>, <empty-tag/>, </close-tag> and text-node. Assuming that all of our markup text is covered by these, we did each line in selection into matching substrings. These are then realigned one-per-line. Having done this, we apply simple indentation by remembering to indent every line whose predecessor contains a start tag. Lines containing end tags are unindented immediately.
Using the group addressing features of Python regex, we can determine the indentation of every line and align the next one accordingly. This, with no further ado, will result in internally consistent indented markup, but with no consideration of the lines outside the selection. By extending the selection to an enclosing element, or at least complying with the indentation levels of the adjacent lines, one could easily improve the results. Its always possible to make use of the default commands.
Another thing to take care of is binding keys to the plugin command and contributing menu entries. It is probably possible somehow, and the default .sublime-menuand .sublime-commands files in Packages/Default at least give an idea. Anyway, here's some code. It has to be saved under Packages/User/whatever.py and can be called from the Sublime Python Console (Ctrl+`) like this: view.run_command('guess_indentation').
Code
import sublime
import sublime_plugin
import re
class GuessIndentationCommand(sublime_plugin.TextCommand):
def run(self, edit):
view = self.view
#view.begin_edit()
# patterns
start_tag = '<\w+(?:\s+[^>\/]+)*\s*>' # tag_start
node_patterns = [start_tag,
start_tag[:-1]+'\/\s*>', # tag_empty
'<\/\s?\w+\s?>', # tag_close
'[^>\s][^<>]*[^<\s]'] # text_node
patterns = '(?:{0})'.format('|'.join(node_patterns))
indentors = re.compile('[ \t]*({0})'.format('|'.join(node_patterns[:1])))
unindentors=re.compile('[ \t]*({0})'.format(node_patterns[2]))
# process selected text
for region in view.sel():
# if selection contains text:
if not region.empty():
selection = view.substr(region)
expanded = []
# divide selected lines into XML elements, if it contains more than one
for line in selection.split('\n'):
elements = re.findall(patterns, line)
if len(elements)>0:
expanded += elements
else:
expanded.append(line)
# indent output
indent=0
indented = []
for line in expanded:
match = unindentors.match(line)
if match:
indent = max(0, indent-1)
# append line to output, unindented if closing tag
indented.append('\t'*indent+line)
if match:
continue
# test for possible indentation candidate
# indentation applies to the NEXT line
match = indentors.match(line)
if match:
indent+=1
# replace selection with aligned output
view.replace(edit, region, '\n'.join(indented))
if its for something simple, i was able to record a macro (tools -> record macro) indenting the tags and then save it and reuse this macro. not sure if that helps any though.

Vim syntax highlighting

Alright, this is probably a stupid question, but....
I've got a file of source code in a proprietary language. I want to edit said file with VIM, instead of their crummy editor. I'd like basic syntax highlighting of the language, but I don't want to spend a bunch of time rolling my own syntax file.
Therefore, does VIM have a basic source highlighting module? It doesn't need to be perfect, I just want it to cover simple things. Currently, my only choices are no syntax highlighting, pick a random language, or roll my own.
EDIT: Source code sample below
{
function letter do
gposition 0, 0
if gender = "M" do
if language = "SPA" OR state = "PR" do
%male spanish letter
gposition .26, .75
pdfimage "MALE SPANISH.pdf", 1, .93
setcolor truewhite
setfillmode 1
%whitebox
gposition 5.25, 1.25
rectangle 2.5, .5
Could this be the correct language?
http://www.iml.ece.mcgill.ca/~stephan/node/17
Rolling your own syntax highlighting is not difficult at all and it would take a few minutes.
For example, I wrote a DSL (called Konira) that uses Python for the most part, but it fails at highlighting my custom DSL statements. This is how the "extra" highlighting looks:
function! KoniraSyntax() abort
let b:current_syntax = 'konira'
syn match KoniraIt '\v^\s+it\s+'
syn match KoniraSkipIf '\v^\s+skip\s+if'
syn match KoniraDescribe '\v^describe\s+'
syn match KoniraRaises '\v^\s+raises\s+'
syn match KoniraBeforeAll '\v^\s+before\s+all'
syn match KoniraBeforeEach '\v^\s+before\s+each'
syn match KoniraAfterEach '\v^\s+after\s+each'
syn match KoniraAfterAll '\v^\s+after\s+all'
hi def link KoniraSkipIf Statement
hi def link KoniraIt Statement
hi def link KoniraDescribe Statement
hi def link KoniraRaises Identifier
hi def link KoniraBeforeAll Statement
hi def link KoniraBeforeEach Statement
hi def link KoniraAfterAll Statement
hi def link KoniraAfterEach Statement
endfunction
As you can see above, I set the current syntax, then I match via regular expression those
statements that I want, and the I apply the type of highlighting that I need on that match.
And you can call it as a regular function when you know (or if you able to detect) that you are editing such a source file:
call KoniraSyntax()

Vim: need help with a tiny script code to highlight

I need a script code to highlight "[Capítulo" and "]" and everything between them. Thank you.
I want it to work everytime I open , for example, a .txt file. Just like code highlighting.
Here's an easy way to do it:
in vim, make sure syntax highlighting is on with :syn on
run the command :highlight to get a listing of all the highlight group names, and samples of what they look like. The Error group looks like it stands out well in my colorscheme, so I'll use that in my example (but you can use any of the other names, like Todo or Search)
:syntax match Error /\[Capítulo[^\]]*\]/
This pattern will keep you from greedily matching the largest chunk. Even though other people are suggesting you use the regular expression /\[Capítulo.*\]/ - it's probably not what you want, because it will match everything in between if there are two or more such patterns on a line.
For example /\[Capítulo.*\]/ will match this entire line:
[Capítulo foo] these words should not be highlighted [Capítulo bar]
The same example but with /\[Capítulo[^\]]*\]/ will only match stuff inside []:
[Capítulo foo] these words should not be highlighted [Capítulo bar]
With regular expressions, it's a common trick to make a group that matches everything but the character that you want to end your match, instead of using the .* which will match as many characters as it can. In this case, we make the group [^\]]* - which says "match everything except ]."
If this works the way you want it to, add the syntax match line without the ":" to your .vimrc
A regular expression is what you're looking for:
Type / to enter in "search mode" and type:
\[Capítulo.*\]/

Resources