/bundle/orgmode/ftplugin/orgmode/plugins/Navigator.py

https://github.com/imxiaohui/vimrc-1 · Python · 313 lines · 229 code · 38 blank · 46 comment · 104 complexity · 454a7043d046895e854ccbb8c716ca42 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. from orgmode import echo, ORGMODE, apply_count
  3. from orgmode.menu import Submenu, ActionEntry
  4. from orgmode.keybinding import Keybinding, MODE_VISUAL, MODE_OPERATOR, Plug
  5. from orgmode.liborgmode.documents import Direction
  6. import vim
  7. class Navigator(object):
  8. u""" Implement navigation in org-mode documents """
  9. def __init__(self):
  10. object.__init__(self)
  11. self.menu = ORGMODE.orgmenu + Submenu(u'&Navigate Headings')
  12. self.keybindings = []
  13. @classmethod
  14. @apply_count
  15. def parent(cls, mode):
  16. u"""
  17. Focus parent heading
  18. :returns: parent heading or None
  19. """
  20. heading = ORGMODE.get_document().current_heading()
  21. if not heading:
  22. if mode == u'visual':
  23. vim.command(u'normal gv'.encode(u'utf-8'))
  24. else:
  25. echo(u'No heading found')
  26. return
  27. if not heading.parent:
  28. if mode == u'visual':
  29. vim.command(u'normal gv'.encode(u'utf-8'))
  30. else:
  31. echo(u'No parent heading found')
  32. return
  33. p = heading.parent
  34. if mode == u'visual':
  35. cls._change_visual_selection(heading, p, direction=Direction.BACKWARD, parent=True)
  36. else:
  37. vim.current.window.cursor = (p.start_vim, p.level + 1)
  38. return p
  39. @classmethod
  40. @apply_count
  41. def parent_next_sibling(cls, mode):
  42. u"""
  43. Focus the parent's next sibling
  44. :returns: parent's next sibling heading or None
  45. """
  46. heading = ORGMODE.get_document().current_heading()
  47. if not heading:
  48. if mode == u'visual':
  49. vim.command(u'normal gv'.encode(u'utf-8'))
  50. else:
  51. echo(u'No heading found')
  52. return
  53. if not heading.parent or not heading.parent.next_sibling:
  54. if mode == u'visual':
  55. vim.command(u'normal gv'.encode(u'utf-8'))
  56. else:
  57. echo(u'No parent heading found')
  58. return
  59. ns = heading.parent.next_sibling
  60. if mode == u'visual':
  61. cls._change_visual_selection(heading, ns, direction=Direction.FORWARD, parent=False)
  62. elif mode == u'operator':
  63. vim.current.window.cursor = (ns.start_vim, 0)
  64. else:
  65. vim.current.window.cursor = (ns.start_vim, ns.level + 1)
  66. return ns
  67. @classmethod
  68. def _change_visual_selection(cls, current_heading, heading, direction=Direction.FORWARD, noheadingfound=False, parent=False):
  69. current = vim.current.window.cursor[0]
  70. line_start, col_start = [ int(i) for i in vim.eval(u'getpos("\'<")'.encode(u'utf-8'))[1:3] ]
  71. line_end, col_end = [ int(i) for i in vim.eval(u'getpos("\'>")'.encode(u'utf-8'))[1:3] ]
  72. f_start = heading.start_vim
  73. f_end = heading.end_vim
  74. swap_cursor = True
  75. # << |visual start
  76. # selection end >>
  77. if current == line_start:
  78. if (direction == Direction.FORWARD and line_end < f_start) or noheadingfound and not direction == Direction.BACKWARD:
  79. swap_cursor = False
  80. # focus heading HERE
  81. # << |visual start
  82. # selection end >>
  83. # << |visual start
  84. # focus heading HERE
  85. # selection end >>
  86. if f_start < line_start and direction == Direction.BACKWARD:
  87. if current_heading.start_vim < line_start and not parent:
  88. line_start = current_heading.start_vim
  89. else:
  90. line_start = f_start
  91. elif (f_start < line_start or f_start < line_end) and not noheadingfound:
  92. line_start = f_start
  93. # << |visual start
  94. # selection end >>
  95. # focus heading HERE
  96. else:
  97. if direction == Direction.FORWARD:
  98. if line_end < f_start and not line_start == f_start - 1 and current_heading:
  99. # focus end of previous heading instead of beginning of next heading
  100. line_start = line_end
  101. line_end = f_start - 1
  102. else:
  103. # focus end of next heading
  104. line_start = line_end
  105. line_end = f_end
  106. elif direction == Direction.BACKWARD:
  107. if line_end < f_end:
  108. pass
  109. else:
  110. line_start = line_end
  111. line_end = f_end
  112. # << visual start
  113. # selection end| >>
  114. else:
  115. # focus heading HERE
  116. # << visual start
  117. # selection end| >>
  118. if line_start > f_start and line_end > f_end and not parent:
  119. line_end = f_end
  120. swap_cursor = False
  121. elif (line_start > f_start or \
  122. line_start == f_start) and line_end <= f_end and direction == Direction.BACKWARD:
  123. line_end = line_start
  124. line_start = f_start
  125. # << visual start
  126. # selection end and focus heading end HERE| >>
  127. # << visual start
  128. # focus heading HERE
  129. # selection end| >>
  130. # << visual start
  131. # selection end| >>
  132. # focus heading HERE
  133. else:
  134. if direction == Direction.FORWARD:
  135. if line_end < f_start - 1:
  136. # focus end of previous heading instead of beginning of next heading
  137. line_end = f_start - 1
  138. else:
  139. # focus end of next heading
  140. line_end = f_end
  141. else:
  142. line_end = f_end
  143. swap_cursor = False
  144. move_col_start = u'%dl' % (col_start - 1) if (col_start - 1) > 0 and (col_start - 1) < 2000000000 else u''
  145. move_col_end = u'%dl' % (col_end - 1) if (col_end - 1) > 0 and (col_end - 1) < 2000000000 else u''
  146. swap = u'o' if swap_cursor else u''
  147. vim.command((u'normal %dgg%s%s%dgg%s%s' % \
  148. (line_start, move_col_start, vim.eval(u'visualmode()'.encode(u'utf-8')), line_end, move_col_end, swap)).encode(u'utf-8'))
  149. @classmethod
  150. def _focus_heading(cls, mode, direction=Direction.FORWARD, skip_children=False):
  151. u"""
  152. Focus next or previous heading in the given direction
  153. :direction: True for next heading, False for previous heading
  154. :returns: next heading or None
  155. """
  156. d = ORGMODE.get_document()
  157. current_heading = d.current_heading()
  158. heading = current_heading
  159. focus_heading = None
  160. # FIXME this is just a piece of really ugly and unmaintainable code. It
  161. # should be rewritten
  162. if not heading:
  163. if direction == Direction.FORWARD and d.headings \
  164. and vim.current.window.cursor[0] < d.headings[0].start_vim:
  165. # the cursor is in the meta information are, therefore focus
  166. # first heading
  167. focus_heading = d.headings[0]
  168. if not (heading or focus_heading):
  169. if mode == u'visual':
  170. # restore visual selection when no heading was found
  171. vim.command(u'normal gv'.encode(u'utf-8'))
  172. else:
  173. echo(u'No heading found')
  174. return
  175. elif direction == Direction.BACKWARD:
  176. if vim.current.window.cursor[0] != heading.start_vim:
  177. # the cursor is in the body of the current heading, therefore
  178. # the current heading will be focused
  179. if mode == u'visual':
  180. line_start, col_start = [ int(i) for i in vim.eval(u'getpos("\'<")'.encode(u'utf-8'))[1:3] ]
  181. line_end, col_end = [ int(i) for i in vim.eval(u'getpos("\'>")'.encode(u'utf-8'))[1:3] ]
  182. if line_start >= heading.start_vim and line_end > heading.start_vim:
  183. focus_heading = heading
  184. else:
  185. focus_heading = heading
  186. # so far no heading has been found that the next focus should be on
  187. if not focus_heading:
  188. if not skip_children and direction == Direction.FORWARD and heading.children:
  189. focus_heading = heading.children[0]
  190. elif direction == Direction.FORWARD and heading.next_sibling:
  191. focus_heading = heading.next_sibling
  192. elif direction == Direction.BACKWARD and heading.previous_sibling:
  193. focus_heading = heading.previous_sibling
  194. if not skip_children:
  195. while focus_heading.children:
  196. focus_heading = focus_heading.children[-1]
  197. else:
  198. if direction == Direction.FORWARD:
  199. focus_heading = current_heading.next_heading
  200. else:
  201. focus_heading = current_heading.previous_heading
  202. noheadingfound = False
  203. if not focus_heading:
  204. if mode in (u'visual', u'operator'):
  205. # the cursor seems to be on the last or first heading of this
  206. # document and performes another next/previous operation
  207. focus_heading = heading
  208. noheadingfound = True
  209. else:
  210. if direction == Direction.FORWARD:
  211. echo(u'Already focussing last heading')
  212. else:
  213. echo(u'Already focussing first heading')
  214. return
  215. if mode == u'visual':
  216. cls._change_visual_selection(current_heading, focus_heading, direction=direction, noheadingfound=noheadingfound)
  217. elif mode == u'operator':
  218. if direction == Direction.FORWARD and vim.current.window.cursor[0] >= focus_heading.start_vim:
  219. vim.current.window.cursor = (focus_heading.end_vim, len(vim.current.buffer[focus_heading.end].decode(u'utf-8')))
  220. else:
  221. vim.current.window.cursor = (focus_heading.start_vim, 0)
  222. else:
  223. vim.current.window.cursor = (focus_heading.start_vim, focus_heading.level + 1)
  224. if noheadingfound:
  225. return
  226. return focus_heading
  227. @classmethod
  228. @apply_count
  229. def previous(cls, mode, skip_children=False):
  230. u"""
  231. Focus previous heading
  232. """
  233. return cls._focus_heading(mode, direction=Direction.BACKWARD, skip_children=skip_children)
  234. @classmethod
  235. @apply_count
  236. def next(cls, mode, skip_children=False):
  237. u"""
  238. Focus next heading
  239. """
  240. return cls._focus_heading(mode, direction=Direction.FORWARD, skip_children=skip_children)
  241. def register(self):
  242. # normal mode
  243. self.keybindings.append(Keybinding(u'g{', Plug('OrgJumpToParentNormal', u':py ORGMODE.plugins[u"Navigator"].parent(mode=u"normal")<CR>')))
  244. self.menu + ActionEntry(u'&Up', self.keybindings[-1])
  245. self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingNormal', u':py ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"normal")<CR>')))
  246. self.menu + ActionEntry(u'&Down', self.keybindings[-1])
  247. self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousNormal', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"normal")<CR>')))
  248. self.menu + ActionEntry(u'&Previous', self.keybindings[-1])
  249. self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextNormal', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"normal")<CR>')))
  250. self.menu + ActionEntry(u'&Next', self.keybindings[-1])
  251. # visual mode
  252. self.keybindings.append(Keybinding(u'g{', Plug(u'OrgJumpToParentVisual', u'<Esc>:<C-u>py ORGMODE.plugins[u"Navigator"].parent(mode=u"visual")<CR>', mode=MODE_VISUAL)))
  253. self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingVisual', u'<Esc>:<C-u>py ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"visual")<CR>', mode=MODE_VISUAL)))
  254. self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousVisual', u'<Esc>:<C-u>py ORGMODE.plugins[u"Navigator"].previous(mode=u"visual")<CR>', mode=MODE_VISUAL)))
  255. self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextVisual', u'<Esc>:<C-u>py ORGMODE.plugins[u"Navigator"].next(mode=u"visual")<CR>', mode=MODE_VISUAL)))
  256. # operator-pending mode
  257. self.keybindings.append(Keybinding(u'g{', Plug(u'OrgJumpToParentOperator', u':<C-u>py ORGMODE.plugins[u"Navigator"].parent(mode=u"operator")<CR>', mode=MODE_OPERATOR)))
  258. self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingOperator', u':<C-u>py ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"operator")<CR>', mode=MODE_OPERATOR)))
  259. self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousOperator', u':<C-u>py ORGMODE.plugins[u"Navigator"].previous(mode=u"operator")<CR>', mode=MODE_OPERATOR)))
  260. self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextOperator', u':<C-u>py ORGMODE.plugins[u"Navigator"].next(mode=u"operator")<CR>', mode=MODE_OPERATOR)))
  261. # section wise movement (skip children)
  262. # normal mode
  263. self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenNormal', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"normal", skip_children=True)<CR>')))
  264. self.menu + ActionEntry(u'Ne&xt Same Level', self.keybindings[-1])
  265. self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenNormal', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"normal", skip_children=True)<CR>')))
  266. self.menu + ActionEntry(u'Pre&vious Same Level', self.keybindings[-1])
  267. # visual mode
  268. self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenVisual', u'<Esc>:<C-u>py ORGMODE.plugins[u"Navigator"].previous(mode=u"visual", skip_children=True)<CR>', mode=MODE_VISUAL)))
  269. self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenVisual', u'<Esc>:<C-u>py ORGMODE.plugins[u"Navigator"].next(mode=u"visual", skip_children=True)<CR>', mode=MODE_VISUAL)))
  270. # operator-pending mode
  271. self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenOperator', u':<C-u>py ORGMODE.plugins[u"Navigator"].previous(mode=u"operator", skip_children=True)<CR>', mode=MODE_OPERATOR)))
  272. self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenOperator', u':<C-u>py ORGMODE.plugins[u"Navigator"].next(mode=u"operator", skip_children=True)<CR>', mode=MODE_OPERATOR)))