PageRenderTime 97ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/pwiki/ViHelper.py

https://bitbucket.org/xkjq/wikidpad_svn
Python | 3164 lines | 2867 code | 157 blank | 140 comment | 140 complexity | f10142cc6bd661acb1b89a30583d420b MD5 | raw file
Possible License(s): LGPL-2.1
  1. import wx, wx.xrc
  2. import wx.lib.dialogs
  3. import SystemInfo
  4. from wxHelper import GUI_ID, getAccelPairFromKeyDown, getTextFromClipboard
  5. from collections import defaultdict
  6. from StringOps import pathEnc, urlQuote
  7. import os
  8. import ConfigParser
  9. import re
  10. import copy
  11. import string
  12. import PluginManager
  13. import subprocess
  14. from wxHelper import * # Needed for XrcControls
  15. from WindowLayout import setWindowSize
  16. #TODO: Multiple registers
  17. # Page marks
  18. # Alt-combinations
  19. # .rc
  20. # TODO: should be configurable
  21. AUTOCOMPLETE_BOX_HEIGHT = 50
  22. # Key accels use a different sep in >= 2.9
  23. if wx.version() >= 2.9:
  24. ACCEL_SEP = "+"
  25. else:
  26. ACCEL_SEP = "-"
  27. def formatListBox(x, y):
  28. html = "<table width=100% height=5px><tr><td>{0}</td><td align='right'><font color='gray'>{1}</font></td></tr></table>".format(x, y)
  29. return html
  30. class ViHelper():
  31. """
  32. Base class for ViHandlers to inherit from.
  33. Contains code and functions that are relevent to VI emulation in
  34. both editor and preview mode.
  35. """
  36. # Modes
  37. # Current these are only (partly) implemented for the editor
  38. NORMAL, INSERT, VISUAL, REPLACE = range(4)
  39. MODE_TEXT = {
  40. 0 : u"",
  41. 1 : u"--INSERT--",
  42. 2 : u"--VISUAL--",
  43. 3 : u"--REPLACE--"
  44. }
  45. # Default key bindings - can be overridden by wikidrc
  46. KEY_BINDINGS = {
  47. u"!" : 33,
  48. u"\"" : 34,
  49. u"#" : 35,
  50. u"$" : 36,
  51. u"%" : 37,
  52. u"&" : 38,
  53. u"'" : 39,
  54. u"(" : 40,
  55. u")" : 41,
  56. u"*" : 42,
  57. u"+" : 43,
  58. u"," : 44,
  59. u"-" : 45,
  60. u"." : 46,
  61. u"/" : 47,
  62. u"0" : 48,
  63. u"1" : 49,
  64. u"2" : 50,
  65. u"3" : 51,
  66. u"4" : 52,
  67. u"5" : 53,
  68. u"6" : 54,
  69. u"7" : 55,
  70. u"8" : 56,
  71. u"9" : 57,
  72. u":" : 58,
  73. u";" : 59,
  74. u"<" : 60,
  75. u"=" : 61,
  76. u">" : 62,
  77. u"?" : 63,
  78. u"@" : 64,
  79. u"A" : 65,
  80. u"B" : 66,
  81. u"C" : 67,
  82. u"D" : 68,
  83. u"E" : 69,
  84. u"F" : 70,
  85. u"G" : 71,
  86. u"H" : 72,
  87. u"I" : 73,
  88. u"J" : 74,
  89. u"K" : 75,
  90. u"L" : 76,
  91. u"M" : 77,
  92. u"N" : 78,
  93. u"O" : 79,
  94. u"P" : 80,
  95. u"Q" : 81,
  96. u"R" : 82,
  97. u"S" : 83,
  98. u"T" : 84,
  99. u"U" : 85,
  100. u"V" : 86,
  101. u"W" : 87,
  102. u"X" : 88,
  103. u"Y" : 89,
  104. u"Z" : 90,
  105. u"[" : 91,
  106. u"\\" : 92,
  107. u"]" : 93,
  108. u"^" : 94,
  109. u"_" : 95,
  110. u"`" : 96,
  111. u"a" : 97,
  112. u"b" : 98,
  113. u"c" : 99,
  114. u"d" : 100,
  115. u"e" : 101,
  116. u"f" : 102,
  117. u"g" : 103,
  118. u"h" : 104,
  119. u"i" : 105,
  120. u"j" : 106,
  121. u"k" : 107,
  122. u"l" : 108,
  123. u"m" : 109,
  124. u"n" : 110,
  125. u"o" : 111,
  126. u"p" : 112,
  127. u"q" : 113,
  128. u"r" : 114,
  129. u"s" : 115,
  130. u"t" : 116,
  131. u"u" : 117,
  132. u"v" : 118,
  133. u"w" : 119,
  134. u"x" : 120,
  135. u"y" : 121,
  136. u"z" : 122,
  137. u"{" : 123,
  138. u"|" : 124,
  139. u"}" : 125,
  140. u"~" : 126,
  141. }
  142. CMD_INPUT_DELAY = 1000
  143. STRIP_BULLETS_ON_LINE_JOIN = True
  144. def __init__(self, ctrl):
  145. # ctrl is WikiTxtCtrl in the case of the editor,
  146. # WikiHtmlViewWk for the preview mode.
  147. self.ctrl = ctrl
  148. self.key_map = {}
  149. for varName in vars(wx):
  150. if varName.startswith("WXK_"):
  151. self.key_map[getattr(wx, varName)] = varName
  152. self.mode = 0
  153. self._motion = []
  154. self._motion_wildcard = []
  155. self._wildcard = []
  156. self._acceptable_keys = None
  157. self.key_inputs = []
  158. self.key_number_modifier = [] # holds the count
  159. self.count = 1
  160. self.true_count = False
  161. self.hintDialog = None
  162. self.marks = defaultdict(dict)
  163. self.last_find_cmd = None
  164. self.last_search_args = None
  165. self.last_search_cmd = None
  166. self.jumps = []
  167. self.current_jump = -1
  168. self.input_window = self.ctrl.presenter.getMainControl().windowLayouter.getWindowByName("vi input")
  169. self.input_search_history = ViInputHistory()
  170. self.input_cmd_history = ViInputHistory()
  171. self.last_cmd = None
  172. self.insert_action = []
  173. self.selection_mode = u"NORMAL"
  174. self.tag_input = False
  175. # The following dictionary holds the menu shortcuts that have been
  176. # disabled upon entering ViMode
  177. self.menuShortCuts = {}
  178. self.viKeyAccels = set()
  179. self.register = ViRegister(self.ctrl)
  180. # Settings are stored as a dict
  181. self.settings = {
  182. "filter_wikipages" : True,
  183. "caret_scroll" : False, # Large performance hit
  184. # The following settings apply to SetHeading()
  185. "blank_line_above_headings" : True,
  186. "strip_headings" : True,
  187. # No. spaces to put between +++ and heading text
  188. "pad_headings" : 0,
  189. "gvim_path" : u"gvim",
  190. "vim_path" : u"vim",
  191. "caret_colour_normal" : "#FF0000",
  192. "caret_colour_visual" : "#FFD700",
  193. "caret_colour_insert" : "#0000FF",
  194. "caret_colour_replace" : "#8B0000",
  195. "caret_colour_command" : "#00FFFF",
  196. "set_wrap_indent_mode": 1,
  197. "set_wrap_start_indent": 0,
  198. "min_wikipage_search_len" : 2,
  199. }
  200. self.LoadSettings()
  201. self.RegisterPlugins()
  202. def RegisterPlugins(self):
  203. """
  204. Register the plugins to be loaded.
  205. """
  206. self.pluginFunctions = []
  207. main_control = self.ctrl.presenter.getMainControl()
  208. self.pluginFunctions = reduce(lambda a, b: a+list(b),
  209. main_control.viPluginFunctions.describeViFunctions(
  210. main_control), [])
  211. def LoadPlugins(self, presenter):
  212. """
  213. Helper which loads the plugins.
  214. To be called by derived class.
  215. """
  216. # Load plugin functions
  217. k = self.KEY_BINDINGS
  218. for keys, presenter_type, vi_mode, function in self.pluginFunctions:
  219. try:
  220. if presenter in presenter_type:
  221. def returnKey(key):
  222. if len(key) > 1 and key[0] == u"key":
  223. if type(key[1]) == tuple:
  224. l = list(key[1])
  225. key_char = l.pop()
  226. l.extend([k[key_char]])
  227. return tuple(l)
  228. else:
  229. return k[key[1]]
  230. elif key == "motion" or "m":
  231. return "m"
  232. elif key == "*":
  233. return "*"
  234. else:
  235. raise PluginKeyError(u"ERROR LOADING PLUGIN")
  236. key_chain = tuple([returnKey(i) for i in keys])
  237. for mode in vi_mode:
  238. self.keys[mode][key_chain] = function
  239. except PluginKeyError:
  240. continue
  241. self.pluginFunctions = []
  242. self.GenerateKeyKindings()
  243. def ReloadPlugins(self, name):
  244. self.RegisterPlugins()
  245. self.LoadPlugins(name)
  246. def LoadSettings(self):
  247. """
  248. Settings are loaded from the file vi.rc in the wikidpad global config
  249. dir
  250. Can be called at any time to update / reload settings
  251. ? May need to regenerate keybindings if they have been changed
  252. NOTE: Should move out of ViHelper as per tab setting are probably
  253. not required
  254. """
  255. rc_file = None
  256. rc_file_names = (".WikidPad.virc", "WikidPad.virc")
  257. config_dir = wx.GetApp().globalConfigDir
  258. for n in rc_file_names:
  259. path = pathEnc(os.path.join(config_dir, n))
  260. if os.path.isfile(path):
  261. rc_file = path
  262. break
  263. if rc_file is not None:
  264. config = ConfigParser.ConfigParser()
  265. config.read(rc_file)
  266. # Load custom key bindings
  267. try:
  268. for key in config.options("keys"):
  269. try:
  270. self.KEY_BINDINGS[key] = config.getint("keys", key)
  271. except ValueError:
  272. print "Keycode must be a integer: {0}".format(key)
  273. except ConfigParser.NoSectionError:
  274. pass
  275. try:
  276. for setting in config.options("settings"):
  277. if setting in self.settings:
  278. try:
  279. self.settings[setting] = config.getboolean(
  280. "settings", setting)
  281. except ValueError:
  282. print "Setting '{1}' must be boolean".format(setting)
  283. except ConfigParser.NoSectionError:
  284. pass
  285. self.ApplySettings()
  286. def ApplySettings(self):
  287. pass
  288. def OnChar(self, evt):
  289. """
  290. Handles EVT_CHAR events necessary for MS Windows
  291. """
  292. m = self.mode
  293. key = evt.GetKeyCode()
  294. # OnChar seems to throw different keycodes if ctrl is pressed.
  295. # a = 1, b = 2 ... z = 26
  296. # will not handle different cases
  297. if evt.ControlDown():
  298. key = key + 96
  299. key = ("Ctrl", key)
  300. self.HandleKey(key, m, evt)
  301. def OnViKeyDown(self, evt):
  302. """
  303. Handle keypresses when in Vi mode
  304. Ideally much of this would be moved to ViHelper
  305. """
  306. m = self.mode
  307. key = evt.GetKeyCode()
  308. if m == ViHelper.INSERT:
  309. # TODO: Allow navigation with Ctrl-N / Ctrl-P
  310. accP = getAccelPairFromKeyDown(evt)
  311. matchesAccelPair = self.ctrl.presenter.getMainControl().\
  312. keyBindings.matchesAccelPair
  313. if matchesAccelPair("AutoComplete", accP):
  314. # AutoComplete is normally Ctrl-Space
  315. # Handle autocompletion
  316. self.ctrl.autoComplete()
  317. if self.ctrl.AutoCompActive():
  318. self.OnAutocompleteKeyDown(evt)
  319. # The following code is mostly duplicated from OnKeyDown (should be
  320. # rewritten to avoid duplication)
  321. # TODO Check all modifiers
  322. if not evt.ControlDown() and not evt.ShiftDown():
  323. if key == wx.WXK_TAB:
  324. if self.ctrl.pageType == u"form":
  325. if not self.ctrl._goToNextFormField():
  326. self.ctrl.presenter.getMainControl().showStatusMessage(
  327. _(u"No more fields in this 'form' page"), -1)
  328. return
  329. evt.Skip()
  330. elif key == wx.WXK_RETURN and not self.ctrl.AutoCompActive():
  331. text = self.ctrl.GetText()
  332. wikiDocument = self.ctrl.presenter.getWikiDocument()
  333. bytePos = self.ctrl.GetCurrentPos()
  334. lineStartBytePos = self.ctrl.PositionFromLine(
  335. self.ctrl.LineFromPosition(bytePos))
  336. lineStartCharPos = len(self.ctrl.GetTextRange(0,
  337. lineStartBytePos))
  338. charPos = lineStartCharPos + len(self.ctrl.GetTextRange(
  339. lineStartBytePos, bytePos))
  340. autoUnbullet = self.ctrl.presenter.getConfig().getboolean("main",
  341. "editor_autoUnbullets", False)
  342. settings = {
  343. "autoUnbullet": autoUnbullet,
  344. "autoBullets": self.ctrl.autoBullets,
  345. "autoIndent": self.ctrl.autoIndent
  346. }
  347. if self.ctrl.wikiLanguageHelper.handleNewLineBeforeEditor(
  348. self.ctrl, text, charPos, lineStartCharPos,
  349. wikiDocument, settings):
  350. evt.Skip()
  351. return
  352. # Hack to maintain consistency when pressing return
  353. # on an empty bullet
  354. elif bytePos != self.ctrl.GetCurrentPos():
  355. return
  356. # Pass modifier keys on
  357. if key in (wx.WXK_CONTROL, wx.WXK_ALT, wx.WXK_SHIFT):
  358. return
  359. # On linux we can just use GetRawKeyCode() and work directly with
  360. # its return. On windows we have to skip this event (for keys which
  361. # will produce a char event to wait for EVT_CHAR (self.OnChar()) to
  362. # get the correct key translation
  363. elif key not in self.key_map:
  364. key = evt.GetRawKeyCode()
  365. else:
  366. # Keys present in the key_map should be consitent across
  367. # all platforms and can be handled directly.
  368. key = self.AddModifierToKeychain(key, evt)
  369. #self.HandleKey(key, m, evt)
  370. if not self.HandleKey(key, m, evt):
  371. # For wxPython 2.9.5 we need this otherwise menu accels don't
  372. # seem to be triggered (though they worked in previous versions?)
  373. evt.Skip()
  374. return
  375. # What about os-x?
  376. if not SystemInfo.isLinux():
  377. # Manual fix for some windows problems may be necessary
  378. # e.g. Ctrl-[ won't work
  379. evt.Skip()
  380. return
  381. key = self.AddModifierToKeychain(key, evt)
  382. if not self.HandleKey(key, m, evt):
  383. # For wxPython 2.9.5 we need this otherwise menu accels don't
  384. # seem to be triggered (though they worked in previous versions?)
  385. evt.Skip()
  386. def AddModifierToKeychain(self, key, evt):
  387. """
  388. Checks a key event for modifiers (ctrl / alt) and adds them to
  389. the key if they are present
  390. """
  391. mods = []
  392. if evt.ControlDown():
  393. mods.append("Ctrl")
  394. if evt.AltDown():
  395. mods.append("Alt")
  396. if mods:
  397. mods.extend([key])
  398. print mods
  399. key = tuple(mods)
  400. return key
  401. def EndInsertMode(self):
  402. pass
  403. def EndReplaceMode(self):
  404. pass
  405. def LeaveVisualMode(self):
  406. pass
  407. def HandleKey(self, key, m, evt):
  408. # There should be a better way to monitor for selection changed
  409. if self.HasSelection():
  410. self.EnterVisualMode()
  411. # TODO: Replace with override keys? break and run function
  412. # Escape, Ctrl-[, Ctrl-C
  413. # In VIM Ctrl-C triggers *InsertLeave*
  414. if key == wx.WXK_ESCAPE or key == ("Ctrl", 91) or key == ("Ctrl", 99):
  415. # TODO: Move into ViHandler?
  416. self.EndInsertMode()
  417. self.EndReplaceMode()
  418. self.LeaveVisualMode()
  419. self.FlushBuffers()
  420. return True
  421. # Registers
  422. if m != 1 and key == 34 and self._acceptable_keys is None \
  423. and not self.key_inputs: # "
  424. self.register.select_register = True
  425. return True
  426. elif self.register.select_register:
  427. self.register.SelectRegister(key)
  428. self.register.select_register = False
  429. return True
  430. if m in [1, 3]: # Insert mode, replace mode,
  431. # Store each keyevent
  432. # NOTE:
  433. # !!may need to seperate insert and replace modes!!
  434. # what about autocomplete?
  435. # It would be possbile to just store the text that is inserted
  436. # however then actions would be ignored
  437. self.insert_action.append(key)
  438. # Data is reset if the mouse is used or if a non char is pressed
  439. # Arrow up / arrow down
  440. if key in [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT]:
  441. self.EndBeginUndo()
  442. self.insert_action = []
  443. if not self.RunKeyChain((key,), m):
  444. evt.Skip()
  445. return False
  446. return True
  447. if self._acceptable_keys is None or \
  448. "*" not in self._acceptable_keys:
  449. if 48 <= key <= 57: # Normal
  450. if self.SetNumber(key-48):
  451. return True
  452. elif 65456 <= key <= 65465: # Numpad
  453. if self.SetNumber(key-65456):
  454. return True
  455. self.SetCount()
  456. if self._motion and self._acceptable_keys is None:
  457. #self._acceptable_keys = None
  458. self._motion.append(key)
  459. temp = self._motion[:-1]
  460. temp.append("*")
  461. if tuple(self._motion) in self.motion_keys[m]:
  462. self.RunKeyChain(tuple(self.key_inputs), m)
  463. return True
  464. #self._motion = []
  465. elif tuple(temp) in self.motion_keys[m]:
  466. self._motion[-1] = "*"
  467. self._motion_wildcard.append(key)
  468. self.RunKeyChain(tuple(self.key_inputs), m)
  469. #self._motion = []
  470. return True
  471. elif tuple(self._motion) in self.motion_key_mods[m]:
  472. #self._acceptable_keys = self.motion_key_mods[m][tuple(self._motion)]
  473. return True
  474. self.FlushBuffers()
  475. return True
  476. if self._acceptable_keys is not None:
  477. if key in self._acceptable_keys:
  478. self._acceptable_keys = None
  479. pass
  480. elif "*" in self._acceptable_keys:
  481. self._wildcard.append(key)
  482. self.key_inputs.append("*")
  483. self._acceptable_keys = None
  484. self.RunKeyChain(tuple(self.key_inputs), m)
  485. return True
  486. elif "m" in self._acceptable_keys:
  487. self._acceptable_keys = None
  488. self._motion.append(key)
  489. if (key,) in self.motion_keys[m]:
  490. self.key_inputs.append("m")
  491. self.RunKeyChain(tuple(self.key_inputs), m)
  492. return True
  493. elif (key,) == -999:
  494. self.key_inputs.append("m")
  495. self.RunKeyChain(tuple(self.key_inputs), m)
  496. if (key,) in self.motion_key_mods[m]:
  497. self.key_inputs.append("m")
  498. return True
  499. self.key_inputs.append(key)
  500. self.updateViStatus()
  501. key_chain = tuple(self.key_inputs)
  502. if self.RunKeyChain(key_chain, m):
  503. return True
  504. self.FlushBuffers()
  505. # If a chain command has been started prevent evt.Skip() from being
  506. # called
  507. if len(key_chain) > 1:
  508. return True
  509. try:
  510. if "Ctrl" in key or "Alt" in key:
  511. return False
  512. except TypeError:
  513. pass
  514. return True
  515. def KeyCommandInProgress(self):
  516. return self.key_inputs
  517. def NextKeyCommandCanBeMotion(self):
  518. """
  519. Checks if the next key can be a motion cmd
  520. """
  521. if "m" in self._acceptable_keys:
  522. return True
  523. return False
  524. def SetCount(self):
  525. self.count = 1
  526. self.true_count = False # True if count is specified
  527. if len(self.key_number_modifier) > 0:
  528. self.count = int("".join(map(str, self.key_number_modifier)))
  529. # Set a max count
  530. if self.count > 10000:
  531. self.count = 10000
  532. self.true_count = True
  533. def SetNumber(self, n):
  534. # TODO: move to ViHelper
  535. # If 0 is first modifier it is a command
  536. if len(self.key_number_modifier) < 1 and n == 0:
  537. return False
  538. self.key_number_modifier.append(n)
  539. self.updateViStatus(True)
  540. return True
  541. def GetCharFromCode(self, keycode):
  542. """
  543. Converts keykeycode to unikeycode character. If no keykeycode is specified
  544. returns an empty string.
  545. @param keycode: Raw keycode value
  546. @return: Requested character
  547. """
  548. # TODO: Rewrite
  549. if keycode is not None:
  550. # Check for modifiers
  551. if type(keycode) == tuple:
  552. l = list(keycode)
  553. k = l.pop()
  554. mods = ACCEL_SEP.join(l)
  555. return "{0}{1}{2}".format(mods, ACCEL_SEP, unichr(k))
  556. try:
  557. return unichr(keycode)
  558. # This may occur when special keys (e.g. WXK_SPACE) are used
  559. except TypeError, ValueError: # >wx2.9 ?valueerror?
  560. return keycode
  561. else:
  562. return u""
  563. def Mark(self, code):
  564. """
  565. Set marks can be any alpha charcter (a-zA-Z)
  566. @param code: keycode of mark to be set
  567. # TODO: save marks across sessions?
  568. """
  569. char = self.GetCharFromCode(code)
  570. if char is not None and char.isalpha():
  571. self._SetMark(code)
  572. self.visualBell("BLUE")
  573. else:
  574. self.visualBell("RED")
  575. self.updateViStatus()
  576. def _SetMark():
  577. """
  578. Dummy function to be overridden
  579. """
  580. def SetDefaultCaretColour(self):
  581. self.default_caret_colour = self.ctrl.GetCaretForeground()
  582. def GenerateKeyKindings(self):
  583. """Stub to be overridden by derived class"""
  584. def GenerateMotionKeys(self, keys):
  585. key_mods = defaultdict(dict)
  586. for mode in keys:
  587. key_mods[mode] = set()
  588. for accel in keys[mode]:
  589. if keys[mode][accel][0] > 0:
  590. key_mods[mode].add(accel)
  591. return key_mods
  592. def GenerateKeyModifiers(self, keys):
  593. """
  594. Takes dictionary of key combinations and returns all possible
  595. key starter combinations
  596. @param keys: see self.keys in derived classes
  597. """
  598. key_mods = defaultdict(dict)
  599. for mode in keys:
  600. key_mods[mode] = defaultdict(set)
  601. for accel in keys[mode]:
  602. if len(accel) > 1:
  603. for i in range(1, len(accel)):
  604. if i == 1:
  605. key_mods[mode][(accel[0],)].add(accel[1])
  606. else:
  607. key_mods[mode][accel[:i]].add(accel[i])
  608. return key_mods
  609. def GenerateKeyAccelerators(self, keys):
  610. """
  611. This could be improved
  612. """
  613. key_accels = set()
  614. for j in keys:
  615. for accels in keys[j]:
  616. for accel in accels:
  617. if type(accel) == tuple and len(accel) > 1:
  618. l = list(accel)
  619. k = l.pop()
  620. mods = ACCEL_SEP.join(l)
  621. # wx accels chars are always uppercase
  622. to_add = "{0}{1}{2}".format(mods, ACCEL_SEP, unichr(k).upper())
  623. key_accels.add(to_add)
  624. self.viKeyAccels.update(key_accels)
  625. def Repeat(self, func, count=None, arg=None):
  626. """
  627. Base function called if a command needs to be repeated
  628. a number of times.
  629. @param func: function to be run
  630. @param count: number of times to run the function, if not specified
  631. manually will use the input count (which defaults to 1)
  632. @param arg: argument to run function with, can be single or multiple
  633. arguments in the form of a dict
  634. """
  635. if count is None:
  636. count = self.count
  637. for i in range(count):
  638. if arg is not None:
  639. if type(arg) == dict:
  640. func(**arg)
  641. else:
  642. func(arg)
  643. else:
  644. func()
  645. def RunKeyChain(self, key_chain, mode):
  646. self.updateViStatus()
  647. if key_chain in self.keys[mode]:
  648. self.RunFunction(key_chain)
  649. self.FlushBuffers()
  650. return True
  651. else:
  652. if key_chain in self.key_mods[mode]:
  653. # TODO: better fix
  654. self.SetCaretColour(self.settings['caret_colour_command'])
  655. self._acceptable_keys = self.key_mods[mode][key_chain]
  656. return True
  657. return False
  658. def RunFunction(self, key, motion=None, motion_wildcard=None,
  659. wildcards=None, text_to_select=None, repeat=False):
  660. """
  661. Called when a key command is run
  662. keys is a dictionary which holds the "key" and its
  663. respective function.
  664. """
  665. if motion is None:
  666. motion = self._motion
  667. if wildcards is None:
  668. wildcards = self._wildcard
  669. if motion_wildcard is None:
  670. motion_wildcard = tuple(self._motion_wildcard)
  671. def RunFunc(func, args):
  672. if type(args) == dict:
  673. ret = func(**args)
  674. elif args is not None:
  675. ret = func(args)
  676. else:
  677. ret = func()
  678. keys = self.keys[self.mode]
  679. if text_to_select is not None:
  680. # Use visual keys instead
  681. keys = self.keys[ViHelper.VISUAL]
  682. # If we are already in visual mode use the selected text
  683. # Otherwise we use the same amount of text used for initial cmd
  684. # NOTE: this should prob be put into another cmd (in WikiTxtCtrl)
  685. if not self.mode == ViHelper.VISUAL:
  686. lines, n = text_to_select
  687. if lines:
  688. start_line = self.ctrl.GetCurrentLine()
  689. self.SelectLines(start_line, start_line - 1 + n,
  690. include_eol=True)
  691. else:
  692. self.StartSelection()
  693. self.MoveCaretPos(n, allow_last_char=True)
  694. self.SelectSelection(2)
  695. com_type, command, repeatable, selection_type = keys[key]
  696. func, args = command
  697. # If in visual mode need to prep in case we change selection direction
  698. start_selection_direction = None
  699. if self.mode == ViHelper.VISUAL:
  700. start_selection_direction = self.SelectionIsForward()
  701. # If a motion is present in the command (but not the main command)
  702. # it needs to be run first
  703. if "m" in key:
  704. # TODO: finish
  705. # If the motion is a mouse event it should have already been run
  706. if motion != -999:
  707. # If in visual mode we don't want to change the selection start point
  708. if self.mode != ViHelper.VISUAL:
  709. # Otherwise the "pre motion" commands work by setting a start point
  710. # at the current positions, running the motion command and
  711. # finishing with a "post motion" command, i.e. deleting the
  712. # text that was selected.
  713. self.StartSelection()
  714. motion_key = tuple(motion)
  715. # Extract the cmd we need to run (it is irrelevent if it is
  716. # repeatable or if it has a different selection_type)
  717. motion_com_type, (motion_func, motion_args), junk, junk = keys[motion_key]
  718. if motion_wildcard:
  719. motion_args = tuple(motion_wildcard)
  720. if len(motion_args) == 1:
  721. motion_args = motion_args[0]
  722. else:
  723. motion_args = tuple(motion_args)
  724. RunFunc(motion_func, motion_args)
  725. # Test if the motion has caused a movement in the caret
  726. # If not consider it an invalid cmd
  727. if self._anchor == self.ctrl.GetCurrentPos():
  728. return False
  729. self.SelectSelection(motion_com_type)
  730. # If in visual mode we save some details about the selection so the
  731. # command can be repeated
  732. selected_text = None
  733. if self.mode == ViHelper.VISUAL:
  734. # TODO: fix line selection mode
  735. selected_text = self.GetSelectionDetails(selection_type)
  736. if type(key) == tuple and "*" in key:
  737. args = tuple(wildcards)
  738. if len(args) == 1:
  739. args = args[0]
  740. else:
  741. args = tuple(args)
  742. # Run the actual function
  743. RunFunc(func, args)
  744. # If the command is repeatable save its type and any other settings
  745. if repeatable in [1, 2, 3] and not repeat:
  746. self.last_cmd = [repeatable, key, self.count, motion, \
  747. motion_wildcard, wildcards, \
  748. selected_text]
  749. # Some commands should cause the mode to revert back to normal if run
  750. # from visual mode, others shouldn't.
  751. if self.mode == ViHelper.VISUAL:
  752. if com_type < 1:
  753. self.SetMode(ViHelper.NORMAL)
  754. else:
  755. if start_selection_direction is not None:
  756. end_selection_direction = \
  757. self.ctrl.GetCurrentPos() > self._anchor
  758. if start_selection_direction != end_selection_direction:
  759. if end_selection_direction:
  760. self._anchor = self._anchor - 1
  761. else:
  762. self._anchor = self._anchor + 1
  763. self.SelectSelection(com_type)
  764. self.FlushBuffers()
  765. def FlushBuffers(self):
  766. """
  767. Clear modifiers and start keys so next input will be fresh
  768. Should be called after (most?) successful inputs and all failed
  769. ones.
  770. """
  771. self._acceptable_keys = None
  772. self._motion = []
  773. self._motion_wildcard = []
  774. self._wildcard = []
  775. self.key_inputs = []
  776. self.key_modifier = []
  777. self.key_number_modifier = []
  778. self.updateViStatus()
  779. self.FlushBuffersExtra()
  780. def FlushBuffersExtra(self):
  781. """
  782. To be overidden by derived class
  783. """
  784. pass
  785. def SetSelMode(self, mode):
  786. self.selection_mode = mode
  787. def GetSelMode(self):
  788. return self.selection_mode
  789. def HasSelection(self):
  790. """
  791. Should be overridden by child class if necessary
  792. """
  793. return False
  794. def SelectionIsForward(self):
  795. """
  796. Should be overridden by child class if necessary
  797. """
  798. return True
  799. def GetSelectionDetails(self, selection_type=None):
  800. """
  801. Should be overridden by child class if necessary
  802. """
  803. return (True, len(self.ctrl.GetSelectedText()))
  804. def _GetSelectionRange(self):
  805. return None
  806. def SetCaretColour(self, colour):
  807. # TODO: implement this
  808. pass
  809. def minmax(self, a, b):
  810. return min(a, b), max(a, b)
  811. def updateViStatus(self, force=False):
  812. # can this be right aligned?
  813. mode = self.mode
  814. text = u""
  815. if mode in self.keys:
  816. cmd = u"".join([self.GetCharFromCode(i) for i in self.key_inputs])
  817. text = u"{0}{1}{2}".format(
  818. ViHelper.MODE_TEXT[self.mode],
  819. u"".join(map(str, self.key_number_modifier)),
  820. cmd
  821. )
  822. self.ctrl.presenter.getMainControl().statusBar.SetStatusText(text , 0)
  823. def _enableMenuShortcuts(self, enable):
  824. # TODO: should only be called once (at startup / plugin load)
  825. if enable and len(self.menuShortCuts) < 1:
  826. return
  827. self.menu_bar = self.ctrl.presenter.getMainControl().mainmenu
  828. if enable:
  829. for i in self.menuShortCuts:
  830. self.menu_bar.FindItemById(i).SetText(self.menuShortCuts[i])
  831. self.ctrl.presenter.getMainControl() \
  832. .SetAcceleratorTable( self.accelTable)
  833. else:
  834. self.accelTable = self.ctrl.presenter.getMainControl() \
  835. .GetAcceleratorTable()
  836. self.ctrl.presenter.getMainControl() \
  837. .SetAcceleratorTable(wx.NullAcceleratorTable)
  838. menus = self.menu_bar.GetMenus()
  839. menu_items = []
  840. def getMenuItems(menu):
  841. for i in menu.GetMenuItems():
  842. menu_items.append(i.GetId())
  843. if i.GetSubMenu() is not None:
  844. getMenuItems(i.GetSubMenu())
  845. for menu, x in menus:
  846. getMenuItems(menu)
  847. for i in menu_items:
  848. menu_item = self.menu_bar.FindItemById(i)
  849. accel = menu_item.GetAccel()
  850. if accel is not None:
  851. try:
  852. if accel.ToString() in self.viKeyAccels:
  853. label = menu_item.GetItemLabel()
  854. self.menuShortCuts[i] = (label)
  855. # Removing the end part of the label is enough to disable the
  856. # accelerator. This is used instead of SetAccel() so as to
  857. # preserve menu accelerators.
  858. # NOTE: doesn't seem to override ctrl-n!
  859. menu_item.SetText(label.split("\t")[0]+"\tNone")
  860. except:
  861. # Key errors appear in windows! (probably due to
  862. # unicode support??).
  863. if unichr(accel.GetKeyCode()) in self.viKeyAccels:
  864. label = menu_item.GetItemLabel()
  865. self.menuShortCuts[i] = (label)
  866. menu_item.SetText(label.split("\t")[0]+"\tNone")
  867. #-----------------------------------------------------------------------------
  868. # The following functions are common to both preview and editor mode
  869. # -----------------------------------------------------------------------------
  870. # Jumps
  871. # TODO: generalise so they work with wikihtmlview as well
  872. # should work in lines (so if line is deleted)
  873. def AddJumpPosition(self, pos=None):
  874. if pos is None: pos = self.ctrl.GetCurrentPos()
  875. current_page = self.ctrl.presenter.getWikiWord()
  876. if self.jumps:
  877. last_page, last_pos = self.jumps[self.current_jump]
  878. if last_page == current_page and pos == last_pos:
  879. return
  880. if self.current_jump < len(self.jumps):
  881. self.jumps = self.jumps[:self.current_jump+1]
  882. self.jumps.append((current_page, pos))
  883. self.current_jump += 1
  884. def GotoNextJump(self):
  885. if self.current_jump + 1 < len(self.jumps):
  886. self.current_jump += 1
  887. else:
  888. return
  889. word, pos = self.jumps[self.current_jump]
  890. if word != self.ctrl.presenter.getWikiWord():
  891. self.ctrl.presenter.openWikiPage(word)
  892. self.ctrl.GotoPos(pos)
  893. def GotoPreviousJump(self):
  894. if self.current_jump + 1 == len(self.jumps):
  895. self.AddJumpPosition(self.ctrl.GetCurrentPos())
  896. if self.current_jump - 1 >= 0:
  897. self.current_jump -= 1
  898. else:
  899. return
  900. word, pos = self.jumps[self.current_jump]
  901. if word != self.ctrl.presenter.getWikiWord():
  902. self.ctrl.presenter.openWikiPage(word)
  903. self.ctrl.GotoPos(pos)
  904. def SwitchEditorPreview(self, scName=None):
  905. mainControl = self.ctrl.presenter.getMainControl()
  906. mainControl.setDocPagePresenterSubControl(scName)
  907. def StartForwardSearch(self, initial_input=None):
  908. self.StartSearchInput(initial_input=initial_input, forward=True)
  909. def StartReverseSearch(self, initial_input=None):
  910. self.StartSearchInput(initial_input=initial_input, forward=False)
  911. def StartSearchInput(self, initial_input=None, forward=True):
  912. text = initial_input
  913. if self.HasSelection():
  914. text = self.ctrl.GetSelectedText()
  915. text = text.split("\n", 1)[0]
  916. text = text[:30]
  917. self.ctrl.presenter.setTabTitleColour("BLUE")
  918. self.input_window.StartSearch(self.ctrl, self.input_search_history, text, forward)
  919. def ContinueLastSearchSameDirection(self):
  920. """Helper function to allow repeats"""
  921. self.ContinueLastSearch(False)
  922. def ContinueLastSearchReverseDirection(self):
  923. """Helper function to allow repeats"""
  924. self.ContinueLastSearch(True)
  925. def ContinueLastSearch(self, reverse):
  926. """
  927. Repeats last search command
  928. """
  929. args = self.last_search_args
  930. if args is not None:
  931. # If "N" we need to reverse the search direction
  932. if reverse:
  933. args['forward'] = not args['forward']
  934. args['repeat_search'] = True
  935. self._SearchText(**args)
  936. # Restore search direction (could use copy())
  937. if reverse:
  938. args['forward'] = not args['forward']
  939. def _SearchText(self):
  940. raise NotImplementedError, "To be overridden by derived class"
  941. def GoForwardInHistory(self):
  942. pageHistDeepness = self.ctrl.presenter.getPageHistory().getDeepness()[1]
  943. if pageHistDeepness == 0:
  944. self.visualBell()
  945. return
  946. self.ctrl.presenter.getPageHistory().goInHistory(self.count)
  947. def GoBackwardInHistory(self):
  948. pageHistDeepness = self.ctrl.presenter.getPageHistory().getDeepness()[0]
  949. if pageHistDeepness == 0:
  950. self.visualBell()
  951. return
  952. self.ctrl.presenter.getPageHistory().goInHistory(-self.count)
  953. def ViewParents(self, direct=False):
  954. """
  955. Note: the way this works may change in the future
  956. """
  957. presenter = self.ctrl.presenter
  958. word = self.ctrl.presenter.getWikiWord()
  959. # If no parents give a notification and exit
  960. if len(presenter.getMainControl().getWikiData(). \
  961. getParentRelationships(word)) == 0:
  962. self.visualBell()
  963. return
  964. path = []
  965. if direct:
  966. # Is it better to open each page? (slower but history recorded)
  967. for n in range(self.count):
  968. parents = presenter.getMainControl().getWikiData().getParentRelationships(word)
  969. if len(parents) == 1:
  970. # No need to loop if two pages are each others parents
  971. # Loop will end as soon as we are about to go back to
  972. # a page we were just on (3+ page loops can still occur)
  973. if n > 0 and path[n-1] == word:
  974. n = self.count-1
  975. else:
  976. word = parents[0]
  977. path.append(word)
  978. if n == self.count-1:
  979. presenter.openWikiPage(word, forceTreeSyncFromRoot=True)
  980. presenter.getMainControl().getMainAreaPanel().\
  981. showPresenter(presenter)
  982. presenter.SetFocus()
  983. return
  984. else:
  985. presenter.openWikiPage(word, forceTreeSyncFromRoot=True)
  986. presenter.getMainControl().getMainAreaPanel().\
  987. showPresenter(presenter)
  988. presenter.SetFocus()
  989. break
  990. presenter.getMainControl().viewParents(word)
  991. def CopyWikiWord(self):
  992. """
  993. Copy current wikiword to clipboard
  994. """
  995. def SwitchTabs(self, left=False):
  996. """
  997. Switch to n(th) tab.
  998. Positive numbers go right, negative left.
  999. If tab end is reached will wrap around
  1000. """
  1001. n = self.count
  1002. if left: n = -n
  1003. mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel()
  1004. pageCount = mainAreaPanel.GetPageCount()
  1005. currentTabNum = mainAreaPanel.GetSelection() + 1
  1006. if currentTabNum + n > pageCount:
  1007. newTabNum = currentTabNum + n % pageCount
  1008. if newTabNum > pageCount:
  1009. newTabNum -= pageCount
  1010. elif currentTabNum + n < 1:
  1011. newTabNum = currentTabNum - (pageCount - n % pageCount)
  1012. if newTabNum < 1:
  1013. newTabNum += pageCount
  1014. else:
  1015. newTabNum = currentTabNum + n
  1016. # Switch tab
  1017. mainAreaPanel.SetSelection(newTabNum-1)
  1018. #mainAreaPanel.presenters[mainAreaPanel.GetSelection()].SetFocus()
  1019. def CloseCurrentTab(self, junk=None):
  1020. """
  1021. Closes currently focused tab
  1022. """
  1023. mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel()
  1024. wx.CallAfter(mainAreaPanel.closePresenterTab, mainAreaPanel.getCurrentPresenter())
  1025. #return True
  1026. def OpenHomePage(self, inNewTab=False):
  1027. """
  1028. Opens home page.
  1029. If inNewTab=True opens in a new forground tab
  1030. """
  1031. presenter = self.ctrl.presenter
  1032. wikiword = presenter.getMainControl().getWikiDocument().getWikiName()
  1033. if inNewTab:
  1034. presenter = self.ctrl.presenter.getMainControl().\
  1035. createNewDocPagePresenterTab()
  1036. presenter.switchSubControl("preview", False)
  1037. # Now open wiki
  1038. presenter.openWikiPage(wikiword, forceTreeSyncFromRoot=True)
  1039. presenter.getMainControl().getMainAreaPanel().\
  1040. showPresenter(presenter)
  1041. presenter.SetFocus()
  1042. def GoogleSelection(self):
  1043. self.StartCmdInput("google ", run_cmd=True)
  1044. #--------------------------------------------------------------------
  1045. # Misc commands
  1046. #--------------------------------------------------------------------
  1047. def viError(self, text):
  1048. """
  1049. Display a visual error message
  1050. """
  1051. self.visualBell(close_delay=10000, text=text)
  1052. def visualBell(self, colour="RED", close_delay=100, text="" ):
  1053. """
  1054. Display a visual sign to alert user input has been
  1055. recieved.
  1056. Sign is a popup box that overlays the leftmost segment
  1057. of the status bar.
  1058. Default is colour is red however a number of different
  1059. colours can be used.
  1060. """
  1061. sb = self.ctrl.presenter.getMainControl().GetStatusBar()
  1062. rect = sb.GetFieldRect(0)
  1063. if SystemInfo.isOSX():
  1064. # needed on Mac OSX to avoid cropped text
  1065. rect = wx._core.Rect(rect.x, rect.y - 2, rect.width, rect.height + 4)
  1066. rect.SetPosition(sb.ClientToScreen(rect.GetPosition()))
  1067. bell = ViVisualBell(self.ctrl, -1, rect, colour, close_delay, text)
  1068. def StartCmdInput(self, initial_input=None, run_cmd=False):
  1069. """
  1070. Starts a : cmd input for the currently active (or soon to be
  1071. activated) tab.
  1072. """
  1073. # TODO: handle switching between presenters
  1074. selection_range = None
  1075. if self.mode == ViHelper.VISUAL:
  1076. if initial_input is None:
  1077. initial_input = u"'<,'>"
  1078. else:
  1079. initial_input = "{0}{1}".format(initial_input, self.ctrl.GetSelectedText())
  1080. selection_range = self.ctrl.vi._GetSelectionRange()
  1081. self.ctrl.presenter.setTabTitleColour("RED")
  1082. self.input_window.StartCmd(self.ctrl, self.input_cmd_history,
  1083. initial_input, selection_range=selection_range,
  1084. run_cmd=run_cmd)
  1085. def RepeatLastSubCmd(self, ignore_flags):
  1086. self.input_window.cmd_parser.RepeatSubCmd(ignore_flags)
  1087. def EndViInput(self):
  1088. """
  1089. Called when input dialog is closed
  1090. """
  1091. self.ctrl.presenter.setTabTitleColour("BLACK")
  1092. self.ctrl.presenter.getMainControl().windowLayouter.collapseWindow("vi input")
  1093. def GotoSelectionStart(self):
  1094. """
  1095. Goto start of selection
  1096. """
  1097. #raise NotImplementedError, "To be overridden by derived class"
  1098. def GotoSelectionEnd(self):
  1099. """
  1100. Goto end of selection
  1101. """
  1102. #raise NotImplementedError, "To be overridden by derived class"
  1103. # I will move this to wxHelper later (MB)
  1104. try:
  1105. class wxPopupOrFrame(wx.PopupWindow):
  1106. def __init__(self, parent, id=-1, style=None):
  1107. wx.PopupWindow.__init__(self, parent)
  1108. except AttributeError:
  1109. class wxPopupOrFrame(wx.Frame):
  1110. def __init__(self, parent, id=-1,
  1111. style=wx.NO_BORDER|wx.FRAME_NO_TASKBAR|wx.FRAME_FLOAT_ON_PARENT):
  1112. wx.Frame.__init__(self, parent, id, style=style)
  1113. # NOTE: is popup window available on macs yet?
  1114. class ViVisualBell(wxPopupOrFrame):
  1115. """
  1116. Popupwindow designed to cover the status bar.
  1117. Its intention is to give visual feedback that a command has
  1118. been received in cases where no other visual change is observed
  1119. It can also be used to display error messages (set close_delay to -1 and
  1120. the popup will remain open until the next keyevent).
  1121. """
  1122. COLOURS = {
  1123. "RED" : wx.Colour(255, 0, 0),
  1124. "GREEN" : wx.Colour(0, 255, 0),
  1125. "YELLOW" : wx.Colour(255, 255, 0),
  1126. "BLUE" : wx.Colour(0, 0, 255),
  1127. "WHITE" : wx.Colour(0, 0, 0),
  1128. }
  1129. def __init__(self, parent, id, rect, colour="RED", close_delay=100,
  1130. text=""):
  1131. wxPopupOrFrame.__init__(self, parent)
  1132. self.SetPosition(rect.GetPosition())
  1133. self.SetSize(rect.GetSize())
  1134. self.SetBackgroundColour(ViVisualBell.COLOURS[colour])
  1135. self.Show()
  1136. if text:
  1137. self.text = wx.TextCtrl(self, -1,
  1138. text, style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
  1139. self.text.SetBackgroundColour(ViVisualBell.COLOURS[colour])
  1140. self.text.SetForegroundColour(ViVisualBell.COLOURS["WHITE"])
  1141. sizer = wx.BoxSizer(wx.HORIZONTAL)
  1142. sizer.Add(self.text, 1, wx.ALL | wx.EXPAND, 0)
  1143. self.SetSizer(sizer)
  1144. self.Layout()
  1145. self.Show()
  1146. if close_delay > 0:
  1147. wx.EVT_TIMER(self, GUI_ID.TIMER_VISUAL_BELL_CLOSE,
  1148. self.OnClose)
  1149. self.closeTimer = wx.Timer(self, GUI_ID.TIMER_VISUAL_BELL_CLOSE)
  1150. self.closeTimer.Start(close_delay, True)
  1151. else:
  1152. wx.EVT_KEY_DOWN(self.text, self.OnClose)
  1153. wx.EVT_KILL_FOCUS(self.text, self.OnClose)
  1154. self.SetFocus()
  1155. def OnClose(self, evt):
  1156. #self.timer.Stop()
  1157. self.Destroy()
  1158. class ViHintDialog(wx.Frame):
  1159. COLOR_YELLOW = wx.Colour(255, 255, 0);
  1160. COLOR_GREEN = wx.Colour(0, 255, 0);
  1161. COLOR_DARK_GREEN = wx.Colour(0, 100, 0);
  1162. COLOR_RED = wx.Colour(255, 0, 0);
  1163. def __init__(self, parent, id, viCtrl, rect, font, \
  1164. mainControl, tabMode=0, primary_link=None):
  1165. # Frame title is invisible but is helpful for workarounds with
  1166. # third-party tools
  1167. wx.Frame.__init__(self, parent, id, u"WikidPad Hints",
  1168. rect.GetPosition(), rect.GetSize(),
  1169. wx.NO_BORDER | wx.FRAME_FLOAT_ON_PARENT)
  1170. self.tabMode = tabMode
  1171. self.parent = parent
  1172. self.primary_link = primary_link
  1173. self.viCtrl = viCtrl
  1174. self.mainControl = mainControl
  1175. self.tfInput = wx.TextCtrl(self, GUI_ID.INC_SEARCH_TEXT_FIELD,
  1176. _(u"Follow Hint:"), style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
  1177. self.tfInput.SetFont(font)
  1178. # Use a different colour if links are being opened in a different tab
  1179. if tabMode == 0:
  1180. self.colour = ViHintDialog.COLOR_GREEN
  1181. else:
  1182. self.colour = ViHintDialog.COLOR_DARK_GREEN
  1183. self.tfInput.SetBackgroundColour(self.colour)
  1184. mainsizer = wx.BoxSizer(wx.HORIZONTAL)
  1185. mainsizer.Add(self.tfInput, 1, wx.ALL | wx.EXPAND, 0)
  1186. self.SetSizer(mainsizer)
  1187. self.Layout()
  1188. self.tfInput.SelectAll() #added for Mac compatibility
  1189. self.tfInput.SetFocus()
  1190. config = self.mainControl.getConfig()
  1191. # Just use the same delays as incSearch
  1192. self.closeDelay = 1000 * config.getint("main", "incSearch_autoOffDelay",
  1193. 0) # Milliseconds to close or 0 to deactivate
  1194. wx.EVT_TEXT(self, GUI_ID.INC_SEARCH_TEXT_FIELD, self.OnText)
  1195. wx.EVT_KEY_DOWN(self.tfInput, self.OnKeyDownInput)
  1196. wx.EVT_KILL_FOCUS(self.tfInput, self.OnKillFocus)
  1197. wx.EVT_TIMER(self, GUI_ID.TIMER_INC_SEARCH_CLOSE,
  1198. self.OnTimerIncSearchClose)
  1199. wx.EVT_MOUSE_EVENTS(self.tfInput, self.OnMouseAnyInput)
  1200. if self.closeDelay:
  1201. self.closeTimer = wx.Timer(self, GUI_ID.TIMER_INC_SEARCH_CLOSE)
  1202. self.closeTimer.Start(self.closeDelay, True)
  1203. #def Close(self):
  1204. # wx.Frame.Close(self)
  1205. # self.ctrl.SetFocus()
  1206. def OnKillFocus(self, evt):
  1207. self.viCtrl.forgetFollowHint()
  1208. self.Close()
  1209. def OnText(self, evt):
  1210. self.viCtrl.searchStr = self.tfInput.GetValue()
  1211. link_number, link = self.viCtrl.executeFollowHint(self.tfInput.GetValue())
  1212. if link_number < 1:
  1213. # Nothing found
  1214. self.tfInput.SetBackgroundColour(ViHintDialog.COLOR_RED)
  1215. elif link_number == 1:
  1216. # Single link found
  1217. # launch it and finish
  1218. self.tfInput.SetBackgroundColour(self.colour)
  1219. self.parent._activateLink(link, tabMode=self.tabMode)
  1220. self.Close()
  1221. else:
  1222. # Multiple links found
  1223. self.tfInput.SetBackgroundColour(self.colour)
  1224. self.primary_link = link
  1225. def OnMouseAnyInput(self, evt):
  1226. # if evt.Button(wx.MOUSE_BTN_ANY) and self.closeDelay:
  1227. # Workaround for name clash in wx.MouseEvent.Button:
  1228. if wx._core_.MouseEvent_Button(evt, wx.MOUSE_BTN_ANY) and self.closeDelay:
  1229. # If a mouse button was pressed/released, restart timer
  1230. self.closeTimer.Start(self.closeDelay, True)
  1231. evt.Skip()
  1232. def OnKeyDownInput(self, evt):
  1233. if self.closeDelay:
  1234. self.closeTimer.Start(self.closeDelay, True)
  1235. key = evt.GetKeyCode()
  1236. accP = getAccelPairFromKeyDown(evt)
  1237. matchesAccelPair = self.mainControl.keyBindings.matchesAccelPair
  1238. searchString = self.tfInput.GetValue()
  1239. foundPos = -2
  1240. if accP in ((wx.ACCEL_NORMAL, wx.WXK_NUMPAD_ENTER),
  1241. (wx.ACCEL_NORMAL, wx.WXK_RETURN)):
  1242. # Return pressed
  1243. self.viCtrl.endFollowHint()
  1244. if self.primary_link is not None:
  1245. self.parent._activateLink(self.primary_link, tabMode=self.tabMode)
  1246. self.Close()
  1247. elif accP == (wx.ACCEL_NORMAL, wx.WXK_ESCAPE):
  1248. # Esc -> Abort inc. search, go back to start
  1249. self.viCtrl.resetFollowHint()
  1250. self.Close()
  1251. elif matchesAccelPair("ContinueSearch", accP):
  1252. foundPos = self.viCtrl.executeFollowHint(searchString)
  1253. # do the next search on another ctrl-f
  1254. elif matchesAccelPair("StartFollowHint", accP):
  1255. foundPos = self.viCtrl.executeFollowHint(searchString)
  1256. elif accP in ((wx.ACCEL_NORMAL, wx.WXK_DOWN),
  1257. (wx.ACCEL_NORMAL, wx.WXK_PAGEDOWN),
  1258. (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_DOWN),
  1259. (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_PAGEDOWN),
  1260. (wx.ACCEL_NORMAL, wx.WXK_NEXT)):
  1261. foundPos = self.viCtrl.executeFollowHint(searchString)
  1262. elif matchesAccelPair("ActivateLink", accP):
  1263. # ActivateLink is normally Ctrl-L
  1264. self.viCtrl.endFollowHint()
  1265. self.Close()
  1266. self.viCtrl.OnKeyDown(evt)
  1267. elif matchesAccelPair("ActivateLinkNewTab", accP):
  1268. # ActivateLinkNewTab is normally Ctrl-Alt-L
  1269. self.viCtrl.endFollowHint()
  1270. self.Close()
  1271. self.viCtrl.OnKeyDown(evt)
  1272. elif matchesAccelPair("ActivateLink2", accP):
  1273. # ActivateLink2 is normally Ctrl-Return
  1274. self.viCtrl.endFollowHint()
  1275. self.Close()
  1276. self.viCtrl.OnKeyDown(evt)
  1277. elif matchesAccelPair("ActivateLinkBackground", accP):
  1278. # ActivateLinkNewTab is normally Ctrl-Alt-L
  1279. self.viCtrl.endFollowHint()
  1280. self.Close()
  1281. self.viCtrl.OnKeyDown(evt)
  1282. # handle the other keys
  1283. else:
  1284. evt.Skip()
  1285. if foundPos == False:
  1286. # Nothing found
  1287. self.tfInput.SetBackgroundColour(ViHintDialog.COLOR_YELLOW)
  1288. else:
  1289. # Found
  1290. self.tfInput.SetBackgroundColour(self.colour)
  1291. # Else don't change
  1292. if SystemInfo.isOSX():
  1293. # Fix focus handling after close
  1294. def Close(self):
  1295. wx.Frame.Close(self)
  1296. wx.CallAfter(self.viCtrl.SetFocus)
  1297. def OnTimerIncSearchClose(self, evt):
  1298. self.viCtrl.endFollowHint() # TODO forgetFollowHint() instead?
  1299. self.Close()
  1300. class ViRegister():
  1301. def __init__(self, ctrl):
  1302. self.ctrl = ctrl
  1303. self.select_register = False
  1304. self.current_reg = None
  1305. # Uppercase registers do not exist (although they can be selected)
  1306. self.alpha = "abcdefghijklmnopqrstuvwxyz"
  1307. self.special = '"+-.'
  1308. self.registers = {}
  1309. # Create the named (alpha) registers
  1310. for i in self.alpha:
  1311. self.registers[i] = None
  1312. # Create the special registers
  1313. for i in self.special:
  1314. self.registers[i] = None
  1315. # Create numbered registers
  1316. for i in range(0, 10):
  1317. self.registers[str(i)] = None
  1318. self.registers['"'] = u""
  1319. def SelectRegister(self, key_code):
  1320. if key_code is None:
  1321. self.current_reg = None
  1322. return
  1323. if type(key_code) == int:
  1324. reg = unichr(key_code)
  1325. else:
  1326. reg = key_code
  1327. if reg in self.registers:
  1328. self.current_reg = reg
  1329. return True
  1330. # Uppercase alpha regs
  1331. elif reg.lower() in self.registers:
  1332. self.current_reg = reg
  1333. return True
  1334. else:
  1335. self.current_reg = None
  1336. return False
  1337. def GetSelectedRegister(self):
  1338. return self.current_reg
  1339. def SetCurrentRegister(self, value, yank_register=False):
  1340. # Whenever a register is set the unnamed (") register is also.
  1341. self.registers['"'] = value
  1342. if self.current_reg is None:
  1343. if yank_register:
  1344. self.SetRegisterZero(value)
  1345. else:
  1346. # TODO: decide if following vim here is sensible.
  1347. # If the deleted text spans multiple lines it is put in the
  1348. # numbered register
  1349. if "\n" in value:
  1350. self.SetNumberedRegister(value)
  1351. # Otherwise the small delete register is used.
  1352. else:
  1353. self.SetRegister("-", value)
  1354. # Lowercase letters replace the contents of the selected register
  1355. elif self.current_reg in self.alpha:
  1356. self.registers[self.current_reg] = value
  1357. # Uppercase letters append
  1358. elif self.current_reg.lower() in self.alpha:
  1359. current_reg = self.current_reg.lower()
  1360. self.registers[current_reg] = self.registers[current_reg] + value
  1361. # The "+ reg copies selected text to the clipboard
  1362. elif self.current_reg == "+":
  1363. self.ctrl.Copy()
  1364. self.current_reg = None
  1365. def SetRegister(self, register, value):
  1366. self.registers[register] = value
  1367. def SetRegisterZero(self, value):
  1368. """
  1369. Helper to set the "0 register.
  1370. """
  1371. self.registers["0"] = value
  1372. def SetNumberedRegister(self, value):
  1373. """
  1374. Puts the selection into the "1 register and shifts
  1375. the other registers up by one
  1376. e.g.
  1377. "1 -> "2, "2 -> "3, etc...
  1378. Register "9 is lost in the process
  1379. """
  1380. for i in range (2, 10)[::-1]:
  1381. self.registers[str(i)] = self.registers[str(i-1)]
  1382. self.registers["1"] = value
  1383. def GetRegister(self, reg):
  1384. if reg in self.registers:
  1385. return self.registers[reg]
  1386. def GetCurrentRegister(self):
  1387. if self.current_reg == "+":
  1388. text = getTextFromClipboard()
  1389. elif self.current_reg is None:
  1390. text = self.registers['"']
  1391. # If the register is alpha we need to check if it is uppercase and
  1392. # convert it if it is.
  1393. elif self.current_reg in string.ascii_letters:
  1394. text = self.registers[self.current_reg.lower()]
  1395. elif self.current_reg in self.registers:
  1396. text = self.registers[self.current_reg]
  1397. else: # should never occur
  1398. return
  1399. self.current_reg = None
  1400. return text
  1401. class CmdParser():
  1402. def __init__(self, ctrl, viInputListBox, selection_range=None, ):
  1403. self.ctrl = ctrl
  1404. self.viInputListBox = viInputListBox
  1405. self.selection_range = selection_range
  1406. self.last_sub_cmd = None
  1407. self.cmds = {
  1408. "&" : (self.Pass, self.RepeatSubCmd, "Repeat last command"),
  1409. "&&" : (self.Pass, self.RepeatSubCmdWithFlags,
  1410. "Repeat last command with flags"),
  1411. "reloadplugins" : (self.Pass, self.ReloadPlugins,
  1412. "Reload plugins (use sparingly)"),
  1413. "parents" : (self.GetParentPages, self.OpenWikiPageCurrentTab,
  1414. "Goto parent page in current page"),
  1415. "tabparents" : (self.GetParentPages, self.OpenWikiPageNewTab,
  1416. "Goto parent page in new tab"),
  1417. "bgparents" : (self.GetParentPages, self.OpenWikiPageBackgroundTab,
  1418. "Goto parent page in background tab"),
  1419. "w" : (self.Pass, self.SaveCurrentPage,
  1420. "Write (save) current page"),
  1421. "write" : (self.Pass, self.SaveCurrentPage,
  1422. "Write (save) current page"),
  1423. "open" : (self.GetWikiPages, self.OpenWikiPageCurrentTab,
  1424. "Open page in current tab"),
  1425. "edit" : (self.GetWikiPages, self.OpenWikiPageCurrentTab,
  1426. "Open page in current tab"),
  1427. "tabopen" : (self.GetWikiPages, self.OpenWikiPageNewTab,
  1428. "Open pagge in new tab"),
  1429. "bgtabopen" : (self.GetWikiPages, self.OpenWikiPageBackgroundTab,
  1430. "Open page in new background tab"),
  1431. "winopen" : (self.GetWikiPages, self.OpenWikiPageNewWindow,
  1432. "Open page in new window"),
  1433. "tab" : (self.GetTabs, self.GotoTab, "Goto tab"),
  1434. "buffer" : (self.GetTabs, self.GotoTab, "Goto tab"),
  1435. "split" : (self.GetWikiPages, self.SplitTab, "Split tab"),
  1436. "google" : (self.GetWikiPagesOrSearch, self.OpenPageInGoogle,
  1437. "Search google for ..."),
  1438. "wikipedia" : (self.GetWikiPagesOrSearch, self.OpenPageInWikipedia,
  1439. "Search wikipedia for ..."),
  1440. # TODO: rewrite with vi like confirmation
  1441. "deletepage" : (self.GetDefinedWikiPages,
  1442. self.ctrl.presenter.getMainControl().showWikiWordDeleteDialog,
  1443. "Delete page"),
  1444. "delpage" : (self.GetDefinedWikiPages,
  1445. self.ctrl.presenter.getMainControl().showWikiWordDeleteDialog,
  1446. "Delete page"),
  1447. "renamepage" : (self.GetDefinedWikiPages,
  1448. self.ctrl.presenter.getMainControl().showWikiWordRenameDialog,
  1449. "Rename page"),
  1450. # Currently bdelete and bwipeout are currently synonymous
  1451. "quit" : (self.GetTabs, self.CloseTab, "Close tab"),
  1452. "bdelete" : (self.GetTabs, self.CloseTab, "Close tab"),
  1453. "bwipeout" : (self.GetTabs, self.CloseTab, "Close tab"),
  1454. "quitall" : (self.Pass, self.CloseWiki, "Close wiki"),
  1455. "tabonly" : (self.GetTabs, self.CloseOtherTabs,
  1456. "Close all other tabs"),
  1457. "exit" : (self.Pass, self.CloseWiki,
  1458. "Close all other tabs"),
  1459. "reloadplugins" : (self.Pass, self.ReloadPlugins,
  1460. "Reload plugins (use sparingly)"),
  1461. # Stuff below is for debugging
  1462. "start_pdb_debug" : (self.Pass, self.StartPDBDebug,
  1463. "Begin a PDB debug session"),
  1464. "inspect" : (self.Pass, self.StartInspection,
  1465. "Launch the wxPython inspection tool"),
  1466. "list-keybindings" : (self.Pass, self.ShowKeybindings,
  1467. "Displays a list of the currently \
  1468. loaded keybindings"),
  1469. }
  1470. if self.ctrl.presenter.getWikiDocument().getDbtype() == \
  1471. u"original_sqlite":
  1472. self.cmds["vim"] = (self.GetWikiPages, self.EditWithVim,
  1473. "Edit page with vim")
  1474. self.cmds["gvim"] = (self.GetWikiPages, self.EditWithGvim,
  1475. "Edit page with gvim")
  1476. # Attempt to load webkit specific commands
  1477. try:
  1478. if self.ctrl.ViewSource:
  1479. self.cmds["viewsource"] = (self.Pass, self.ctrl.ViewSource,
  1480. "View current pages HTML source")
  1481. except AttributeError:
  1482. pass
  1483. # marks? search patterns?
  1484. self.cmd_range_starters = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, u".", u"$", u"%", u",")
  1485. self.range_cmds = {
  1486. u"s" : self.SearchAndReplace,
  1487. u"sort" : self.Sort
  1488. }
  1489. # TODO: :s repeats last command
  1490. self.range_regex = u"(\d+|%|\.|\$|'.)?,?(\d+|%|\.|\$|'.)({0})(.*$)".format(
  1491. "|".join(self.range_cmds.keys()))
  1492. def StartPDBDebug(self, args=None):
  1493. import pdb; pdb.set_trace()
  1494. def StartInspection(self, args=None):
  1495. import wx.lib.inspection
  1496. wx.lib.inspection.InspectionTool().Show()
  1497. def ShowKeybindings(self, args=None):
  1498. # A quick and dirty way to view all currently registered vi
  1499. # keybindings and the functions they call
  1500. keys = self.ctrl.vi.keys
  1501. text = []
  1502. for mode in keys:
  1503. text.append("")
  1504. text.append("MODE: {0}".format(mode))
  1505. for binding in keys[mode]:
  1506. chain = " ".join([self.ctrl.vi.GetCharFromCode(i)
  1507. for i in binding])
  1508. text.append("{0} : {1}".format(chain,
  1509. keys[mode][binding][1][0].__name__))
  1510. text = "\n".join(text)
  1511. dlg = wx.lib.dialogs.ScrolledMessageDialog(
  1512. self.ctrl.presenter.getMainControl(), text, "Keybindings")
  1513. if dlg.ShowModal():
  1514. pass
  1515. dlg.Destroy()
  1516. def SearchAndReplace(self, pattern, ignore_flags=False):
  1517. """
  1518. Function to mimic the :s behavior of vi(m)
  1519. It is designed to be mostly compatable but it currently (and for at
  1520. least the forseable future) has a number of important differences from
  1521. the native implementation.
  1522. TODO: implement flags
  1523. """
  1524. # As in vim the expression delimeter can be one of a number of
  1525. # characters
  1526. delims = "/;$|^%,"
  1527. if pattern[0] in delims:
  1528. delim = u"\{0}".format(pattern[0])
  1529. else:
  1530. self.ctrl.vi.viError(
  1531. _("Error: {0} is not a valid delimiter".format(
  1532. pattern[0])))
  1533. return 2
  1534. # NOTE: Vim does not require all arguments to be present
  1535. # Current implementation here does
  1536. try:
  1537. search, replace, flags = re.split(r"(?<!\\){0}".format(delim), pattern)[1:]
  1538. except ValueError:
  1539. self.ctrl.vi.viError(_("Incorrect :sub cmd (unable to split patterns using '{0}')".format(delim)))
  1540. return 2
  1541. # First check if there are any pattern modifiers
  1542. #
  1543. # Currently we only check for \V
  1544. # if it exists we escape the search pattern (so it acts as a literal string)
  1545. if search.startswith("\V"):
  1546. search = re.escape(search[2:])
  1547. count = 1
  1548. # TODO: Flags
  1549. # & : flags from previous sub
  1550. # c : confirm (see :s_flags
  1551. # e : no error
  1552. # i : ignore case
  1553. # I : don't ignore case
  1554. # n : report match number (no substitution
  1555. # p : print the line containing the last substitute
  1556. # # : like [p] and prepend line number
  1557. # l : like [p] but print the text like [:list]
  1558. if u"g" in flags:
  1559. count = 0
  1560. re_flags = re.M
  1561. # Hack for replaing newline chars
  1562. search = re.sub(r"(?<!\\)\\n", "\n", search)
  1563. try:
  1564. search_regex = re.compile(search, flags=re_flags)
  1565. except re.error as e:
  1566. self.ctrl.vi.viError(_("re compile error: {0}".format(e)))
  1567. return False
  1568. self.ctrl.vi.AddJumpPosition()
  1569. text_to_sub = self.ctrl.GetSelectedText()
  1570. # If our regex contains a newline character we need to search over
  1571. # all text
  1572. if "\n" not in search:
  1573. new_text = []
  1574. # We do the subs on a per line basis as by default vim only
  1575. # replaces the first occurance
  1576. for i in text_to_sub.split("\n"):
  1577. try:
  1578. new_text.append(search_regex.sub(replace, i, count))
  1579. except re.error:
  1580. return False
  1581. self.ctrl.ReplaceSelection("\n".join(new_text))
  1582. else:
  1583. try:
  1584. self.ctrl.ReplaceSelection(search_regex.sub(replace, text_to_sub))
  1585. except re.error as e:
  1586. self.ctrl.vi.viError(_("Error '{0}')".format(e)))
  1587. return False
  1588. self.last_sub_cmd = pattern
  1589. return True
  1590. def Sort(self, sort_type=None):
  1591. eol_char = self.ctrl.GetEOLChar()
  1592. sorted_text = eol_char.join(
  1593. sorted(self.ctrl.GetSelectedText().split(eol_char)))
  1594. self.ctrl.ReplaceSelection(sorted_text)
  1595. def RepeatSubCmdWithFlags(self):
  1596. self.RepeatSubCmd(ignore_flags=False)
  1597. def RepeatSubCmd(self, ignore_flags=True):
  1598. cmd = self.last_sub_cmd
  1599. if cmd is not None:
  1600. if ignore_flags:
  1601. # TODO: strip flags
  1602. pass
  1603. return self.ExecuteRangeCmd(cmd)
  1604. else:
  1605. return False
  1606. def Pass(self, junk=None):
  1607. return None, None, None
  1608. def CheckForRangeCmd(self, text_input):
  1609. # TODO: improve cmd checking
  1610. if re.match(self.range_regex, text_input):
  1611. return True
  1612. elif re.match("(\d+|%|\.|\$)?,?(\d+|%|\.|\$)({0})".format(
  1613. "|".join(self.range_cmds.keys())), text_input):
  1614. return True
  1615. elif re.match("(\d+|%|\.|\$)?,?(\d+|%|\.|\$)", text_input):
  1616. return True
  1617. elif re.match("(\d+|%|\.|\$)?,", text_input):
  1618. return True
  1619. elif re.match("(\d+|%|\.|\$)", text_input):
  1620. return True
  1621. else:
  1622. return False
  1623. def ExecuteRangeCmd(self, text_input):
  1624. try:
  1625. start_range, end_range, cmd, args = re.match(self.range_regex, text_input).groups()
  1626. except AttributeError as e:
  1627. self.ctrl.vi.viError(_("Error '{0}')".format(e)))
  1628. try:
  1629. if start_range is not None:
  1630. start_range = int(start_range) - 1
  1631. except ValueError:
  1632. pass
  1633. try:
  1634. if end_range is not None:
  1635. end_range = int(end_range) - 1
  1636. except ValueError:
  1637. pass
  1638. # Ranges are by default lines
  1639. if start_range == u"%" or end_range == u"%":
  1640. start_range = 0
  1641. end_range = self.ctrl.GetLineCount()
  1642. elif start_range in (None, u""):
  1643. start_range = end_range
  1644. # Convert line ranges to char positions
  1645. if type(start_range) == int:
  1646. start_range = self.ctrl.vi.GetLineStartPos(start_range)
  1647. elif start_range == u".":
  1648. start_range = self.ctrl.vi.GetLineStartPos(
  1649. self.ctrl.GetCurrentLine())
  1650. elif len(start_range) == 2 and start_range.startswith(u"'"):
  1651. if start_range[1] == u"<":
  1652. start_range = self.selection_range[0]
  1653. else:
  1654. page = self.ctrl.presenter.getWikiWord()
  1655. # TODO: create helper to prevent mark going past page end?
  1656. try:
  1657. start_range = self.ctrl.vi.marks[page][ord(start_range[1])]
  1658. except KeyError:
  1659. return False # mark not set
  1660. else:
  1661. return False # invalid start_range input
  1662. if type(end_range) == int:
  1663. end_range = self.ctrl.GetLineEndPosition(end_range) + 1
  1664. elif end_range == u"$":
  1665. end_range = self.ctrl.GetLineEndPosition(
  1666. self.ctrl.GetLineCount()) + 1
  1667. elif end_range == u".":
  1668. end_range = self.ctrl.GetLineEndPosition(
  1669. self.ctrl.GetCurrentLine()) + 1
  1670. elif len(end_range) == 2 and end_range.startswith(u"'"):
  1671. if end_range[1] == u">":
  1672. end_range = self.selection_range[1]
  1673. else:
  1674. page = self.ctrl.presenter.getWikiWord()
  1675. try:
  1676. end_range = self.ctrl.vi.marks[page][ord(end_range[1])]
  1677. except KeyError:
  1678. return False # mark not set
  1679. else:
  1680. return False # invalid end_range input
  1681. self.ctrl.SetSelection(start_range, end_range)
  1682. self.ctrl.vi.BeginUndo()
  1683. rel = self.range_cmds[cmd](args)
  1684. self.ctrl.vi.EndUndo()
  1685. self.ClearInput()
  1686. # If the cmd is :s save it so it can be repeated
  1687. if rel and cmd == "s":
  1688. self.last_sub_cmd = text_input
  1689. return rel
  1690. def ParseCmd(self, text_input):
  1691. if self.CheckForRangeCmd(text_input):
  1692. return True, False
  1693. split_cmd = text_input.split(" ")
  1694. args = False
  1695. if len(split_cmd) > 1:
  1696. if split_cmd[1] != u"":
  1697. args = True
  1698. else:
  1699. args = False
  1700. cmd_list = []
  1701. for cmd in self.cmds:
  1702. if cmd.startswith(split_cmd[0]):
  1703. cmd_list.append(cmd)
  1704. return cmd_list, args
  1705. def ParseCmdWithArgs(self, text_input):
  1706. if self.CheckForRangeCmd(text_input):
  1707. return []
  1708. split_cmd = text_input.split(u" ")
  1709. arg = None
  1710. action = split_cmd[0]
  1711. if len(split_cmd) > 1:
  1712. arg = u" ".join(split_cmd[1:])
  1713. cmd_list = []
  1714. list_box = []
  1715. for cmd in self.cmds:
  1716. if cmd.startswith(action):
  1717. if arg is not None or text_input.endswith(u" "):
  1718. return self.cmds[cmd][0](arg)
  1719. else:
  1720. cmd_list.append(cmd)
  1721. list_box.append(formatListBox(cmd, self.cmds[cmd][2]))
  1722. return cmd_list, list_box, cmd_list
  1723. def RunCmd(self, text_input, viInputListBox_selection):
  1724. """
  1725. Handles the executing of a : command
  1726. """
  1727. if self.CheckForRangeCmd(text_input):
  1728. return self.ExecuteRangeCmd(text_input)
  1729. if viInputListBox_selection > -1 and self.viInputListBox.HasData():
  1730. arg = (0, self.viInputListBox.GetData(viInputListBox_selection))
  1731. else:
  1732. arg = None
  1733. split_cmd = [i for i in text_input.split(" ") if len(i) > 0]
  1734. action = split_cmd[0]
  1735. if arg is None and len(split_cmd) > 1: #and len(split_cmd[1]) > 0:
  1736. arg = (1, u" ".join(split_cmd[1:]))
  1737. # If a full cmd name has been entered use it
  1738. if action in self.cmds:
  1739. return self.cmds[action][1](arg)
  1740. # Otherwise use the first one found in the cmd list
  1741. # TODO: sort order
  1742. for cmd in self.cmds:
  1743. if cmd.startswith(action):
  1744. return self.cmds[cmd][1](arg)
  1745. self.ClearInput()
  1746. def ClearInput(self):
  1747. self.cmd_list = []
  1748. ##############################################
  1749. ### Helpers
  1750. ##############################################
  1751. def GetTabFromArgs(self, args, default_to_current=True):
  1752. """
  1753. Helper to get a tab (buffer) from an argument
  1754. @param args: The tab to select
  1755. @param default_to_current: Default - True. If True and no arg
  1756. current tab will be return, else None.
  1757. @return: The presenter belonging to the current tab.
  1758. """
  1759. if args is None:
  1760. if default_to_current:
  1761. return self.ctrl.presenter
  1762. else:
  1763. return None
  1764. arg_type, arg = args
  1765. if arg_type == 0:
  1766. return arg
  1767. elif arg_type == 1:
  1768. if arg.strip() == u"":
  1769. return self.ctrl.presenter
  1770. tabs = self.GetTabs(arg)
  1771. if not tabs[0]:
  1772. return None
  1773. return tabs[0][0]
  1774. def GetWikiPageFromArgs(self, args, default_to_current_page=False):
  1775. """
  1776. Helper to get wikipage from string / list data
  1777. @return: wikipage (formatted in link format)
  1778. """
  1779. if args is None:
  1780. if default_to_current_page:
  1781. current_page = self.ctrl.presenter.getMainControl().\
  1782. getCurrentWikiWord()
  1783. return ((current_page, 0, current_page, -1, -1),)
  1784. else:
  1785. return False
  1786. arg_type, arg = args
  1787. # If args is a string just use it as a wikiword directly
  1788. if arg_type == 1:
  1789. if arg.strip() == u"":
  1790. return False
  1791. # Do we want to allow whitespaced wikiwords?
  1792. arg = arg.strip()
  1793. # We create the required link format here
  1794. return ((arg, 0, arg, -1, -1),)
  1795. # If arg type is data the format should be correct already
  1796. elif arg_type == 0:
  1797. return args
  1798. def OpenWikiPage(self, args, tab_mode):
  1799. """
  1800. Helper to open wikipage
  1801. Parses args through GetWikiPagesFromArgs()
  1802. """
  1803. #if not type(link_info) == tuple:
  1804. # value = ((link_info, 0, link_info, -1, -1),)
  1805. #else:
  1806. # value = (link_info,)
  1807. # TODO: think about using more of the link data
  1808. value = self.GetWikiPageFromArgs(args)
  1809. wikiword = value[0][2]
  1810. strip_whitespace_from_wikiword = True
  1811. if strip_whitespace_from_wikiword:
  1812. wikiword = wikiword.strip()
  1813. return self.ctrl.presenter.getMainControl().activatePageByUnifiedName(
  1814. u"wikipage/" + wikiword, tabMode=tab_mode,
  1815. firstcharpos=value[0][3], charlength=value[0][4])
  1816. ##############################################
  1817. def ReloadPlugins(self, args=None):
  1818. """
  1819. Reload plugins globally using reloadMenuPlugins() and then
  1820. on a per tab basis. i.e. applies changes to all open tabs -
  1821. reloadMenuPlugins() does not reload vi plugins.
  1822. NOTE: Excessive use of this function will lead to a noticable
  1823. increase in memory usage. i.e. should only be used for
  1824. dev purposes.
  1825. """
  1826. self.ctrl.presenter.getMainControl().reloadMenuPlugins()
  1827. # NOTE: should probably unify names
  1828. for scName, name in (("textedit", "editor"), ("preview", "preview")):
  1829. for tab in self.ctrl.presenter.getMainControl().getMainAreaPanel().getPresenters():
  1830. # Try and reload key bindings for each presenter on each tab
  1831. # (if they exist)
  1832. try:
  1833. tab.getSubControl(scName).vi.ReloadPlugins(name)
  1834. except AttributeError:
  1835. pass
  1836. def EditWithVim(self, args=None):
  1837. """
  1838. Edit "page" in vim (using a subprocess)
  1839. If no pages specified current page is used
  1840. Only works with original_sqlite backend
  1841. """
  1842. page = self.GetWikiPageFromArgs(args,
  1843. default_to_current_page=True)[0][0]
  1844. mainCtrl = self.ctrl.presenter.getMainControl()
  1845. if page is None:
  1846. page = self.ctrl.presenter.getWikiWord()
  1847. file_path = self.ctrl.presenter.getMainControl().getWikiData().\
  1848. getWikiWordFileName(page)
  1849. p = subprocess.Popen([self.ctrl.vi.settings['vim_path'], file_path],
  1850. shell=True)
  1851. return True
  1852. def EditWithGvim(self, args=None):
  1853. """
  1854. Same as above (EditWithVim) but using gvim instead
  1855. """
  1856. page = self.GetWikiPageFromArgs(args,
  1857. default_to_current_page=True)[0][0]
  1858. mainCtrl = self.ctrl.presenter.getMainControl()
  1859. if page is None:
  1860. page = self.ctrl.presenter.getWikiWord()
  1861. file_path = self.ctrl.presenter.getMainControl().getWikiData().\
  1862. getWikiWordFileName(page)
  1863. p = subprocess.Popen([self.ctrl.vi.settings['gvim_path'], file_path])
  1864. return True
  1865. def OpenPageInGoogle(self, args=None):
  1866. """
  1867. Searches google for "args" (default = current page)
  1868. Suggested args: Wikipages
  1869. """
  1870. page = self.GetWikiPageFromArgs(args,
  1871. default_to_current_page=True)[0][0]
  1872. mainCtrl = self.ctrl.presenter.getMainControl()
  1873. if page is None:
  1874. page = self.ctrl.presenter.getWikiWord()
  1875. mainCtrl.launchUrl("https://www.google.com/search?q={0}".format(
  1876. urlQuote(page)))
  1877. return True
  1878. def OpenPageInWikipedia(self, args=None):
  1879. page = self.GetWikiPageFromArgs(args,
  1880. default_to_current_page=True)[0][0]
  1881. mainCtrl = self.ctrl.presenter.getMainControl()
  1882. if page is None:
  1883. page = self.ctrl.presenter.getWikiWord()
  1884. mainCtrl.launchUrl("https://www.wikipedia.org/wiki/{0}".format(
  1885. urlQuote(page)))
  1886. return True
  1887. def CloseOtherTabs(self, args=None):
  1888. """
  1889. Closes all tabs except for one specified by "args"
  1890. @return True if successful, False if not (tab does not exist)
  1891. """
  1892. if self.GotoTab(args):
  1893. self.ctrl.presenter.getMainControl().getMainAreaPanel()\
  1894. ._closeAllButCurrentTab()
  1895. return True
  1896. return False
  1897. def SaveCurrentPage(self, args=None):
  1898. """
  1899. Force save of current page
  1900. """
  1901. self.ctrl.presenter.saveCurrentDocPage()
  1902. return True
  1903. def OpenWikiPageCurrentTab(self, args):
  1904. return self.OpenWikiPage(args, 0)
  1905. def OpenWikiPageNewTab(self, args):
  1906. return self.OpenWikiPage(args, 2)
  1907. def OpenWikiPageBackgroundTab(self, args):
  1908. return self.OpenWikiPage(args, 3)
  1909. def OpenWikiPageNewWindow(self, args):
  1910. return self.OpenWikiPage(args, 6)
  1911. def CloseTab(self, args=None):
  1912. """
  1913. Close specified tab
  1914. @args: tab to close
  1915. @return: True if successful, False if not
  1916. """
  1917. #if text_input is None:
  1918. # self.ctrl.vi.CloseCurrentTab()
  1919. # return True
  1920. tab = self.GetTabFromArgs(args)
  1921. if tab is None:
  1922. return False
  1923. wx.CallAfter(self.ctrl.presenter.getMainControl().getMainAreaPanel()\
  1924. .closePresenterTab, tab)
  1925. return True
  1926. def GotoTab(self, args=None):
  1927. """
  1928. Goto specified tab
  1929. """
  1930. tab = self.GetTabFromArgs(args)
  1931. if tab is None:
  1932. return False
  1933. self.ctrl.presenter.getMainControl().getMainAreaPanel()\
  1934. .showPresenter(tab)
  1935. return True
  1936. #return False
  1937. def SplitTab(self, args=None):
  1938. if args is None:
  1939. presenter = self.CloneCurrentTab()
  1940. else:
  1941. presenter = self.OpenWikiPageNewTab(args)
  1942. presenter.makeCurrent()
  1943. mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel()
  1944. page = mainAreaPanel.GetPageIndex(presenter)
  1945. wx.CallAfter(mainAreaPanel.Split, page, wx.RIGHT)
  1946. def CloneCurrentTab(self):
  1947. return self.ctrl.presenter.getMainControl().activatePageByUnifiedName(u"wikipage/" + self.ctrl.presenter.getWikiWord(), tabMode=2)
  1948. def CloseWiki(self, arg=None):
  1949. """
  1950. Close current wiki
  1951. """
  1952. self.ctrl.presenter.getMainControl().exitWiki()
  1953. ###########################################################
  1954. def GetTabs(self, text_input):
  1955. """
  1956. Return a list of currently open tabs (suitable for suggest box)
  1957. @return_type: tuple - (list, list, list)
  1958. @return: (tab instances, tab names, tab names)
  1959. """
  1960. mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel()
  1961. page_count = mainAreaPanel.GetPageCount()
  1962. tabs = []
  1963. tab_names = []
  1964. for i in range(page_count):
  1965. page = mainAreaPanel.GetPage(i)
  1966. wikiword = page.getWikiWord()
  1967. if wikiword.startswith(text_input):
  1968. tab_names.append(wikiword)
  1969. tabs.append(page)
  1970. return tabs, tab_names, tab_names
  1971. def GetDefinedWikiPages(self, search_text):
  1972. if search_text is None or search.text.strip() == u"":
  1973. return None, (_(u"Enter wikiword..."),), None
  1974. results = self.ctrl.presenter.getMainControl().getWikiData().\
  1975. getAllDefinedWikiPageNames()
  1976. self.cmd_list = results
  1977. if search_text.strip() == u"":
  1978. return results
  1979. results = [i for i in self.cmd_list if i.find(search_text) > -1]
  1980. if not results:
  1981. return None, None, None
  1982. return results
  1983. def GetWikiPagesOrSearch(self, search_text):
  1984. if search_text is None or search_text.strip() == u"":
  1985. return None, (_(u"Enter wikiword (or text) to search for..."),), \
  1986. None
  1987. return self.GetWikiPages(search_text)
  1988. def GetWikiPages(self, search_text):
  1989. if search_text is None or \
  1990. len(search_text.strip()) < self.ctrl.vi.settings['min_wikipage_search_len']:
  1991. return None, (_(u"Enter wikiword..."),), None
  1992. results = self.ctrl.presenter.getMainControl().getWikiData().\
  1993. getWikiWordMatchTermsWith(
  1994. search_text, orderBy="word", descend=False)
  1995. # Quick hack to filter repetative alias'
  1996. if self.ctrl.vi.settings["filter_wikipages"]:
  1997. pages = [x[2] for x in results if x[4] > -1 and x[3] == -1]
  1998. l = []
  1999. for x in results:
  2000. # Orginial wikiwords are always displayed
  2001. if x[4] > -1:
  2002. pass
  2003. else:
  2004. alias = x[0].lower()
  2005. wikiword = x[2]
  2006. wikiword_mod = wikiword.lower()
  2007. # Ignore alias if it points to the same page as the
  2008. # the item above it in the list
  2009. if l and l[-1][2] == wikiword:
  2010. continue
  2011. for r in (("'s", ""), ("-", ""), (" ", "")):
  2012. alias = alias.replace(*r)
  2013. wikiword_mod = wikiword_mod.replace(*r)
  2014. # Only hide aliases if the actually page is in the list
  2015. if (alias in wikiword_mod or wikiword_mod in alias) and \
  2016. wikiword in pages:
  2017. continue
  2018. l.append(x)
  2019. results = l
  2020. if not results:
  2021. return None, None, None
  2022. formatted_results = [formatListBox(i[0], i[2]) for i in results]
  2023. # NOTE: it would be possible to open pages at specific positions
  2024. pages = [i[2] for i in results]
  2025. return pages, formatted_results, pages
  2026. def GetParentPages(self, search_text):
  2027. presenter = self.ctrl.presenter
  2028. word = presenter.getWikiWord()
  2029. parents = presenter.getMainControl().getWikiData(). \
  2030. getParentRelationships(word)
  2031. # If no parents give a notification and exit
  2032. if len(parents) == 0:
  2033. self.ctrl.vi.visualBell()
  2034. return None, (_(u"Page has no parents"),), None
  2035. return parents, parents, parents
  2036. class ViInputHistory():
  2037. def __init__(self):
  2038. self.cmd_history = []
  2039. self.cmd_position = -1
  2040. def AddCmd(self, cmd):
  2041. self.cmd_history.append(cmd)
  2042. self.cmd_position = len(self.cmd_history) - 1
  2043. def IncrementCmdPos(self):
  2044. self.cmd_position += 1
  2045. def GoForwardInHistory(self):
  2046. if self.cmd_position < 0:
  2047. return False
  2048. if self.cmd_position + 1 >= len(self.cmd_history):
  2049. return u""
  2050. self.cmd_position = min(len(self.cmd_history)-1, self.cmd_position + 1)
  2051. return self.cmd_history[self.cmd_position]
  2052. def GoBackwardsInHistory(self):
  2053. if self.cmd_position < 0:
  2054. return False
  2055. cmd = self.cmd_history[self.cmd_position]
  2056. self.cmd_position = max(0, self.cmd_position - 1)
  2057. return cmd
  2058. # NOTE: It may make more sense to seperate the search and cmdline components
  2059. # into to seperate classes
  2060. class ViInputDialog(wx.Panel):
  2061. COLOR_YELLOW = wx.Colour(255, 255, 0);
  2062. COLOR_GREEN = wx.Colour(0, 255, 0);
  2063. COLOR_RED = wx.Colour(255, 0, 0);
  2064. COLOR_WHITE = wx.Colour(255, 255, 255);
  2065. def __init__(self, parent, id, mainControl):
  2066. # # Frame title is invisible but is helpful for workarounds with
  2067. # # third-party tools
  2068. # wx.Frame.__init__(self, parent, id, u"ViInputDialog",
  2069. # rect.GetPosition(), rect.GetSize(),
  2070. # wx.NO_BORDER | wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR)
  2071. d = wx.PrePanel()
  2072. self.PostCreate(d)
  2073. self.mainControl = mainControl
  2074. listBox = ViCmdList(parent)
  2075. res = wx.xrc.XmlResource.Get()
  2076. res.LoadOnPanel(self, parent, "ViInputDialog")
  2077. self.ctrls = XrcControls(self)
  2078. res.AttachUnknownControl("viInputListBox", listBox, self)
  2079. self.sizeVisible = True
  2080. # self.dialog_start_pos = rect.GetPosition()
  2081. # self.dialog_start_size = rect.GetSize()
  2082. #self.dialog_start_size = rect.GetSize()
  2083. wx.EVT_SIZE(self, self.OnSize)
  2084. self.run_cmd_timer = wx.Timer(self, GUI_ID.TIMER_VI_UPDATE_CMD)
  2085. wx.EVT_TIMER(self, GUI_ID.TIMER_VI_UPDATE_CMD, self.CheckViInput)
  2086. self.ctrls.viInputTextField.SetBackgroundColour(
  2087. ViInputDialog.COLOR_YELLOW)
  2088. self.closeDelay = 0 # Milliseconds to close or 0 to deactivate
  2089. #wx.EVT_SET_FOCUS(self.ctrls.viInputListBox, self.FocusInputField)
  2090. wx.EVT_TEXT(self, GUI_ID.viInputTextField, self.OnText)
  2091. wx.EVT_KEY_DOWN(self.ctrls.viInputTextField, self.OnKeyDownInput)
  2092. wx.EVT_TIMER(self, GUI_ID.TIMER_INC_SEARCH_CLOSE,
  2093. self.OnTimerIncViInputClose)
  2094. wx.EVT_MOUSE_EVENTS(self.ctrls.viInputTextField, self.OnMouseAnyInput)
  2095. wx.EVT_LEFT_DOWN(self.ctrls.viInputListBox, self.OnLeftMouseListBox)
  2096. wx.EVT_LEFT_DCLICK(self.ctrls.viInputListBox, self.OnLeftMouseDoubleListBox)
  2097. if self.closeDelay:
  2098. self.closeTimer = wx.Timer(self, GUI_ID.TIMER_INC_SEARCH_CLOSE)
  2099. self.closeTimer.Start(self.closeDelay, True)
  2100. self.selection_range = None
  2101. self.list_selection = None
  2102. self.block_kill_focus = False
  2103. wx.EVT_KILL_FOCUS(self.ctrls.viInputTextField, self.OnKillFocus)
  2104. def StartCmd(self, ctrl, cmd_history, text, selection_range=None,
  2105. run_cmd=False):
  2106. self.search = False
  2107. self.selection_range = selection_range
  2108. self.StartInput(text, ctrl, cmd_history)
  2109. if run_cmd:
  2110. wx.CallAfter(self.ExecuteCurrentCmd)
  2111. def StartSearch(self, ctrl, cmd_history, text, forward):
  2112. """
  2113. Called to start a search input
  2114. """
  2115. self.search = True
  2116. self.search_args = {
  2117. 'text' : text,
  2118. 'forward' : forward,
  2119. 'match_case' : False,
  2120. 'whole_word' : False,
  2121. 'wrap' : True,
  2122. 'regex' : True,
  2123. }
  2124. self.StartInput(text, ctrl, cmd_history)
  2125. def StartInput(self, initial_input, ctrl, cmd_history):
  2126. """
  2127. Code common to both search and cmd inputs
  2128. """
  2129. self.ctrl = ctrl
  2130. self.ctrls.viInputListBox.ClearData()
  2131. self.cmd_list = []
  2132. self.cmd_parser = CmdParser(self.ctrl, self.ctrls.viInputListBox, self.selection_range)
  2133. self.initial_scroll_pos = self.ctrl.GetScrollAndCaretPosition()
  2134. self.block_list_reload = False
  2135. if initial_input is not None:
  2136. self.ctrls.viInputTextField.AppendText(initial_input)
  2137. self.ctrls.viInputTextField.SetSelection(-1, -1)
  2138. self.cmd_history = cmd_history
  2139. self.UpdateLayout()
  2140. self.ShowPanel()
  2141. wx.CallAfter(self.FocusInputField, None)
  2142. def close(self):
  2143. pass
  2144. def Close(self):
  2145. self.ctrl.vi.RemoveSelection()
  2146. self.cmd_parser.ClearInput()
  2147. self.ClearListBox()
  2148. self.ctrls.viInputTextField.Clear()
  2149. self.ctrl.vi.EndViInput()
  2150. self.ctrl.SetFocus()
  2151. def UpdateLayout(self, show_viInputListBox=False):
  2152. pass
  2153. def OnKillFocus(self, evt):
  2154. """
  2155. Called if a user clicks outside of the viInputPanel
  2156. """
  2157. if self.search and not self.block_kill_focus:
  2158. self.Close()
  2159. def FocusInputField(self, evt=None):
  2160. self.ctrls.viInputTextField.SetFocus()
  2161. self.ctrls.viInputTextField.SetInsertionPointEnd()
  2162. def GetInput(self):
  2163. """
  2164. Helper to get current text in input box
  2165. """
  2166. return self.ctrls.viInputTextField.GetValue()
  2167. def SetInput(self, text, clear=False):
  2168. # Check if we just want to change the cmd argument
  2169. if not clear:
  2170. current_text = self.GetInput().split(" ")
  2171. if len(current_text) > 1:
  2172. text = "{0} {1}".format(current_text[0], text)
  2173. if text:
  2174. self.ctrls.viInputTextField.SetValue(text)
  2175. self.ctrls.viInputTextField.SetInsertionPointEnd()
  2176. def OnLeftMouseListBox(self, evt):
  2177. evt.Skip()
  2178. wx.CallAfter(self.FocusInputField)
  2179. wx.CallAfter(self.PostLeftMouseListBox)
  2180. def OnLeftMouseDoubleListBox(self, evt):
  2181. evt.Skip()
  2182. wx.CallAfter(self.PostLeftMouseDoubleListBox)
  2183. def PostLeftMouseListBox(self):
  2184. self.block_list_reload = True
  2185. self.SetInput(self.ctrls.viInputListBox.GetCurrentArg())
  2186. self.block_list_reload = False
  2187. def PostLeftMouseDoubleListBox(self):
  2188. self.PostLeftMouseListBox()
  2189. self.list_selectionn = True
  2190. self.ExecuteCurrentCmd()
  2191. def OnText(self, evt):
  2192. """
  2193. Called whenever new text is inserted into the input box
  2194. """
  2195. if self.search:
  2196. self.RunSearch()
  2197. # cmd
  2198. else:
  2199. if self.block_list_reload:
  2200. return
  2201. cmd, args = self.cmd_parser.ParseCmd(self.GetInput())
  2202. if cmd:
  2203. if args:
  2204. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_WHITE)
  2205. #self.ctrls.viInputListBox.Clear()
  2206. self.run_cmd_timer.Start(self.ctrl.vi.CMD_INPUT_DELAY)
  2207. else:
  2208. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_GREEN)
  2209. self.CheckViInput()
  2210. else:
  2211. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_RED)
  2212. def RunSearch(self):
  2213. text = self.GetInput()
  2214. if len(text) < 1:
  2215. return
  2216. self.search_args[u"text"] = text
  2217. # would .copy() be better?
  2218. temp_search_args = dict(self.search_args)
  2219. temp_search_args[u"select_text"] = True
  2220. self.block_kill_focus = True
  2221. # TODO: set flags from config?
  2222. result = self.ctrl.vi._SearchText(**temp_search_args)
  2223. self.FocusInputField()
  2224. self.block_kill_focus = False
  2225. if not result:
  2226. self.ctrl.vi.visualBell("RED")
  2227. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_YELLOW)
  2228. else:
  2229. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_GREEN)
  2230. def ExecuteCmd(self, text_input):
  2231. # Should this close the input?
  2232. if len(text_input) < 1:
  2233. return False
  2234. self.cmd_history.AddCmd(text_input)
  2235. if self.search:
  2236. self.search_args[u"text"] = text_input
  2237. self.ctrl.vi.last_search_args = copy.copy(self.search_args)
  2238. self.ctrl.vi.GotoSelectionStart()
  2239. else:
  2240. if self.cmd_parser.RunCmd(text_input, self.list_selection):
  2241. self.ctrl.vi.visualBell("GREEN")
  2242. else:
  2243. self.ctrls.viInputTextField.SetBackgroundColour(
  2244. ViInputDialog.COLOR_YELLOW)
  2245. self.ctrl.vi.visualBell("RED")
  2246. def CheckViInput(self, evt=None):
  2247. # TODO: cleanup
  2248. self.run_cmd_timer.Stop()
  2249. if self.cmd_parser.CheckForRangeCmd(self.ctrls.viInputTextField.GetValue()):
  2250. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_WHITE)
  2251. return
  2252. valid_cmd = self.ParseViInput(self.ctrls.viInputTextField.GetValue())
  2253. if valid_cmd == False:
  2254. # Nothing found
  2255. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_YELLOW)
  2256. else:
  2257. # Found
  2258. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_GREEN)
  2259. def ParseViInput(self, input_text):
  2260. data, formatted_data, args = self.cmd_parser.ParseCmdWithArgs(input_text)
  2261. #if cmd_list != self.cmd_list:
  2262. # self.cmd_list = cmd_list
  2263. # self.PopulateListBox(data)
  2264. self.PopulateListBox(data, formatted_data, args)
  2265. if not data:
  2266. return False
  2267. return True
  2268. def ForgetViInput(self):
  2269. """
  2270. Called if user cancels the input.
  2271. """
  2272. # Set previous selection?
  2273. self.cmd_history.AddCmd(self.ctrls.viInputTextField.GetValue())
  2274. pos, x, y = self.initial_scroll_pos
  2275. wx.CallAfter(self.ctrl.SetScrollAndCaretPosition, pos, x, y)
  2276. def ClearListBox(self):
  2277. self.ctrls.viInputListBox.ClearData()
  2278. def PopulateListBox(self, data, formatted_data, args):
  2279. if data is None or not data:
  2280. if formatted_data:
  2281. self.ctrls.viInputListBox.SetData(data=None,
  2282. formatted_data=formatted_data)
  2283. else:
  2284. # No items
  2285. self.ctrls.viInputListBox.SetData(None)
  2286. return
  2287. else:
  2288. self.ctrls.viInputListBox.SetData(data, formatted_data, args)
  2289. #self.ctrls.viInputListBox.SetSelection(0)
  2290. self.UpdateLayout(show_viInputListBox=True)
  2291. def OnMouseAnyInput(self, evt):
  2292. # if evt.Button(wx.MOUSE_BTN_ANY) and self.closeDelay:
  2293. # Workaround for name clash in wx.MouseEvent.Button:
  2294. if wx._core_.MouseEvent_Button(evt, wx.MOUSE_BTN_ANY) and self.closeDelay:
  2295. # If a mouse button was pressed/released, restart timer
  2296. self.closeTimer.Start(self.closeDelay, True)
  2297. evt.Skip()
  2298. def OnKeyDownInput(self, evt):
  2299. if self.closeDelay:
  2300. self.closeTimer.Start(self.closeDelay, True)
  2301. key = evt.GetKeyCode()
  2302. accP = getAccelPairFromKeyDown(evt)
  2303. matchesAccelPair = self.mainControl.keyBindings.matchesAccelPair
  2304. searchString = self.GetInput()
  2305. self.list_selection = None
  2306. # If shift is presesd (by itself) we can safely skip it
  2307. if key == 306:
  2308. return
  2309. foundPos = -2
  2310. if accP in ((wx.ACCEL_NORMAL, wx.WXK_NUMPAD_ENTER),
  2311. (wx.ACCEL_NORMAL, wx.WXK_RETURN)):
  2312. # Return pressed
  2313. self.ExecuteCurrentCmd()
  2314. elif accP == (wx.ACCEL_NORMAL, wx.WXK_ESCAPE) or \
  2315. (accP == (wx.ACCEL_NORMAL, wx.WXK_BACK)
  2316. and len(searchString) == 0) or \
  2317. accP == (wx.ACCEL_CTRL, 91):
  2318. # TODO: add ctrl-c (ctrl-[)?
  2319. # Esc -> Abort input, go back to start
  2320. self.ForgetViInput()
  2321. self.Close()
  2322. elif accP == (wx.ACCEL_NORMAL, wx.WXK_BACK):
  2323. # When text is deleted we the search start is reset to the initial
  2324. # position
  2325. pos, x, y = self.initial_scroll_pos
  2326. self.ctrl.SetScrollAndCaretPosition(pos, x, y)
  2327. evt.Skip() # Skip so text is still deleted
  2328. # Arrow keys can be used to navigate the cmd_lie history
  2329. elif accP == (wx.ACCEL_NORMAL, wx.WXK_UP):
  2330. self.SetInput(self.cmd_history.GoBackwardsInHistory(), clear=True)
  2331. elif accP == (wx.ACCEL_NORMAL, wx.WXK_DOWN):
  2332. self.SetInput(self.cmd_history.GoForwardInHistory(), clear=True)
  2333. elif accP == (wx.ACCEL_NORMAL, wx.WXK_TAB):
  2334. self.SelectNextListBoxItem()
  2335. elif accP == (wx.ACCEL_SHIFT, wx.WXK_TAB):
  2336. self.SelectPreviousListBoxItem()
  2337. elif matchesAccelPair("ActivateLink", accP) or \
  2338. matchesAccelPair("ActivateLinkNewTab", accP) or \
  2339. matchesAccelPair("ActivateLink2", accP) or \
  2340. matchesAccelPair("ActivateLinkBackground", accP) or \
  2341. matchesAccelPair("ActivateLinkNewWindow", accP):
  2342. # ActivateLink is normally Ctrl-L
  2343. # ActivateLinkNewTab is normally Ctrl-Alt-L
  2344. # ActivateLink2 is normally Ctrl-Return
  2345. # ActivateLinkNewTab is normally Ctrl-Alt-L
  2346. self.Close()
  2347. self.ctrl.OnKeyDown(evt)
  2348. elif accP == (wx.ACCEL_NORMAL, wx.WXK_SPACE):
  2349. # This may be used in the future
  2350. self.ctrls.viInputListBox.SetSelection(-1)
  2351. evt.Skip()
  2352. pass
  2353. # handle the other keys
  2354. else:
  2355. self.ctrls.viInputListBox.SetSelection(-1)
  2356. evt.Skip()
  2357. if foundPos == False:
  2358. # Nothing found
  2359. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_YELLOW)
  2360. else:
  2361. # Found
  2362. self.ctrls.viInputTextField.SetBackgroundColour(ViInputDialog.COLOR_GREEN)
  2363. # Else don't change
  2364. def ExecuteCurrentCmd(self):
  2365. self.ExecuteCmd(self.GetInput())
  2366. self.Close()
  2367. def SelectNextListBoxItem(self):
  2368. self.MoveListBoxSelection(1)
  2369. def SelectPreviousListBoxItem(self):
  2370. self.MoveListBoxSelection(-1)
  2371. def MoveListBoxSelection(self, offset):
  2372. if not self.ctrls.viInputListBox.HasData():
  2373. self.ctrl.vi.visualBell(u"RED")
  2374. return
  2375. if offset < 0:
  2376. select = max
  2377. n = 0
  2378. else:
  2379. select = min
  2380. n = self.ctrls.viInputListBox.GetCount()
  2381. sel_no = select(n, self.ctrls.viInputListBox.GetSelection() + offset)
  2382. self.ctrls.viInputListBox.SetSelection(sel_no)
  2383. #split_text = self.GetInput().split(u" ")
  2384. self.list_selection = sel_no
  2385. self.block_list_reload = True
  2386. self.SetInput(self.ctrls.viInputListBox.GetCurrentArg())
  2387. #if len(split_text) > 1:# and split_text[1] != u"":
  2388. # self.ctrls.viInputTextField.SetValue("{0} {1}".format(self.ctrls.viInputTextField.GetValue().split(u" ")[0], self.ctrls.viInputListBox.GetStringSelection()))
  2389. #else:
  2390. # self.ctrls.viInputTextField.SetValue("{0}".format(self.ctrls.viInputListBox.GetStringSelection()))
  2391. self.ctrls.viInputTextField.SetInsertionPointEnd()
  2392. self.block_list_reload = False
  2393. self.run_cmd_timer.Stop()
  2394. def OnTimerIncViInputClose(self, evt):
  2395. self.Close()
  2396. def OnSize(self, evt):
  2397. evt.Skip()
  2398. # #oldVisible = self.isVisibleEffect()
  2399. # size = evt.GetSize()
  2400. # self.sizeVisible = size.GetHeight() >= 5 and size.GetWidth() >= 5
  2401. def ShowPanel(self):
  2402. self.mainControl.windowLayouter.expandWindow("vi input")
  2403. self.FocusInputField()
  2404. class ViCmdList(wx.HtmlListBox):
  2405. def __init__(self, parent):
  2406. """
  2407. Html list box which holds completion info.p
  2408. Consists of 3 parts
  2409. formatted_data: html formatted string as will appear in the list box
  2410. data: associated data (is not restricted to a particular type)
  2411. args: simple string that will be used as the arg if listbox item is
  2412. selected
  2413. """
  2414. wx.HtmlListBox.__init__(self, parent, -1)
  2415. self.parent = parent
  2416. wx.EVT_LISTBOX_DCLICK(self, -1, self.OnDClick)
  2417. self.ClearData()
  2418. def ClearData(self):
  2419. self.data = None
  2420. self.formatted_data = []
  2421. self.args = []
  2422. self.SetItemCount(0)
  2423. def HasData(self):
  2424. if self.data is None:
  2425. return False
  2426. else:
  2427. return True
  2428. def SetData(self, data, formatted_data=[], args=[]):
  2429. if data is None:
  2430. if formatted_data:
  2431. self.formatted_data = formatted_data
  2432. else:
  2433. self.formatted_data = [u"No items / data found."]
  2434. self.data = None
  2435. else:
  2436. self.formatted_data = formatted_data
  2437. self.data = data
  2438. self.args = args
  2439. self.SetItemCount(len(self.formatted_data))
  2440. self.Refresh()
  2441. def GetArg(self, n):
  2442. return self.args[n]
  2443. def GetData(self, n):
  2444. return self.data[n]
  2445. def GetCurrentArg(self):
  2446. return self.args[self.GetSelection()]
  2447. def GetCurrentData(self):
  2448. return self.data[self.GetSelection()]
  2449. #def AppendItems(self, formatted_data, data, args):
  2450. # self.data.append(data)
  2451. # self.Refresh()
  2452. def OnGetItem(self, n):
  2453. if self.formatted_data is not None:
  2454. return self.formatted_data[n]
  2455. else:
  2456. return None
  2457. def GetCount(self):
  2458. return len(self.data)
  2459. def OnDClick(self, evt):
  2460. pass
  2461. class PluginKeyError(Exception): pass