mirror of
1
0
Fork 0
ultimate-vim/vim_plugin_candinates_src/VOoM-4.3/VOoM-4.3/plugin/voom/voom.py

1958 lines
64 KiB
Python

# voom.py
# Last Modified: 2012-04-16
# VOoM -- Vim two-pane outliner, plugin for Python-enabled Vim version 7.x
# Version: 4.3
# Website: http://www.vim.org/scripts/script.php?script_id=2657
# Author: Vlad Irnov (vlad DOT irnov AT gmail DOT com)
# License: This program is free software. It comes without any warranty,
# to the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want To
# Public License, Version 2, as published by Sam Hocevar.
# See http://sam.zoy.org/wtfpl/COPYING for more details.
"""This module is meant to be imported by voom.vim ."""
import vim
import sys, os, re
import traceback
import bisect
# lazy imports
shuffle = None # random.shuffle
#Vim = sys.modules['__main__']
# see voom.vim for conventions
# voom_WhatEver() is Python code for Voom_WhatEver() function in voom.vim
#---Constants and Settings--------------------{{{1=
# VO is instance of VoomOutline class, stored in dict VOOMS
# create VOOMS in voom.vim: less disruption if this module is reloaded
#VOOMS = {} # {body: VO, ...}
# {filetype: make_head_<filetype> function, ...}
MAKE_HEAD = {}
# default start fold marker string and regexp
MARKER = '{{{' #}}}
MARKER_RE = re.compile(r'{{{(\d+)(x?)') #}}}
# {'markdown': 'markdown', 'pandoc': 'markdown', ...}
if vim.eval("exists('g:voom_ft_modes')")=='1':
FT_MODES = vim.eval('g:voom_ft_modes')
else:
FT_MODES = {}
# default markup mode
if vim.eval("exists('g:voom_default_mode')")=='1':
MODE = vim.eval('g:voom_default_mode')
else:
MODE = ''
#---Outline Construction----------------------{{{1o
class VoomOutline: #{{{2
"""Outline data for one Body buffer.
Instantiated from Body by Voom_Init().
"""
def __init__(self,body):
assert body == int(vim.eval("bufnr('')"))
def voom_Init(body): #{{{2
VO = VoomOutline(body)
VO.bnodes = [] # Body lnums of headlines
VO.levels = [] # headline levels
VO.body = body
VO.Body = vim.current.buffer
VO.tree = None # will set later
VO.Tree = None # will set later
VO.snLn = 1 # will change later if different
# first Tree line is Body buffer name and path
VO.bname = vim.eval('l:firstLine')
# Body &filetype
VO.filetype = vim.eval('&filetype')
VO.enc = get_vim_encoding()
# start fold marker string and regexp (default and 'fmr' modes)
marker = vim.eval('&foldmarker').split(',')[0]
VO.marker = marker
if marker==MARKER:
VO.marker_re = MARKER_RE
else:
VO.marker_re = re.compile(re.escape(marker) + r'(\d+)(x?)')
# chars to strip from right side of Tree headlines (default and 'fmr' modes)
if vim.eval("exists('g:voom_rstrip_chars_{&ft}')")=="1":
VO.rstrip_chars = vim.eval("g:voom_rstrip_chars_{&ft}")
else:
VO.rstrip_chars = vim.eval("&commentstring").split('%s')[0].strip() + " \t"
### get markup mode, l:qargs is mode's name ###
mModule = 0
qargs = vim.eval('l:qargs').strip() or FT_MODES.get(VO.filetype, MODE)
if qargs:
mName = 'voom_mode_%s' %qargs
try:
mModule = __import__(mName)
VO.bname += ', %s' %qargs
vim.command("call Voom_WarningMsg('VOoM: mode ''%s'' [%s]')" %(qargs.replace("'","''"), os.path.abspath(mModule.__file__).replace("'","''")))
except ImportError:
vim.command("call Voom_ErrorMsg('VOoM: cannot import Python module %s')" %mName.replace("'","''"))
return
VO.mModule = mModule
### define mode-specific methods ###
# no markup mode, default behavior
if not mModule:
VO.mmode = 0
if VO.filetype in MAKE_HEAD:
VO.makeOutline = makeOutlineH
else:
VO.makeOutline = makeOutline
VO.newHeadline = newHeadline
VO.changeLevBodyHead = changeLevBodyHead
VO.hook_doBodyAfterOop = 0
# markup mode for fold markers, similar to the default behavior
elif getattr(mModule,'MODE_FMR',0):
VO.mmode = 0
f = getattr(mModule,'hook_makeOutline',0)
if f:
VO.makeOutline = f
elif VO.filetype in MAKE_HEAD:
VO.makeOutline = makeOutlineH
else:
VO.makeOutline = makeOutline
VO.newHeadline = getattr(mModule,'hook_newHeadline',0) or newHeadline
VO.changeLevBodyHead = changeLevBodyHead
VO.hook_doBodyAfterOop = 0
# markup mode not for fold markers
else:
VO.mmode = 1
VO.makeOutline = getattr(mModule,'hook_makeOutline',0) or makeOutline
VO.newHeadline = getattr(mModule,'hook_newHeadline',0) or newHeadline
# These must be False if not defined by the markup mode.
VO.changeLevBodyHead = getattr(mModule,'hook_changeLevBodyHead',0)
VO.hook_doBodyAfterOop = getattr(mModule,'hook_doBodyAfterOop',0)
### the end ###
vim.command('let l:mmode=%s' %VO.mmode)
VOOMS[body] = VO
def voom_TreeCreate(): #{{{2
"""This is part of Voom_TreeCreate(), called from Tree."""
body = int(vim.eval('a:body'))
blnr = int(vim.eval('l:blnr')) # Body cursor lnum
VO = VOOMS[body]
if VO.mmode:
computeSnLn(body, blnr)
# reST, wiki files often have most headlines at level >1
vim.command('setl fdl=2')
return
bnodes = VO.bnodes
Body = VO.Body
z = len(bnodes)
### compute snLn, create Tree folding
# find bnode marked with '='
# find bnodes marked with 'o'
snLn = 0
marker_re = VO.marker_re
marker_re_search = marker_re.search
oFolds = []
for i in xrange(1,z):
bline = Body[bnodes[i]-1]
# part of Body headline after marker+level+'x'
bline2 = bline[marker_re_search(bline).end():]
if not bline2: continue
if bline2[0]=='=':
snLn = i+1
elif bline2[0]=='o':
oFolds.append(i+1)
if bline2[1:] and bline2[1]=='=':
snLn = i+1
# create Tree folding
if oFolds:
cFolds = foldingFlip(VO,2,z,oFolds)
foldingCreate(2,z,cFolds)
if snLn:
vim.command('call Voom_SetSnLn(%s,%s)' %(body,snLn))
VO.snLn = snLn
# set blnShow if Body cursor is on or before the first headline
if z > 1 and blnr <= bnodes[1]:
vim.command('let l:blnShow=%s' %bnodes[snLn-1])
else:
# no Body headline is marked with =
# select current Body node
computeSnLn(body, blnr)
def makeOutline(VO, blines): #{{{2
"""Return (tlines, bnodes, levels) for Body lines blines.
blines is either Vim buffer object (Body) or list of buffer lines.
"""
# blines is usually Body. It is list of clipboard lines during Paste.
# This function is slower when blines is Vim buffer object instead of
# Python list. But overall time to do outline update is the same and memory
# usage is less because we don't create new list (see v3.0 notes)
# Optimized for buffers in which most lines don't have fold markers.
# NOTE: duplicate code with makeOutlineH(), only head construction is different
marker = VO.marker
marker_re_search = VO.marker_re.search
Z = len(blines)
tlines, bnodes, levels = [], [], []
tlines_add, bnodes_add, levels_add = tlines.append, bnodes.append, levels.append
c = VO.rstrip_chars
for i in xrange(Z):
if not marker in blines[i]: continue
bline = blines[i]
m = marker_re_search(bline)
if not m: continue
lev = int(m.group(1))
head = bline[:m.start()].lstrip().rstrip(c).strip('-=~').strip()
tline = ' %s%s|%s' %(m.group(2) or ' ', '. '*(lev-1), head)
tlines_add(tline)
bnodes_add(i+1)
levels_add(lev)
return (tlines, bnodes, levels)
def makeOutlineH(VO, blines): #{{{2
"""Identical to makeOutline(), duplicate code. The only difference is that
a custom function is used to construct Tree headline text.
"""
# NOTE: duplicate code with makeOutline(), only head construction is different
marker = VO.marker
marker_re_search = VO.marker_re.search
Z = len(blines)
tlines, bnodes, levels = [], [], []
tlines_add, bnodes_add, levels_add = tlines.append, bnodes.append, levels.append
h = MAKE_HEAD[VO.filetype]
for i in xrange(Z):
if not marker in blines[i]: continue
bline = blines[i]
m = marker_re_search(bline)
if not m: continue
lev = int(m.group(1))
head = h(bline,m)
tline = ' %s%s|%s' %(m.group(2) or ' ', '. '*(lev-1), head)
tlines_add(tline)
bnodes_add(i+1)
levels_add(lev)
return (tlines, bnodes, levels)
#--- make_head functions --- {{{2
def make_head_html(bline,match):
s = bline[:match.start()].strip().strip('-=~').strip()
if s.endswith('<!'):
return s[:-2].strip()
else:
return s
MAKE_HEAD['html'] = make_head_html
#def make_head_vim(bline,match):
# return bline[:match.start()].lstrip().rstrip('" \t').strip('-=~').strip()
#MAKE_HEAD['vim'] = make_head_vim
#def make_head_py(bline,match):
# return bline[:match.start()].lstrip().rstrip('# \t').strip('-=~').strip()
#for ft in 'python ruby perl tcl'.split():
# MAKE_HEAD[ft] = make_head_py
def updateTree(body,tree): #{{{2
"""Construct outline for Body body.
Update lines in Tree buffer if needed.
This can be run from any buffer as long as Tree is set to ma.
"""
### Construct outline.
VO = VOOMS[body]
assert VO.tree == tree
#blines = VO.Body[:] # wasteful, see v3.0 notes
tlines, bnodes, levels = VO.makeOutline(VO, VO.Body)
tlines[0:0], bnodes[0:0], levels[0:0] = [VO.bname], [1], [1]
VO.bnodes, VO.levels = bnodes, levels
### Add the = mark.
snLn = VO.snLn
Z = len(bnodes)
# snLn got larger than the number of nodes because some nodes were
# deleted while editing the Body
if snLn > Z:
snLn = Z
vim.command('call Voom_SetSnLn(%s,%s)' %(body,snLn))
VO.snLn = snLn
tlines[snLn-1] = '=%s' %tlines[snLn-1][1:]
### Compare Tree lines, draw as needed.
# Draw all Tree lines only when needed. This is optimization for large
# outlines, e.g. >1000 Tree lines. Drawing all lines is slower than
# comparing all lines and then drawing nothing or just one line.
Tree = VO.Tree
#tlines_ = Tree[:]
if not len(Tree)==len(tlines):
Tree[:] = tlines
vim.command('let l:ok=1')
return
# If only one line is modified, draw that line only. This ensures that
# editing (and inserting) a single headline in a large outline is fast.
# If more than one line is modified, draw all lines from first changed line
# to the end of buffer.
draw_one = False
for i in xrange(len(tlines)):
if not tlines[i]==Tree[i]:
if draw_one==False:
draw_one = True
diff = i
else:
Tree[diff:] = tlines[diff:]
vim.command('let l:ok=1')
return
if draw_one:
Tree[diff] = tlines[diff]
vim.command('let l:ok=1')
# why l:ok is needed: ../../doc/voom.txt#id_20110213212708
def computeSnLn(body, blnr): #{{{2
"""Compute Tree lnum for node at line blnr in Body body.
Assign Vim and Python snLn vars.
"""
# snLn should be 1 if blnr is before the first node, top of Body
VO = VOOMS[body]
snLn = bisect.bisect_right(VO.bnodes, blnr)
vim.command('call Voom_SetSnLn(%s,%s)' %(body,snLn))
VO.snLn = snLn
def voom_UnVoom(body): #{{{2
if body in VOOMS: del VOOMS[body]
#---Outline Traversal-------------------------{{{1
# Functions for getting node's parents, children, ancestors, etc.
# Nodes here are Tree buffer lnums.
# All we do is traverse VO.levels.
def nodeHasChildren(VO, lnum): #{{{2
"""Determine if node at Tree line lnum has children."""
levels = VO.levels
if lnum==1 or lnum==len(levels): return False
elif levels[lnum-1] < levels[lnum]: return True
else: return False
def nodeSubnodes(VO, lnum): #{{{2
"""Number of all subnodes for node at Tree line lnum."""
levels = VO.levels
z = len(levels)
if lnum==1 or lnum==z: return 0
lev = levels[lnum-1]
for i in xrange(lnum,z):
if levels[i]<=lev:
return i-lnum
return z-lnum
def nodeParent(VO, lnum): #{{{2
"""Return lnum of closest parent of node at Tree line lnum."""
levels = VO.levels
lev = levels[lnum-1]
if lev==1: return None
for i in xrange(lnum-2,0,-1):
if levels[i] < lev: return i+1
def nodeAncestors(VO, lnum): #{{{2
"""Return lnums of ancestors of node at Tree line lnum."""
levels = VO.levels
lev = levels[lnum-1]
if lev==1: return []
ancestors = []
for i in xrange(lnum-2,0,-1):
levi = levels[i]
if levi < lev:
lev = levi
ancestors.append(i+1)
if lev==1:
ancestors.reverse()
return ancestors
# we get here if there are no nodes at level 1 (wiki mode)
ancestors.reverse()
return ancestors
def nodeUNL(VO, lnum): #{{{2
"""Compute UNL of node at Tree line lnum.
Return list of headlines.
"""
Tree = VO.Tree
levels = VO.levels
if lnum==1: return ['top-of-buffer']
parents = nodeAncestors(VO,lnum)
parents.append(lnum)
heads = [Tree[ln-1].split('|',1)[1] for ln in parents]
return heads
def nodeSiblings(VO, lnum): #{{{2
"""Return lnums of siblings for node at Tree line lnum.
These are nodes with the same parent and level as lnum node. Sorted in
ascending order. lnum itself is included. First node (line 1) is never
included, that is minimum lnum in results is 2.
"""
levels = VO.levels
lev = levels[lnum-1]
siblings = []
# scan back
for i in xrange(lnum-1,0,-1):
levi = levels[i]
if levi < lev:
break
elif levi==lev:
siblings[0:0] = [i+1]
# scan forward
for i in xrange(lnum,len(levels)):
levi = levels[i]
if levi < lev:
break
elif levi==lev:
siblings.append(i+1)
return siblings
def rangeSiblings(VO, lnum1, lnum2): #{{{2
"""Return lnums of siblings for nodes in Tree range lnum1,lnum2.
These are nodes with the same parent and level as lnum1 node.
First node (first Tree line) is never included, that is minimum lnum in results is 2.
Return None if range is ivalid.
"""
if lnum1==1: lnum1 = 2
if lnum1 > lnum2: return None
levels = VO.levels
lev = levels[lnum1-1]
siblings = [lnum1]
for i in xrange(lnum1,lnum2):
levi = levels[i]
# invalid range
if levi < lev:
return None
elif levi==lev:
siblings.append(i+1)
return siblings
def getSiblingsGroups(VO, siblings): #{{{2
"""Return list of groups of siblings in the region defined by 'siblings'
group, which is list of siblings in ascending order (Tree lnums).
Siblings in each group are nodes with the same parent and level.
Siblings in each group are in ascending order.
List of groups is reverse-sorted by level of siblings and by parent lnum:
from RIGHT TO LEFT and from BOTTOM TO TOP.
"""
if not siblings: return []
levels = VO.levels
lnum1, lnum2 = siblings[0], siblings[-1]
lnum2 = lnum2 + nodeSubnodes(VO,lnum2)
# get all parents (nodes with children) in the range
parents = [i for i in xrange(lnum1,lnum2) if levels[i-1]<levels[i]]
if not parents:
return [siblings]
# get children for each parent
results_dec = [(levels[lnum1-1], 0, siblings)]
for p in parents:
sibs = [p+1]
lev = levels[p] # level of siblings of this parent
for i in xrange(p+1, lnum2):
levi = levels[i]
if levi==lev:
sibs.append(i+1)
elif levi < lev:
break
results_dec.append((lev, p, sibs))
results_dec.sort()
results_dec.reverse()
results = [i[2] for i in results_dec]
assert len(parents)+1 == len(results)
return results
def nodesBodyRange(VO, ln1, ln2, withSubnodes=False): #{{{2
"""Return Body start and end lnums (bln1, bln2) corresponding to nodes at
Tree lnums ln1 to ln2. Include ln2's subnodes if withSubnodes."""
bln1 = VO.bnodes[ln1-1]
if withSubnodes:
ln2 += nodeSubnodes(VO,ln2)
if ln2 < len(VO.bnodes):
bln2 = VO.bnodes[ln2]-1
else:
bln2 = len(VO.Body)
return (bln1,bln2)
# (bln1,bln2) can be (1,0), see voom_TreeSelect()
# this is what we want: getbufline(body,1,0)==[]
#---Outline Navigation------------------------{{{1
def voom_TreeSelect(): #{{{2
# Get first and last lnums of Body node for Tree line lnum.
lnum = int(vim.eval('l:lnum'))
body = int(vim.eval('l:body'))
VO = VOOMS[body]
VO.snLn = lnum
vim.command('let l:blnum1=%s' %(VO.bnodes[lnum-1]))
if lnum < len(VO.bnodes):
vim.command('let l:blnum2=%s' %(VO.bnodes[lnum]-1 or 1))
else:
vim.command("let l:blnum2=%s" %(len(VO.Body)+1))
# "or 1" takes care of situation when:
# lnum is 1 (first Tree line) and first Body line is a headline.
# In that case VO.bnodes is [1, 1, ...] and (l:blnum1,l:blnum2) is (1,0)
def voom_TreeToStartupNode(): #{{{2
body = int(vim.eval('l:body'))
VO = VOOMS[body]
bnodes = VO.bnodes
Body = VO.Body
marker_re = VO.marker_re
z = len(bnodes)
# find Body headlines marked with '='
lnums = []
for i in xrange(1,z):
bline = Body[bnodes[i]-1]
# part of Body headline after marker+level+'x'+'o'
bline2 = bline[marker_re.search(bline).end():]
if not bline2: continue
if bline2[0]=='=':
lnums.append(i+1)
elif bline2[0]=='o':
if bline2[1:] and bline2[1]=='=':
lnums.append(i+1)
vim.command('let l:lnums=%s' %repr(lnums))
def voom_EchoUNL(): #{{{2
bufType = vim.eval('l:bufType')
body = int(vim.eval('l:body'))
tree = int(vim.eval('l:tree'))
lnum = int(vim.eval('l:lnum'))
VO = VOOMS[body]
assert VO.tree == tree
if bufType=='Body':
lnum = bisect.bisect_right(VO.bnodes, lnum)
heads = nodeUNL(VO,lnum)
UNL = ' -> '.join(heads)
vim.command("let @n='%s'" %UNL.replace("'", "''"))
for h in heads[:-1]:
vim.command("echon '%s'" %(h.replace("'", "''")))
vim.command("echohl TabLineFill")
vim.command("echon ' -> '")
vim.command("echohl None")
vim.command("echon '%s'" %(heads[-1].replace("'", "''")))
def voom_Grep(): #{{{2
body = int(vim.eval('l:body'))
tree = int(vim.eval('l:tree'))
VO = VOOMS[body]
assert VO.tree == tree
bnodes = VO.bnodes
matchesAND, matchesNOT = vim.eval('l:matchesAND'), vim.eval('l:matchesNOT')
# convert blnums of mathes into tlnums, that is node numbers
tlnumsAND, tlnumsNOT = [], [] # lists of AND and NOT "tlnums" dicts
counts = {} # {tlnum: count of all AND matches in this node, ...}
blnums = {} # {tlnum: first AND match in this node, ...}
for L in matchesAND:
tlnums = {} # {tlnum of node with a match:0, ...}
L.pop()
for bln in L:
bln = int(bln)
tln = bisect.bisect_right(bnodes, bln)
if not tln in blnums:
blnums[tln] = bln
elif blnums[tln] > bln:
blnums[tln] = bln
if tln in counts:
counts[tln]+=1
else:
counts[tln] = 1
tlnums[tln] = 0
tlnumsAND.append(tlnums)
for L in matchesNOT:
tlnums = {} # {tlnum of node with a match:0, ...}
L.pop()
for bln in L:
bln = int(bln)
tln = bisect.bisect_right(bnodes, bln)
tlnums[tln] = 0
tlnumsNOT.append(tlnums)
# if there are only NOT patterns
if not matchesAND:
tlnumsAND = [{}.fromkeys(range(1,len(bnodes)+1))]
# compute intersection
results = intersectDicts(tlnumsAND, tlnumsNOT)
results = results.keys()
results.sort()
#print results
# need this to left-align UNLs in the qflist
max_size = 0
for t in results:
if not matchesAND:
blnums[t] = bnodes[t-1]
counts[t] = 0
size = len('%s%s%s' %(t, counts[t], blnums[t]))
if size > max_size:
max_size = size
# list of dictionaries for setloclist() or setqflist()
loclist = []
for t in results:
size = len('%s%s%s' %(t, counts[t], blnums[t]))
spaces = ' '*(max_size - size)
UNL = ' -> '.join(nodeUNL(VO,t)).replace("'", "''")
text = 'n%s:%s%s|%s' %(t, counts[t], spaces, UNL)
d = "{'text':'%s', 'lnum':%s, 'bufnr':%s}, " %(text, blnums[t], body)
loclist .append(d)
#print '\n'.join(loclist)
vim.command("call setqflist([%s],'a')" %(''.join(loclist)) )
def intersectDicts(dictsAND, dictsNOT): #{{{2
"""Arguments are two lists of dictionaries. Keys are Tree lnums.
Return dict: intersection of all dicts in dictsAND and non-itersection with
all dicts in dictsNOT.
"""
if not dictsAND: return {}
D1 = dictsAND[0]
if len(dictsAND)==1:
res = D1
else:
res = {}
# get intersection with all other AND dicts
for D in dictsAND[1:]:
for item in D1:
if item in D: res[item] = 0
# get non-intersection with NOT dicts
for D in dictsNOT:
keys = res.keys()
for key in keys:
if key in D: del res[key]
return res
#---Outline Operations------------------------{{{1o
# voom_Oop... functions are called from Voom_Oop... Vim functions.
# They use local Vim vars set by the caller and can create and change Vim vars.
# Most of them set lines in Tree and Body via vim.buffer objects.
# Default l:blnShow is -1.
# Returning before setting l:blnShow means no changes were made.
def changeLevTreeHead(h, levDelta): #{{{2
"""Increase of decrese level of Tree headline by levDelta:
insert or delete levDelta*". " string.
"""
if levDelta > 0:
return '%s%s%s' %(h[:2], '. '*levDelta, h[2:])
elif levDelta < 0:
return '%s%s' %(h[:2], h[2-2*levDelta:])
else:
return h
def changeLevBodyHead(VO, h, levDelta): #{{{2
"""Increase of decrease level number of Body headline by levDelta.
NOTE: markup modes can replace this function with hook_changeLevBodyHead.
"""
if levDelta==0: return h
m = VO.marker_re.search(h)
level = int(m.group(1))
return '%s%s%s' %(h[:m.start(1)], level+levDelta, h[m.end(1):])
def newHeadline(VO, level, blnum, ln): #{{{2
"""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'
bodyLines = ['---%s--- %s%s' %(tree_head, VO.marker, level), '']
return (tree_head, bodyLines)
def setClipboard(s): #{{{2
"""Set Vim + register (system clipboard) to string s."""
# important: use '' for Vim string
vim.command("let @+='%s'" %s.replace("'", "''"))
# The above failed once: empty clipboard after copy/delete >5MB outline. Could
# not reproduce after Windows restart. Probably stale system. Thus the
# following check. It adds about 0.09 sec for each 1MB in the clipboard.
# 30-40% increase overall in the time of Copy operation (yy).
if not vim.eval('len(@+)')=='%s' %len(s):
vim.command("echoerr 'VOoM: error setting clipboard'")
def voom_OopVerify(): #{{{2
body, tree = int(vim.eval('a:body')), int(vim.eval('a:tree'))
VO = VOOMS[body]
assert VO.tree == tree
tlines, bnodes, levels = VO.makeOutline(VO, VO.Body)
if not len(VO.Tree)==len(tlines)+1:
vim.command("echoerr 'VOoM: wrong Tree size'")
return
tlines[0:0], bnodes[0:0], levels[0:0] = [VO.bname], [1], [1]
snLn = VO.snLn
tlines[snLn-1] = '=%s' %tlines[snLn-1][1:]
ok = True
if not VO.Tree[:] == tlines:
vim.command("echoerr 'VOoM: DIFFERENT Tree lines'")
ok = False
if not VO.bnodes == bnodes:
vim.command("echoerr 'VOoM: DIFFERENT bnodes'")
ok = False
if not VO.levels == levels:
vim.command("echoerr 'VOoM: DIFFERENT levels'")
ok = False
if ok:
vim.command("let l:ok=1")
def voom_OopSelEnd(): #{{{2
"""This is part of Voom_Oop() checks.
Selection in Tree starts at line ln1 and ends at line ln2.
Selection can have many sibling nodes: nodes with the same level as ln1 node.
Return lnum of last node in the last sibling node's branch.
Return 0 if selection is invalid.
"""
body = int(vim.eval('l:body'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
if ln1==1: return 0
levels = VOOMS[body].levels
z, lev0 = len(levels), levels[ln1-1]
for i in xrange(ln1,z):
lev = levels[i]
# invalid selection: there is node with level smaller than that of ln1 node
if i+1 <= ln2 and lev < lev0: return 0
# node after the last sibling node's branch
elif i+1 > ln2 and lev <= lev0: return i
return z
def voom_OopSelectBodyRange(): # {{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
VO = VOOMS[body]
assert VO.tree == tree
bln1, bln2 = nodesBodyRange(VO, ln1, ln2)
vim.command("let [l:bln1,l:bln2]=[%s,%s]" %(bln1,bln2))
def voom_OopInsert(as_child=False): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln, ln_status = int(vim.eval('l:ln')), vim.eval('l:ln_status')
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree, levels, snLn = VO.Body, VO.Tree, VO.levels, VO.snLn
# Compute where to insert and at what level.
# Insert new headline after node at ln.
# If node is folded, insert after the end of node's tree.
# default level
lev = levels[ln-1]
# after first Tree line
if ln==1: lev=1
# as_child always inserts as first child of current node, even if it's folded
elif as_child: lev+=1
# after last Tree line, same level
elif ln==len(levels): pass
# node has children, it can be folded
elif lev < levels[ln]:
# folded: insert after current node's branch, same level
if ln_status=='folded': ln += nodeSubnodes(VO,ln)
# not folded, insert as child
else: lev+=1
# remove = mark before modifying Tree
Tree[snLn-1] = ' ' + Tree[snLn-1][1:]
# insert headline in Tree and Body
# bLnum is Body lnum after which to insert new headline
if ln < len(levels):
bLnum = VO.bnodes[ln]-1
else:
bLnum = len(Body)
tree_head, bodyLines = VO.newHeadline(VO,lev,bLnum,ln)
treeLine = '= %s|%s' %('. '*(lev-1), tree_head)
Tree[ln:ln] = [treeLine]
Body[bLnum:bLnum] = bodyLines
vim.command('let l:bLnum=%s' %(bLnum+1))
# write = mark and set snLn to new headline
Tree[ln] = '=' + Tree[ln][1:]
VO.snLn = ln+1
vim.command('call Voom_SetSnLn(%s,%s)' %(body, ln+1))
def voom_OopCopy(): #{{{2
body = int(vim.eval('l:body'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
VO = VOOMS[body]
Body, bnodes = VO.Body, VO.bnodes
# body lines to copy
bln1 = bnodes[ln1-1]
if ln2 < len(bnodes): bln2 = bnodes[ln2]-1
else: bln2 = len(Body)
blines = Body[bln1-1:bln2]
setClipboard('\n'.join(blines))
def voom_OopCut(): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
lnUp1 = int(vim.eval('l:lnUp1'))
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
# diagram {{{
# .............. blnUp1-1
# ============== blnUp1=bnodes[lnUp1-1]
# ..............
# ============== bln1=bnodes[ln1-1]
# range being
# deleted
# .............. bln2=bnodes[ln2]-1, or last Body line
# ==============
# .............. }}}
### copy and delete body lines
bln1 = bnodes[ln1-1]
if ln2 < len(bnodes): bln2 = bnodes[ln2]-1
else: bln2 = len(Body)
blines = Body[bln1-1:bln2]
setClipboard('\n'.join(blines))
Body[bln1-1:bln2] = []
blnShow = bnodes[lnUp1-1] # does not change
### update bnodes
# decrement lnums after deleted range
delta = bln2-bln1+1
for i in xrange(ln2,len(bnodes)):
bnodes[i]-=delta
# cut
bnodes[ln1-1:ln2] = []
### delete range in levels (same as in Tree)
levels[ln1-1:ln2] = []
if VO.hook_doBodyAfterOop:
VO.hook_doBodyAfterOop(VO, 'cut', 0, None, None, None, None, bln1-1, ln1-1)
### ---go back to Tree---
vim.command('let l:blnShow=%s' %blnShow)
vim.command("call Voom_OopFromBody(%s,%s,%s,1)" %(body,tree, blnShow))
### remove = mark before modifying Tree
snLn = VO.snLn
Tree[snLn-1] = ' ' + Tree[snLn-1][1:]
### delete range in Tree (same as in levels))
Tree[ln1-1:ln2] = []
### add snLn mark
Tree[lnUp1-1] = '=' + Tree[lnUp1-1][1:]
VO.snLn = lnUp1
def voom_OopPaste(): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln, ln_status = int(vim.eval('l:ln')), vim.eval('l:ln_status')
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
levels, bnodes = VO.levels, VO.bnodes
### clipboard
pText = vim.eval('@+')
if not pText:
vim.command("call Voom_ErrorMsg('VOoM (paste): clipboard is empty')")
vim.command("call Voom_OopFromBody(%s,%s,-1,1)" %(body,tree))
return
pBlines = pText.split('\n') # Body lines to paste
pTlines, pBnodes, pLevels = VO.makeOutline(VO, pBlines)
### verify that clipboard is a valid outline
if pBnodes==[] or pBnodes[0]!=1:
vim.command("call Voom_ErrorMsg('VOoM (paste): invalid clipboard--first line is not a headline')")
vim.command("call Voom_OopFromBody(%s,%s,-1,1)" %(body,tree))
return
lev_ = pLevels[0]
for lev in pLevels:
# there is node with level smaller than that of the first node
if lev < pLevels[0]:
vim.command("call Voom_ErrorMsg('VOoM (paste): invalid clipboard--root level error')")
vim.command("call Voom_OopFromBody(%s,%s,-1,1)" %(body,tree))
return
# level incremented by 2 or more
elif lev-lev_ > 1:
vim.command("call Voom_WarningMsg('VOoM (paste): inconsistent levels in clipboard--level incremented by >1', ' ')")
lev_ = lev
### compute where to insert and at what level
# insert nodes after node at ln at level lev
# if node is folded, insert after the end of node's tree
lev = levels[ln-1] # default level
# after first Tree line: use level of next node in case min level is not 1 (wiki mode)
if ln==1:
if len(levels)>1: lev = levels[1]
else: lev=1
# after last Tree line, same level
elif ln==len(levels): pass
# node has children, it can be folded
elif lev < levels[ln]:
# folded: insert after current node's branch, same level
if ln_status=='folded': ln += nodeSubnodes(VO,ln)
# not folded, insert as child
else: lev+=1
### adjust levels of nodes being inserted
levDelta = lev - pLevels[0]
if levDelta:
pTlines = [changeLevTreeHead(h, levDelta) for h in pTlines]
pLevels = [(lev+levDelta) for lev in pLevels]
f = VO.changeLevBodyHead
if f:
for bl in pBnodes:
pBlines[bl-1] = f(VO, pBlines[bl-1], levDelta)
### insert body lines in Body
# bln is Body lnum after which to insert
if ln < len(bnodes): bln = bnodes[ln]-1
else: bln = len(Body)
Body[bln:bln] = pBlines
blnShow = bln+1
### update bnodes
# increment bnodes being pasted
for i in xrange(0,len(pBnodes)):
pBnodes[i]+=bln
# increment bnodes after pasted region
delta = len(pBlines)
for i in xrange(ln,len(bnodes)):
bnodes[i]+=delta
# insert pBnodes after ln
bnodes[ln:ln] = pBnodes
### insert new levels in levels (same as in Tree)
levels[ln:ln] = pLevels
### start and end lnums of inserted region
ln1 = ln+1
ln2 = ln+len(pBnodes)
if VO.hook_doBodyAfterOop:
VO.hook_doBodyAfterOop(VO, 'paste', levDelta,
blnShow, ln1,
blnShow+len(pBlines)-1, ln2,
None, None)
### ---go back to Tree---
vim.command("call Voom_OopFromBody(%s,%s,%s,1)" %(body,tree, blnShow))
# remove = mark before modifying Tree
snLn = VO.snLn
Tree[snLn-1] = ' ' + Tree[snLn-1][1:]
### insert new headlines in Tree (same as in levels)
Tree[ln:ln] = pTlines
### start and end lnums of inserted region
vim.command('let l:ln1=%s' %ln1)
vim.command('let l:ln2=%s' %ln2)
# set snLn to first headline of inserted nodes
Tree[ln1-1] = '=' + Tree[ln1-1][1:]
VO.snLn = ln1
# we don't get here if previous code fails
vim.command('let l:blnShow=%s' %blnShow)
def voom_OopUp(): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
lnUp1, lnUp2 = int(vim.eval('l:lnUp1')), int(vim.eval('l:lnUp2'))
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
# diagram {{{
# .............. blnUp1-1
# ============== blnUp1=bnodes[lnUp1-1]
# range before
# which to move
# ..............
# ============== bln1=bnodes[ln1-1]
# range being
# moved
# .............. bln2=bnodes[ln2]-1, or last Body line
# ==============
# .............. }}}
### compute change in level
# current level of root nodes in selection
levOld = levels[ln1-1]
# new level of root nodes in selection
# lnUp1 is fist child of lnUp2, insert also as first child
if levels[lnUp2-1] + 1 == levels[lnUp1-1]:
levNew = levels[lnUp1-1]
# all other cases, includes insertion after folded node
else:
levNew = levels[lnUp2-1]
levDelta = levNew-levOld
### body lines to move
bln1 = bnodes[ln1-1]
if ln2 < len(bnodes): bln2 = bnodes[ln2]-1
else: bln2 = len(Body)
blines = Body[bln1-1:bln2]
if levDelta:
f = VO.changeLevBodyHead
if f:
for bl in bnodes[ln1-1:ln2]:
blines[bl-bln1] = f(VO, blines[bl-bln1], levDelta)
### move body lines: cut, then insert
# insert before line blnUp1, it will not change after bnodes update
blnUp1 = bnodes[lnUp1-1]
blnShow = blnUp1
Body[bln1-1:bln2] = []
Body[blnUp1-1:blnUp1-1] = blines
###update bnodes
# increment lnums in the range before which the move is made
delta = bln2-bln1+1
for i in xrange(lnUp1-1,ln1-1):
bnodes[i]+=delta
# decrement lnums in the range which is being moved
delta = bln1-blnUp1
for i in xrange(ln1-1,ln2):
bnodes[i]-=delta
# cut, insert
nLines = bnodes[ln1-1:ln2]
bnodes[ln1-1:ln2] = []
bnodes[lnUp1-1:lnUp1-1] = nLines
### update levels (same as for Tree)
nLevels = levels[ln1-1:ln2]
if levDelta:
nLevels = [(lev+levDelta) for lev in nLevels]
# cut, then insert
levels[ln1-1:ln2] = []
levels[lnUp1-1:lnUp1-1] = nLevels
if VO.hook_doBodyAfterOop:
VO.hook_doBodyAfterOop(VO, 'up', levDelta,
blnShow, lnUp1,
blnShow+len(blines)-1, lnUp1+len(nLevels)-1,
bln1-1+len(blines), ln1-1+len(nLevels))
### ---go back to Tree---
vim.command("call Voom_OopFromBody(%s,%s,%s,1)" %(body,tree, blnShow))
### remove snLn mark before modifying Tree
snLn = VO.snLn
Tree[snLn-1] = ' ' + Tree[snLn-1][1:]
### update Tree (same as for levels)
tlines = Tree[ln1-1:ln2]
if levDelta:
tlines = [changeLevTreeHead(h, levDelta) for h in tlines]
# cut, then insert
Tree[ln1-1:ln2] = []
Tree[lnUp1-1:lnUp1-1] = tlines
### add snLn mark
Tree[lnUp1-1] = '=' + Tree[lnUp1-1][1:]
VO.snLn = lnUp1
# we don't get here only if previous code fails
vim.command('let l:blnShow=%s' %blnShow)
def voom_OopDown(): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
lnDn1, lnDn1_status = int(vim.eval('l:lnDn1')), vim.eval('l:lnDn1_status')
# note: lnDn1 == ln2+1
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
# diagram {{{
# ..............
# ============== bln1=bnodes[ln1-1]
# range being
# moved
# .............. bln2=bnodes[ln2]-1
# ============== blnDn1=bnodes[lnDn1-1]
# range after
# which to move
# .............. blnIns=bnodes[lnIns]-1, or last Body line
# ==============
# .............. }}}
### compute change in level, and line after which to insert
# current level
levOld = levels[ln1-1]
# new level is either that of lnDn1 or +1
levNew = levels[lnDn1-1]
# line afer which to insert
lnIns = lnDn1
if lnDn1==len(levels): # end of Tree
pass
# lnDn1 has children; insert as child unless it's folded
elif levels[lnDn1-1] < levels[lnDn1]:
if lnDn1_status=='folded':
lnIns += nodeSubnodes(VO,lnDn1)
else:
levNew+=1
levDelta = levNew-levOld
### body lines to move
bln1 = bnodes[ln1-1]
bln2 = bnodes[ln2]-1
blines = Body[bln1-1:bln2]
if levDelta:
f = VO.changeLevBodyHead
if f:
for bl in bnodes[ln1-1:ln2]:
blines[bl-bln1] = f(VO, blines[bl-bln1], levDelta)
### move body lines: insert, then cut
if lnIns < len(bnodes): blnIns = bnodes[lnIns]-1
else: blnIns = len(Body)
Body[blnIns:blnIns] = blines
Body[bln1-1:bln2] = []
### update bnodes
# increment lnums in the range which is being moved
delta = blnIns-bln2
for i in xrange(ln1-1,ln2):
bnodes[i]+=delta
# decrement lnums in the range after which the move is made
delta = bln2-bln1+1
for i in xrange(ln2,lnIns):
bnodes[i]-=delta
# insert, cut
nLines = bnodes[ln1-1:ln2]
bnodes[lnIns:lnIns] = nLines
bnodes[ln1-1:ln2] = []
### compute and set new snLn, blnShow
snLn_ = VO.snLn
snLn = lnIns+1-(ln2-ln1+1)
VO.snLn = snLn
vim.command('let snLn=%s' %snLn)
blnShow = bnodes[snLn-1] # must compute after bnodes update
### update levels (same as for Tree)
nLevels = levels[ln1-1:ln2]
if levDelta:
nLevels = [(lev+levDelta) for lev in nLevels]
# insert, then cut
levels[lnIns:lnIns] = nLevels
levels[ln1-1:ln2] = []
if VO.hook_doBodyAfterOop:
VO.hook_doBodyAfterOop(VO, 'down', levDelta,
blnShow, snLn,
blnShow+len(blines)-1, snLn+len(nLevels)-1,
bln1-1, ln1-1)
### ---go back to Tree---
vim.command("call Voom_OopFromBody(%s,%s,%s,1)" %(body,tree, blnShow))
### remove snLn mark before modifying Tree
Tree[snLn_-1] = ' ' + Tree[snLn_-1][1:]
### update Tree (same as for levels)
tlines = Tree[ln1-1:ln2]
if levDelta:
tlines = [changeLevTreeHead(h, levDelta) for h in tlines]
# insert, then cut
Tree[lnIns:lnIns] = tlines
Tree[ln1-1:ln2] = []
### add snLn mark
Tree[snLn-1] = '=' + Tree[snLn-1][1:]
# we don't get here only if previous code fails
vim.command('let l:blnShow=%s' %blnShow)
def voom_OopRight(): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
### Move right means increment level by 1 for all nodes in the range.
# can't move right if ln1 node is child of previous node
if levels[ln1-1] > levels[ln1-2]:
vim.command("call Voom_OopFromBody(%s,%s,-1,1)" %(body,tree))
return
### change levels of Body headlines
f = VO.changeLevBodyHead
if f:
for bln in bnodes[ln1-1:ln2]:
Body[bln-1] = f(VO, Body[bln-1], 1)
# new snLn will be set to ln1
blnShow = bnodes[ln1-1]
### change levels of VO.levels (same as for Tree)
nLevels = levels[ln1-1:ln2]
nLevels = [(lev+1) for lev in nLevels]
levels[ln1-1:ln2] = nLevels
if VO.hook_doBodyAfterOop:
if ln2 < len(bnodes): blnum2 = bnodes[ln2]-1
else: blnum2 = len(Body)
VO.hook_doBodyAfterOop(VO, 'right', 1, blnShow, ln1, blnum2, ln2, None, None)
### ---go back to Tree---
vim.command("let &fdm=fdm_b")
vim.command("call Voom_OopFromBody(%s,%s,%s,1)" %(body,tree, blnShow))
### change levels of Tree lines (same as for VO.levels)
tlines = Tree[ln1-1:ln2]
tlines = [changeLevTreeHead(h, 1) for h in tlines]
Tree[ln1-1:ln2] = tlines
### set snLn to ln1
snLn = VO.snLn
if not snLn==ln1:
Tree[snLn-1] = ' ' + Tree[snLn-1][1:]
snLn = ln1
Tree[snLn-1] = '=' + Tree[snLn-1][1:]
VO.snLn = snLn
# we don't get here if previous code fails
vim.command('let l:blnShow=%s' %blnShow)
def voom_OopLeft(): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
### Move left means decrement level by 1 for all nodes in the range.
# can't move left if at top level 1
if levels[ln1-1]==1:
vim.command("call Voom_OopFromBody(%s,%s,-1,1)" %(body,tree))
return
# don't move left if the range is not at the end of subtree
if ln2 < len(levels) and levels[ln2]==levels[ln1-1]:
vim.command("call Voom_OopFromBody(%s,%s,-1,1)" %(body,tree))
return
### change levels of Body headlines
f = VO.changeLevBodyHead
if f:
for bln in bnodes[ln1-1:ln2]:
Body[bln-1] = f(VO, Body[bln-1], -1)
# new snLn will be set to ln1
blnShow = bnodes[ln1-1]
### change levels of VO.levels (same as for Tree)
nLevels = levels[ln1-1:ln2]
nLevels = [(lev-1) for lev in nLevels]
levels[ln1-1:ln2] = nLevels
if VO.hook_doBodyAfterOop:
if ln2 < len(bnodes): blnum2 = bnodes[ln2]-1
else: blnum2 = len(Body)
VO.hook_doBodyAfterOop(VO, 'left', -1, blnShow, ln1, blnum2, ln2, None, None)
### ---go back to Tree---
vim.command("let &fdm=fdm_b")
vim.command("call Voom_OopFromBody(%s,%s,%s,1)" %(body,tree, blnShow))
### change levels of Tree lines (same as for VO.levels)
tlines = Tree[ln1-1:ln2]
tlines = [changeLevTreeHead(h, -1) for h in tlines]
Tree[ln1-1:ln2] = tlines
### set snLn to ln1
snLn = VO.snLn
if not snLn==ln1:
Tree[snLn-1] = ' ' + Tree[snLn-1][1:]
snLn = ln1
Tree[snLn-1] = '=' + Tree[snLn-1][1:]
VO.snLn = snLn
# we don't get here if previous code fails
vim.command('let l:blnShow=%s' %blnShow)
def voom_OopMark(): # {{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
marker_re = VO.marker_re
for i in xrange(ln1-1,ln2):
# insert 'x' in Tree line
tline = Tree[i]
if tline[1]!='x':
Tree[i] = '%sx%s' %(tline[0], tline[2:])
# insert 'x' in Body headline
bln = bnodes[i]
bline = Body[bln-1]
end = marker_re.search(bline).end(1)
Body[bln-1] = '%sx%s' %(bline[:end], bline[end:])
def voom_OopUnmark(): # {{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('l:ln1')), int(vim.eval('l:ln2'))
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
marker_re = VO.marker_re
for i in xrange(ln1-1,ln2):
# remove 'x' from Tree line
tline = Tree[i]
if tline[1]=='x':
Tree[i] = '%s %s' %(tline[0], tline[2:])
# remove 'x' from Body headline
bln = bnodes[i]
bline = Body[bln-1]
end = marker_re.search(bline).end(1)
# remove one 'x', not enough
#Body[bln-1] = '%s%s' %(bline[:end], bline[end+1:])
# remove all consecutive 'x' chars
Body[bln-1] = '%s%s' %(bline[:end], bline[end:].lstrip('x'))
def voom_OopMarkStartup(): # {{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln = int(vim.eval('l:ln'))
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
marker_re = VO.marker_re
if ln==1:
bln_selected = 0
else:
bln_selected = bnodes[ln-1]
# remove '=' from all other Body headlines
# also, strip 'x' and 'o' after removed '='
for bln in bnodes[1:]:
if bln==bln_selected: continue
bline = Body[bln-1]
end = marker_re.search(bline).end()
bline2 = bline[end:]
if not bline2: continue
if bline2[0]=='=':
Body[bln-1] = '%s%s' %(bline[:end], bline[end:].lstrip('=xo'))
elif bline2[0]=='o' and bline2[1:] and bline2[1]=='=':
Body[bln-1] = '%s%s' %(bline[:end+1], bline[end+1:].lstrip('=xo'))
if ln==1: return
# insert '=' in current Body headline, but only if it's not there already
bline = Body[bln_selected-1]
end = marker_re.search(bline).end()
bline2 = bline[end:]
if not bline2:
Body[bln_selected-1] = '%s=' %bline
return
if bline2[0]=='=':
return
elif bline2[0]=='o' and bline2[1:] and bline2[1]=='=':
return
elif bline2[0]=='o':
end+=1
Body[bln_selected-1] = '%s=%s' %(bline[:end], bline[end:])
#--- Tree Folding Operations --- {{{2
# Opened/Closed Tree buffer folds are equivalent to Expanded/Contracted nodes.
# By default, folds are closed.
# Opened folds are marked by 'o' in Body headlines (after 'x', before '=').
#
# To determine which folds are currently closed/opened, we open all closed
# folds one by one, from top to bottom, starting from top level visible folds.
# This produces list of closed folds.
#
# To restore folding according to a list of closed folds:
# open all folds;
# close folds from bottom to top.
#
# Conventions:
# cFolds --lnums of closed folds
# oFolds --lnums of opened folds
# ln, ln1, ln2 --Tree line number
#
# NOTE: Cursor position and window view are not restored here.
# See also:
# ../../doc/voom.txt#id_20110120011733
def voom_OopFolding(action): #{{{3
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
VO = VOOMS[body]
assert VO.tree == tree
# check and adjust range lnums
# don't worry about invalid range lnums: Vim checks that
if not action=='cleanup':
ln1, ln2 = int(vim.eval('a:ln1')), int(vim.eval('a:ln2'))
if ln2<ln1: ln1,ln2=ln2,ln1 # probably redundant
if ln2==1: return
#if ln1==1: ln1=2
if ln1==ln2:
ln2 = ln2 + nodeSubnodes(VO, ln2)
if ln1==ln2: return
if action=='save':
cFolds = foldingGet(ln1, ln2)
foldingWrite(VO, ln1, ln2, cFolds)
elif action=='restore':
cFolds = foldingRead(VO, ln1, ln2)
foldingCreate(ln1, ln2, cFolds)
elif action=='cleanup':
foldingCleanup(VO)
def foldingGet(ln1, ln2): #{{{3
"""Get all closed folds in line range ln1-ln2, including subfolds.
If line ln2 is visible and is folded, its subfolds are included.
Executed in Tree buffer.
"""
cFolds = []
lnum = ln1
# go through top level folded lines (visible closed folds)
while lnum < ln2+1:
# line lnum is first line of a closed fold
if int(vim.eval('foldclosed(%s)' %lnum))==lnum:
cFolds.append(lnum)
# line after this fold and subfolds
foldend = int(vim.eval('foldclosedend(%s)' %lnum))+1
lnum0 = lnum
lnum = foldend
vim.command('keepj normal! %sGzo' %lnum0)
# open every folded line in this fold
for ln in xrange(lnum0+1, foldend):
# line ln is first line of a closed fold
if int(vim.eval('foldclosed(%s)' %ln))==ln:
cFolds.append(ln)
vim.command('keepj normal! %sGzo' %ln)
else:
lnum+=1
cFolds.reverse()
# close back opened folds
for ln in cFolds:
vim.command('keepj normal! %sGzc' %ln)
return cFolds
def foldingCreate(ln1, ln2, cFolds): #{{{3
"""Create folds in range ln1-ln2 from a list of closed folds in that range.
The list must be reverse sorted. Must not contain nodes without children.
Executed in Tree buffer.
"""
#cFolds.sort()
#cFolds.reverse()
#vim.command('%s,%sfoldopen!' %(ln1,ln2))
# see ../../doc/voom.txt#id_20110120011733
vim.command(r'try | %s,%sfoldopen! | catch /^Vim\%%((\a\+)\)\=:E490/ | endtry'
%(ln1,ln2))
for ln in cFolds:
vim.command('keepj normal! %sGzc' %ln)
def foldingFlip(VO, ln1, ln2, folds): #{{{3
"""Convert list of opened/closed folds in range ln1-ln2 into list of
closed/opened folds.
"""
# Important: this also eliminates lnums of nodes without children,
# so we don't get Vim E490 (no fold found) error on :foldclose.
folds = {}.fromkeys(folds)
folds_flipped = []
for ln in xrange(ln1,ln2+1):
if nodeHasChildren(VO, ln) and not ln in folds:
folds_flipped.append(ln)
folds_flipped.reverse()
return folds_flipped
def foldingRead(VO, ln1, ln2): #{{{3
"""Read "o" marks in Body headlines."""
cFolds = []
marker_re = VO.marker_re
bnodes = VO.bnodes
Body = VO.Body
for ln in xrange(ln1,ln2+1):
if not nodeHasChildren(VO, ln):
continue
bline = Body[bnodes[ln-1]-1]
end = marker_re.search(bline).end()
if end<len(bline) and bline[end]=='o':
continue
else:
cFolds.append(ln)
cFolds.reverse()
return cFolds
def foldingWrite(VO, ln1, ln2, cFolds): #{{{3
"""Write "o" marks in Body headlines."""
cFolds = {}.fromkeys(cFolds)
marker_re = VO.marker_re
bnodes = VO.bnodes
Body = VO.Body
for ln in xrange(ln1,ln2+1):
if not nodeHasChildren(VO, ln):
continue
bln = bnodes[ln-1]
bline = Body[bln-1]
end = marker_re.search(bline).end()
isClosed = ln in cFolds
# headline is marked with 'o'
if end<len(bline) and bline[end]=='o':
# remove 'o' mark
if isClosed:
Body[bln-1] = '%s%s' %(bline[:end], bline[end:].lstrip('ox'))
# headline is not marked with 'o'
else:
# add 'o' mark
if not isClosed:
if end==len(bline):
Body[bln-1] = '%so' %bline
elif bline[end] != 'o':
Body[bln-1] = '%so%s' %(bline[:end], bline[end:])
def foldingCleanup(VO): #{{{3
"""Remove "o" marks from from nodes without children."""
marker_re = VO.marker_re
bnodes = VO.bnodes
Body = VO.Body
for ln in xrange(2,len(bnodes)+1):
if nodeHasChildren(VO, ln): continue
bln = bnodes[ln-1]
bline = Body[bln-1]
end = marker_re.search(bline).end()
if end<len(bline) and bline[end]=='o':
Body[bln-1] = '%s%s' %(bline[:end], bline[end:].lstrip('ox'))
#--- Sort Operations --- {{{2
# 1) Sort siblings of the current node.
# - Get list of siblings of the current node (as Tree lnums).
# Two nodes are siblings if they have the same parent and the same level.
# - Construct list of corresponding Tree headlines. Decorate with indexes and
# Tree lnums. Sort by headline text.
# - Construct new Body region from nodes in sorted order. Replace the region.
# IMPORTANT: this does not change outline data (Tree, VO.levels, VO.bnodes)
# for nodes with smaller levels or for nodes outside of the siblings region.
# Thus, recursive sort is possible.
#
# 2) Deep (recursive) sort: sort siblings of the current node and siblings in
# all subnodes. Sort as above for all groups of siblings in the affected
# region, starting from the most deeply nested.
# - Construct list of groups of all siblings: top to bottom, decorate each
# siblings group with level and parent lnum.
# - Reverse sort the list by levels.
# - Do sort for each group of siblings in the list: from right to left and from
# bottom to top.
#
# 3) We modify only the Body buffer. We then do global outline update to redraw
# the Tree and to update outline data. Performing targeted update as in other
# outline operations is too tedious.
def voom_OopSort(): #{{{3
# Returning before setting l:blnShow means no changes were made.
### parse options {{{
oDeep = False
D = {'oIgnorecase':0, 'oUnicode':0, 'oEnc':0, 'oReverse':0, 'oFlip':0, 'oShuffle':0}
options = vim.eval('a:qargs')
options = options.strip().split()
for o in options:
if o=='deep': oDeep = True
elif o=='i': D['oIgnorecase'] = 1
elif o=='u': D['oUnicode'] = 1
elif o=='r': D['oReverse'] = 1 # sort in reverse order
elif o=='flip': D['oFlip'] = 1 # reverse without sorting
elif o=='shuffle': D['oShuffle'] = 1
else:
vim.command("call Voom_ErrorMsg('VOoM (sort): invalid option: %s')" %o.replace("'","''"))
vim.command("call Voom_WarningMsg('VOoM (sort): valid options are: deep, i (ignore-case), u (unicode), r (reverse-sort), flip, shuffle')")
return
if (D['oReverse'] + D['oFlip'] + D['oShuffle']) > 1:
vim.command("call Voom_ErrorMsg('VOoM (sort): these options cannot be combined: r, flip, shuffle')")
return
if D['oShuffle']:
global shuffle
if shuffle is None: from random import shuffle
if D['oUnicode']:
D['oEnc'] = get_vim_encoding()
###### }}}
### get other Vim data, compute 'siblings' {{{
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
ln1, ln2 = int(vim.eval('a:ln1')), int(vim.eval('a:ln2'))
if ln2<ln1: ln1,ln2=ln2,ln1 # probably redundant
VO = VOOMS[body]
assert VO.tree == tree
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
if ln1==ln2:
# Tree lnums of all siblings of the current node
siblings = nodeSiblings(VO,ln1)
else:
# Tree lnums of all siblings in the range
siblings = rangeSiblings(VO,ln1,ln2)
if not siblings:
vim.command("call Voom_ErrorMsg('VOoM (sort): invalid Tree selection')")
return
###### }}}
#print ln1, ln2, siblings
### do sorting
# progress flags: (got >1 siblings, order changed after sort)
flag1,flag2 = 0,0
if not oDeep:
flag1,flag2 = sortSiblings(VO, siblings, **D)
else:
siblings_groups = getSiblingsGroups(VO,siblings)
for group in siblings_groups:
m, n = sortSiblings(VO, group, **D)
flag1+=m; flag2+=n
if flag1==0:
vim.command("call Voom_WarningMsg('VOoM (sort): nothing to sort')")
return
elif flag2==0:
vim.command("call Voom_WarningMsg('VOoM (sort): already sorted')")
return
# Show first sibling. Tracking the current node and bnode is too hard.
lnum1 = siblings[0]
lnum2 = siblings[-1] + nodeSubnodes(VO,siblings[-1])
blnShow = bnodes[lnum1-1]
vim.command('let [l:blnShow,l:lnum1,l:lnum2]=[%s,%s,%s]' %(blnShow,lnum1,lnum2))
def sortSiblings(VO, siblings, oIgnorecase, oUnicode, oEnc, oReverse, oFlip, oShuffle): #{{{3
"""Sort sibling nodes. 'siblings' is list of Tree lnums in ascending order.
This only modifies Body buffer. Outline data are not updated.
Return progress flags (flag1,flag2), see voom_OopSort().
"""
sibs = siblings
if len(sibs) < 2:
return (0,0)
Body, Tree = VO.Body, VO.Tree
bnodes, levels = VO.bnodes, VO.levels
z, Z = len(sibs), len(bnodes)
### decorate siblings for sorting
# [(Tree headline text, index, lnum), ...]
sibs_dec = []
for i in xrange(z):
sib = sibs[i]
head = Tree[sib-1].split('|',1)[1]
if oUnicode and oEnc:
head = unicode(head, oEnc, 'replace')
if oIgnorecase:
head = head.lower()
sibs_dec.append((head, i, sib))
### sort
if oReverse:
sibs_dec.sort(key=lambda x: x[0], reverse=True)
elif oFlip:
sibs_dec.reverse()
elif oShuffle:
shuffle(sibs_dec)
else:
sibs_dec.sort()
sibs_sorted = [i[2] for i in sibs_dec]
#print sibs_dec; print sibs_sorted
if sibs==sibs_sorted:
return (1,0)
### blnum1, blnum2: first and last Body lnums of the affected region
blnum1 = bnodes[sibs[0]-1]
n = sibs[-1] + nodeSubnodes(VO,sibs[-1])
if n < Z:
blnum2 = bnodes[n]-1
else:
blnum2 = len(Body)
### construct new Body region
blines = []
for i in xrange(z):
sib = sibs[i]
j = sibs_dec[i][1] # index into sibs that points to new sib
sib_new = sibs[j]
# get Body region for sib_new branch
bln1 = bnodes[sib_new-1]
if j+1 < z:
sib_next = sibs[j+1]
bln2 = bnodes[sib_next-1]-1
else:
node_last = sib_new + nodeSubnodes(VO,sib_new)
if node_last < Z:
bln2 = bnodes[node_last]-1
else:
bln2 = len(Body)
blines.extend(Body[bln1-1:bln2])
### replace Body region with the new, sorted region
body_len = len(Body)
Body[blnum1-1:blnum2] = blines
assert body_len == len(Body)
return (1,1)
#---EXECUTE SCRIPT----------------------------{{{1
#
def voom_GetVoomRange(withSubnodes=0): #{{{2
body = int(vim.eval('l:body'))
VO = VOOMS[body]
lnum = int(vim.eval('a:lnum'))
if vim.eval('l:bufType')=='Body':
lnum = bisect.bisect_right(VO.bnodes, lnum)
bln1, bln2 = nodesBodyRange(VO, lnum, lnum, withSubnodes)
vim.command("let [l:bln1,l:bln2]=[%s,%s]" %(bln1,bln2))
def voom_GetBuffRange(): #{{{2
body = int(vim.eval('l:body'))
ln1, ln2 = int(vim.eval('a:ln1')), int(vim.eval('a:ln2'))
VO = VOOMS[body]
bln1, bln2 = nodesBodyRange(VO, ln1, ln2)
vim.command("let [l:bln1,l:bln2]=[%s,%s]" %(bln1,bln2))
def voom_Exec(): #{{{2
if vim.eval('l:bufType')=='Tree':
Buf = VOOMS[int(vim.eval('l:body'))].Body
else:
Buf = vim.current.buffer
bln1, bln2 = int(vim.eval('l:bln1')), int(vim.eval('l:bln2'))
blines = Buf[bln1-1:bln2]
# specifiy script encoding (Vim internal encoding) on the first line
enc = '# -*- coding: %s -*-' %get_vim_encoding()
# prepend extra \n's to make traceback lnums match buffer lnums
# TODO: find less silly way to adjust traceback lnums
script = '%s\n%s%s\n' %(enc, '\n'*(bln1-2), '\n'.join(blines))
d = {'vim':vim, 'VOOMS':VOOMS, 'voom':sys.modules['voom']}
try:
exec script in d
#except Exception: # does not catch vim.error
except:
#traceback.print_exc() # writes to sys.stderr
printTraceback(bln1,bln2)
print '---end of Python script (%s-%s)---' %(bln1,bln2)
# id_20101214100357
# NOTES on printing Python tracebacks and Vim errors.
#
# When there is no PyLog, we want Python traceback echoed as Vim error message.
# Writing to sys.stderr accomplishes that:
# :py sys.stderr.write('oopsy-doopsy')
# Drawback: writing to default sys.stderr (no PyLog) triggers Vim error.
# Thus, without PyLog there are two useless lines on top with Vim error:
# Error detected while processing function Voom_Exec:
# line 63:
#
# Vim code:
#
# 1) PyLog is enabled. Must execute this inside try/catch/entry.
# Otherwise, something weird happens when Vim error occurs, most likely
# Vim error echoing interferes with PyLog scrolling.
# The only downside is that only v:exception is printed, no details
# about Vim error location (v:throwpoint is useless).
#
# 2) PyLog is not enabled. Do not execute this inside try/catch/endtry.
# Python traceback is not printed if we do.
#
def printTraceback(bln1,bln2): #{{{2
"""Print traceback from exception caught during Voomexec."""
out = None
# like traceback.format_exc(), traceback.print_exc()
try:
etype, value, tb = sys.exc_info()
out = traceback.format_exception(etype, value, tb)
#out = traceback.format_exception(etype, value, tb.tb_next)
finally:
etype = value = tb = None
if not out:
sys.stderr.write('ERROR: Voomexec failed to format Python traceback')
return
info = ' ...exception executing script (%s-%s)...\n' %(bln1,bln2)
if bln1==1:
info += ' ...subtract 1 from traceback lnums to get buffer lnums...\n'
out[1:2] = [info]
#out[1:1] = [info]
sys.stderr.write(''.join(out))
#---LOG BUFFER--------------------------------{{{1
#
class LogBufferClass: #{{{2
"""A file-like object for replacing sys.stdout and sys.stdin with a Vim buffer."""
def __init__(self): #{{{3
self.buffer = vim.current.buffer
self.logbnr = vim.eval('bufnr("")')
self.buffer[0] = 'Python Log buffer ...'
#self.encoding = vim.eval('&enc')
self.encoding = get_vim_encoding()
self.join = False
def write(self,s): #{{{3
"""Append string to buffer, scroll Log windows in all tabs."""
# Messages are terminated by sending '\n' (null string? ^@).
# Thus "print '\n'" sends '\n' twice.
# The message itself can contain '\n's.
# One line can be sent in many strings which don't always end with \n.
# This is certainly true for Python errors and for 'print a, b, ...' .
# Can't append unicode strings. This produces an error:
# :py vim.current.buffer.append(u'test')
# Can't have '\n' in appended list items, so always use splitlines().
# A trailing \n is lost after splitlines(), but not for '\n\n' etc.
#print self.buffer.name
if not s: return
# Nasty things happen when printing to unloaded PyLog buffer.
# This also catches printing to noexisting buffer, as in pydoc help() glitch.
if vim.eval("bufloaded(%s)" %self.logbnr)=='0':
vim.command("echoerr 'VOoM (PyLog): PyLog buffer %s is unloaded or doesn''t exist'" %self.logbnr)
vim.command("echoerr 'VOoM (PyLog): unable to write string:'")
vim.command("echom '%s'" %(repr(s).replace("'", "''")) )
vim.command("echoerr 'VOoM (PyLog): please try executing command Voomlog to fix'")
return
try:
if type(s) == type(u" "):
s = s.encode(self.encoding)
# Join with previous message if it had no ending newline.
if self.join==True:
s = self.buffer[-1] + s
del self.buffer[-1]
if s[-1]=='\n':
self.join = False
else:
self.join = True
self.buffer.append(s.splitlines())
except:
# list of all exception lines, no newlines in items
exc_lines = traceback.format_exc().splitlines()
self.buffer.append('')
self.buffer.append('VOoM: exception writing to PyLog buffer:')
self.buffer.append(repr(s))
self.buffer.append(exc_lines)
self.buffer.append('')
vim.command('call Voom_LogScroll()')
#---misc--------------------------------------{{{1
def get_vim_encoding(): #{{{2
"""Return Vim internal encoding."""
# When &enc is any Unicode Vim allegedly uses utf-8 internally.
# See |encoding|, mbyte.c, values are from |encoding-values|
enc = vim.eval('&enc')
if enc in ('utf-8','ucs-2','ucs-2le','utf-16','utf-16le','ucs-4','ucs-4le'):
return 'utf-8'
return enc
# modelines {{{1
# vim:fdm=marker:fdl=0:
# vim:foldtext=getline(v\:foldstart).'...'.(v\:foldend-v\:foldstart):