/lib/pwiki/ViHelper.py
Python | 3164 lines | 2867 code | 157 blank | 140 comment | 140 complexity | f10142cc6bd661acb1b89a30583d420b MD5 | raw file
Possible License(s): LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- import wx, wx.xrc
- import wx.lib.dialogs
- import SystemInfo
- from wxHelper import GUI_ID, getAccelPairFromKeyDown, getTextFromClipboard
- from collections import defaultdict
- from StringOps import pathEnc, urlQuote
- import os
- import ConfigParser
- import re
- import copy
- import string
- import PluginManager
- import subprocess
-
- from wxHelper import * # Needed for XrcControls
-
- from WindowLayout import setWindowSize
-
- #TODO: Multiple registers
- # Page marks
- # Alt-combinations
- # .rc
-
-
- # TODO: should be configurable
- AUTOCOMPLETE_BOX_HEIGHT = 50
-
- # Key accels use a different sep in >= 2.9
- if wx.version() >= 2.9:
- ACCEL_SEP = "+"
- else:
- ACCEL_SEP = "-"
-
- def formatListBox(x, y):
- html = "<table width=100% height=5px><tr><td>{0}</td><td align='right'><font color='gray'>{1}</font></td></tr></table>".format(x, y)
- return html
-
- class ViHelper():
- """
- Base class for ViHandlers to inherit from.
-
- Contains code and functions that are relevent to VI emulation in
- both editor and preview mode.
- """
- # Modes
- # Current these are only (partly) implemented for the editor
- NORMAL, INSERT, VISUAL, REPLACE = range(4)
-
- MODE_TEXT = {
- 0 : u"",
- 1 : u"--INSERT--",
- 2 : u"--VISUAL--",
- 3 : u"--REPLACE--"
- }
-
- # Default key bindings - can be overridden by wikidrc
- KEY_BINDINGS = {
- u"!" : 33,
- u"\"" : 34,
- u"#" : 35,
- u"$" : 36,
- u"%" : 37,
- u"&" : 38,
- u"'" : 39,
- u"(" : 40,
- u")" : 41,
- u"*" : 42,
- u"+" : 43,
- u"," : 44,
- u"-" : 45,
- u"." : 46,
- u"/" : 47,
- u"0" : 48,
- u"1" : 49,
- u"2" : 50,
- u"3" : 51,
- u"4" : 52,
- u"5" : 53,
- u"6" : 54,
- u"7" : 55,
- u"8" : 56,
- u"9" : 57,
- u":" : 58,
- u";" : 59,
- u"<" : 60,
- u"=" : 61,
- u">" : 62,
- u"?" : 63,
- u"@" : 64,
- u"A" : 65,
- u"B" : 66,
- u"C" : 67,
- u"D" : 68,
- u"E" : 69,
- u"F" : 70,
- u"G" : 71,
- u"H" : 72,
- u"I" : 73,
- u"J" : 74,
- u"K" : 75,
- u"L" : 76,
- u"M" : 77,
- u"N" : 78,
- u"O" : 79,
- u"P" : 80,
- u"Q" : 81,
- u"R" : 82,
- u"S" : 83,
- u"T" : 84,
- u"U" : 85,
- u"V" : 86,
- u"W" : 87,
- u"X" : 88,
- u"Y" : 89,
- u"Z" : 90,
- u"[" : 91,
- u"\\" : 92,
- u"]" : 93,
- u"^" : 94,
- u"_" : 95,
- u"`" : 96,
- u"a" : 97,
- u"b" : 98,
- u"c" : 99,
- u"d" : 100,
- u"e" : 101,
- u"f" : 102,
- u"g" : 103,
- u"h" : 104,
- u"i" : 105,
- u"j" : 106,
- u"k" : 107,
- u"l" : 108,
- u"m" : 109,
- u"n" : 110,
- u"o" : 111,
- u"p" : 112,
- u"q" : 113,
- u"r" : 114,
- u"s" : 115,
- u"t" : 116,
- u"u" : 117,
- u"v" : 118,
- u"w" : 119,
- u"x" : 120,
- u"y" : 121,
- u"z" : 122,
- u"{" : 123,
- u"|" : 124,
- u"}" : 125,
- u"~" : 126,
- }
-
-
- CMD_INPUT_DELAY = 1000
- STRIP_BULLETS_ON_LINE_JOIN = True
-
-
- def __init__(self, ctrl):
- # ctrl is WikiTxtCtrl in the case of the editor,
- # WikiHtmlViewWk for the preview mode.
- self.ctrl = ctrl
-
- self.key_map = {}
- for varName in vars(wx):
- if varName.startswith("WXK_"):
- self.key_map[getattr(wx, varName)] = varName
-
-
- self.mode = 0
-
- self._motion = []
- self._motion_wildcard = []
- self._wildcard = []
- self._acceptable_keys = None
- self.key_inputs = []
-
- self.key_number_modifier = [] # holds the count
-
- self.count = 1
- self.true_count = False
-
- self.hintDialog = None
-
- self.marks = defaultdict(dict)
-
- self.last_find_cmd = None
-
- self.last_search_args = None
- self.last_search_cmd = None
- self.jumps = []
- self.current_jump = -1
-
- self.input_window = self.ctrl.presenter.getMainControl().windowLayouter.getWindowByName("vi input")
-
- self.input_search_history = ViInputHistory()
- self.input_cmd_history = ViInputHistory()
-
- self.last_cmd = None
- self.insert_action = []
-
- self.selection_mode = u"NORMAL"
-
- self.tag_input = False
-
- # The following dictionary holds the menu shortcuts that have been
- # disabled upon entering ViMode
- self.menuShortCuts = {}
-
- self.viKeyAccels = set()
-
- self.register = ViRegister(self.ctrl)
-
-
- # Settings are stored as a dict
- self.settings = {
- "filter_wikipages" : True,
- "caret_scroll" : False, # Large performance hit
-
- # The following settings apply to SetHeading()
- "blank_line_above_headings" : True,
- "strip_headings" : True,
-
- # No. spaces to put between +++ and heading text
- "pad_headings" : 0,
-
- "gvim_path" : u"gvim",
- "vim_path" : u"vim",
-
- "caret_colour_normal" : "#FF0000",
- "caret_colour_visual" : "#FFD700",
- "caret_colour_insert" : "#0000FF",
- "caret_colour_replace" : "#8B0000",
- "caret_colour_command" : "#00FFFF",
-
- "set_wrap_indent_mode": 1,
- "set_wrap_start_indent": 0,
-
-
- "min_wikipage_search_len" : 2,
-
- }
- self.LoadSettings()
-
- self.RegisterPlugins()
-
- def RegisterPlugins(self):
- """
- Register the plugins to be loaded.
- """
- self.pluginFunctions = []
- main_control = self.ctrl.presenter.getMainControl()
-
- self.pluginFunctions = reduce(lambda a, b: a+list(b),
- main_control.viPluginFunctions.describeViFunctions(
- main_control), [])
-
- def LoadPlugins(self, presenter):
- """
- Helper which loads the plugins.
-
- To be called by derived class.
- """
- # Load plugin functions
- k = self.KEY_BINDINGS
- for keys, presenter_type, vi_mode, function in self.pluginFunctions:
- try:
- if presenter in presenter_type:
- def returnKey(key):
- if len(key) > 1 and key[0] == u"key":
- if type(key[1]) == tuple:
- l = list(key[1])
- key_char = l.pop()
-
- l.extend([k[key_char]])
-
- return tuple(l)
-
- else:
- return k[key[1]]
- elif key == "motion" or "m":
- return "m"
- elif key == "*":
- return "*"
- else:
- raise PluginKeyError(u"ERROR LOADING PLUGIN")
-
- key_chain = tuple([returnKey(i) for i in keys])
- for mode in vi_mode:
- self.keys[mode][key_chain] = function
- except PluginKeyError:
- continue
-
- self.pluginFunctions = []
-
- self.GenerateKeyKindings()
-
- def ReloadPlugins(self, name):
- self.RegisterPlugins()
- self.LoadPlugins(name)
-
- def LoadSettings(self):
- """
- Settings are loaded from the file vi.rc in the wikidpad global config
- dir
-
- Can be called at any time to update / reload settings
-
- ? May need to regenerate keybindings if they have been changed
-
- NOTE: Should move out of ViHelper as per tab setting are probably
- not required
- """
- rc_file = None
- rc_file_names = (".WikidPad.virc", "WikidPad.virc")
-
- config_dir = wx.GetApp().globalConfigDir
-
- for n in rc_file_names:
- path = pathEnc(os.path.join(config_dir, n))
- if os.path.isfile(path):
- rc_file = path
- break
-
- if rc_file is not None:
- config = ConfigParser.ConfigParser()
- config.read(rc_file)
-
- # Load custom key bindings
- try:
- for key in config.options("keys"):
- try:
- self.KEY_BINDINGS[key] = config.getint("keys", key)
- except ValueError:
- print "Keycode must be a integer: {0}".format(key)
- except ConfigParser.NoSectionError:
- pass
-
-
- try:
- for setting in config.options("settings"):
- if setting in self.settings:
- try:
- self.settings[setting] = config.getboolean(
- "settings", setting)
- except ValueError:
- print "Setting '{1}' must be boolean".format(setting)
-
- except ConfigParser.NoSectionError:
- pass
-
- self.ApplySettings()
-
- def ApplySettings(self):
- pass
-
- def OnChar(self, evt):
- """
- Handles EVT_CHAR events necessary for MS Windows
- """
- m = self.mode
-
- key = evt.GetKeyCode()
-
- # OnChar seems to throw different keycodes if ctrl is pressed.
- # a = 1, b = 2 ... z = 26
- # will not handle different cases
- if evt.ControlDown():
- key = key + 96
- key = ("Ctrl", key)
-
- self.HandleKey(key, m, evt)
-
-
- def OnViKeyDown(self, evt):
- """
- Handle keypresses when in Vi mode
-
- Ideally much of this would be moved to ViHelper
-
- """
-
- m = self.mode
- key = evt.GetKeyCode()
-
- if m == ViHelper.INSERT:
-
- # TODO: Allow navigation with Ctrl-N / Ctrl-P
- accP = getAccelPairFromKeyDown(evt)
- matchesAccelPair = self.ctrl.presenter.getMainControl().\
- keyBindings.matchesAccelPair
-
- if matchesAccelPair("AutoComplete", accP):
- # AutoComplete is normally Ctrl-Space
- # Handle autocompletion
- self.ctrl.autoComplete()
-
- if self.ctrl.AutoCompActive():
- self.OnAutocompleteKeyDown(evt)
-
- # The following code is mostly duplicated from OnKeyDown (should be
- # rewritten to avoid duplication)
- # TODO Check all modifiers
- if not evt.ControlDown() and not evt.ShiftDown():
- if key == wx.WXK_TAB:
- if self.ctrl.pageType == u"form":
- if not self.ctrl._goToNextFormField():
- self.ctrl.presenter.getMainControl().showStatusMessage(
- _(u"No more fields in this 'form' page"), -1)
- return
- evt.Skip()
- elif key == wx.WXK_RETURN and not self.ctrl.AutoCompActive():
- text = self.ctrl.GetText()
- wikiDocument = self.ctrl.presenter.getWikiDocument()
- bytePos = self.ctrl.GetCurrentPos()
- lineStartBytePos = self.ctrl.PositionFromLine(
- self.ctrl.LineFromPosition(bytePos))
-
- lineStartCharPos = len(self.ctrl.GetTextRange(0,
- lineStartBytePos))
- charPos = lineStartCharPos + len(self.ctrl.GetTextRange(
- lineStartBytePos, bytePos))
-
- autoUnbullet = self.ctrl.presenter.getConfig().getboolean("main",
- "editor_autoUnbullets", False)
-
- settings = {
- "autoUnbullet": autoUnbullet,
- "autoBullets": self.ctrl.autoBullets,
- "autoIndent": self.ctrl.autoIndent
- }
-
- if self.ctrl.wikiLanguageHelper.handleNewLineBeforeEditor(
- self.ctrl, text, charPos, lineStartCharPos,
- wikiDocument, settings):
- evt.Skip()
- return
- # Hack to maintain consistency when pressing return
- # on an empty bullet
- elif bytePos != self.ctrl.GetCurrentPos():
- return
-
- # Pass modifier keys on
- if key in (wx.WXK_CONTROL, wx.WXK_ALT, wx.WXK_SHIFT):
- return
-
- # On linux we can just use GetRawKeyCode() and work directly with
- # its return. On windows we have to skip this event (for keys which
- # will produce a char event to wait for EVT_CHAR (self.OnChar()) to
- # get the correct key translation
- elif key not in self.key_map:
- key = evt.GetRawKeyCode()
- else:
- # Keys present in the key_map should be consitent across
- # all platforms and can be handled directly.
- key = self.AddModifierToKeychain(key, evt)
- #self.HandleKey(key, m, evt)
- if not self.HandleKey(key, m, evt):
- # For wxPython 2.9.5 we need this otherwise menu accels don't
- # seem to be triggered (though they worked in previous versions?)
- evt.Skip()
- return
-
-
- # What about os-x?
- if not SystemInfo.isLinux():
- # Manual fix for some windows problems may be necessary
- # e.g. Ctrl-[ won't work
- evt.Skip()
- return
-
- key = self.AddModifierToKeychain(key, evt)
-
- if not self.HandleKey(key, m, evt):
- # For wxPython 2.9.5 we need this otherwise menu accels don't
- # seem to be triggered (though they worked in previous versions?)
- evt.Skip()
-
- def AddModifierToKeychain(self, key, evt):
- """
- Checks a key event for modifiers (ctrl / alt) and adds them to
- the key if they are present
-
- """
- mods = []
- if evt.ControlDown():
- mods.append("Ctrl")
-
- if evt.AltDown():
- mods.append("Alt")
-
- if mods:
- mods.extend([key])
- print mods
- key = tuple(mods)
-
- return key
-
-
- def EndInsertMode(self):
- pass
-
- def EndReplaceMode(self):
- pass
-
- def LeaveVisualMode(self):
- pass
-
- def HandleKey(self, key, m, evt):
-
- # There should be a better way to monitor for selection changed
- if self.HasSelection():
- self.EnterVisualMode()
-
-
- # TODO: Replace with override keys? break and run function
- # Escape, Ctrl-[, Ctrl-C
- # In VIM Ctrl-C triggers *InsertLeave*
- if key == wx.WXK_ESCAPE or key == ("Ctrl", 91) or key == ("Ctrl", 99):
- # TODO: Move into ViHandler?
- self.EndInsertMode()
- self.EndReplaceMode()
- self.LeaveVisualMode()
- self.FlushBuffers()
- return True
-
- # Registers
- if m != 1 and key == 34 and self._acceptable_keys is None \
- and not self.key_inputs: # "
- self.register.select_register = True
- return True
- elif self.register.select_register:
- self.register.SelectRegister(key)
- self.register.select_register = False
- return True
-
-
- if m in [1, 3]: # Insert mode, replace mode,
- # Store each keyevent
- # NOTE:
- # !!may need to seperate insert and replace modes!!
- # what about autocomplete?
- # It would be possbile to just store the text that is inserted
- # however then actions would be ignored
- self.insert_action.append(key)
-
- # Data is reset if the mouse is used or if a non char is pressed
- # Arrow up / arrow down
- if key in [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT]:
- self.EndBeginUndo()
- self.insert_action = []
- if not self.RunKeyChain((key,), m):
- evt.Skip()
- return False
- return True
-
- if self._acceptable_keys is None or \
- "*" not in self._acceptable_keys:
- if 48 <= key <= 57: # Normal
- if self.SetNumber(key-48):
- return True
- elif 65456 <= key <= 65465: # Numpad
- if self.SetNumber(key-65456):
- return True
-
- self.SetCount()
-
- if self._motion and self._acceptable_keys is None:
- #self._acceptable_keys = None
- self._motion.append(key)
-
- temp = self._motion[:-1]
- temp.append("*")
- if tuple(self._motion) in self.motion_keys[m]:
- self.RunKeyChain(tuple(self.key_inputs), m)
- return True
- #self._motion = []
- elif tuple(temp) in self.motion_keys[m]:
- self._motion[-1] = "*"
- self._motion_wildcard.append(key)
- self.RunKeyChain(tuple(self.key_inputs), m)
- #self._motion = []
- return True
-
- elif tuple(self._motion) in self.motion_key_mods[m]:
- #self._acceptable_keys = self.motion_key_mods[m][tuple(self._motion)]
- return True
-
- self.FlushBuffers()
- return True
-
-
- if self._acceptable_keys is not None:
- if key in self._acceptable_keys:
- self._acceptable_keys = None
- pass
- elif "*" in self._acceptable_keys:
- self._wildcard.append(key)
- self.key_inputs.append("*")
- self._acceptable_keys = None
- self.RunKeyChain(tuple(self.key_inputs), m)
-
- return True
- elif "m" in self._acceptable_keys:
- self._acceptable_keys = None
- self._motion.append(key)
- if (key,) in self.motion_keys[m]:
- self.key_inputs.append("m")
- self.RunKeyChain(tuple(self.key_inputs), m)
- return True
- elif (key,) == -999:
- self.key_inputs.append("m")
- self.RunKeyChain(tuple(self.key_inputs), m)
- if (key,) in self.motion_key_mods[m]:
- self.key_inputs.append("m")
- return True
-
-
- self.key_inputs.append(key)
- self.updateViStatus()
-
- key_chain = tuple(self.key_inputs)
-
- if self.RunKeyChain(key_chain, m):
- return True
-
- self.FlushBuffers()
-
- # If a chain command has been started prevent evt.Skip() from being
- # called
- if len(key_chain) > 1:
- return True
-
- try:
- if "Ctrl" in key or "Alt" in key:
- return False
- except TypeError:
- pass
-
- return True
-
- def KeyCommandInProgress(self):
- return self.key_inputs
-
- def NextKeyCommandCanBeMotion(self):
- """
- Checks if the next key can be a motion cmd
- """
- if "m" in self._acceptable_keys:
- return True
-
- return False
-
-
- def SetCount(self):
- self.count = 1
- self.true_count = False # True if count is specified
- if len(self.key_number_modifier) > 0:
- self.count = int("".join(map(str, self.key_number_modifier)))
-
- # Set a max count
- if self.count > 10000:
- self.count = 10000
- self.true_count = True
-
- def SetNumber(self, n):
- # TODO: move to ViHelper
- # If 0 is first modifier it is a command
- if len(self.key_number_modifier) < 1 and n == 0:
- return False
- self.key_number_modifier.append(n)
- self.updateViStatus(True)
- return True
-
- def GetCharFromCode(self, keycode):
- """
- Converts keykeycode to unikeycode character. If no keykeycode is specified
- returns an empty string.
-
- @param keycode: Raw keycode value
-
- @return: Requested character
- """
- # TODO: Rewrite
- if keycode is not None:
- # Check for modifiers
- if type(keycode) == tuple:
- l = list(keycode)
- k = l.pop()
- mods = ACCEL_SEP.join(l)
- return "{0}{1}{2}".format(mods, ACCEL_SEP, unichr(k))
- try:
- return unichr(keycode)
- # This may occur when special keys (e.g. WXK_SPACE) are used
- except TypeError, ValueError: # >wx2.9 ?valueerror?
- return keycode
- else:
- return u""
-
- def Mark(self, code):
- """
- Set marks can be any alpha charcter (a-zA-Z)
-
- @param code: keycode of mark to be set
-
- # TODO: save marks across sessions?
- """
- char = self.GetCharFromCode(code)
- if char is not None and char.isalpha():
- self._SetMark(code)
- self.visualBell("BLUE")
- else:
- self.visualBell("RED")
- self.updateViStatus()
-
- def _SetMark():
- """
- Dummy function to be overridden
- """
-
- def SetDefaultCaretColour(self):
- self.default_caret_colour = self.ctrl.GetCaretForeground()
-
- def GenerateKeyKindings(self):
- """Stub to be overridden by derived class"""
-
- def GenerateMotionKeys(self, keys):
- key_mods = defaultdict(dict)
- for mode in keys:
- key_mods[mode] = set()
- for accel in keys[mode]:
- if keys[mode][accel][0] > 0:
- key_mods[mode].add(accel)
-
- return key_mods
-
- def GenerateKeyModifiers(self, keys):
- """
- Takes dictionary of key combinations and returns all possible
- key starter combinations
-
- @param keys: see self.keys in derived classes
- """
- key_mods = defaultdict(dict)
- for mode in keys:
- key_mods[mode] = defaultdict(set)
- for accel in keys[mode]:
- if len(accel) > 1:
- for i in range(1, len(accel)):
- if i == 1:
- key_mods[mode][(accel[0],)].add(accel[1])
- else:
- key_mods[mode][accel[:i]].add(accel[i])
-
- return key_mods
-
- def GenerateKeyAccelerators(self, keys):
- """
- This could be improved
- """
- key_accels = set()
- for j in keys:
- for accels in keys[j]:
- for accel in accels:
- if type(accel) == tuple and len(accel) > 1:
- l = list(accel)
- k = l.pop()
- mods = ACCEL_SEP.join(l)
- # wx accels chars are always uppercase
- to_add = "{0}{1}{2}".format(mods, ACCEL_SEP, unichr(k).upper())
- key_accels.add(to_add)
-
- self.viKeyAccels.update(key_accels)
-
- def Repeat(self, func, count=None, arg=None):
- """
- Base function called if a command needs to be repeated
- a number of times.
-
- @param func: function to be run
- @param count: number of times to run the function, if not specified
- manually will use the input count (which defaults to 1)
- @param arg: argument to run function with, can be single or multiple
- arguments in the form of a dict
- """
- if count is None:
- count = self.count
- for i in range(count):
- if arg is not None:
- if type(arg) == dict:
- func(**arg)
- else:
- func(arg)
- else:
- func()
-
- def RunKeyChain(self, key_chain, mode):
- self.updateViStatus()
- if key_chain in self.keys[mode]:
- self.RunFunction(key_chain)
- self.FlushBuffers()
- return True
- else:
- if key_chain in self.key_mods[mode]:
- # TODO: better fix
- self.SetCaretColour(self.settings['caret_colour_command'])
-
- self._acceptable_keys = self.key_mods[mode][key_chain]
- return True
- return False
-
- def RunFunction(self, key, motion=None, motion_wildcard=None,
- wildcards=None, text_to_select=None, repeat=False):
- """
- Called when a key command is run
-
- keys is a dictionary which holds the "key" and its
- respective function.
-
- """
- if motion is None:
- motion = self._motion
- if wildcards is None:
- wildcards = self._wildcard
- if motion_wildcard is None:
- motion_wildcard = tuple(self._motion_wildcard)
-
-
- def RunFunc(func, args):
- if type(args) == dict:
- ret = func(**args)
- elif args is not None:
- ret = func(args)
- else:
- ret = func()
-
- keys = self.keys[self.mode]
-
- if text_to_select is not None:
- # Use visual keys instead
- keys = self.keys[ViHelper.VISUAL]
-
- # If we are already in visual mode use the selected text
- # Otherwise we use the same amount of text used for initial cmd
- # NOTE: this should prob be put into another cmd (in WikiTxtCtrl)
- if not self.mode == ViHelper.VISUAL:
- lines, n = text_to_select
- if lines:
- start_line = self.ctrl.GetCurrentLine()
- self.SelectLines(start_line, start_line - 1 + n,
- include_eol=True)
- else:
- self.StartSelection()
- self.MoveCaretPos(n, allow_last_char=True)
- self.SelectSelection(2)
-
- com_type, command, repeatable, selection_type = keys[key]
- func, args = command
-
-
- # If in visual mode need to prep in case we change selection direction
- start_selection_direction = None
- if self.mode == ViHelper.VISUAL:
- start_selection_direction = self.SelectionIsForward()
-
-
- # If a motion is present in the command (but not the main command)
- # it needs to be run first
- if "m" in key:
- # TODO: finish
- # If the motion is a mouse event it should have already been run
- if motion != -999:
- # If in visual mode we don't want to change the selection start point
- if self.mode != ViHelper.VISUAL:
- # Otherwise the "pre motion" commands work by setting a start point
- # at the current positions, running the motion command and
- # finishing with a "post motion" command, i.e. deleting the
- # text that was selected.
- self.StartSelection()
-
- motion_key = tuple(motion)
-
- # Extract the cmd we need to run (it is irrelevent if it is
- # repeatable or if it has a different selection_type)
- motion_com_type, (motion_func, motion_args), junk, junk = keys[motion_key]
- if motion_wildcard:
- motion_args = tuple(motion_wildcard)
- if len(motion_args) == 1:
- motion_args = motion_args[0]
- else:
- motion_args = tuple(motion_args)
-
- RunFunc(motion_func, motion_args)
-
- # Test if the motion has caused a movement in the caret
- # If not consider it an invalid cmd
- if self._anchor == self.ctrl.GetCurrentPos():
- return False
-
- self.SelectSelection(motion_com_type)
-
- # If in visual mode we save some details about the selection so the
- # command can be repeated
- selected_text = None
- if self.mode == ViHelper.VISUAL:
- # TODO: fix line selection mode
- selected_text = self.GetSelectionDetails(selection_type)
-
- if type(key) == tuple and "*" in key:
- args = tuple(wildcards)
- if len(args) == 1:
- args = args[0]
- else:
- args = tuple(args)
- # Run the actual function
- RunFunc(func, args)
-
- # If the command is repeatable save its type and any other settings
- if repeatable in [1, 2, 3] and not repeat:
- self.last_cmd = [repeatable, key, self.count, motion, \
- motion_wildcard, wildcards, \
- selected_text]
-
- # Some commands should cause the mode to revert back to normal if run
- # from visual mode, others shouldn't.
- if self.mode == ViHelper.VISUAL:
- if com_type < 1:
- self.SetMode(ViHelper.NORMAL)
- else:
- if start_selection_direction is not None:
- end_selection_direction = \
- self.ctrl.GetCurrentPos() > self._anchor
-
- if start_selection_direction != end_selection_direction:
- if end_selection_direction:
- self._anchor = self._anchor - 1
- else:
- self._anchor = self._anchor + 1
-
- self.SelectSelection(com_type)
-
- self.FlushBuffers()
-
- def FlushBuffers(self):
- """
- Clear modifiers and start keys so next input will be fresh
-
- Should be called after (most?) successful inputs and all failed
- ones.
- """
- self._acceptable_keys = None
-
- self._motion = []
- self._motion_wildcard = []
- self._wildcard = []
- self.key_inputs = []
-
- self.key_modifier = []
- self.key_number_modifier = []
- self.updateViStatus()
-
- self.FlushBuffersExtra()
-
-
- def FlushBuffersExtra(self):
- """
- To be overidden by derived class
- """
- pass
-
- def SetSelMode(self, mode):
- self.selection_mode = mode
-
- def GetSelMode(self):
- return self.selection_mode
-
- def HasSelection(self):
- """
- Should be overridden by child class if necessary
- """
- return False
-
- def SelectionIsForward(self):
- """
- Should be overridden by child class if necessary
- """
- return True
-
- def GetSelectionDetails(self, selection_type=None):
- """
- Should be overridden by child class if necessary
- """
- return (True, len(self.ctrl.GetSelectedText()))
-
- def _GetSelectionRange(self):
- return None
-
- def SetCaretColour(self, colour):
- # TODO: implement this
- pass
-
- def minmax(self, a, b):
- return min(a, b), max(a, b)
-
- def updateViStatus(self, force=False):
- # can this be right aligned?
- mode = self.mode
- text = u""
- if mode in self.keys:
- cmd = u"".join([self.GetCharFromCode(i) for i in self.key_inputs])
- text = u"{0}{1}{2}".format(
- ViHelper.MODE_TEXT[self.mode],
- u"".join(map(str, self.key_number_modifier)),
- cmd
- )
-
- self.ctrl.presenter.getMainControl().statusBar.SetStatusText(text , 0)
-
- def _enableMenuShortcuts(self, enable):
- # TODO: should only be called once (at startup / plugin load)
- if enable and len(self.menuShortCuts) < 1:
- return
-
- self.menu_bar = self.ctrl.presenter.getMainControl().mainmenu
-
- if enable:
- for i in self.menuShortCuts:
- self.menu_bar.FindItemById(i).SetText(self.menuShortCuts[i])
- self.ctrl.presenter.getMainControl() \
- .SetAcceleratorTable( self.accelTable)
- else:
- self.accelTable = self.ctrl.presenter.getMainControl() \
- .GetAcceleratorTable()
-
- self.ctrl.presenter.getMainControl() \
- .SetAcceleratorTable(wx.NullAcceleratorTable)
-
- menus = self.menu_bar.GetMenus()
-
- menu_items = []
-
- def getMenuItems(menu):
- for i in menu.GetMenuItems():
- menu_items.append(i.GetId())
- if i.GetSubMenu() is not None:
- getMenuItems(i.GetSubMenu())
-
- for menu, x in menus:
- getMenuItems(menu)
-
- for i in menu_items:
- menu_item = self.menu_bar.FindItemById(i)
- accel = menu_item.GetAccel()
- if accel is not None:
- try:
- if accel.ToString() in self.viKeyAccels:
- label = menu_item.GetItemLabel()
- self.menuShortCuts[i] = (label)
- # Removing the end part of the label is enough to disable the
- # accelerator. This is used instead of SetAccel() so as to
- # preserve menu accelerators.
- # NOTE: doesn't seem to override ctrl-n!
- menu_item.SetText(label.split("\t")[0]+"\tNone")
- except:
- # Key errors appear in windows! (probably due to
- # unicode support??).
- if unichr(accel.GetKeyCode()) in self.viKeyAccels:
- label = menu_item.GetItemLabel()
- self.menuShortCuts[i] = (label)
- menu_item.SetText(label.split("\t")[0]+"\tNone")
-
-
- #-----------------------------------------------------------------------------
- # The following functions are common to both preview and editor mode
- # -----------------------------------------------------------------------------
-
- # Jumps
-
- # TODO: generalise so they work with wikihtmlview as well
- # should work in lines (so if line is deleted)
- def AddJumpPosition(self, pos=None):
- if pos is None: pos = self.ctrl.GetCurrentPos()
-
- current_page = self.ctrl.presenter.getWikiWord()
- if self.jumps:
- last_page, last_pos = self.jumps[self.current_jump]
- if last_page == current_page and pos == last_pos:
- return
-
- if self.current_jump < len(self.jumps):
- self.jumps = self.jumps[:self.current_jump+1]
- self.jumps.append((current_page, pos))
- self.current_jump += 1
-
- def GotoNextJump(self):
- if self.current_jump + 1 < len(self.jumps):
- self.current_jump += 1
- else:
- return
-
- word, pos = self.jumps[self.current_jump]
- if word != self.ctrl.presenter.getWikiWord():
- self.ctrl.presenter.openWikiPage(word)
- self.ctrl.GotoPos(pos)
-
- def GotoPreviousJump(self):
- if self.current_jump + 1 == len(self.jumps):
- self.AddJumpPosition(self.ctrl.GetCurrentPos())
- if self.current_jump - 1 >= 0:
- self.current_jump -= 1
- else:
- return
- word, pos = self.jumps[self.current_jump]
- if word != self.ctrl.presenter.getWikiWord():
- self.ctrl.presenter.openWikiPage(word)
- self.ctrl.GotoPos(pos)
-
-
-
- def SwitchEditorPreview(self, scName=None):
- mainControl = self.ctrl.presenter.getMainControl()
- mainControl.setDocPagePresenterSubControl(scName)
-
- def StartForwardSearch(self, initial_input=None):
- self.StartSearchInput(initial_input=initial_input, forward=True)
-
- def StartReverseSearch(self, initial_input=None):
- self.StartSearchInput(initial_input=initial_input, forward=False)
-
- def StartSearchInput(self, initial_input=None, forward=True):
-
- text = initial_input
-
- if self.HasSelection():
- text = self.ctrl.GetSelectedText()
- text = text.split("\n", 1)[0]
- text = text[:30]
-
- self.ctrl.presenter.setTabTitleColour("BLUE")
-
- self.input_window.StartSearch(self.ctrl, self.input_search_history, text, forward)
-
-
- def ContinueLastSearchSameDirection(self):
- """Helper function to allow repeats"""
- self.ContinueLastSearch(False)
-
- def ContinueLastSearchReverseDirection(self):
- """Helper function to allow repeats"""
- self.ContinueLastSearch(True)
-
- def ContinueLastSearch(self, reverse):
- """
- Repeats last search command
- """
- args = self.last_search_args
- if args is not None:
- # If "N" we need to reverse the search direction
- if reverse:
- args['forward'] = not args['forward']
-
- args['repeat_search'] = True
-
- self._SearchText(**args)
-
- # Restore search direction (could use copy())
- if reverse:
- args['forward'] = not args['forward']
-
- def _SearchText(self):
- raise NotImplementedError, "To be overridden by derived class"
-
- def GoForwardInHistory(self):
- pageHistDeepness = self.ctrl.presenter.getPageHistory().getDeepness()[1]
- if pageHistDeepness == 0:
- self.visualBell()
- return
- self.ctrl.presenter.getPageHistory().goInHistory(self.count)
-
- def GoBackwardInHistory(self):
- pageHistDeepness = self.ctrl.presenter.getPageHistory().getDeepness()[0]
- if pageHistDeepness == 0:
- self.visualBell()
- return
- self.ctrl.presenter.getPageHistory().goInHistory(-self.count)
-
- def ViewParents(self, direct=False):
- """
- Note: the way this works may change in the future
- """
- presenter = self.ctrl.presenter
- word = self.ctrl.presenter.getWikiWord()
-
- # If no parents give a notification and exit
- if len(presenter.getMainControl().getWikiData(). \
- getParentRelationships(word)) == 0:
-
- self.visualBell()
- return
-
- path = []
- if direct:
- # Is it better to open each page? (slower but history recorded)
- for n in range(self.count):
- parents = presenter.getMainControl().getWikiData().getParentRelationships(word)
-
- if len(parents) == 1:
-
- # No need to loop if two pages are each others parents
- # Loop will end as soon as we are about to go back to
- # a page we were just on (3+ page loops can still occur)
- if n > 0 and path[n-1] == word:
- n = self.count-1
- else:
- word = parents[0]
- path.append(word)
-
- if n == self.count-1:
- presenter.openWikiPage(word, forceTreeSyncFromRoot=True)
- presenter.getMainControl().getMainAreaPanel().\
- showPresenter(presenter)
- presenter.SetFocus()
- return
- else:
- presenter.openWikiPage(word, forceTreeSyncFromRoot=True)
- presenter.getMainControl().getMainAreaPanel().\
- showPresenter(presenter)
- presenter.SetFocus()
- break
-
- presenter.getMainControl().viewParents(word)
-
- def CopyWikiWord(self):
- """
- Copy current wikiword to clipboard
- """
-
- def SwitchTabs(self, left=False):
- """
- Switch to n(th) tab.
- Positive numbers go right, negative left.
-
- If tab end is reached will wrap around
- """
- n = self.count
-
- if left: n = -n
-
- mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel()
- pageCount = mainAreaPanel.GetPageCount()
- currentTabNum = mainAreaPanel.GetSelection() + 1
-
- if currentTabNum + n > pageCount:
- newTabNum = currentTabNum + n % pageCount
- if newTabNum > pageCount:
- newTabNum -= pageCount
- elif currentTabNum + n < 1:
- newTabNum = currentTabNum - (pageCount - n % pageCount)
- if newTabNum < 1:
- newTabNum += pageCount
- else:
- newTabNum = currentTabNum + n
-
- # Switch tab
- mainAreaPanel.SetSelection(newTabNum-1)
- #mainAreaPanel.presenters[mainAreaPanel.GetSelection()].SetFocus()
-
- def CloseCurrentTab(self, junk=None):
- """
- Closes currently focused tab
- """
- mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel()
- wx.CallAfter(mainAreaPanel.closePresenterTab, mainAreaPanel.getCurrentPresenter())
- #return True
-
- def OpenHomePage(self, inNewTab=False):
- """
- Opens home page.
-
- If inNewTab=True opens in a new forground tab
- """
- presenter = self.ctrl.presenter
-
- wikiword = presenter.getMainControl().getWikiDocument().getWikiName()
-
- if inNewTab:
- presenter = self.ctrl.presenter.getMainControl().\
- createNewDocPagePresenterTab()
- presenter.switchSubControl("preview", False)
-
-
- # Now open wiki
- presenter.openWikiPage(wikiword, forceTreeSyncFromRoot=True)
- presenter.getMainControl().getMainAreaPanel().\
- showPresenter(presenter)
- presenter.SetFocus()
-
- def GoogleSelection(self):
- self.StartCmdInput("google ", run_cmd=True)
-
-
- #--------------------------------------------------------------------
- # Misc commands
- #--------------------------------------------------------------------
- def viError(self, text):
- """
- Display a visual error message
-
- """
- self.visualBell(close_delay=10000, text=text)
-
- def visualBell(self, colour="RED", close_delay=100, text="" ):
- """
- Display a visual sign to alert user input has been
- recieved.
-
- Sign is a popup box that overlays the leftmost segment
- of the status bar.
-
- Default is colour is red however a number of different
- colours can be used.
- """
- sb = self.ctrl.presenter.getMainControl().GetStatusBar()
-
- rect = sb.GetFieldRect(0)
- if SystemInfo.isOSX():
- # needed on Mac OSX to avoid cropped text
- rect = wx._core.Rect(rect.x, rect.y - 2, rect.width, rect.height + 4)
-
- rect.SetPosition(sb.ClientToScreen(rect.GetPosition()))
-
- bell = ViVisualBell(self.ctrl, -1, rect, colour, close_delay, text)
-
- def StartCmdInput(self, initial_input=None, run_cmd=False):
- """
- Starts a : cmd input for the currently active (or soon to be
- activated) tab.
-
- """
- # TODO: handle switching between presenters
- selection_range = None
- if self.mode == ViHelper.VISUAL:
- if initial_input is None:
- initial_input = u"'<,'>"
- else:
- initial_input = "{0}{1}".format(initial_input, self.ctrl.GetSelectedText())
- selection_range = self.ctrl.vi._GetSelectionRange()
-
- self.ctrl.presenter.setTabTitleColour("RED")
-
- self.input_window.StartCmd(self.ctrl, self.input_cmd_history,
- initial_input, selection_range=selection_range,
- run_cmd=run_cmd)
-
-
- def RepeatLastSubCmd(self, ignore_flags):
- self.input_window.cmd_parser.RepeatSubCmd(ignore_flags)
-
-
- def EndViInput(self):
- """
- Called when input dialog is closed
- """
- self.ctrl.presenter.setTabTitleColour("BLACK")
- self.ctrl.presenter.getMainControl().windowLayouter.collapseWindow("vi input")
-
- def GotoSelectionStart(self):
- """
- Goto start of selection
- """
- #raise NotImplementedError, "To be overridden by derived class"
-
- def GotoSelectionEnd(self):
- """
- Goto end of selection
- """
- #raise NotImplementedError, "To be overridden by derived class"
-
-
-
-
-
-
- # I will move this to wxHelper later (MB)
- try:
- class wxPopupOrFrame(wx.PopupWindow):
- def __init__(self, parent, id=-1, style=None):
- wx.PopupWindow.__init__(self, parent)
-
- except AttributeError:
- class wxPopupOrFrame(wx.Frame):
- def __init__(self, parent, id=-1,
- style=wx.NO_BORDER|wx.FRAME_NO_TASKBAR|wx.FRAME_FLOAT_ON_PARENT):
- wx.Frame.__init__(self, parent, id, style=style)
-
-
- # NOTE: is popup window available on macs yet?
- class ViVisualBell(wxPopupOrFrame):
- """
- Popupwindow designed to cover the status bar.
-
- Its intention is to give visual feedback that a command has
- been received in cases where no other visual change is observed
-
- It can also be used to display error messages (set close_delay to -1 and
- the popup will remain open until the next keyevent).
- """
-
- COLOURS = {
- "RED" : wx.Colour(255, 0, 0),
- "GREEN" : wx.Colour(0, 255, 0),
- "YELLOW" : wx.Colour(255, 255, 0),
- "BLUE" : wx.Colour(0, 0, 255),
- "WHITE" : wx.Colour(0, 0, 0),
- }
-
-
- def __init__(self, parent, id, rect, colour="RED", close_delay=100,
- text=""):
- wxPopupOrFrame.__init__(self, parent)
- self.SetPosition(rect.GetPosition())
- self.SetSize(rect.GetSize())
- self.SetBackgroundColour(ViVisualBell.COLOURS[colour])
- self.Show()
-
- if text:
- self.text = wx.TextCtrl(self, -1,
- text, style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
- self.text.SetBackgroundColour(ViVisualBell.COLOURS[colour])
- self.text.SetForegroundColour(ViVisualBell.COLOURS["WHITE"])
-
- sizer = wx.BoxSizer(wx.HORIZONTAL)
- sizer.Add(self.text, 1, wx.ALL | wx.EXPAND, 0)
-
- self.SetSizer(sizer)
- self.Layout()
-
- self.Show()
- if close_delay > 0:
- wx.EVT_TIMER(self, GUI_ID.TIMER_VISUAL_BELL_CLOSE,
- self.OnClose)
-
- self.closeTimer = wx.Timer(self, GUI_ID.TIMER_VISUAL_BELL_CLOSE)
- self.closeTimer.Start(close_delay, True)
- else:
- wx.EVT_KEY_DOWN(self.text, self.OnClose)
- wx.EVT_KILL_FOCUS(self.text, self.OnClose)
- self.SetFocus()
-
-
- def OnClose(self, evt):
- #self.timer.Stop()
- self.Destroy()
-
-
-
- class ViHintDialog(wx.Frame):
-
- COLOR_YELLO…
Large files files are truncated, but you can click here to view the full file