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 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145
|
# File: voom_vim.py
# Last Modified: 2017-02-18
# Version: 5.3
# 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/
"""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
PY_VERSION = sys.version_info[0]
IS_PY2 = PY_VERSION==2
if PY_VERSION > 2:
xrange = range
#Vim = sys.modules['__main__']
# See autoload/voom.vim ( ../../voom.vim ) for conventions.
# Function "voom_WhatEver()" in this file is Python code for Vim function
# "voom#WhatEver()" in "autoload/voom.vim" .
#---Constants and Settings--------------------{{{1=
# VO is instance of VoomOutline class, stored in dict VOOMS.
# VOOMS is created in autoload/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*[1-9]\d*)(x?)') #}}}
# {'markdown': 'markdown', 'tex': 'latex', ...}
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':
DEFAULT_MODE = vim.eval('g:voom_default_mode')
else:
DEFAULT_MODE = 'fmr'
# which Vim register to use for copy/cut/paste operations
if vim.eval("exists('g:voom_clipboard_register')")=='1':
CLIPBOARD = vim.eval('g:voom_clipboard_register')
elif vim.eval("has('clipboard')")=='1':
CLIPBOARD = '+'
else:
CLIPBOARD = 'o'
# allow/disallow Move Left when nodes are not at the end of their subtree
if vim.eval("exists('g:voom_always_allow_move_left')")=='1':
ALWAYS_ALLOW_MOVE_LEFT = int(vim.eval('g:voom_always_allow_move_left')) != 0
else:
ALWAYS_ALLOW_MOVE_LEFT = False
#---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()
### get markup mode, l:qargs is mode's name ###
mmode = vim.eval('l:qargs').strip() or FT_MODES.get(VO.filetype, DEFAULT_MODE)
mName = 'voom_vimplugin2657.voom_mode_%s' %mmode
try:
__import__(mName)
mModule = sys.modules[mName]
except ImportError:
vim.command("call voom#ErrorMsg('VOoM: cannot import Python module: %s')" %mName.replace("'","''"))
return
# no need to catch other import errors -- Vim code will check l:MTYPE
VO.mModule = mModule
VO.mmode = mmode
VO.bname += ', %s' %mmode
vim.command("let l:mmode='%s'" %mmode.replace("'","''"))
### define mode-specific methods ###
VO.MTYPE = getattr(mModule, 'MTYPE', 1)
# "fmr" mode, markup mode for fold markers
if VO.MTYPE == 0:
f = getattr(mModule, 'hook_makeOutline', 0)
if f:
VO.makeOutline = f
else:
if 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
# start fold marker string and regexp ("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*[1-9]\d*)(x?)')
# chars to strip from right side of Tree headlines ("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"
# not an "fmr" markup mode: not for fold markers
else:
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 ###
# if we don't get here because of error, l:MTYPE is not set and Vim code bails out
vim.command('let l:MTYPE=%s' %VO.MTYPE)
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('a:blnr')) # Body cursor lnum
VO = VOOMS[body]
# VO.MTYPE other than 0 means it is not an "fmr" mode
if VO.MTYPE:
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: VOoM**voom_notes.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]
def voom_Voominfo(): #{{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
vimvars = vim.eval('l:vimvars')
print('%s CURRENT VOoM OUTLINE %s' %('-'*10, '-'*18))
if not tree:
print('current buffer %s is not a VOoM buffer' %body)
else:
VO = VOOMS[body]
assert VO.tree == tree
print(VO.bname)
print('Body buffer %s, Tree buffer %s' % (body, tree))
print('markup mode: %s' % (VO.mmode))
print('markup mode file: "%s"' % (os.path.abspath(VO.mModule.__file__)))
if VO.MTYPE==0:
print('headline markers: %s1, %s2, %s3, ...' % (VO.marker, VO.marker, VO.marker))
print('%s VOoM INTERNALS %s' %('-'*10, '-'*24))
print('Python version: %s' % (sys.version))
print('s:PYCMD = %s' % repr((vim.eval('s:PYCMD'))))
if vimvars:
print("_VOoM2657.FT_MODES = %s" % repr(FT_MODES))
print("_VOoM2657.DEFAULT_MODE = %s" % repr(DEFAULT_MODE))
print("_VOoM2657.CLIPBOARD = %s" % repr(CLIPBOARD))
print("_VOoM2657.ALWAYS_ALLOW_MOVE_LEFT = %s" % repr(ALWAYS_ALLOW_MOVE_LEFT))
print('_VOoM2657 : "%s"' % (os.path.abspath(sys.modules['voom_vimplugin2657.voom_vim'].__file__)))
print(vimvars)
def voom_ReloadAllPre(): #{{{2
if IS_PY2:
sys.exc_clear()
del sys.modules['voom_vimplugin2657.voom_vim']
for k in list(sys.modules.keys()):
#if k.startswith('voom_vimplugin2657.voom_mode_'):
if k.startswith('voom_vimplugin2657.'):
del sys.modules[k]
#---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("'", "''"))
vim.command("echo ''") # prevent fusion with previous message
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')
inhAND, inhNOT = vim.eval('l:inhAND'), vim.eval('l:inhNOT')
# Convert blnums of mathes into tlnums, that is node numbers.
tlnumsAND, tlnumsNOT = [], [] # lists of AND and NOT "tlnums" dicts
# Process AND matches.
counts = {} # {tlnum: count of all AND matches in this node, ...}
blnums = {} # {tlnum: blnum of first AND match in this node, ...}
inh_only = {} # tlnums of nodes added to an AND match by inheritance only
idx = 0 # index into matchesAND and inhAND
for L in matchesAND:
tlnums = {} # {tlnum of node with a match:0, ...}
for bln in L:
bln = int(bln)
tln = bisect.bisect_right(bnodes, bln)
tlnums[tln] = 0
if tln in counts:
counts[tln]+=1
else:
counts[tln] = 1
if not tln in blnums:
blnums[tln] = bln
elif blnums[tln] > bln or counts[tln]==1:
blnums[tln] = bln
# inheritace: add subnodes for each node with a match
if int(inhAND[idx]):
for t in list(tlnums.keys()):
subn = nodeSubnodes(VO,t)
for s in xrange(t+1,t+subn+1):
if not s in tlnums:
tlnums[s] = 0
counts[s] = 0
blnums[s] = bnodes[s-1]
# node has no match, added thank to inheritance only
inh_only[s] = 0
idx+=1
tlnumsAND.append(tlnums)
# Process NOT matches.
idx = 0 # index into matchesNOT and inhNOT
for L in matchesNOT:
tlnums = {} # {tlnum of node with a match:0, ...}
for bln in L:
bln = int(bln)
tln = bisect.bisect_right(bnodes, bln)
tlnums[tln] = 0
# inheritace: add subnodes for each node with a match
if int(inhNOT[idx]):
for t in list(tlnums.keys()):
subn = nodeSubnodes(VO,t)
for s in xrange(t+1,t+subn+1):
tlnums[s] = 0
idx+=1
tlnumsNOT.append(tlnums)
# There are only NOT patterns.
if not matchesAND:
tlnumsAND = [{}.fromkeys(xrange(1,len(bnodes)+1))]
# Compute intersection.
results = sorted(intersectDicts(tlnumsAND, tlnumsNOT))
#print(results)
# Compute max_size to left-align UNLs in the qflist.
# Add missing data for each node in results.
nNs = {} # {tlnum : 'N' if node has all AND matches, 'n' otherwise, ...}
max_size = 0
for t in results:
# there are only NOT patterns
if not matchesAND:
blnums[t] = bnodes[t-1]
counts[t] = 0
nNs[t] = 'n'
# some nodes in results do not contain all AND matches
elif inh_only:
if t in inh_only:
nNs[t] = 'n'
else:
nNs[t] = 'N'
# every node in results contains all AND matches
else:
nNs[t] = 'N'
size = len('%s%s%s' %(t, counts[t], blnums[t]))
if size > max_size:
max_size = size
# Make 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)
text = '%s%s:%s%s|%s' %(nNs[t], 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 {}
if len(dictsAND)==1:
res = dictsAND[0]
else:
res = {}
# intersection with other dicts in dictsAND
for key in dictsAND[0]:
for d in dictsAND[1:]:
if not key in d:
break
else:
res[key] = 0
# non-intersection with all dicts in dictsNOT
for d in dictsNOT:
for key in d:
if key in res:
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.
#
# l:blnShow is initially set by the VimScript caller to -1.
# Returning before setting l:blnShow means no changes were made.
# If Python code fails, l:blnShow also stays at -1.
# Subsequent VimScript code relies on l:blnShow.
def setLevTreeLines(tlines, levels, j): #{{{2
"""Set level of each Tree line in tlines to corresponding level from levels.
levels should be VO.levels.
j is index of the first item in levels.
"""
results = []
i = 0
for t in tlines:
results.append('%s%s%s' %(t[:2], '. '*(levels[j+i]-1), t[t.index('|'):]))
i+=1
return results
def changeLevBodyHead(VO, h, levDelta): #{{{2
"""Increase or 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 CLIPBOARD (usually +) to string s."""
# important: use '' for Vim string
vim.command("let @%s = '%s'" %(CLIPBOARD, 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.
# Python 2. The check adds about 0.09 sec for each 1MB in the clipboard.
# 30-40% increase overall in the time of Copy operation (yy).
if IS_PY2: # Python 2: s is bytes string
len_s = len(s)
else: # Python 3: s is unicode string
len_s = len(s.encode(get_vim_encoding(), 'replace'))
if not vim.eval('len(@%s)' %CLIPBOARD) == '%s' % len_s:
vim.command("call voom#ErrorMsg('VOoM: error setting clipboard (Vim register %s)')" %CLIPBOARD)
# empty clipboard to prevent Paste with erroneous data
vim.command("let @%s=''" %CLIPBOARD)
return -1
return 0
def voom_OopVerify(): #{{{2
body, tree = int(vim.eval('a:body')), int(vim.eval('a:tree'))
VO = VOOMS[body]
assert VO.tree == tree
ok = True
tlines, bnodes, levels = VO.makeOutline(VO, VO.Body)
if not len(VO.Tree)==len(tlines)+1:
vim.command("call voom#ErrorMsg('VOoM: outline verification failed: wrong Tree size')")
vim.command("call voom#ErrorMsg('VOoM: OUTLINE MAY BE CORRUPT!!! YOU MUST UNDO THE LAST OPERATION!!!')")
ok = False
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:]
if not VO.bnodes == bnodes:
vim.command("call voom#ErrorMsg('VOoM: outline verification failed: wrong bnodes')")
vim.command("call voom#ErrorMsg('VOoM: OUTLINE MAY BE CORRUPT!!! YOU MUST UNDO THE LAST OPERATION!!!')")
return
if not VO.levels == levels:
ok = False
vim.command("call voom#ErrorMsg('VOoM: outline verification failed: wrong levels')")
if not VO.Tree[:] == tlines:
ok = False
vim.command("call voom#ErrorMsg('VOoM: outline verification failed: wrong Tree lines')")
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_OopEdit(): # {{{2
body, tree = int(vim.eval('l:body')), int(vim.eval('l:tree'))
lnum, op = int(vim.eval('l:lnum')), vim.eval('a:op')
VO = VOOMS[body]
assert VO.tree == tree
if op=='i':
bLnr = VO.bnodes[lnum-1]
elif op=='I':
if lnum < len(VO.bnodes):
bLnr = VO.bnodes[lnum]-1
else:
bLnr = len(VO.Body)
vim.command("let l:bLnr=%s" %(bLnr))
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))
vim.command('let l:pyOK=1')
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]
error = setClipboard('\n'.join(blines))
if error:
vim.command("call voom#ErrorMsg('VOoM (cut): outline operation aborted')")
vim.command("call voom#OopFromBody(%s,%s,-1)" %(body,tree))
vim.command('let l:pyOK=1')
return
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("call voom#OopFromBody(%s,%s,%s)" %(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
# do this last to tell vim script that there were no errors
vim.command('let l:blnShow=%s' %blnShow)
vim.command('let l:pyOK=1')
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('@%s' %CLIPBOARD)
if not pText:
vim.command("call voom#ErrorMsg('VOoM (paste): clipboard is empty')")
vim.command("call voom#OopFromBody(%s,%s,-1)" %(body,tree))
vim.command('let l:pyOK=1')
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)" %(body,tree))
vim.command('let l:pyOK=1')
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)" %(body,tree))
vim.command('let l:pyOK=1')
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:
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
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)" %(body,tree,blnShow))
# remove = mark before modifying Tree
snLn = VO.snLn
Tree[snLn-1] = ' ' + Tree[snLn-1][1:]
### adjust levels of new headlines, insert them in Tree
if levDelta:
pTlines = setLevTreeLines(pTlines, levels, ln1-1)
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
# do this last to tell vim script that there were no errors
vim.command('let l:blnShow=%s' %blnShow)
vim.command('let l:pyOK=1')
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)" %(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 = setLevTreeLines(tlines, levels, lnUp1-1)
# 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
# do this last to tell vim script that there were no errors
vim.command('let l:blnShow=%s' %blnShow)
vim.command('let l:pyOK=1')
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)" %(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 = setLevTreeLines(tlines, levels, snLn-1)
# insert, then cut
Tree[lnIns:lnIns] = tlines
Tree[ln1-1:ln2] = []
### add snLn mark
Tree[snLn-1] = '=' + Tree[snLn-1][1:]
# do this last to tell vim script that there were no errors
vim.command('let l:blnShow=%s' %blnShow)
vim.command('let l:pyOK=1')
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.
cannotmove = False
# can't move right if ln1 node is child of previous node
if levels[ln1-1] > levels[ln1-2]:
cannotmove = True
# move right is not allowed
elif VO.MTYPE > 1:
cannotmove = True
vim.command("call voom#ErrorMsg('VOoM: operation ''Move Right'' is not available in this markup mode')")
if cannotmove:
vim.command("let &fdm=b_fdm")
vim.command("call voom#OopFromBody(%s,%s,-1)" %(body,tree))
vim.command('let l:doverif=0')
vim.command('let l:pyOK=1')
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=b_fdm")
vim.command("call voom#OopFromBody(%s,%s,%s)" %(body,tree,blnShow))
### change levels of Tree lines (same as for VO.levels)
tlines = Tree[ln1-1:ln2]
tlines = setLevTreeLines(tlines, levels, ln1-1)
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
# do this last to tell vim script that there were no errors
vim.command('let l:blnShow=%s' %blnShow)
vim.command('let l:pyOK=1')
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.
cannotmove = False
# can't move left if at top level 1
if levels[ln1-1] <= 1:
cannotmove = True
# don't move left if the range is not at the end of subtree
elif not ALWAYS_ALLOW_MOVE_LEFT and ln2 < len(levels) and levels[ln2]==levels[ln1-1]:
cannotmove = True
# move left is not allowed
elif VO.MTYPE > 1:
cannotmove = True
vim.command("call voom#ErrorMsg('VOoM: operation ''Move Left'' is not available in this markup mode')")
if cannotmove:
vim.command("let &fdm=b_fdm")
vim.command("call voom#OopFromBody(%s,%s,-1)" %(body,tree))
vim.command('let l:doverif=0')
vim.command('let l:pyOK=1')
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=b_fdm")
vim.command("call voom#OopFromBody(%s,%s,%s)" %(body,tree,blnShow))
### change levels of Tree lines (same as for VO.levels)
tlines = Tree[ln1-1:ln2]
tlines = setLevTreeLines(tlines, levels, ln1-1)
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
# do this last to tell vim script that there were no errors
vim.command('let l:blnShow=%s' %blnShow)
vim.command('let l:pyOK=1')
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:])
vim.command('let l:pyOK=1')
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'))
vim.command('let l:pyOK=1')
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:
vim.command('let l:pyOK=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
vim.command('let l:pyOK=1')
return
if bline2[0]=='=':
vim.command('let l:pyOK=1')
return
elif bline2[0]=='o' and bline2[1:] and bline2[1]=='=':
vim.command('let l:pyOK=1')
return
elif bline2[0]=='o':
end+=1
Body[bln_selected-1] = '%s=%s' %(bline[:end], bline[end:])
vim.command('let l:pyOK=1')
#--- 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: VOoM**voom_notes.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:
vim.command('let l:pyOK=1')
return
#if ln1==1: ln1=2
if ln1==ln2:
ln2 = ln2 + nodeSubnodes(VO, ln2)
if ln1==ln2:
vim.command('let l:pyOK=1')
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)
vim.command('let l:pyOK=1')
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 VOoM**voom_notes.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, 'oBytes':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=='r': D['oReverse'] = 1 # sort in reverse order
elif o=='flip': D['oFlip'] = 1 # reverse without sorting
elif o=='shuffle': D['oShuffle'] = 1
elif o=='bytes': D['oBytes'] = 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), r (reverse-sort), flip, shuffle, bytes')")
vim.command('let l:pyOK=1')
return
if (D['oReverse'] + D['oFlip'] + D['oShuffle']) > 1:
vim.command("call voom#ErrorMsg('VOoM (sort): these options cannot be combined: r, flip, shuffle')")
vim.command('let l:pyOK=1')
return
if D['oShuffle']:
global shuffle
if shuffle is None: from random import shuffle
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('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
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')")
vim.command('let l:pyOK=1')
return
###### }}}
#print('ln1=%s ln2=%s siblings=%s' % (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')")
vim.command('let l:pyOK=1')
return
elif flag2==0:
vim.command("call voom#WarningMsg('VOoM (sort): already sorted')")
vim.command('let l:pyOK=1')
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))
vim.command('let l:pyOK=1')
def sortSiblings(VO, siblings, oIgnorecase, oBytes, 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)
sibs_dec = [] # list of siblings for sorting
if oFlip or oShuffle: # flip or shuffle: Tree headlines don't matter
# make list of siblings for sorting
# [(0, index, lnum), ...]
for i in xrange(z):
sib = sibs[i]
sibs_dec.append((0, i, sib))
if oFlip:
sibs_dec.reverse()
elif oShuffle:
shuffle(sibs_dec)
else: # sort, reverse sort: according to Tree headlines
# make list of siblings for sorting, decorate with headline text
# [(Tree headline text, index, lnum), ...]
for i in xrange(z):
sib = sibs[i]
head = Tree[sib-1].split('|',1)[1]
if IS_PY2:
if not oBytes:
head = unicode(head, oEnc, 'replace')
else:
if oBytes:
head = bytes(head, oEnc, 'replace')
if oIgnorecase:
head = head.lower()
sibs_dec.append((head, i, sib))
if oReverse: # reverse sort
sibs_dec.sort(key=lambda x: x[0], reverse=True)
else: # sort
sibs_dec.sort()
sibs_sorted = [i[2] for i in sibs_dec]
#print(sibs_dec); print(sibs_sorted)
# don't sort if already sorted (can happen with shuffle)
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_GetBufRange(): #{{{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
_code_ = '%s\n%s%s\n' %(enc, '\n'*(bln1-2), '\n'.join(blines))
_globs_ = {'vim': vim, '_VOoM2657': sys.modules['voom_vimplugin2657.voom_vim']}
try:
exec(_code_, _globs_)
#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 %s Log buffer ...' % PY_VERSION
self.join = False
if IS_PY2:
self.encoding = get_vim_encoding()
self.type_u = type(u" ")
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 have '\n' in appended list items, so always use splitlines().
# A trailing \n is lost after splitlines(), but not for '\n\n' etc.
#
# Vim 7.2, 7.3 (Python 2 only): cannot append unicode strings. This produces an error:
# :py vim.current.buffer.append(u'test')
# Vim 7.4, Python 2 and 3: no such problem, the above works.
# Vim 7.4, Python 3: type(s)==type(u" ") is usually true, s.encode() results in error.
#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("call voom#ErrorMsg('VOoM (PyLog): PyLog buffer %s is unloaded or doesn''t exist')" %self.logbnr)
vim.command("call voom#ErrorMsg('VOoM (PyLog): unable to write string:')")
vim.command("echom '%s'" %(repr(s).replace("'", "''")) )
vim.command("call voom#ErrorMsg('VOoM (PyLog): please try executing command :Voomlog to fix')")
return
try:
if IS_PY2 and type(s) == self.type_u: # needed for Vim 7.2, 7.3
s = s.encode(self.encoding)
# Join with previous message if it had no ending newline.
if self.join:
s = self.buffer[-1] + s
del self.buffer[-1]
self.join = not s[-1]=='\n'
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):
|