1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
|
# File: voom_mode_python.py
# Last Modified: 2017-01-07
# Description: VOoM -- two-pane outliner plugin for Python-enabled Vim
# Website: http://www.vim.org/scripts/script.php?script_id=2657
# Author: Vlad Irnov (vlad DOT irnov AT gmail DOT com)
# License: CC0, see http://creativecommons.org/publicdomain/zero/1.0/
"""
VOoM markup mode for Python code.
See |voom-mode-python|, ../../../doc/voom.txt#*voom-mode-python*
"""
import sys
if sys.version_info[0] > 2:
xrange = range
import token, tokenize
import traceback
import vim
def hook_makeOutline(VO, blines):
"""Return (tlines, bnodes, levels) for Body lines blines.
blines is either Vim buffer object (Body) or list of buffer lines.
"""
Z = len(blines)
tlines, bnodes, levels = [], [], []
tlines_add, bnodes_add, levels_add = tlines.append, bnodes.append, levels.append
#ignore_lnums, func_lnums = get_lnums_from_tokenize(blines)
try:
ignore_lnums, func_lnums = get_lnums_from_tokenize(blines)
except (IndentationError, tokenize.TokenError):
vim.command("call voom#ErrorMsg('VOoM: EXCEPTION WHILE PARSING PYTHON OUTLINE')")
# DO NOT print to sys.stderr -- triggers Vim error when default stderr (no PyLog)
#traceback.print_exc() --this goes to sys.stderr
#print traceback.format_exc() --ok but no highlighting
lines = traceback.format_exc().replace("'","''").split('\n')
for ln in lines:
vim.command("call voom#ErrorMsg('%s')" %ln)
return (['= |!!!ERROR: OUTLINE IS INVALID'], [1], [1])
isHead = False # True if current line is a headline
indents = [0,] # indents of previous levels
funcLevels = [] # levels of previous def or class
indentError = '' # inconsistent indent
isDecor = 0 # keeps track of decorators, set to lnum of the first decorator
X = ' ' # char in Tree's column 2 (marks)
for i in xrange(Z):
bnode = i + 1
if bnode in ignore_lnums: continue
bline = blines[i]
bline_s = bline.strip()
if not bline_s: continue
if bline_s.startswith('#'):
# ignore comment lines consisting only of #, -, =, spaces, tabs (separators, pretty headers)
if not bline_s.lstrip('# \t-='): continue
isComment = True
else:
isComment = False
bline_ls = bline.lstrip()
# compute indent and level
indent = len(bline) - len(bline_ls)
if indent > indents[-1]:
indents.append(indent)
elif indent < indents[-1]:
while indents and (indents[-1] > indent):
indents.pop()
if indents[-1]==indent:
indentError = ''
else:
indentError = '!!! '
lev = len(indents)
# First line after the end of a class or def block.
if funcLevels and lev <= funcLevels[-1]:
isHead = True
while funcLevels and funcLevels[-1] >= lev:
funcLevels.pop()
# First line of a class or def block.
if bnode in func_lnums:
isHead = True
if isDecor:
bnode = isDecor
isDecor = 0
X = 'd'
if not funcLevels or (lev > funcLevels[-1]):
funcLevels.append(lev)
# Line after a decorator. Not a def or class.
elif isDecor:
# ingore valid lines between the first decorator and function/class
if bline_s.startswith('@') or isComment or not bline_s:
isHead = False
continue
# Invalid line after a decorator (should be syntax error): anything
# other than another decorator, comment, blank line, def/class.
# If it looks like a headline, let it be a headline.
else:
isDecor = 0
# Decorator line (the first one if a group of several).
elif bline_s.startswith('@'):
isDecor = bnode
isHead = False
continue
# Special comment line (unconditional headline). Not a separator or pretty header line.
elif isComment:
if bline_s.startswith('###') or bline_s.startswith('#--') or bline_s.startswith('#=='):
isHead = True
if isHead:
##########################################
# Take care of pretty headers like this. #
##########################################
if isComment:
# add preceding lines to the current node if they consist only of #, =, -, whitespace
while bnode > 1:
bline_p = blines[bnode-2].lstrip()
if not bline_p.startswith('#') or bline_p.lstrip('# \t-='):
break
else:
bnode -= 1
# the end
isHead = False
tline = ' %s%s|%s%s' %(X, '. '*(lev-1), indentError, bline_s)
X = ' '
tlines_add(tline)
bnodes_add(bnode)
levels_add(lev)
return (tlines, bnodes, levels)
class BLines:
"""Wrapper around Vim buffer object or list of Body lines to provide
readline() method for use with tokenize.generate_tokens().
"""
def __init__(self, blines):
self.blines = blines
self.size = len(blines)
self.idx = -1
def readline(self):
self.idx += 1
if self.idx == self.size:
return ''
return "%s\n" %self.blines[self.idx]
### toktypes of tokens
STRING = token.STRING
NAME = token.NAME
NEWLINE = token.NEWLINE
def get_lnums_from_tokenize(blines):
"""Return dicts. Keys are Body lnums.
The main purpose is to get list of lnums to ignore: multi-line strings and
expressions.
"""
# lnums to ignore: multi-line strings and expressions other than the first line
ignore_lnums = {}
# lnums of 'class' and 'def' tokens
func_lnums = {}
inName = False
for tok in tokenize.generate_tokens(BLines(blines).readline):
toktype, toktext, (srow, scol), (erow, ecol), line = tok
#print('token.tok_name[toktype]=%s tok=%s' %(token.tok_name[toktype], tok))
if toktype == NAME:
if not inName:
inName = True
srow_name = srow
if toktext in ('def','class'):
func_lnums[srow] = toktext
elif toktype == NEWLINE and inName:
inName = False
if srow_name != erow:
for i in xrange(srow_name+1, erow+1):
ignore_lnums[i] = 0
elif toktype == STRING:
if srow != erow:
for i in xrange(srow+1, erow+1):
ignore_lnums[i] = 0
return (ignore_lnums, func_lnums)
def get_body_indent(body):
"""Return string used for indenting Body lines."""
et = int(vim.eval("getbufvar(%s,'&et')" %body))
if et:
ts = int(vim.eval("getbufvar(%s,'&ts')" %body))
return ' '*ts
else:
return '\t'
def hook_newHeadline(VO, level, blnum, tlnum):
"""Return (tree_head, bodyLines).
tree_head is new headline string in Tree buffer (text after |).
bodyLines is list of lines to insert in Body buffer.
"""
tree_head = '### NewHeadline'
indent = get_body_indent(VO.body)
body_head = '%s%s' %(indent*(level-1), tree_head)
return (tree_head, [body_head])
#def hook_changeLevBodyHead(VO, h, levDelta):
#"""Increase of decrease level number of Body headline by levDelta."""
#if levDelta==0: return h
def hook_doBodyAfterOop(VO, oop, levDelta, blnum1, tlnum1, blnum2, tlnum2, blnumCut, tlnumCut):
# this is instead of hook_changeLevBodyHead()
#print('oop=%s levDelta=%s blnum1=%s tlnum1=%s blnum2=%s tlnum2=%s tlnumCut=%s blnumCut=%s' % (oop, levDelta, blnum1, tlnum1, blnum2, tlnum2, tlnumCut, blnumCut))
Body = VO.Body
Z = len(Body)
ind = get_body_indent(VO.body)
# levDelta is wrong when pasting because hook_makeOutline() looks at relative indent
# determine level of pasted region from indent of its first line
if oop=='paste':
bline1 = Body[blnum1-1]
lev = int((len(bline1) - len(bline1.lstrip())) / len(ind)) + 1
levDelta = VO.levels[tlnum1-1] - lev
if not levDelta: return
indent = abs(levDelta) * ind
#--- copied from voom_mode_thevimoutliner.py -----------------------------
if blnum1:
assert blnum1 == VO.bnodes[tlnum1-1]
if tlnum2 < len(VO.bnodes):
assert blnum2 == VO.bnodes[tlnum2]-1
else:
assert blnum2 == Z
# dedent (if possible) or indent every non-blank line in Body region blnum1,blnum2
blines = []
for i in xrange(blnum1-1,blnum2):
line = Body[i]
if not line.strip():
blines.append(line)
continue
if levDelta > 0:
line = '%s%s' %(indent,line)
elif levDelta < 0 and line.startswith(indent):
line = line[len(indent):]
blines.append(line)
# replace Body region
Body[blnum1-1:blnum2] = blines
assert len(Body)==Z
|