Monday, March 17, 2014

Context-Sensitive Python Syntax Highlighting

" Author: Eric Pruitt
" Description: Context-sensitive highlighting for `else` clauses.

" These are blocks that need to be excluded from the LoopElse and TryElse
" regions to avoid unnecessarily activating them. The current list allows this
" script to work with the default Python syntax file included with Vim (at
" least the one that comes with 7.3 and 7.4) as well as Dmitry Vasiliev's
" [enhanced syntax file](http://www.vim.org/scripts/script.php?script_id=790).
" If highlighting appears to be broken with another syntax file, determine the
" by moving the cursor on top of the improperly highlighted word and executing
" the following command:
"
" :echo synIDattr(synID(line("."), col("."), 1), "name")
"
" Add the displayed name to the list below, then restart Vim.
let s:excludedRegions = [
\ "pythonDoctestValue",
\ "pythonBytesContent",
\ "pythonBytesError",
\ "pythonDottedName",
\ "pythonFunction",
\]

let s:indentLevels = range(15, 0, -1)
let s:allMatchNames =
\ join([""] + s:indentLevels, ",pythonLoopElse") .
\ join([""] + s:indentLevels, ",pythonTryElse")

let s:allbutList = join(s:excludedRegions, ",")

"syn keyword pythonRepeat contained else
"syn keyword None else

for s:indentLevel in s:indentLevels
" Remove current pythonLoopElse name from the list of excluded blocks.
let s:invalidContexts = substitute(s:allMatchNames, ',pythonLoopElse' . s:indentLevel . '\>', '', '')

execute printf(
\ 'syn region pythonLoopBlock%i start="^\( \|\t\)\{%i\}\(for\|while\)" end="^\( \|\t\)\{,%i\}\S\+" transparent keepend contains=ALLBUT,%s%s',
\ s:indentLevel,
\ s:indentLevel,
\ s:indentLevel,
\ s:allbutList,
\ s:invalidContexts)
execute printf(
\ 'syn match pythonLoopElse%i "^\( \|\t\)\{%i\}else\>" contained',
\ s:indentLevel,
\ s:indentLevel)
execute printf('hi def link pythonLoopElse%i Repeat', s:indentLevel)
endfor

for s:indentLevel in s:indentLevels
let s:invalidContexts = substitute(s:allMatchNames, ',pythonTryElse' . s:indentLevel . '\>', '', '')
execute printf(
\ 'syn region pythonTryBlock%i start="^\( \|\t\)\{%i\}\(except\)" end="^\( \|\t\)\{%i\}\S\+" transparent keepend contains=ALLBUT,%s%s',
\ s:indentLevel,
\ s:indentLevel,
\ s:indentLevel,
\ s:allbutList,
\ s:invalidContexts)
execute printf(
\ 'syn match pythonTryElse%i "^\( \)\{%i\}else\>" contained',
\ s:indentLevel,
\ s:indentLevel)
execute printf('hi def link pythonTryElse%i Exception', s:indentLevel)
endfor
# "else" is never highlighted correctly when the loop blocks are not indented.
while False:
pass
else:
pass

if False:
# This works OK
while False:
pass
else:
pass

# The "else" is not highligted correctly
while False:
while False:
pass
else:
pass

# This (still) works OK
if False:
pass
else:
pass

class Highlight():
# The second function defintion loses its highlighting
def nothing():
while False:
while False:
pass

def function():
pass

I'm attempting to create a syntax file for Python loaded from
$VIM/after/syntax/python.vim that generates rules to highlight "else"
based on context; python allows "else" to be used for "while" and "for"
loops as well as in "try" blocks after "except" entries. I've gotten
fairly close to achieving the behaviour I want, but I'm stuck now and
have not been able to figure out how to resolve some remaining issues.
I've attached the script to this email along with a sample Python file
with some test cases. Note that being able to see a difference in the
"else" coloring is contingent on the color scheme you're using having a
different coloring for "Repeater" and "Conditional" tokens, so I've also
included a screenshot of how things render on my system.

Eric

--
--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vim_use+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments: