PageRenderTime 125ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/pwiki/WikiTxtCtrl.py

https://bitbucket.org/xkjq/wikidpad_svn
Python | 7961 lines | 7593 code | 196 blank | 172 comment | 14 complexity | f4e308862a3cff5d0fbdfcda1e526217 MD5 | raw file
Possible License(s): LGPL-2.1
  1. from __future__ import with_statement
  2. ## import hotshot
  3. ## _prof = hotshot.Profile("hotshot.prf")
  4. import pdb
  5. import traceback, codecs
  6. from cStringIO import StringIO
  7. import string, itertools, contextlib
  8. import re # import pwiki.srePersistent as re
  9. import threading
  10. import subprocess
  11. import textwrap
  12. from os.path import exists, dirname, isfile, isdir, join, basename
  13. from os import rename, unlink, listdir
  14. from time import time, sleep
  15. import wx, wx.stc
  16. from Consts import FormatTypes
  17. from .Utilities import DUMBTHREADSTOP, callInMainThread, ThreadHolder, \
  18. calcResizeArIntoBoundingBox, DictFromFields
  19. from .wxHelper import GUI_ID, getTextFromClipboard, copyTextToClipboard, \
  20. wxKeyFunctionSink, getAccelPairFromKeyDown, appendToMenuByMenuDesc
  21. from . import wxHelper
  22. from . import OsAbstract
  23. from .WikiExceptions import *
  24. from .SystemInfo import isUnicode, isOSX, isLinux, isWindows
  25. from .ParseUtilities import getFootnoteAnchorDict
  26. from .EnhancedScintillaControl import StyleCollector
  27. from .SearchableScintillaControl import SearchableScintillaControl
  28. from . import Configuration
  29. from . import AdditionalDialogs
  30. from . import WikiTxtDialogs
  31. # image stuff
  32. import imghdr
  33. # import WikiFormatting
  34. from . import DocPages
  35. from . import UserActionCoord, WindowLayout
  36. from .SearchAndReplace import SearchReplaceOperation
  37. from . import StringOps
  38. from . import SpellChecker
  39. # from StringOps import * # TODO Remove this
  40. # mbcsDec, uniToGui, guiToUni, \
  41. # wikiWordToLabel, revStr, lineendToInternal, lineendToOs
  42. # NOTE: TEMPORY
  43. import inspect
  44. from ViHelper import ViHintDialog, ViHelper
  45. from collections import defaultdict
  46. try:
  47. import WindowsHacks
  48. except:
  49. if isWindows():
  50. traceback.print_exc()
  51. WindowsHacks = None
  52. # Python compiler flag for float division
  53. CO_FUTURE_DIVISION = 0x2000
  54. class WikiTxtCtrl(SearchableScintillaControl):
  55. NUMBER_MARGIN = 0
  56. FOLD_MARGIN = 2
  57. SELECT_MARGIN = 1
  58. # Not the best of all possible solutions
  59. SUGGESTION_CMD_IDS = [GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_0,
  60. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_1,
  61. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_2,
  62. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_3,
  63. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_4,
  64. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_5,
  65. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_6,
  66. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_7,
  67. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_8,
  68. GUI_ID.CMD_REPLACE_THIS_SPELLING_WITH_SUGGESTION_9]
  69. def __init__(self, presenter, parent, ID):
  70. SearchableScintillaControl.__init__(self, presenter,
  71. presenter.getMainControl(), parent, ID)
  72. self.evalScope = None
  73. self.stylingThreadHolder = ThreadHolder()
  74. self.calltipThreadHolder = ThreadHolder()
  75. self.contextMenuThreadHolder = ThreadHolder()
  76. self.clearStylingCache()
  77. self.pageType = "normal" # The pagetype controls some special editor behaviour
  78. # self.idleCounter = 0 # Used to reduce idle load
  79. # self.loadedDocPage = None
  80. self.lastFont = None
  81. self.ignoreOnChange = False
  82. self.dwellLockCounter = 0 # Don't process dwell start messages if >0
  83. self.wikiLanguageHelper = None
  84. self.templateIdRecycler = wxHelper.IdRecycler()
  85. self.vi = None # Contains ViHandler instance if vi key handling enabled
  86. # If autocompletion word was choosen, how many bytes to delete backward
  87. # before inserting word
  88. self.autoCompBackBytesMap = {} # Maps selected word to number of backbytes
  89. # Inline image
  90. self.tooltip_image = None
  91. # configurable editor settings
  92. config = self.presenter.getConfig()
  93. # TODO: set wrap indent mode (for wx >= 2.9)
  94. self.setWrapMode(config.getboolean("main", "wrap_mode"))
  95. self.SetIndentationGuides(config.getboolean("main", "indentation_guides"))
  96. self.autoIndent = config.getboolean("main", "auto_indent")
  97. self.autoBullets = config.getboolean("main", "auto_bullets")
  98. self.setShowLineNumbers(config.getboolean("main", "show_lineNumbers"))
  99. self.foldingActive = config.getboolean("main", "editor_useFolding")
  100. self.tabsToSpaces = config.getboolean("main", "editor_tabsToSpaces")
  101. # editor settings
  102. self.applyBasicSciSettings()
  103. self.defaultFont = config.get("main", "font",
  104. self.presenter.getDefaultFontFaces()["mono"])
  105. self.CallTipSetForeground(wx.Colour(0, 0, 0))
  106. shorthintDelay = self.presenter.getConfig().getint("main",
  107. "editor_shortHint_delay", 500)
  108. self.SetMouseDwellTime(shorthintDelay)
  109. # Popup menu must be created by Python code to replace clipboard functions
  110. # for unicode build on Win 98/ME
  111. self.UsePopUp(0)
  112. self.SetMarginMask(self.FOLD_MARGIN, wx.stc.STC_MASK_FOLDERS)
  113. self.SetMarginMask(self.NUMBER_MARGIN, 0)
  114. self.SetMarginMask(self.SELECT_MARGIN, 0)
  115. if self.foldingActive:
  116. self.SetMarginWidth(self.FOLD_MARGIN, 16)
  117. else:
  118. self.SetMarginWidth(self.FOLD_MARGIN, 0)
  119. self.SetMarginWidth(self.SELECT_MARGIN, 16)
  120. self.SetMarginWidth(self.NUMBER_MARGIN, 0)
  121. self.SetMarginType(self.FOLD_MARGIN, wx.stc.STC_MARGIN_SYMBOL)
  122. self.SetMarginType(self.SELECT_MARGIN, wx.stc.STC_MARGIN_SYMBOL)
  123. self.SetMarginType(self.NUMBER_MARGIN, wx.stc.STC_MARGIN_NUMBER)
  124. # Optical details
  125. self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_PLUS)
  126. self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_MINUS)
  127. self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_EMPTY)
  128. self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY)
  129. self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_EMPTY)
  130. self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY)
  131. self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY)
  132. self.SetFoldFlags(16)
  133. self.SetMarginSensitive(self.FOLD_MARGIN, True)
  134. self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" %
  135. self.presenter.getDefaultFontFaces())
  136. # self.setFoldingActive(self.foldingActive)
  137. for i in xrange(32):
  138. self.StyleSetEOLFilled(i, True)
  139. # i plan on lexing myself
  140. self.SetLexer(wx.stc.STC_LEX_CONTAINER)
  141. # make the text control a drop target for files and text
  142. self.SetDropTarget(WikiTxtCtrlDropTarget(self))
  143. # self.CmdKeyClearAll()
  144. #
  145. # # register some keyboard commands
  146. # self.CmdKeyAssign(ord('+'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMIN)
  147. # self.CmdKeyAssign(ord('-'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMOUT)
  148. # self.CmdKeyAssign(wx.stc.STC_KEY_HOME, 0, wx.stc.STC_CMD_HOMEWRAP)
  149. # self.CmdKeyAssign(wx.stc.STC_KEY_END, 0, wx.stc.STC_CMD_LINEENDWRAP)
  150. # self.CmdKeyAssign(wx.stc.STC_KEY_HOME, wx.stc.STC_SCMOD_SHIFT,
  151. # wx.stc.STC_CMD_HOMEWRAPEXTEND)
  152. # self.CmdKeyAssign(wx.stc.STC_KEY_END, wx.stc.STC_SCMOD_SHIFT,
  153. # wx.stc.STC_CMD_LINEENDWRAPEXTEND)
  154. #
  155. #
  156. # # Clear all key mappings for clipboard operations
  157. # # PersonalWikiFrame handles them and calls the special clipboard functions
  158. # # instead of the normal ones
  159. # self.CmdKeyClear(wx.stc.STC_KEY_INSERT, wx.stc.STC_SCMOD_CTRL)
  160. # self.CmdKeyClear(wx.stc.STC_KEY_INSERT, wx.stc.STC_SCMOD_SHIFT)
  161. # self.CmdKeyClear(wx.stc.STC_KEY_DELETE, wx.stc.STC_SCMOD_SHIFT)
  162. #
  163. # self.CmdKeyClear(ord('X'), wx.stc.STC_SCMOD_CTRL)
  164. # self.CmdKeyClear(ord('C'), wx.stc.STC_SCMOD_CTRL)
  165. # self.CmdKeyClear(ord('V'), wx.stc.STC_SCMOD_CTRL)
  166. self.SetModEventMask(
  167. wx.stc.STC_MOD_INSERTTEXT | wx.stc.STC_MOD_DELETETEXT)
  168. # set the autocomplete separator
  169. self.AutoCompSetSeparator(1) # ord('~')
  170. self.AutoCompSetTypeSeparator(2) # ord('?')
  171. # register some event handlers
  172. self.presenterListener = wxKeyFunctionSink((
  173. ("saving all pages", self.onSavingAllPages),
  174. ("closing current wiki", self.onClosingCurrentWiki),
  175. ("dropping current wiki", self.onDroppingCurrentWiki),
  176. ("reloaded current doc page", self.onReloadedCurrentPage)
  177. ), self.presenter.getMiscEvent())
  178. self.__sinkApp = wxKeyFunctionSink((
  179. ("options changed", self.onOptionsChanged),
  180. ), wx.GetApp().getMiscEvent(), self)
  181. self.__sinkGlobalConfig = wxKeyFunctionSink((
  182. ("changed configuration", self.onChangedConfiguration),
  183. ), wx.GetApp().getGlobalConfig().getMiscEvent(), self)
  184. # if not self.presenter.getMainControl().isMainWindowConstructed():
  185. # # Install event handler to wait for construction
  186. # self.__sinkMainFrame = wxKeyFunctionSink((
  187. # ("constructed main window", self.onConstructedMainWindow),
  188. # ), self.presenter.getMainControl().getMiscEvent(), self)
  189. # else:
  190. # self.onConstructedMainWindow(None)
  191. self.__sinkMainFrame = wxKeyFunctionSink((
  192. ("idle visible", self.onIdleVisible),
  193. ), self.presenter.getMainControl().getMiscEvent(), self)
  194. # self.presenter.getMiscEvent().addListener(self.presenterListener)
  195. self.wikiPageSink = wxKeyFunctionSink((
  196. ("updated wiki page", self.onWikiPageUpdated), # fired by a WikiPage
  197. ("modified spell checker session", self.OnStyleNeeded), # ???
  198. ("changed read only flag", self.onPageChangedReadOnlyFlag)
  199. ))
  200. wx.stc.EVT_STC_STYLENEEDED(self, ID, self.OnStyleNeeded)
  201. wx.stc.EVT_STC_CHARADDED(self, ID, self.OnCharAdded)
  202. wx.stc.EVT_STC_MODIFIED(self, ID, self.OnModified)
  203. wx.stc.EVT_STC_USERLISTSELECTION(self, ID, self.OnUserListSelection)
  204. wx.stc.EVT_STC_MARGINCLICK(self, ID, self.OnMarginClick)
  205. wx.stc.EVT_STC_DWELLSTART(self, ID, self.OnDwellStart)
  206. wx.stc.EVT_STC_DWELLEND(self, ID, self.OnDwellEnd)
  207. # wx.EVT_LEFT_DOWN(self, self.OnClick)
  208. wx.EVT_MIDDLE_DOWN(self, self.OnMiddleDown)
  209. wx.EVT_LEFT_DCLICK(self, self.OnDoubleClick)
  210. # if config.getboolean("main", "editor_useImeWorkaround", False):
  211. # wx.EVT_CHAR(self, self.OnChar_ImeWorkaround)
  212. wx.EVT_SET_FOCUS(self, self.OnSetFocus)
  213. wx.EVT_CONTEXT_MENU(self, self.OnContextMenu)
  214. # self.incSearchCharStartPos = 0
  215. self.incSearchPreviousHiddenLines = None
  216. self.incSearchPreviousHiddenStartLine = -1
  217. self.onlineSpellCheckerActive = SpellChecker.isSpellCheckSupported() and \
  218. self.presenter.getConfig().getboolean(
  219. "main", "editor_onlineSpellChecker_active", False)
  220. self.optionColorizeSearchFragments = self.presenter.getConfig()\
  221. .getboolean("main", "editor_colorizeSearchFragments", False)
  222. self.onOptionsChanged(None)
  223. # when was a key pressed last. used to check idle time.
  224. self.lastKeyPressed = time()
  225. self.eolMode = self.GetEOLMode()
  226. # Check if modifiers where pressed since last extended logical line move
  227. # See self.moveSelectedLinesOneUp() for the reason of this
  228. self.modifiersPressedSinceExtLogLineMove = False
  229. self.contextMenuTokens = None
  230. self.contextMenuSpellCheckSuggestions = None
  231. # Connect context menu events to functions
  232. wx.EVT_MENU(self, GUI_ID.CMD_UNDO, lambda evt: self.Undo())
  233. wx.EVT_MENU(self, GUI_ID.CMD_REDO, lambda evt: self.Redo())
  234. wx.EVT_MENU(self, GUI_ID.CMD_CLIPBOARD_CUT, lambda evt: self.Cut())
  235. wx.EVT_MENU(self, GUI_ID.CMD_CLIPBOARD_COPY, lambda evt: self.Copy())
  236. wx.EVT_MENU(self, GUI_ID.CMD_CLIPBOARD_PASTE, lambda evt: self.Paste())
  237. wx.EVT_MENU(self, GUI_ID.CMD_CLIPBOARD_PASTE_RAW_HTML,
  238. lambda evt: self.pasteRawHtml())
  239. wx.EVT_MENU(self, GUI_ID.CMD_SELECT_ALL, lambda evt: self.SelectAll())
  240. wx.EVT_MENU(self, GUI_ID.CMD_TEXT_DELETE, lambda evt: self.ReplaceSelection(""))
  241. wx.EVT_MENU(self, GUI_ID.CMD_ZOOM_IN,
  242. lambda evt: self.CmdKeyExecute(wx.stc.STC_CMD_ZOOMIN))
  243. wx.EVT_MENU(self, GUI_ID.CMD_ZOOM_OUT,
  244. lambda evt: self.CmdKeyExecute(wx.stc.STC_CMD_ZOOMOUT))
  245. for sps in self.SUGGESTION_CMD_IDS:
  246. wx.EVT_MENU(self, sps, self.OnReplaceThisSpellingWithSuggestion)
  247. wx.EVT_MENU(self, GUI_ID.CMD_ADD_THIS_SPELLING_SESSION,
  248. self.OnAddThisSpellingToIgnoreSession)
  249. wx.EVT_MENU(self, GUI_ID.CMD_ADD_THIS_SPELLING_GLOBAL,
  250. self.OnAddThisSpellingToIgnoreGlobal)
  251. wx.EVT_MENU(self, GUI_ID.CMD_ADD_THIS_SPELLING_LOCAL,
  252. self.OnAddThisSpellingToIgnoreLocal)
  253. wx.EVT_MENU(self, GUI_ID.CMD_LOGICAL_LINE_UP,
  254. self.OnLogicalLineMove)
  255. wx.EVT_MENU(self, GUI_ID.CMD_LOGICAL_LINE_UP_WITH_INDENT,
  256. self.OnLogicalLineMove)
  257. wx.EVT_MENU(self, GUI_ID.CMD_LOGICAL_LINE_DOWN,
  258. self.OnLogicalLineMove)
  259. wx.EVT_MENU(self, GUI_ID.CMD_LOGICAL_LINE_DOWN_WITH_INDENT,
  260. self.OnLogicalLineMove)
  261. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_THIS, self.OnActivateThis)
  262. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_THIS,
  263. self.OnActivateNewTabThis)
  264. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS,
  265. self.OnActivateNewTabBackgroundThis)
  266. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_WINDOW_THIS,
  267. self.OnActivateNewWindowThis)
  268. # Passing the evt here is not strictly necessary, but it may be
  269. # used in the future
  270. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_THIS_LEFT,
  271. lambda evt: self.OnActivateThis(evt, u"left"))
  272. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_THIS_LEFT,
  273. lambda evt: self.OnActivateNewTabThis(evt, u"left"))
  274. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_LEFT,
  275. lambda evt: self.OnActivateNewTabBackgroundThis(evt, u"left"))
  276. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_THIS_RIGHT,
  277. lambda evt: self.OnActivateThis(evt, u"right"))
  278. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_THIS_RIGHT,
  279. lambda evt: self.OnActivateNewTabThis(evt, u"right"))
  280. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_RIGHT,
  281. lambda evt: self.OnActivateNewTabBackgroundThis(evt, u"right"))
  282. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_THIS_ABOVE,
  283. lambda evt: self.OnActivateThis(evt, u"above"))
  284. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_THIS_ABOVE,
  285. lambda evt: self.OnActivateNewTabThis(evt, u"above"))
  286. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_ABOVE,
  287. lambda evt: self.OnActivateNewTabBackgroundThis(evt, u"above"))
  288. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_THIS_BELOW,
  289. lambda evt: self.OnActivateThis(evt, u"below"))
  290. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_THIS_BELOW,
  291. lambda evt: self.OnActivateNewTabThis(evt, u"below"))
  292. wx.EVT_MENU(self, GUI_ID.CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_BELOW,
  293. lambda evt: self.OnActivateNewTabBackgroundThis(evt, u"below"))
  294. wx.EVT_MENU(self, GUI_ID.CMD_CONVERT_URL_ABSOLUTE_RELATIVE_THIS,
  295. self.OnConvertUrlAbsoluteRelativeThis)
  296. wx.EVT_MENU(self, GUI_ID.CMD_OPEN_CONTAINING_FOLDER_THIS,
  297. self.OnOpenContainingFolderThis)
  298. wx.EVT_MENU(self, GUI_ID.CMD_DELETE_FILE,
  299. self.OnDeleteFile)
  300. wx.EVT_MENU(self, GUI_ID.CMD_RENAME_FILE,
  301. self.OnRenameFile)
  302. wx.EVT_MENU(self, GUI_ID.CMD_CLIPBOARD_COPY_URL_TO_THIS_ANCHOR,
  303. self.OnClipboardCopyUrlToThisAnchor)
  304. wx.EVT_MENU(self, GUI_ID.CMD_SELECT_TEMPLATE, self.OnSelectTemplate)
  305. # 2.8 does not support SetEditable - Define a dummy function for now
  306. if wx.version().startswith("2.8"):
  307. def SetEditable(self, state):
  308. pass
  309. # def __getattr__(self, attr):
  310. # return getattr(self.cnt, attr)
  311. def getLoadedDocPage(self):
  312. return self.presenter.getDocPage()
  313. def close(self):
  314. """
  315. Close the editor (=prepare for destruction)
  316. """
  317. self.stylingThreadHolder.setThread(None)
  318. self.calltipThreadHolder.setThread(None)
  319. self.contextMenuThreadHolder.setThread(None)
  320. self.unloadCurrentDocPage({}) # ?
  321. self.presenterListener.disconnect()
  322. # self.presenter.getMiscEvent().removeListener(self.presenterListener)
  323. # def onConstructedMainWindow(self, evt):
  324. # """
  325. # Now we can register idle handler.
  326. # """
  327. # wx.EVT_IDLE(self, self.OnIdle)
  328. def Copy(self, text=None):
  329. if text is None:
  330. text = self.GetSelectedText()
  331. if len(text) == 0:
  332. return
  333. cbIcept = self.presenter.getMainControl().getClipboardInterceptor()
  334. if cbIcept is not None:
  335. cbIcept.informCopyInWikidPadStart(text=text)
  336. try:
  337. copyTextToClipboard(text)
  338. finally:
  339. cbIcept.informCopyInWikidPadStop()
  340. else:
  341. copyTextToClipboard(text)
  342. def Paste(self):
  343. # Text pasted?
  344. text = getTextFromClipboard()
  345. if text:
  346. self.ReplaceSelection(text)
  347. return True
  348. # File(name)s pasted?
  349. filenames = wxHelper.getFilesFromClipboard()
  350. if filenames is not None:
  351. mc = self.presenter.getMainControl()
  352. paramDict = {"editor": self, "filenames": filenames,
  353. "x": -1, "y": -1, "main control": mc,
  354. "processDirectly": True}
  355. # mc.getUserActionCoord().runAction(
  356. # u"action/editor/this/paste/files/insert/url/ask", paramDict)
  357. mc.getUserActionCoord().reactOnUserEvent(
  358. u"event/paste/editor/files", paramDict)
  359. return True
  360. fs = self.presenter.getWikiDocument().getFileStorage()
  361. imgsav = WikiTxtDialogs.ImagePasteSaver()
  362. imgsav.readOptionsFromConfig(self.presenter.getConfig())
  363. # Bitmap pasted?
  364. bmp = wxHelper.getBitmapFromClipboard()
  365. if bmp is not None:
  366. img = bmp.ConvertToImage()
  367. del bmp
  368. if self.presenter.getConfig().getboolean("main",
  369. "editor_imagePaste_askOnEachPaste", True):
  370. # Options say to present dialog on an image paste operation
  371. # Send image so it can be used for preview
  372. dlg = WikiTxtDialogs.ImagePasteDialog(
  373. self.presenter.getMainControl(), -1, imgsav, img)
  374. try:
  375. dlg.ShowModal()
  376. imgsav = dlg.getImagePasteSaver()
  377. finally:
  378. dlg.Destroy()
  379. destPath = imgsav.saveFile(fs, img)
  380. if destPath is None:
  381. # Couldn't find unused filename or saving denied
  382. return True
  383. url = self.presenter.getWikiDocument().makeAbsPathRelUrl(destPath)
  384. if url is None:
  385. url = u"file:" + StringOps.urlFromPathname(destPath)
  386. self.ReplaceSelection(url)
  387. return True
  388. if not WindowsHacks:
  389. return False
  390. # Windows Meta File pasted?
  391. destPath = imgsav.saveWmfFromClipboardToFileStorage(fs)
  392. if destPath is not None:
  393. url = self.presenter.getWikiDocument().makeAbsPathRelUrl(destPath)
  394. # Windows Meta File pasted?
  395. destPath = imgsav.saveWmfFromClipboardToFileStorage(fs)
  396. if destPath is not None:
  397. url = self.presenter.getWikiDocument().makeAbsPathRelUrl(destPath)
  398. if url is None:
  399. url = u"file:" + StringOps.urlFromPathname(destPath)
  400. self.ReplaceSelection(url)
  401. return True
  402. # Text pasted?
  403. text = getTextFromClipboard()
  404. if text:
  405. self.ReplaceSelection(text)
  406. return True
  407. return False
  408. def pasteRawHtml(self):
  409. rawHtml, url = wxHelper.getHtmlFromClipboard()
  410. if rawHtml:
  411. return self.wikiLanguageHelper.handlePasteRawHtml(self, rawHtml, {})
  412. return False
  413. def onCmdCopy(self, miscevt):
  414. if wx.Window.FindFocus() != self:
  415. return
  416. self.Copy()
  417. def setLayerVisible(self, vis, scName=""):
  418. """
  419. Informs the widget if it is really visible on the screen or not
  420. """
  421. # if vis:
  422. # self.Enable(True)
  423. self.Enable(vis)
  424. def isCharWrap(self):
  425. docPage = self.getLoadedDocPage()
  426. if docPage is not None:
  427. return docPage.getAttributeOrGlobal(u"wrap_type", u"word").lower()\
  428. .startswith(u"char")
  429. else:
  430. return False
  431. def setWrapMode(self, onOrOff, charWrap=None):
  432. if charWrap is None:
  433. charWrap = self.isCharWrap()
  434. if onOrOff:
  435. if charWrap:
  436. self.SetWrapMode(wx.stc.STC_WRAP_CHAR)
  437. else:
  438. self.SetWrapMode(wx.stc.STC_WRAP_WORD)
  439. else:
  440. self.SetWrapMode(wx.stc.STC_WRAP_NONE)
  441. def getWrapMode(self):
  442. return self.GetWrapMode() != wx.stc.STC_WRAP_NONE
  443. def setAutoIndent(self, onOff):
  444. self.autoIndent = onOff
  445. def getAutoIndent(self):
  446. return self.autoIndent
  447. def setAutoBullets(self, onOff):
  448. self.autoBullets = onOff
  449. def getAutoBullets(self):
  450. return self.autoBullets
  451. def setTabsToSpaces(self, onOff):
  452. self.tabsToSpaces = onOff
  453. self.SetUseTabs(not onOff)
  454. def getTabsToSpaces(self):
  455. return self.tabsToSpaces
  456. def setShowLineNumbers(self, onOrOff):
  457. if onOrOff:
  458. self.SetMarginWidth(self.NUMBER_MARGIN,
  459. self.TextWidth(wx.stc.STC_STYLE_LINENUMBER, "_99999"))
  460. self.SetMarginWidth(self.SELECT_MARGIN, 0)
  461. else:
  462. self.SetMarginWidth(self.NUMBER_MARGIN, 0)
  463. self.SetMarginWidth(self.SELECT_MARGIN, 16)
  464. def getShowLineNumbers(self):
  465. return self.GetMarginWidth(self.NUMBER_MARGIN) != 0
  466. def setFoldingActive(self, onOrOff, forceSync=False):
  467. """
  468. forceSync -- when setting folding on, the folding is completed
  469. before function returns iff forceSync is True
  470. """
  471. if onOrOff:
  472. self.SetMarginWidth(self.FOLD_MARGIN, 16)
  473. self.foldingActive = True
  474. if forceSync:
  475. try:
  476. self.applyFolding(self.processFolding(
  477. self.getPageAst(), DUMBTHREADSTOP))
  478. except NoPageAstException:
  479. return
  480. else:
  481. self.OnStyleNeeded(None)
  482. else:
  483. self.SetMarginWidth(self.FOLD_MARGIN, 0)
  484. self.unfoldAll()
  485. self.foldingActive = False
  486. def getFoldingActive(self):
  487. return self.foldingActive
  488. def SetStyles(self, styleFaces = None):
  489. self.SetStyleBits(5)
  490. # create the styles
  491. if styleFaces is None:
  492. styleFaces = self.presenter.getDefaultFontFaces()
  493. config = self.presenter.getConfig()
  494. styles = self.presenter.getMainControl().getPresentationExt()\
  495. .getStyles(styleFaces, config)
  496. for type, style in styles:
  497. self.StyleSetSpec(type, style)
  498. if type == wx.stc.STC_STYLE_CALLTIP:
  499. self.CallTipUseStyle(10)
  500. self.IndicatorSetStyle(2, wx.stc.STC_INDIC_SQUIGGLE)
  501. self.IndicatorSetForeground(2, wx.Colour(255, 0, 0))
  502. def SetText(self, text, emptyUndo=True):
  503. """
  504. Overrides the wxStyledTextCtrl method.
  505. text -- Unicode text content to set
  506. """
  507. self.incSearchCharStartPos = 0
  508. self.clearStylingCache()
  509. self.pageType = "normal"
  510. self.SetSelection(-1, -1)
  511. self.ignoreOnChange = True
  512. if isUnicode():
  513. wx.stc.StyledTextCtrl.SetText(self, text)
  514. else:
  515. wx.stc.StyledTextCtrl.SetText(self,
  516. StringOps.mbcsEnc(text, "replace")[0])
  517. self.ignoreOnChange = False
  518. if emptyUndo:
  519. self.EmptyUndoBuffer()
  520. # self.applyBasicSciSettings()
  521. def replaceText(self, text):
  522. if isUnicode():
  523. wx.stc.StyledTextCtrl.SetText(self, text)
  524. else:
  525. wx.stc.StyledTextCtrl.SetText(self,
  526. StringOps.mbcsEnc(text, "replace")[0])
  527. def replaceTextAreaByCharPos(self, newText, start, end):
  528. text = self.GetText()
  529. bs = self.bytelenSct(text[:start])
  530. be = bs + self.bytelenSct(text[start:end])
  531. self.SetTargetStart(bs)
  532. self.SetTargetEnd(be)
  533. if isUnicode():
  534. self.ReplaceTarget(newText)
  535. else:
  536. self.ReplaceTarget(StringOps.mbcsEnc(newText, "replace")[0])
  537. # text = self.GetText()
  538. # text = text[:pos] + newText + text[(pos + len):]
  539. #
  540. # self.replaceText(text)
  541. def showSelectionByCharPos(self, start, end):
  542. """
  543. Same as SetSelectionByCharPos(), but scrolls to position correctly
  544. """
  545. text = self.GetText()
  546. bs = self.bytelenSct(text[:start])
  547. be = bs + self.bytelenSct(text[start:end])
  548. self.ensureTextRangeByBytePosExpanded(bs, be)
  549. super(WikiTxtCtrl, self).showSelectionByCharPos(start, end)
  550. def applyBasicSciSettings(self):
  551. """
  552. Apply the basic Scintilla settings which are resetted to wrong
  553. default values by some operations
  554. """
  555. if isUnicode():
  556. self.SetCodePage(wx.stc.STC_CP_UTF8)
  557. self.SetTabIndents(True)
  558. self.SetBackSpaceUnIndents(True)
  559. self.SetUseTabs(not self.tabsToSpaces)
  560. self.SetEOLMode(wx.stc.STC_EOL_LF)
  561. tabWidth = self.presenter.getConfig().getint("main",
  562. "editor_tabWidth", 4)
  563. self.SetIndent(tabWidth)
  564. self.SetTabWidth(tabWidth)
  565. self.AutoCompSetFillUps(u":=") # TODO Add '.'?
  566. # self.SetYCaretPolicy(wxSTC_CARET_SLOP, 2)
  567. # self.SetYCaretPolicy(wxSTC_CARET_JUMPS | wxSTC_CARET_EVEN, 4)
  568. self.SetYCaretPolicy(wx.stc.STC_CARET_SLOP | wx.stc.STC_CARET_EVEN, 4)
  569. def saveLoadedDocPage(self):
  570. """
  571. Save loaded wiki page into database. Does not check if dirty
  572. """
  573. if self.getLoadedDocPage() is None:
  574. return
  575. page = self.getLoadedDocPage()
  576. # if not self.loadedDocPage.getDirty()[0]:
  577. # return
  578. # text = self.GetText()
  579. # page.replaceLiveText(text)
  580. if self.presenter.getMainControl().saveDocPage(page):
  581. self.SetSavePoint()
  582. def unloadCurrentDocPage(self, evtprops=None):
  583. ## _prof.start()
  584. # Stop threads
  585. self.stylingThreadHolder.setThread(None)
  586. self.calltipThreadHolder.setThread(None)
  587. docPage = self.getLoadedDocPage()
  588. if docPage is not None:
  589. wikiWord = docPage.getWikiWord()
  590. if wikiWord is not None:
  591. docPage.setPresentation((self.GetCurrentPos(),
  592. self.GetScrollPos(wx.HORIZONTAL),
  593. self.GetScrollPos(wx.VERTICAL)), 0)
  594. docPage.setPresentation((self.getFoldInfo(),), 5)
  595. if docPage.getDirty()[0]:
  596. self.saveLoadedDocPage()
  597. docPage.removeTxtEditor(self)
  598. self.SetDocPointer(None)
  599. self.applyBasicSciSettings()
  600. self.wikiPageSink.disconnect()
  601. self.presenter.setDocPage(None)
  602. self.clearStylingCache()
  603. # self.stylebytes = None
  604. # self.foldingseq = None
  605. # self.pageAst = None
  606. self.pageType = "normal"
  607. ## _prof.stop()
  608. def loadFuncPage(self, funcPage, evtprops=None):
  609. self.unloadCurrentDocPage(evtprops)
  610. # set the editor text
  611. content = None
  612. wikiDataManager = self.presenter.getWikiDocument()
  613. self.presenter.setDocPage(funcPage)
  614. if self.getLoadedDocPage() is None:
  615. return # TODO How to handle?
  616. globalAttrs = wikiDataManager.getWikiData().getGlobalAttributes()
  617. # get the font that should be used in the editor
  618. font = globalAttrs.get("global.font", self.defaultFont)
  619. # set the styles in the editor to the font
  620. if self.lastFont != font:
  621. faces = self.presenter.getDefaultFontFaces().copy()
  622. faces["mono"] = font
  623. self.SetStyles(faces)
  624. self.lastEditorFont = font
  625. # this updates depending on attribute "wrap_type" (word or character)
  626. self.setWrapMode(self.getWrapMode())
  627. # p2 = evtprops.copy()
  628. # p2.update({"loading current page": True})
  629. # self.pWiki.fireMiscEventProps(p2) # TODO Remove this hack
  630. self.wikiPageSink.setEventSource(self.getLoadedDocPage().getMiscEvent())
  631. otherEditor = self.getLoadedDocPage().getTxtEditor()
  632. if otherEditor is not None:
  633. # Another editor contains already this page, so share its
  634. # Scintilla document object for synchronized editing
  635. self.SetDocPointer(otherEditor.GetDocPointer())
  636. self.applyBasicSciSettings()
  637. else:
  638. # Load content
  639. try:
  640. content = self.getLoadedDocPage().getLiveText()
  641. except WikiFileNotFoundException, e:
  642. assert 0 # TODO
  643. # now fill the text into the editor
  644. self.SetReadOnly(False)
  645. self.SetText(content)
  646. if self.wikiLanguageHelper is None or \
  647. self.wikiLanguageHelper.getWikiLanguageName() != \
  648. self.getLoadedDocPage().getWikiLanguageName():
  649. wx.GetApp().freeWikiLanguageHelper(self.wikiLanguageHelper)
  650. self.wikiLanguageHelper = self.getLoadedDocPage().createWikiLanguageHelper()
  651. self.getLoadedDocPage().addTxtEditor(self)
  652. self._checkForReadOnly()
  653. self.presenter.setTitle(self.getLoadedDocPage().getTitle())
  654. def loadWikiPage(self, wikiPage, evtprops=None):
  655. """
  656. Save loaded page, if necessary, then load wikiPage into editor
  657. """
  658. self.unloadCurrentDocPage(evtprops)
  659. # set the editor text
  660. wikiDataManager = self.presenter.getWikiDocument()
  661. self.presenter.setDocPage(wikiPage)
  662. docPage = self.getLoadedDocPage()
  663. if docPage is None:
  664. return # TODO How to handle?
  665. self.wikiPageSink.setEventSource(docPage.getMiscEvent())
  666. otherEditor = docPage.getTxtEditor()
  667. if otherEditor is not None:
  668. # Another editor contains already this page, so share its
  669. # Scintilla document object for synchronized editing
  670. self.SetDocPointer(otherEditor.GetDocPointer())
  671. self.applyBasicSciSettings()
  672. else:
  673. # Load content
  674. try:
  675. content = docPage.getLiveText()
  676. except WikiFileNotFoundException, e:
  677. assert 0 # TODO
  678. # now fill the text into the editor
  679. self.SetReadOnly(False)
  680. self.setTextAgaUpdated(content)
  681. if self.wikiLanguageHelper is None or \
  682. self.wikiLanguageHelper.getWikiLanguageName() != \
  683. docPage.getWikiLanguageName():
  684. wx.GetApp().freeWikiLanguageHelper(self.wikiLanguageHelper)
  685. self.wikiLanguageHelper = docPage.createWikiLanguageHelper()
  686. docPage.addTxtEditor(self)
  687. self._checkForReadOnly()
  688. if evtprops is None:
  689. evtprops = {}
  690. p2 = evtprops.copy()
  691. p2.update({"loading wiki page": True, "wikiPage": docPage})
  692. self.presenter.fireMiscEventProps(p2) # TODO Remove this hack
  693. # get the font that should be used in the editor
  694. font = docPage.getAttributeOrGlobal("font", self.defaultFont)
  695. # set the styles in the editor to the font
  696. if self.lastFont != font:
  697. faces = self.presenter.getDefaultFontFaces().copy()
  698. faces["mono"] = font
  699. self.SetStyles(faces)
  700. self.lastEditorFont = font
  701. # this updates depending on attribute "wrap_type" (word or character)
  702. self.setWrapMode(self.getWrapMode())
  703. self.pageType = docPage.getAttributes().get(u"pagetype",
  704. [u"normal"])[-1]
  705. if self.pageType == u"normal":
  706. if not docPage.isDefined():
  707. # This is a new, not yet defined page, so go to the end of page
  708. self.GotoPos(self.GetLength())
  709. else:
  710. anchor = evtprops.get("anchor")
  711. if anchor:
  712. # Scroll page according to the anchor
  713. pageAst = self.getPageAst()
  714. anchorNodes = pageAst.iterDeepByName("anchorDef")
  715. for node in anchorNodes:
  716. if node.anchorLink == anchor:
  717. self.gotoCharPos(node.pos + node.strLength)
  718. break
  719. else:
  720. anchor = None # Not found
  721. if not anchor:
  722. # Is there a position given in the eventprops?
  723. firstcharpos = evtprops.get("firstcharpos", -1)
  724. if firstcharpos != -1:
  725. charlength = max(0, evtprops.get("charlength", 0))
  726. self.showSelectionByCharPos(firstcharpos,
  727. firstcharpos + charlength)
  728. anchor = True
  729. if not anchor:
  730. # see if there is a saved position for this page
  731. prst = docPage.getPresentation()
  732. lastPos, scrollPosX, scrollPosY = prst[0:3]
  733. foldInfo = prst[5]
  734. self.setFoldInfo(foldInfo)
  735. self.GotoPos(lastPos)
  736. self.scrollXY(scrollPosX, scrollPosY)
  737. else:
  738. self.handleSpecialPageType()
  739. self.presenter.setTitle(docPage.getTitle())
  740. def handleSpecialPageType(self):
  741. # self.allowRectExtend(self.pageType != u"texttree")
  742. if self.pageType == u"form":
  743. self.GotoPos(0)
  744. self._goToNextFormField()
  745. return True
  746. return False
  747. def onReloadedCurrentPage(self, miscevt):
  748. """
  749. Called when already loaded page should be loaded again, mainly
  750. interesting if a link with anchor is activated
  751. """
  752. if not self.presenter.isCurrent():
  753. return
  754. anchor = miscevt.get("anchor")
  755. if not anchor:
  756. if self.pageType == u"normal":
  757. # Is there a position given in the eventprops?
  758. firstcharpos = miscevt.get("firstcharpos", -1)
  759. if firstcharpos != -1:
  760. charlength = max(0, miscevt.get("charlength", 0))
  761. self.showSelectionByCharPos(firstcharpos,
  762. firstcharpos + charlength)
  763. return
  764. # if not anchor:
  765. # return
  766. docPage = self.getLoadedDocPage()
  767. if not docPage.isDefined():
  768. return
  769. if self.wikiLanguageHelper is None or \
  770. self.wikiLanguageHelper.getWikiLanguageName() != \
  771. docPage.getWikiLanguageName():
  772. wx.GetApp().freeWikiLanguageHelper(self.wikiLanguageHelper)
  773. self.wikiLanguageHelper = docPage.createWikiLanguageHelper()
  774. if self.pageType == u"normal":
  775. # Scroll page according to the anchor
  776. try:
  777. anchorNodes = self.getPageAst().iterDeepByName("anchorDef")
  778. anchorNodes = self.getPageAst().iterDeepByName("anchorDef")
  779. for node in anchorNodes:
  780. if node.anchorLink == anchor:
  781. self.gotoCharPos(node.pos + node.strLength)
  782. break
  783. # else:
  784. # anchor = None # Not found
  785. except NoPageAstException:
  786. return
  787. def _checkForReadOnly(self):
  788. """
  789. Set/unset read-only mode of editor according to read-only state of page.
  790. """
  791. docPage = self.getLoadedDocPage()
  792. if docPage is None:
  793. self.SetReadOnly(True)
  794. else:
  795. self.SetReadOnly(docPage.isReadOnlyEffect())
  796. def _getColorFromOption(self, option, defColTuple):
  797. """
  798. Helper for onOptionsChanged() to read a color from an option
  799. and create a wx.Colour object from it.
  800. """
  801. coltuple = StringOps.colorDescToRgbTuple(self.presenter.getConfig().get(
  802. "main", option))
  803. if coltuple is None:
  804. coltuple = defColTuple
  805. return wx.Colour(*coltuple)
  806. def onPageChangedReadOnlyFlag(self, miscevt):
  807. self._checkForReadOnly()
  808. def onOptionsChanged(self, miscevt):
  809. faces = self.presenter.getDefaultFontFaces().copy()
  810. if isinstance(self.getLoadedDocPage(),
  811. (DocPages.WikiPage, DocPages.AliasWikiPage)):
  812. font = self.getLoadedDocPage().getAttributeOrGlobal("font",
  813. self.defaultFont)
  814. faces["mono"] = font
  815. self.lastEditorFont = font # ???
  816. self._checkForReadOnly()
  817. self.SetStyles(faces)
  818. color = self._getColorFromOption("editor_bg_color", (255, 255, 255))
  819. for i in xrange(32):
  820. self.StyleSetBackground(i, color)
  821. self.StyleSetBackground(wx.stc.STC_STYLE_DEFAULT, color)
  822. self.SetSelForeground(True, self._getColorFromOption(
  823. "editor_selection_fg_color", (0, 0, 0)))
  824. self.SetSelBackground(True, self._getColorFromOption(
  825. "editor_selection_bg_color", (192, 192, 192)))
  826. self.SetCaretForeground(self._getColorFromOption(
  827. "editor_caret_color", (0, 0, 0)))
  828. # Set default color (especially for folding lines)
  829. self.StyleSetForeground(wx.stc.STC_STYLE_DEFAULT, self._getColorFromOption(
  830. "editor_plaintext_color", (0, 0, 0)))
  831. self.StyleSetBackground(wx.stc.STC_STYLE_LINENUMBER, self._getColorFromOption(
  832. "editor_margin_bg_color", (212, 208, 200)))
  833. shorthintDelay = self.presenter.getConfig().getint("main",
  834. "editor_shortHint_delay", 500)
  835. self.SetMouseDwellTime(shorthintDelay)
  836. tabWidth = self.presenter.getConfig().getint("main",
  837. "editor_tabWidth", 4)
  838. self.SetIndent(tabWidth)
  839. self.SetTabWidth(tabWidth)
  840. # this updates depending on attribute "wrap_type" (word or character)
  841. self.setWrapMode(self.getWrapMode())
  842. # To allow switching vi keys on and off without restart
  843. use_vi_navigation = self.presenter.getConfig().getboolean("main",
  844. "editor_compatibility_ViKeys", False)
  845. self.Unbind(wx.EVT_CHAR)
  846. self.Unbind(wx.EVT_KEY_DOWN)
  847. self.Unbind(wx.EVT_KEY_UP)
  848. self.Unbind(wx.EVT_LEFT_UP)
  849. #self.Unbind(wx.EVT_SCROLLWIN)
  850. self.Unbind(wx.EVT_MOUSEWHEEL)
  851. if use_vi_navigation:
  852. if self.vi is None:
  853. self.vi = ViHandler(self)
  854. if not isLinux():
  855. # Need to merge with OnChar_ImeWorkaround
  856. self.Bind(wx.EVT_CHAR, self.vi.OnChar)
  857. self.Bind(wx.EVT_KEY_DOWN, self.vi.OnViKeyDown)
  858. self.Bind(wx.EVT_LEFT_DOWN, self.vi.OnMouseClick)
  859. self.Bind(wx.EVT_LEFT_UP, self.vi.OnLeftMouseUp)
  860. # TODO: Replace with seperate scroll events
  861. #self.Bind(wx.EVT_SCROLLWIN, self.vi.OnScroll)
  862. if self.vi.settings["caret_scroll"]:
  863. self.Bind(wx.EVT_MOUSEWHEEL, self.vi.OnMouseScroll)
  864. # Should probably store shortcut state in a global
  865. # variable otherwise this will be run each time
  866. # a new tab is opened
  867. wx.CallAfter(self.vi._enableMenuShortcuts, False)
  868. else:
  869. if self.vi is not None:
  870. self.vi.TurnOff()
  871. self.vi = None
  872. if self.presenter.getConfig().getboolean("main",
  873. "editor_useImeWorkaround", False):
  874. wx.EVT_CHAR(self, self.OnChar_ImeWorkaround)
  875. self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  876. self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  877. self.Bind(wx.EVT_LEFT_DOWN, self.OnClick)
  878. self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
  879. if not isLinux():
  880. self.Bind(wx.EVT_CHAR, None)
  881. def onChangedConfiguration(self, miscevt):
  882. """
  883. Called when global configuration was changed. Most things are processed
  884. by onOptionsChanged so only the online spell checker switch must be
  885. handled here.
  886. """
  887. restyle = False
  888. newSetting = SpellChecker.isSpellCheckSupported() and \
  889. self.presenter.getConfig().getboolean(
  890. "main", "editor_onlineSpellChecker_active", False)
  891. if newSetting != self.onlineSpellCheckerActive:
  892. self.onlineSpellCheckerActive = newSetting
  893. restyle = True
  894. newSetting = self.presenter.getConfig()\
  895. .getboolean("main", "editor_colorizeSearchFragments", False)
  896. if newSetting != self.optionColorizeSearchFragments:
  897. self.optionColorizeSearchFragments = newSetting
  898. restyle = True
  899. if restyle:
  900. self.OnStyleNeeded(None)
  901. def onWikiPageUpdated(self, miscevt):
  902. if self.getLoadedDocPage() is None or \
  903. not isinstance(self.getLoadedDocPage(),
  904. (DocPages.WikiPage, DocPages.AliasWikiPage)):
  905. return
  906. # get the font that should be used in the editor
  907. font = self.getLoadedDocPage().getAttributeOrGlobal("font",
  908. self.defaultFont)
  909. # set the styles in the editor to the font
  910. if self.lastFont != font:
  911. faces = self.presenter.getDefaultFontFaces().copy()
  912. faces["mono"] = font
  913. self.SetStyles(faces)
  914. self.lastEditorFont = font
  915. # this updates depending on attribute "wrap_type" (word or character)
  916. self.setWrapMode(self.getWrapMode())
  917. self.pageType = self.getLoadedDocPage().getAttributes().get(u"pagetype",
  918. [u"normal"])[-1]
  919. def handleInvalidFileSignature(self, docPage):
  920. """
  921. Called directly from a doc page to repair the editor state if an
  922. invalid file signature was detected.
  923. docPage -- calling docpage
  924. """
  925. if docPage is not self.getLoadedDocPage() or \
  926. not isinstance(docPage,
  927. (DocPages.DataCarryingPage, DocPages.AliasWikiPage)):
  928. return
  929. sd, ud = docPage.getDirty()
  930. if sd:
  931. return # TODO What to do on conflict?
  932. content = docPage.getContent()
  933. docPage.setEditorText(content, dirty=False)
  934. self.ignoreOnChange = True
  935. # TODO: Store/restore selection & scroll pos.
  936. self.setTextAgaUpdated(content)
  937. self.ignoreOnChange = False
  938. def onSavingAllPages(self, miscevt):
  939. if self.getLoadedDocPage() is not None and (
  940. self.getLoadedDocPage().getDirty()[0] or miscevt.get("force",
  941. False)):
  942. self.saveLoadedDocPage()
  943. def onClosingCurrentWiki(self, miscevt):
  944. self.unloadCurrentDocPage()
  945. def onDroppingCurrentWiki(self, miscevt):
  946. """
  947. An access error occurred. Get rid of any data without trying to save
  948. it.
  949. """
  950. if self.getLoadedDocPage() is not None:
  951. self.wikiPageSink.disconnect()
  952. self.SetDocPointer(None)
  953. self.applyBasicSciSettings()
  954. self.getLoadedDocPage().removeTxtEditor(self)
  955. self.presenter.setDocPage(None)
  956. # self.loadedDocPage = None
  957. self.pageType = "normal"
  958. def OnStyleNeeded(self, evt):
  959. "Styles the text of the editor"
  960. docPage = self.getLoadedDocPage()
  961. if docPage is None:
  962. # This avoids further request from STC:
  963. self.stopStcStyler()
  964. return
  965. # get the text to regex against (use doc pages getLiveText because
  966. # it's cached
  967. text = docPage.getLiveText() # self.GetText()
  968. textlen = len(text)
  969. t = self.stylingThreadHolder.getThread()
  970. if t is not None:
  971. self.stylingThreadHolder.setThread(None)
  972. self.clearStylingCache()
  973. if textlen < self.presenter.getConfig().getint(
  974. "main", "sync_highlight_byte_limit"):
  975. # if True:
  976. # Synchronous styling
  977. self.stylingThreadHolder.setThread(None)
  978. self.buildStyling(text, 0, threadstop=DUMBTHREADSTOP)
  979. self.applyStyling(self.stylebytes) # TODO Necessary?
  980. # We can't call applyFolding directly because this in turn
  981. # calls repairFoldingVisibility which can't work while in
  982. # EVT_STC_STYLENEEDED event (at least for wxPython 2.6.2)
  983. # storeStylingAndAst() sends a StyleDoneEvent instead
  984. if self.getFoldingActive():
  985. self.storeStylingAndAst(None, self.foldingseq)
  986. else:
  987. # Asynchronous styling
  988. # This avoids further request from STC:
  989. self.stopStcStyler()
  990. sth = self.stylingThreadHolder
  991. delay = self.presenter.getConfig().getfloat(
  992. "main", "async_highlight_delay")
  993. t = threading.Thread(None, self.buildStyling, args = (text, delay, sth))
  994. sth.setThread(t)
  995. t.setDaemon(True)
  996. t.start()
  997. def _fillTemplateMenu(self, menu):
  998. idRecycler = self.templateIdRecycler
  999. idRecycler.clearAssoc()
  1000. config = self.presenter.getConfig()
  1001. templateRePat = config.get(u"main", u"template_pageNamesRE",
  1002. u"^template/")
  1003. try:
  1004. templateRe = re.compile(templateRePat, re.DOTALL | re.UNICODE)
  1005. except re.error:
  1006. templateRe = re.compile(u"^template/", re.DOTALL | re.UNICODE)
  1007. wikiDocument = self.presenter.getWikiDocument()
  1008. templateNames = [n for n in wikiDocument.getAllDefinedWikiPageNames()
  1009. if templateRe.search(n)]
  1010. wikiDocument.getCollator().sort(templateNames)
  1011. for tn in templateNames:
  1012. menuID, reused = idRecycler.assocGetIdAndReused(tn)
  1013. if not reused:
  1014. # For a new id, an event must be set
  1015. wx.EVT_MENU(self, menuID, self.OnTemplateUsed)
  1016. menu.Append(menuID, StringOps.uniToGui(tn))
  1017. def OnTemplateUsed(self, evt):
  1018. docPage = self.getLoadedDocPage()
  1019. if docPage is None:
  1020. return
  1021. templateName = self.templateIdRecycler.get(evt.GetId())
  1022. if templateName is None:
  1023. return
  1024. wikiDocument = self.presenter.getWikiDocument()
  1025. templatePage = wikiDocument.getWikiPage(templateName)
  1026. content = self.getLoadedDocPage().getContentOfTemplate(templatePage,
  1027. templatePage)
  1028. docPage.setMetaDataFromTemplate(templatePage)
  1029. self.SetText(content, emptyUndo=False)
  1030. self.pageType = docPage.getAttributes().get(u"pagetype",
  1031. [u"normal"])[-1]
  1032. self.handleSpecialPageType()
  1033. # TODO Handle form mode
  1034. self.presenter.informEditorTextChanged(self)
  1035. def OnSelectTemplate(self, evt):
  1036. docPage = self.getLoadedDocPage()
  1037. if docPage is None:
  1038. return
  1039. if not isinstance(docPage, DocPages.WikiPage):
  1040. return
  1041. if not docPage.isDefined() and not docPage.getDirty()[0]:
  1042. title = _(u"Select Template")
  1043. else:
  1044. title = _(u"Select Template (deletes current content!)")
  1045. templateName = AdditionalDialogs.SelectWikiWordDialog.runModal(
  1046. self.presenter.getMainControl(), self, -1,
  1047. title=title)
  1048. if templateName is None:
  1049. return
  1050. wikiDocument = self.presenter.getWikiDocument()
  1051. templatePage = wikiDocument.getWikiPage(templateName)
  1052. content = self.getLoadedDocPage().getContentOfTemplate(templatePage,
  1053. templatePage)
  1054. docPage.setMetaDataFromTemplate(templatePage)
  1055. self.SetText(content, emptyUndo=False)
  1056. self.pageType = docPage.getAttributes().get(u"pagetype",
  1057. [u"normal"])[-1]
  1058. self.handleSpecialPageType()
  1059. self.presenter.informEditorTextChanged(self)
  1060. # TODO Wrong reaction on press of context menu button on keyboard
  1061. def OnContextMenu(self, evt):
  1062. """
  1063. Function to handle context menu events
  1064. As generating the context menu can take a long time, as the
  1065. page needs to be styled, we do it in its own thread
  1066. (self.contextMenuThreadHolder) so the GUI remains responsive
  1067. """
  1068. # Find out the mouse position before starting the thread
  1069. mousePos = self.ScreenToClient(wx.GetMousePosition())
  1070. if mousePos and mousePos != wx.DefaultPosition:
  1071. linkBytePos = self.PositionFromPoint(mousePos)
  1072. else:
  1073. linkBytePos = self.GetCurrentPos()
  1074. # TODO:
  1075. # If thread is already running we don't actually need to
  1076. # restyle the page
  1077. t = self.contextMenuThreadHolder.getThread()
  1078. if t is not None:
  1079. self.contextMenuThreadHolder.setThread(None)
  1080. # Disable editing of the page until the menu has been shown
  1081. # The other option is to kill the thread if the page content
  1082. # is changed
  1083. self.SetEditable(False)
  1084. cth = self.contextMenuThreadHolder
  1085. t = threading.Thread(None, self.OnContextMenuThread,
  1086. args = (mousePos, linkBytePos, cth))
  1087. cth.setThread(t)
  1088. t.setDaemon(True)
  1089. t.start()
  1090. def OnContextMenuThread(self, mousePos, linkBytePos, threadstop):
  1091. """
  1092. Function to be called in its own thread that generates the
  1093. context menu
  1094. TODO: ?halt thread if we move to a different page?
  1095. """
  1096. menu = None
  1097. try:
  1098. # Hack so we can use OnContextMenu - otherwise the menu can get
  1099. # closed by some unknown event - probably mouseup
  1100. # required on linux (and maybe other platforms)
  1101. sleep(0.1)
  1102. leftFold = 0
  1103. for i in range(self.FOLD_MARGIN):
  1104. leftFold += self.GetMarginWidth(i)
  1105. rightFold = leftFold + self.GetMarginWidth(self.FOLD_MARGIN)
  1106. menu = wxHelper.EnhancedPlgSuppMenu(self)
  1107. contextInfo = DictFromFields()
  1108. contextInfo.mousePos = mousePos
  1109. contextInfo.txtCtrl = self
  1110. threadstop.testValidThread()
  1111. if mousePos.x >= leftFold and mousePos.x < rightFold:
  1112. # Right click in fold margin
  1113. contextName = "contextMenu/editor/foldMargin"
  1114. appendToMenuByMenuDesc(menu, FOLD_MENU)
  1115. else:
  1116. contextName = "contextMenu/editor/textArea"
  1117. nodes = self.getTokensForMousePos(linkBytePos=linkBytePos)
  1118. contextInfo.tokens = nodes
  1119. self.contextMenuTokens = nodes
  1120. addActivateItem = False
  1121. addFileUrlItem = False
  1122. addWikiUrlItem = False
  1123. addUrlToClipboardItem = False
  1124. unknownWord = None
  1125. for node in nodes:
  1126. if node.name == "wikiWord":
  1127. addActivateItem = True
  1128. contextInfo.inWikiWord = True
  1129. elif node.name == "urlLink":
  1130. addActivateItem = True
  1131. if node.url.startswith(u"file:") or \
  1132. node.url.startswith(u"rel://"):
  1133. addFileUrlItem = True
  1134. contextInfo.inFileUrl = True
  1135. elif node.url.startswith(u"wiki:") or \
  1136. node.url.startswith(u"wikirel://"):
  1137. addWikiUrlItem = True
  1138. contextInfo.inWikiUrl = True
  1139. elif node.name == "insertion" and node.key == u"page":
  1140. addActivateItem = True
  1141. contextInfo.inPageInsertion = True
  1142. elif node.name == "anchorDef":
  1143. addUrlToClipboardItem = True
  1144. contextInfo.inAnchorDef = True
  1145. elif node.name == "unknownSpelling":
  1146. unknownWord = node.getText()
  1147. contextInfo.inUnknownSpelling = True
  1148. threadstop.testValidThread()
  1149. if unknownWord:
  1150. # Right click on a word not in spelling dictionary
  1151. spellCheckerSession = self.presenter.getWikiDocument()\
  1152. .getOnlineSpellCheckerSession()
  1153. spellCheckerSession.setCurrentDocPage(self.getLoadedDocPage())
  1154. if spellCheckerSession:
  1155. # Show suggestions if available (up to first 5)
  1156. suggestions = spellCheckerSession.suggest(unknownWord)[:5]
  1157. #spellCheckerSession.close()
  1158. if len(suggestions) > 0:
  1159. for s, mid in zip(suggestions, self.SUGGESTION_CMD_IDS):
  1160. menuitem = wx.MenuItem(menu, mid, s)
  1161. font = menuitem.GetFont()
  1162. font.SetWeight(wx.FONTWEIGHT_BOLD)
  1163. menuitem.SetFont(font)
  1164. menu.AppendItem(menuitem)
  1165. self.contextMenuSpellCheckSuggestions = suggestions
  1166. # Show other spelling menu items
  1167. appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_SPELLING)
  1168. threadstop.testValidThread()
  1169. appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_BASE)
  1170. if addActivateItem:
  1171. appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_ACTIVATE)
  1172. # Check if their are any surrounding viewports that we can use
  1173. # TODO: we should be able to use (create) unused viewports
  1174. viewports = self.presenter.getMainControl().\
  1175. getMainAreaPanel().getPossibleTabCtrlDirections(
  1176. self.presenter)
  1177. for direction in viewports:
  1178. if viewports[direction] is not None:
  1179. appendToMenuByMenuDesc(menu,
  1180. _CONTEXT_MENU_INTEXT_ACTIVATE_DIRECTION[
  1181. direction])
  1182. if addFileUrlItem:
  1183. appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_FILE_URL)
  1184. elif addWikiUrlItem:
  1185. appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_WIKI_URL)
  1186. if addUrlToClipboardItem:
  1187. appendToMenuByMenuDesc(menu,
  1188. _CONTEXT_MENU_INTEXT_URL_TO_CLIPBOARD)
  1189. threadstop.testValidThread()
  1190. docPage = self.getLoadedDocPage()
  1191. if isinstance(docPage, DocPages.WikiPage):
  1192. if not docPage.isDefined() and not docPage.getDirty()[0]:
  1193. templateSubmenu = wx.Menu()
  1194. self._fillTemplateMenu(templateSubmenu)
  1195. appendToMenuByMenuDesc(templateSubmenu,
  1196. _CONTEXT_MENU_SELECT_TEMPLATE_IN_TEMPLATE_MENU)
  1197. menu.AppendSeparator()
  1198. menu.AppendMenu(wx.NewId(), _(u'Use Template'),
  1199. templateSubmenu)
  1200. else:
  1201. appendToMenuByMenuDesc(menu,
  1202. _CONTEXT_MENU_SELECT_TEMPLATE)
  1203. appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_BOTTOM)
  1204. threadstop.testValidThread()
  1205. # Enable/Disable appropriate menu items
  1206. item = menu.FindItemById(GUI_ID.CMD_UNDO)
  1207. if item: item.Enable(self.CanUndo())
  1208. item = menu.FindItemById(GUI_ID.CMD_REDO)
  1209. if item: item.Enable(self.CanRedo())
  1210. cancopy = self.GetSelectionStart() != self.GetSelectionEnd()
  1211. item = menu.FindItemById(GUI_ID.CMD_TEXT_DELETE)
  1212. if item: item.Enable(cancopy and not self.GetReadOnly())
  1213. item = menu.FindItemById(GUI_ID.CMD_CLIPBOARD_CUT)
  1214. if item: item.Enable(cancopy and not self.GetReadOnly())
  1215. item = menu.FindItemById(GUI_ID.CMD_CLIPBOARD_COPY)
  1216. if item: item.Enable(cancopy)
  1217. item = menu.FindItemById(GUI_ID.CMD_CLIPBOARD_PASTE)
  1218. if item: item.Enable(self.CanPaste())
  1219. contextInfo = contextInfo.getDict()
  1220. menu.setContext(contextName, contextInfo)
  1221. wx.GetApp().getModifyMenuDispatcher().dispatch(contextName,
  1222. contextInfo, menu)
  1223. threadstop.testValidThread()
  1224. # Should be called in main thread - otherwise we
  1225. # can get issues with threads not completing
  1226. callInMainThread(self.ShowPopupMenu, menu, mousePos)
  1227. except NotCurrentThreadException:
  1228. # Is this necessary ?
  1229. if menu is not None:
  1230. #callInMainThread(menu.Destroy)
  1231. menu.Destroy()
  1232. # NOTE: we don't reenable editing here as if the thread is
  1233. # killed it is because another has been started, right?
  1234. def ShowPopupMenu(self, menu, menu_pos):
  1235. """
  1236. Function that opens the popup menu
  1237. Must be called in the main thread
  1238. """
  1239. self.SetEditable(True)
  1240. # Dwell lock to avoid image popup while context menu is shown
  1241. with self.dwellLock():
  1242. # Show menu
  1243. self.PopupMenu(menu, menu_pos)
  1244. menu.close()
  1245. menu.Destroy()
  1246. self.contextMenuTokens = None
  1247. self.contextMenuSpellCheckSuggestions = None
  1248. #wx.CallAfter(self.SetEditable, True)
  1249. def _goToNextFormField(self):
  1250. """
  1251. If pagetype is "form" this is called when user presses TAB in
  1252. text editor and after loading a form page
  1253. """
  1254. searchOp = SearchReplaceOperation()
  1255. searchOp.wikiWide = False
  1256. searchOp.wildCard = 'regex'
  1257. searchOp.caseSensitive = True
  1258. searchOp.searchStr = "&&[a-zA-Z]"
  1259. text = self.GetText()
  1260. charStartPos = len(self.GetTextRange(0, self.GetSelectionEnd()))
  1261. while True:
  1262. start, end = searchOp.searchText(text, charStartPos)[:2]
  1263. if start is None:
  1264. return False
  1265. fieldcode = text[start + 2]
  1266. if fieldcode == "i":
  1267. self.showSelectionByCharPos(start, end)
  1268. return True
  1269. charStartPos = end
  1270. def handleDropText(self, x, y, text):
  1271. if x != -1:
  1272. # TODO: should probably make this optional
  1273. # If a internal link is droped activate it instead
  1274. if "internaljump/wikipage/" in text and \
  1275. "\n" not in text and \
  1276. " " not in text:
  1277. page = StringOps.flexibleUrlUnquote(text.split("internaljump/")[-1])
  1278. self.presenter.makeCurrent()
  1279. self.presenter.getMainControl().activatePageByUnifiedName(
  1280. page)
  1281. else:
  1282. # Real drop
  1283. self.DoDropText(x, y, text)
  1284. self.gotoCharPos(self.GetSelectionCharPos()[1], scroll=False)
  1285. else:
  1286. self.ReplaceSelection(text)
  1287. self.SetFocus()
  1288. def clearStylingCache(self):
  1289. self.stylebytes = None
  1290. self.foldingseq = None
  1291. # self.pageAst = None
  1292. def stopStcStyler(self):
  1293. """
  1294. Stops further styling requests from Scintilla until text is modified
  1295. """
  1296. self.StartStyling(self.GetLength(), 0xff)
  1297. self.SetStyling(0, 0)
  1298. def storeStylingAndAst(self, stylebytes, foldingseq, styleMask=0xff):
  1299. self.stylebytes = stylebytes
  1300. # self.pageAst = pageAst
  1301. self.foldingseq = foldingseq
  1302. def putStyle():
  1303. if stylebytes:
  1304. self.applyStyling(stylebytes, styleMask)
  1305. if foldingseq:
  1306. self.applyFolding(foldingseq)
  1307. wx.CallAfter(putStyle)
  1308. # self.AddPendingEvent(StyleDoneEvent(stylebytes, foldingseq))
  1309. def buildStyling(self, text, delay, threadstop=DUMBTHREADSTOP):
  1310. try:
  1311. if delay != 0 and not threadstop is DUMBTHREADSTOP:
  1312. sleep(delay)
  1313. threadstop.testValidThread()
  1314. self.presenter.setTabProgressThreadSafe(0, threadstop, wx.BLUE)
  1315. docPage = self.getLoadedDocPage()
  1316. if docPage is None:
  1317. return
  1318. self.presenter.setTabProgressThreadSafe(20, threadstop)
  1319. for i in range(20): # "while True" is too dangerous
  1320. formatDetails = docPage.getFormatDetails()
  1321. pageAst = docPage.getLivePageAst(threadstop=threadstop)
  1322. threadstop.testValidThread()
  1323. if not formatDetails.isEquivTo(docPage.getFormatDetails()):
  1324. continue
  1325. else:
  1326. break
  1327. threadstop.testValidThread()
  1328. self.presenter.setTabProgressThreadSafe(40, threadstop)
  1329. stylebytes = self.processTokens(text, pageAst, threadstop)
  1330. threadstop.testValidThread()
  1331. if self.getFoldingActive():
  1332. foldingseq = self.processFolding(pageAst, threadstop)
  1333. else:
  1334. foldingseq = None
  1335. threadstop.testValidThread()
  1336. self.presenter.setTabProgressThreadSafe(60, threadstop)
  1337. if self.onlineSpellCheckerActive and \
  1338. isinstance(docPage, DocPages.AbstractWikiPage):
  1339. # Show intermediate syntax highlighting results before spell check
  1340. # if we are in asynchronous mode
  1341. if not threadstop is DUMBTHREADSTOP:
  1342. self.storeStylingAndAst(stylebytes, foldingseq, styleMask=0x1f)
  1343. scTokens = docPage.getSpellCheckerUnknownWords(threadstop=threadstop)
  1344. threadstop.testValidThread()
  1345. self.presenter.setTabProgressThreadSafe(80, threadstop)
  1346. if scTokens.getChildrenCount() > 0:
  1347. spellStyleBytes = self.processSpellCheckTokens(text, scTokens,
  1348. threadstop)
  1349. threadstop.testValidThread()
  1350. # TODO: Faster? How?
  1351. stylebytes = "".join([chr(ord(a) | ord(b))
  1352. for a, b in itertools.izip(stylebytes, spellStyleBytes)])
  1353. self.storeStylingAndAst(stylebytes, None, styleMask=0xff)
  1354. else:
  1355. self.storeStylingAndAst(stylebytes, None, styleMask=0xff)
  1356. else:
  1357. self.storeStylingAndAst(stylebytes, foldingseq, styleMask=0xff)
  1358. self.presenter.setTabProgressThreadSafe(100, threadstop)
  1359. except NotCurrentThreadException:
  1360. print "THREAD STOPPED"
  1361. self.presenter.setTabProgressThreadSafe(100, threadstop)
  1362. return
  1363. _TOKEN_TO_STYLENO = {
  1364. "bold": FormatTypes.Bold,
  1365. "italics": FormatTypes.Italic,
  1366. "urlLink": FormatTypes.Url,
  1367. "script": FormatTypes.Script,
  1368. "property": FormatTypes.Attribute, # TODO remove "property"-compatibility
  1369. "attribute": FormatTypes.Attribute,
  1370. "insertion": FormatTypes.Script,
  1371. "anchorDef": FormatTypes.Bold,
  1372. "plainText": FormatTypes.Default
  1373. }
  1374. def _findFragmentSearch(self, linkNode):
  1375. """
  1376. linkNode -- AST node of type "wikiWord"
  1377. returns
  1378. (<first char pos>, <after last char pos>) of targeted search
  1379. fragment if present
  1380. (None, None) if not present
  1381. (-1, -1) if search is not applicable
  1382. """
  1383. unaliasedTarget = self.presenter.getWikiDocument()\
  1384. .getWikiPageNameForLinkTermOrAsIs(linkNode.wikiWord)
  1385. docPage = self.getLoadedDocPage()
  1386. if docPage is None:
  1387. return (-1, -1)
  1388. wikiWord = docPage.getWikiWord()
  1389. if wikiWord is None:
  1390. return (-1, -1)
  1391. if wikiWord == unaliasedTarget:
  1392. forbiddenSearchfragHit = (linkNode.pos,
  1393. linkNode.pos + linkNode.strLength)
  1394. else:
  1395. forbiddenSearchfragHit = (0, 0)
  1396. searchfrag = linkNode.searchFragment
  1397. if searchfrag is None:
  1398. return (-1, -1)
  1399. searchOp = SearchReplaceOperation()
  1400. searchOp.wildCard = "no"
  1401. searchOp.searchStr = searchfrag
  1402. targetPage = self.presenter.getWikiDocument().getWikiPage(
  1403. linkNode.wikiWord)
  1404. found = searchOp.searchDocPageAndText(targetPage,
  1405. targetPage.getLiveText(), 0)
  1406. if found[0] >= forbiddenSearchfragHit[0] and \
  1407. found[0] < forbiddenSearchfragHit[1]:
  1408. # Searchfrag found its own link -> search after link
  1409. found = searchOp.searchDocPageAndText(targetPage,
  1410. targetPage.getLiveText(), forbiddenSearchfragHit[1])
  1411. return found
  1412. def processTokens(self, text, pageAst, threadstop):
  1413. wikiDoc = self.presenter.getWikiDocument()
  1414. stylebytes = StyleCollector(FormatTypes.Default,
  1415. text, self.bytelenSct)
  1416. def process(pageAst, stack):
  1417. for node in pageAst.iterFlatNamed():
  1418. threadstop.testValidThread()
  1419. styleNo = WikiTxtCtrl._TOKEN_TO_STYLENO.get(node.name)
  1420. if styleNo is not None:
  1421. stylebytes.bindStyle(node.pos, node.strLength, styleNo)
  1422. elif node.name == "wikiWord":
  1423. if wikiDoc.isCreatableWikiWord(node.wikiWord):
  1424. styleNo = FormatTypes.WikiWord
  1425. else:
  1426. styleNo = FormatTypes.AvailWikiWord
  1427. if self.optionColorizeSearchFragments and \
  1428. node.searchFragment:
  1429. if self._findFragmentSearch(node)[0] == None:
  1430. # if targetTxt.find(node.searchFragment) == -1:
  1431. searchFragNode = node.fragmentNode
  1432. stylebytes.bindStyle(node.pos,
  1433. searchFragNode.pos - node.pos,
  1434. FormatTypes.AvailWikiWord)
  1435. stylebytes.bindStyle(searchFragNode.pos,
  1436. searchFragNode.strLength,
  1437. FormatTypes.WikiWord)
  1438. stylebytes.bindStyle(searchFragNode.pos +
  1439. searchFragNode.strLength,
  1440. node.strLength -
  1441. (searchFragNode.pos - node.pos) -
  1442. searchFragNode.strLength,
  1443. FormatTypes.AvailWikiWord)
  1444. continue
  1445. stylebytes.bindStyle(node.pos, node.strLength, styleNo)
  1446. elif node.name == "todoEntry":
  1447. process(node, stack + ["todoEntry"])
  1448. elif node.name == "key" and "todoEntry" in stack:
  1449. stylebytes.bindStyle(node.pos, node.strLength,
  1450. FormatTypes.ToDo)
  1451. elif node.name == "value" and "todoEntry" in stack:
  1452. process(node, stack[:])
  1453. elif node.name == "heading":
  1454. if node.level < 5:
  1455. styleNo = FormatTypes.Heading1 + \
  1456. (node.level - 1)
  1457. else:
  1458. styleNo = FormatTypes.Bold
  1459. stylebytes.bindStyle(node.pos, node.strLength, styleNo)
  1460. elif node.name in \
  1461. self.wikiLanguageHelper.getRecursiveStylingNodeNames() or \
  1462. (getattr(node, "helperRecursive", False) and \
  1463. not node.isTerminal()):
  1464. process(node, stack[:])
  1465. process(pageAst, [])
  1466. return stylebytes.value()
  1467. def processSpellCheckTokens(self, text, scTokens, threadstop):
  1468. stylebytes = StyleCollector(0, text, self.bytelenSct)
  1469. for node in scTokens:
  1470. threadstop.testValidThread()
  1471. stylebytes.bindStyle(node.pos, node.strLength,
  1472. wx.stc.STC_INDIC2_MASK)
  1473. return stylebytes.value()
  1474. def getFoldingNodeDict(self):
  1475. """
  1476. Retrieve the folding node dictionary from wiki language which tells
  1477. which AST nodes (other than "heading") should be processed by
  1478. folding.
  1479. The folding node dictionary has the names of the AST node types as keys,
  1480. each value is a tuple (fold, recursive) where
  1481. fold -- True iff node should be folded
  1482. recursive -- True iff node should be processed recursively
  1483. The value tuples may contain more than these two items, processFolding()
  1484. must be able to handle that.
  1485. """
  1486. # TODO: Add option to remove additional nodes from folding
  1487. # (or some of them)
  1488. return self.wikiLanguageHelper.getFoldingNodeDict()
  1489. def processFolding(self, pageAst, threadstop):
  1490. # TODO: allow folding of tables / boxes / figures
  1491. foldingseq = []
  1492. #currLine = 0
  1493. prevLevel = 0
  1494. levelStack = []
  1495. foldHeader = False
  1496. foldNodeDict = self.getFoldingNodeDict()
  1497. def searchAst(ast, foldingseq, prevLevel, levelStack, foldHeader):
  1498. for node in ast:
  1499. threadstop.testValidThread()
  1500. recursive = False
  1501. if node.name is None:
  1502. pass
  1503. elif node.name == "heading":
  1504. while levelStack and (levelStack[-1][0] != "heading" or
  1505. levelStack[-1][1] > node.level) and \
  1506. levelStack[-1][0] != "recursive-node":
  1507. del levelStack[-1]
  1508. if not levelStack or \
  1509. levelStack[-1] != ("heading", node.level):
  1510. levelStack.append(("heading", node.level))
  1511. foldHeader = True
  1512. elif node.name in foldNodeDict:
  1513. fndMode = foldNodeDict[node.name][:2]
  1514. if fndMode == (True, False): # Fold, non recursive
  1515. # No point in folding single line items
  1516. if node.getString().count(u"\n") > 1:
  1517. levelStack.append(("node", 0))
  1518. foldHeader = True
  1519. elif fndMode == (True, True): # Fold, recursive
  1520. levelStack.append(("recursive-node", 0))
  1521. foldHeader = True
  1522. foldingseq, prevLevel, levelStack, foldHeader = \
  1523. searchAst(node, foldingseq, prevLevel, levelStack,
  1524. foldHeader)
  1525. while levelStack[-1][0] == "heading":
  1526. del levelStack[-1]
  1527. del levelStack[-1]
  1528. recursive = True
  1529. elif fndMode == (False, True): # No fold, but recursive
  1530. foldingseq, prevLevel, levelStack, foldHeader = \
  1531. searchAst(node, foldingseq, prevLevel, levelStack,
  1532. foldHeader)
  1533. recursive = True
  1534. if not recursive:
  1535. lfc = node.getString().count(u"\n")
  1536. if len(levelStack) > prevLevel:
  1537. foldHeader = True
  1538. if foldHeader and lfc > 0:
  1539. foldingseq.append(len(levelStack) | wx.stc.STC_FOLDLEVELHEADERFLAG)
  1540. foldHeader = False
  1541. lfc -= 1
  1542. if lfc > 0:
  1543. foldingseq += [len(levelStack) + 1] * lfc
  1544. if levelStack and levelStack[-1][0] == "node":
  1545. del levelStack[-1]
  1546. prevLevel = len(levelStack) + 1
  1547. return foldingseq, prevLevel, levelStack, foldHeader
  1548. foldingseq, prevLevel, levelStack, foldHeader = searchAst(pageAst,
  1549. foldingseq, prevLevel, levelStack, foldHeader)
  1550. # final line
  1551. foldingseq.append(len(levelStack) + 1)
  1552. return foldingseq
  1553. def applyStyling(self, stylebytes, styleMask=0xff):
  1554. if len(stylebytes) == self.GetLength():
  1555. self.StartStyling(0, styleMask)
  1556. self.SetStyleBytes(len(stylebytes), stylebytes)
  1557. def applyFolding(self, foldingseq):
  1558. if foldingseq and self.getFoldingActive() and \
  1559. len(foldingseq) == self.GetLineCount():
  1560. for ln in xrange(len(foldingseq)):
  1561. self.SetFoldLevel(ln, foldingseq[ln])
  1562. self.repairFoldingVisibility()
  1563. def unfoldAll(self):
  1564. """
  1565. Unfold all folded lines
  1566. """
  1567. for i in xrange(self.GetLineCount()):
  1568. self.SetFoldExpanded(i, True)
  1569. self.ShowLines(0, self.GetLineCount()-1)
  1570. def foldAll(self):
  1571. """
  1572. Fold all foldable lines
  1573. """
  1574. if not self.getFoldingActive():
  1575. self.setFoldingActive(True, forceSync=True)
  1576. for ln in xrange(self.GetLineCount()):
  1577. if self.GetFoldLevel(ln) & wx.stc.STC_FOLDLEVELHEADERFLAG and \
  1578. self.GetFoldExpanded(ln):
  1579. self.ToggleFold(ln)
  1580. # self.SetFoldExpanded(ln, False)
  1581. # else:
  1582. # self.HideLines(ln, ln)
  1583. self.Refresh()
  1584. def toggleCurrentFolding(self):
  1585. if not self.getFoldingActive():
  1586. return
  1587. self.ToggleFold(self.LineFromPosition(self.GetCurrentPos()))
  1588. def getFoldInfo(self):
  1589. if not self.getFoldingActive():
  1590. return None
  1591. result = [0] * self.GetLineCount()
  1592. for ln in xrange(self.GetLineCount()):
  1593. levComb = self.GetFoldLevel(ln)
  1594. levOut = levComb & 4095
  1595. if levComb & wx.stc.STC_FOLDLEVELHEADERFLAG:
  1596. levOut |= 4096
  1597. if self.GetFoldExpanded(ln):
  1598. levOut |= 8192
  1599. if self.GetLineVisible(ln):
  1600. levOut |= 16384
  1601. result[ln] = levOut
  1602. return result
  1603. def setFoldInfo(self, fldInfo):
  1604. if fldInfo is None or \
  1605. not self.getFoldingActive() or \
  1606. len(fldInfo) != self.GetLineCount():
  1607. return
  1608. for ln, levIn in enumerate(fldInfo):
  1609. levComb = levIn & 4095
  1610. if levIn & 4096:
  1611. levComb |= wx.stc.STC_FOLDLEVELHEADERFLAG
  1612. self.SetFoldLevel(ln, levComb)
  1613. self.SetFoldExpanded(ln, bool(levIn & 8192))
  1614. if levIn & 16384:
  1615. self.ShowLines(ln, ln)
  1616. else:
  1617. self.HideLines(ln, ln)
  1618. self.repairFoldingVisibility()
  1619. def repairFoldingVisibility(self):
  1620. if not self.getFoldingActive():
  1621. return
  1622. lc = self.GetLineCount()
  1623. if lc == 0:
  1624. return
  1625. self.ShowLines(0, 0)
  1626. if lc == 1:
  1627. return
  1628. combLevel = self.GetFoldLevel(0)
  1629. prevLevel = combLevel & 4095
  1630. prevIsHeader = combLevel & wx.stc.STC_FOLDLEVELHEADERFLAG
  1631. prevIsExpanded = self.GetFoldExpanded(0)
  1632. prevVisible = True # First line must always be visible
  1633. prevLn = 0
  1634. # print "0", prevLevel, bool(prevIsHeader), bool(prevIsExpanded), bool(prevVisible)
  1635. for ln in xrange(1, lc):
  1636. combLevel = self.GetFoldLevel(ln)
  1637. level = combLevel & 4095
  1638. isHeader = combLevel & wx.stc.STC_FOLDLEVELHEADERFLAG
  1639. isExpanded = self.GetFoldExpanded(ln)
  1640. visible = self.GetLineVisible(ln)
  1641. # print ln, level, bool(isHeader), bool(isExpanded), bool(visible)
  1642. if prevVisible and not visible:
  1643. # Previous line visible, current not -> check if we must show it
  1644. if ((level <= prevLevel) and \
  1645. not (prevIsHeader and not prevIsExpanded)) or \
  1646. (prevIsHeader and prevIsExpanded):
  1647. # if current level is not larger than previous this indicates
  1648. # an error except that the previous line is a header line and
  1649. # folded (not expanded).
  1650. # Other possibility of an error is if previous line is a
  1651. # header and IS expanded.
  1652. # Show line in these cases
  1653. self.SetFoldExpanded(prevLn, True) # Needed?
  1654. self.ShowLines(ln, ln)
  1655. # self.EnsureVisible(ln)
  1656. visible = True
  1657. prevLevel = level
  1658. prevIsHeader = isHeader
  1659. prevIsExpanded = isExpanded
  1660. prevVisible = visible
  1661. prevLn = ln
  1662. def snip(self):
  1663. # get the selected text
  1664. text = self.GetSelectedText()
  1665. # copy it to the clipboard also
  1666. self.Copy()
  1667. wikiPage = self.presenter.getWikiDocument().getWikiPageNoError("ScratchPad")
  1668. # wikiPage.appendLiveText("\n%s\n---------------------------\n\n%s\n" %
  1669. # (mbcsDec(strftime("%x %I:%M %p"), "replace")[0], text))
  1670. wikiPage.appendLiveText("\n%s\n---------------------------\n\n%s\n" %
  1671. (StringOps.strftimeUB("%x %I:%M %p"), text))
  1672. def styleSelection(self, startChars, endChars=None):
  1673. """
  1674. """
  1675. if endChars is None:
  1676. endChars = startChars
  1677. (startBytePos, endBytePos) = self.GetSelection()
  1678. if startBytePos == endBytePos:
  1679. (startBytePos, endBytePos) = self.getNearestWordPositions()
  1680. emptySelection = startBytePos == endBytePos # is selection empty
  1681. startCharPos = len(self.GetTextRange(0, startBytePos))
  1682. endCharPos = startCharPos + len(self.GetTextRange(startBytePos, endBytePos))
  1683. self.BeginUndoAction()
  1684. try:
  1685. endCharPos += len(startChars)
  1686. if emptySelection:
  1687. # If selection is empty, cursor will in the end
  1688. # stand between the style characters
  1689. cursorCharPos = endCharPos
  1690. else:
  1691. # If not, it will stand after styled word
  1692. cursorCharPos = endCharPos + len(endChars)
  1693. self.gotoCharPos(startCharPos, scroll=False)
  1694. self.AddText(startChars)
  1695. self.gotoCharPos(endCharPos, scroll=False)
  1696. self.AddText(endChars)
  1697. self.gotoCharPos(cursorCharPos, scroll=False)
  1698. finally:
  1699. self.EndUndoAction()
  1700. def formatSelection(self, formatType):
  1701. start, afterEnd = self.GetSelectionCharPos()
  1702. info = self.wikiLanguageHelper.formatSelectedText(self.GetText(),
  1703. start, afterEnd, formatType, {})
  1704. if info is None:
  1705. return False
  1706. replacement, repStart, repAfterEnd, selStart, selAfterEnd = info[:5]
  1707. self.SetSelectionByCharPos(repStart, repAfterEnd)
  1708. self.ReplaceSelection(replacement)
  1709. self.SetSelectionByCharPos(selStart, selAfterEnd)
  1710. return True
  1711. def getPageAst(self):
  1712. docPage = self.getLoadedDocPage()
  1713. if docPage is None:
  1714. raise NoPageAstException(u"Internal error: No docPage => no page AST")
  1715. return docPage.getLivePageAst()
  1716. def activateTokens(self, nodeList, tabMode=0):
  1717. """
  1718. Helper for activateLink()
  1719. tabMode -- 0:Same tab; 2: new tab in foreground; 3: new tab in background
  1720. """
  1721. if len(nodeList) == 0:
  1722. return False
  1723. for node in nodeList:
  1724. if node.name == "wikiWord":
  1725. searchStr = None
  1726. # open the wiki page
  1727. if tabMode & 2:
  1728. if tabMode == 6:
  1729. # New Window
  1730. presenter = self.presenter.getMainControl().\
  1731. createNewDocPagePresenterTabInNewFrame()
  1732. else:
  1733. # New tab
  1734. presenter = self.presenter.getMainControl().\
  1735. createNewDocPagePresenterTab()
  1736. else:
  1737. # Same tab
  1738. presenter = self.presenter
  1739. titleFromLink = self.presenter.getConfig().getboolean("main",
  1740. "wikiPageTitle_fromLinkTitle", False)
  1741. if not titleFromLink or node.titleNode is None:
  1742. suggNewPageTitle = None
  1743. else:
  1744. suggNewPageTitle = node.titleNode.getString()
  1745. unaliasedTarget = self.presenter.getWikiDocument()\
  1746. .getWikiPageNameForLinkTermOrAsIs(node.wikiWord)
  1747. docPage = self.getLoadedDocPage()
  1748. # Contains start and end character position where a search fragment
  1749. # search should never match
  1750. # If the target wikiword is the current one, the search fragment
  1751. # search should not find the link itself
  1752. forbiddenSearchfragHit = (0, 0)
  1753. if docPage is not None:
  1754. wikiWord = docPage.getWikiWord()
  1755. if wikiWord is not None:
  1756. if wikiWord == unaliasedTarget:
  1757. forbiddenSearchfragHit = (node.pos, node.pos + node.strLength)
  1758. presenter.openWikiPage(unaliasedTarget,
  1759. motionType="child", anchor=node.anchorLink,
  1760. suggNewPageTitle=suggNewPageTitle)
  1761. searchfrag = node.searchFragment
  1762. if searchfrag is not None:
  1763. searchOp = SearchReplaceOperation()
  1764. searchOp.wildCard = "no"
  1765. searchOp.searchStr = searchfrag
  1766. found = presenter.getSubControl("textedit").executeSearch(
  1767. searchOp, 0)
  1768. if found[0] >= forbiddenSearchfragHit[0] and \
  1769. found[0] < forbiddenSearchfragHit[1]:
  1770. # Searchfrag found its own link -> search after link
  1771. presenter.getSubControl("textedit").executeSearch(
  1772. searchOp, forbiddenSearchfragHit[1])
  1773. if not tabMode & 1:
  1774. # Show in foreground
  1775. presenter.getMainControl().getMainAreaPanel().\
  1776. showPresenter(presenter)
  1777. return True
  1778. elif node.name == "urlLink":
  1779. self.presenter.getMainControl().launchUrl(node.url)
  1780. return True
  1781. elif node.name == "insertion":
  1782. if node.key == u"page":
  1783. # open the wiki page
  1784. if tabMode & 2:
  1785. if tabMode == 6:
  1786. # New Window
  1787. presenter = self.presenter.getMainControl().\
  1788. createNewDocPagePresenterTabInNewFrame()
  1789. else:
  1790. # New tab
  1791. presenter = self.presenter.getMainControl().\
  1792. createNewDocPagePresenterTab()
  1793. else:
  1794. # Same tab
  1795. presenter = self.presenter
  1796. presenter.openWikiPage(node.value,
  1797. motionType="child") # , anchor=node.value)
  1798. if not tabMode & 1:
  1799. # Show in foreground (if presenter is in other window,
  1800. # this does nothing)
  1801. presenter.getMainControl().getMainAreaPanel().\
  1802. showPresenter(presenter)
  1803. return True
  1804. # TODO: Make this work correctly
  1805. # elif tok.node.key == u"rel":
  1806. # if tok.node.value == u"back":
  1807. # # Go back in history
  1808. # self.presenter.getMainControl().goBrowserBack()
  1809. elif node.name == "footnote":
  1810. try:
  1811. pageAst = self.getPageAst()
  1812. footnoteId = node.footnoteId
  1813. anchorNode = getFootnoteAnchorDict(pageAst).get(footnoteId)
  1814. if anchorNode is not None:
  1815. if anchorNode.pos != node.pos:
  1816. # Activated footnote was not last -> go to last
  1817. self.gotoCharPos(anchorNode.pos)
  1818. else:
  1819. # Activated footnote was last -> go to first
  1820. for fnNode in pageAst.iterDeepByName("footnote"):
  1821. if fnNode.footnoteId == footnoteId:
  1822. self.gotoCharPos(fnNode.pos)
  1823. break
  1824. return True
  1825. except NoPageAstException:
  1826. return False
  1827. else:
  1828. continue
  1829. return False
  1830. def getTokensForMousePos(self, mousePosition=None, linkBytePos=None):
  1831. # If being called from a thread linkBytePos should be passed
  1832. # explicitaly to avoid segfaulting
  1833. if linkBytePos is None:
  1834. # mouse position overrides current pos
  1835. if mousePosition and mousePosition != wx.DefaultPosition:
  1836. linkBytePos = self.PositionFromPoint(mousePosition)
  1837. else:
  1838. linkBytePos = self.GetCurrentPos()
  1839. try:
  1840. pageAst = self.getPageAst()
  1841. except NoPageAstException:
  1842. return []
  1843. linkCharPos = len(self.GetTextRange(0, linkBytePos))
  1844. result = pageAst.findNodesForCharPos(linkCharPos)
  1845. if linkCharPos > 0:
  1846. # Maybe a token left to the cursor was meant, so check
  1847. # one char to the left
  1848. result += pageAst.findNodesForCharPos(linkCharPos - 1)
  1849. if self.onlineSpellCheckerActive:
  1850. docPage = self.getLoadedDocPage()
  1851. if isinstance(docPage, DocPages.AbstractWikiPage):
  1852. allUnknownWords = docPage.getSpellCheckerUnknownWords()
  1853. wantedUnknownWords = allUnknownWords.findNodesForCharPos(
  1854. linkCharPos)
  1855. if linkCharPos > 0 and len(wantedUnknownWords) == 0:
  1856. # No unknown word found -> try left to cursor
  1857. wantedUnknownWords = allUnknownWords.findNodesForCharPos(
  1858. linkCharPos - 1)
  1859. result += wantedUnknownWords
  1860. return result
  1861. def activateLink(self, mousePosition=None, tabMode=0):
  1862. """
  1863. Activates link (wiki word or URL)
  1864. tabMode -- 0:Same tab; 2: new tab in foreground; 3: new tab in background
  1865. """
  1866. tokens = self.getTokensForMousePos(mousePosition)
  1867. return self.activateTokens(tokens, tabMode)
  1868. def findSimilarWords(self, mousePosition=None):
  1869. """
  1870. Finds similar words to an undefined link
  1871. TODO: make an activateLink option?
  1872. """
  1873. nodeList = self.getTokensForMousePos(mousePosition)
  1874. if len(nodeList) == 0:
  1875. return False
  1876. for node in nodeList:
  1877. if node.name == "wikiWord":
  1878. if self.presenter.getWikiDocument().isDefinedWikiLinkTerm(
  1879. node.wikiWord):
  1880. return False
  1881. dlg = AdditionalDialogs.FindSimilarNamedWikiWordDialog(
  1882. self.presenter, -1, node.wikiWord, 0)
  1883. dlg.CenterOnParent(wx.BOTH)
  1884. dlg.ShowModal()
  1885. dlg.Destroy()
  1886. return
  1887. def OnReplaceThisSpellingWithSuggestion(self, evt):
  1888. if self.contextMenuTokens and self.contextMenuSpellCheckSuggestions:
  1889. for node in self.contextMenuTokens:
  1890. if node.name == "unknownSpelling":
  1891. self.replaceTextAreaByCharPos(
  1892. self.contextMenuSpellCheckSuggestions[
  1893. self.SUGGESTION_CMD_IDS.index(evt.GetId())],
  1894. node.pos, node.pos + node.strLength)
  1895. break
  1896. def OnAddThisSpellingToIgnoreSession(self, evt):
  1897. if self.contextMenuTokens:
  1898. for node in self.contextMenuTokens:
  1899. if node.name == "unknownSpelling":
  1900. self.presenter.getWikiDocument()\
  1901. .getOnlineSpellCheckerSession().addIgnoreWordSession(
  1902. node.getText())
  1903. break
  1904. def OnAddThisSpellingToIgnoreGlobal(self, evt):
  1905. if self.contextMenuTokens:
  1906. for node in self.contextMenuTokens:
  1907. if node.name == "unknownSpelling":
  1908. self.presenter.getWikiDocument()\
  1909. .getOnlineSpellCheckerSession().addIgnoreWordGlobal(
  1910. node.getText())
  1911. break
  1912. def OnAddThisSpellingToIgnoreLocal(self, evt):
  1913. if self.contextMenuTokens:
  1914. for node in self.contextMenuTokens:
  1915. if node.name == "unknownSpelling":
  1916. self.presenter.getWikiDocument()\
  1917. .getOnlineSpellCheckerSession().addIgnoreWordLocal(
  1918. node.getText())
  1919. break
  1920. def GetEditorToActivate(self, direction, makePresenterCurrent=False):
  1921. """
  1922. Helper for OnActive* functions.
  1923. Returns the editor to activate the link on (based on the direction
  1924. parameter)
  1925. """
  1926. if direction is not None:
  1927. presenter = self.presenter.getMainControl().getMainAreaPanel().\
  1928. getActivePresenterTo(direction, self.presenter)
  1929. ed = presenter.getSubControl("textedit")
  1930. if makePresenterCurrent:
  1931. presenter.makeCurrent()
  1932. else:
  1933. ed = self
  1934. return ed
  1935. def OnActivateThis(self, evt, direction=None):
  1936. ed = self.GetEditorToActivate(direction, True)
  1937. if self.contextMenuTokens:
  1938. ed.activateTokens(self.contextMenuTokens, 0)
  1939. def OnActivateNewTabThis(self, evt, direction=None):
  1940. ed = self.GetEditorToActivate(direction, True)
  1941. if self.contextMenuTokens:
  1942. ed.activateTokens(self.contextMenuTokens, 2)
  1943. def OnActivateNewTabBackgroundThis(self, evt, direction=None):
  1944. # If we are opening a background tab assume we want the current
  1945. # tabCtrl to remain active
  1946. presenter = self.presenter
  1947. ed = self.GetEditorToActivate(direction, True)
  1948. if self.contextMenuTokens:
  1949. ed.activateTokens(self.contextMenuTokens, 3)
  1950. wx.CallAfter(presenter.makeCurrent)
  1951. def OnActivateNewWindowThis(self, evt, direction=None):
  1952. ed = self.GetEditorToActivate(direction, True)
  1953. if self.contextMenuTokens:
  1954. ed.activateTokens(self.contextMenuTokens, 6)
  1955. def OnOpenContainingFolderThis(self, evt):
  1956. if self.contextMenuTokens:
  1957. for node in self.contextMenuTokens:
  1958. if node.name == "urlLink":
  1959. link = node.url
  1960. if link.startswith(u"rel://") or link.startswith(u"wikirel://"):
  1961. link = self.presenter.getWikiDocument()\
  1962. .makeRelUrlAbsolute(link)
  1963. if link.startswith(u"file:") or link.startswith(u"wiki:"):
  1964. # TODO: fix
  1965. try:
  1966. path = dirname(StringOps.pathnameFromUrl(link))
  1967. if not exists(StringOps.longPathEnc(path)):
  1968. self.presenter.displayErrorMessage(
  1969. _(u"Folder does not exist"))
  1970. return
  1971. OsAbstract.startFile(self.presenter.getMainControl(),
  1972. path)
  1973. except IOError:
  1974. pass # Error message?
  1975. break
  1976. def OnDeleteFile(self, evt):
  1977. if self.contextMenuTokens:
  1978. for node in self.contextMenuTokens:
  1979. if node.name == "urlLink":
  1980. link = self.presenter.getWikiDocument().makeFileUrlAbsPath(
  1981. node.url)
  1982. if link is None:
  1983. continue
  1984. # link = node.url
  1985. #
  1986. # if link.startswith(u"rel://"):
  1987. # link = StringOps.pathnameFromUrl(self.presenter.getMainControl().makeRelUrlAbsolute(link))
  1988. # else:
  1989. # break
  1990. # path = dirname(link)
  1991. if not isfile(link):
  1992. self.presenter.displayErrorMessage(
  1993. _(u"File does not exist"))
  1994. return
  1995. filename = basename(link)
  1996. choice = wx.MessageBox(
  1997. _("Are you sure you want to delete the file: %s") %
  1998. filename, _("Delete File"),
  1999. wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self)
  2000. if choice == wx.YES:
  2001. OsAbstract.deleteFile(link)
  2002. self.replaceTextAreaByCharPos(u"", node.pos,
  2003. node.pos + node.strLength)
  2004. return
  2005. def OnRenameFile(self, evt):
  2006. if not self.contextMenuTokens:
  2007. return
  2008. for node in self.contextMenuTokens:
  2009. if node.name == "urlLink":
  2010. link = self.presenter.getWikiDocument().makeFileUrlAbsPath(
  2011. node.url)
  2012. if link is not None:
  2013. break
  2014. else:
  2015. return
  2016. # link = node.url
  2017. #
  2018. # if link.startswith(u"rel://"):
  2019. # link = StringOps.pathnameFromUrl(self.presenter.getMainControl().makeRelUrlAbsolute(link))
  2020. # else:
  2021. # break
  2022. if not isfile(link):
  2023. self.presenter.displayErrorMessage(_(u"File does not exist"))
  2024. return
  2025. path = dirname(link)
  2026. filename = basename(link)
  2027. newName = filename
  2028. while True:
  2029. dlg = WikiTxtDialogs.RenameFileDialog(self,
  2030. _(u"Enter new name for file: {0}".format(filename)),
  2031. _(u"Rename File"), newName)
  2032. if dlg.ShowModal() != wx.ID_OK:
  2033. # User cancelled
  2034. dlg.Destroy()
  2035. return
  2036. newName = dlg.GetValue()
  2037. dlg.Destroy()
  2038. newfile = join(path, newName)
  2039. if exists(newfile):
  2040. if not isfile(newfile):
  2041. self.presenter.displayErrorMessage(
  2042. _(u"Target is not a file"))
  2043. continue
  2044. choice = wx.MessageBox(
  2045. _("Target file exists already. Overwrite?"),
  2046. _("Overwrite File"),
  2047. wx.YES_NO | wx.CANCEL | wx.NO_DEFAULT | wx.ICON_QUESTION,
  2048. self)
  2049. if choice == wx.CANCEL:
  2050. return
  2051. elif choice == wx.NO:
  2052. continue
  2053. # Either file doesn't exist or user allowed overwrite
  2054. OsAbstract.moveFile(link, newfile)
  2055. if node.url.startswith(u"rel://"):
  2056. # Relative URL/path
  2057. newUrl = self.presenter.getWikiDocument().makeAbsPathRelUrl(
  2058. newfile)
  2059. else:
  2060. # Absolute URL/path
  2061. newUrl = u"file:" + StringOps.urlFromPathname(newfile)
  2062. self.replaceTextAreaByCharPos(newUrl, node.coreNode.pos,
  2063. node.coreNode.pos + node.coreNode.strLength)
  2064. return
  2065. def convertUrlAbsoluteRelative(self, tokenList):
  2066. for node in tokenList:
  2067. if node.name == "urlLink":
  2068. link = node.url
  2069. if ' ' in node.coreNode.getString():
  2070. addSafe = ' '
  2071. else:
  2072. addSafe = ''
  2073. if link.startswith(u"rel://") or link.startswith(u"wikirel://"):
  2074. link = self.presenter.getWikiDocument()\
  2075. .makeRelUrlAbsolute(link, addSafe=addSafe)
  2076. else:
  2077. link = self.presenter.getWikiDocument()\
  2078. .makeAbsUrlRelative(link, addSafe=addSafe)
  2079. if link is None:
  2080. continue # TODO Message?
  2081. self.replaceTextAreaByCharPos(link, node.coreNode.pos,
  2082. node.coreNode.pos + node.coreNode.strLength)
  2083. break
  2084. def convertSelectedUrlAbsoluteRelative(self):
  2085. tokenList = self.getTokensForMousePos(None)
  2086. self.convertUrlAbsoluteRelative(tokenList)
  2087. def OnConvertUrlAbsoluteRelativeThis(self, evt):
  2088. if self.contextMenuTokens:
  2089. self.convertUrlAbsoluteRelative(self.contextMenuTokens)
  2090. def OnClipboardCopyUrlToThisAnchor(self, evt):
  2091. wikiWord = self.presenter.getWikiWord()
  2092. if wikiWord is None:
  2093. wx.MessageBox(
  2094. _(u"This can only be done for the page of a wiki word"),
  2095. _(u'Not a wiki page'), wx.OK, self)
  2096. return
  2097. path = self.presenter.getWikiDocument().getWikiConfigPath()
  2098. for node in self.contextMenuTokens:
  2099. if node.name == "anchorDef":
  2100. copyTextToClipboard(StringOps.pathWordAndAnchorToWikiUrl(path,
  2101. wikiWord, node.anchorLink))
  2102. return
  2103. # TODO More efficient
  2104. def evalScriptBlocks(self, index=-1):
  2105. """
  2106. Evaluates scripts. Respects "script_security_level" option
  2107. """
  2108. securityLevel = self.presenter.getConfig().getint(
  2109. "main", "script_security_level")
  2110. if securityLevel == 0:
  2111. # No scripts allowed
  2112. # Print warning message
  2113. wx.MessageBox(_(u"Set in menu \"Wiki\", item \"Options...\", "
  2114. "options page \"Security\", \n"
  2115. "item \"Script security\" an appropriate value "
  2116. "to execute a script."), _(u"Script execution disabled"),
  2117. wx.OK, self.presenter.getMainControl())
  2118. return
  2119. SCRIPTFORMAT = "script"
  2120. # it is important to python to have consistent eol's
  2121. self.ConvertEOLs(self.eolMode)
  2122. (startPos, endPos) = self.GetSelection()
  2123. # if no selection eval all scripts
  2124. if startPos == endPos or index > -1:
  2125. # Execute all or selected script blocks on the page (or other
  2126. # related pages)
  2127. try:
  2128. pageAst = self.getPageAst()
  2129. except NoPageAstException:
  2130. return
  2131. scriptNodeGroups = [list(pageAst.iterDeepByName(SCRIPTFORMAT))]
  2132. # process script imports
  2133. if securityLevel > 1: # Local import_scripts attributes allowed
  2134. if self.getLoadedDocPage().getAttributes().has_key(
  2135. u"import_scripts"):
  2136. scriptNames = self.getLoadedDocPage().getAttributes()[
  2137. u"import_scripts"]
  2138. for sn in scriptNames:
  2139. try:
  2140. importPage = self.presenter.getWikiDocument().\
  2141. getWikiPage(sn)
  2142. pageAst = importPage.getLivePageAst()
  2143. scriptNodeGroups.append(list(
  2144. pageAst.iterDeepByName(SCRIPTFORMAT)))
  2145. except:
  2146. pass
  2147. if securityLevel > 2: # global.import_scripts attribute also allowed
  2148. globScriptName = self.presenter.getWikiDocument().getWikiData().\
  2149. getGlobalAttributes().get(u"global.import_scripts")
  2150. if globScriptName is not None:
  2151. try:
  2152. importPage = self.presenter.getWikiDocument().\
  2153. getWikiPage(globScriptName)
  2154. pageAst = importPage.getLivePageAst()
  2155. scriptNodeGroups.append(list(
  2156. pageAst.iterDeepByName(SCRIPTFORMAT)))
  2157. except:
  2158. pass
  2159. if self.presenter.getConfig().getboolean("main",
  2160. "script_search_reverse", False):
  2161. scriptNodeGroups.reverse()
  2162. scriptNodes = reduce(lambda a, b: a + b, scriptNodeGroups)
  2163. for node in scriptNodes:
  2164. script = node.findFlatByName("code").getString()
  2165. script = re.sub(u"^[\r\n\s]+", u"", script)
  2166. script = re.sub(u"[\r\n\s]+$", u"", script)
  2167. try:
  2168. if index == -1:
  2169. script = re.sub(u"^\d:?\s?", u"", script)
  2170. exec(script) in self.evalScope
  2171. elif index > -1 and script.startswith(str(index)):
  2172. script = re.sub(u"^\d:?\s?", u"", script)
  2173. exec(script) in self.evalScope
  2174. break # Execute only the first found script
  2175. except Exception, e:
  2176. s = StringIO()
  2177. traceback.print_exc(file=s)
  2178. self.AddText(_(u"\nException: %s") % s.getvalue())
  2179. else:
  2180. # Evaluate selected text
  2181. text = self.GetSelectedText()
  2182. try:
  2183. compThunk = compile(re.sub(u"[\n\r]", u"", text), "<string>",
  2184. "eval", CO_FUTURE_DIVISION)
  2185. result = eval(compThunk, self.evalScope)
  2186. except Exception, e:
  2187. s = StringIO()
  2188. traceback.print_exc(file=s)
  2189. result = s.getvalue()
  2190. pos = self.GetCurrentPos()
  2191. self.GotoPos(endPos)
  2192. self.AddText(u" = %s" % unicode(result))
  2193. self.GotoPos(pos)
  2194. def cleanAutoGenAreas(self, text):
  2195. """
  2196. Remove any content from the autogenerated areas and return
  2197. cleaned text. Call this before storing page in the database.
  2198. The original text is returned if option
  2199. "process_autogenerated_areas" is False.
  2200. """
  2201. return text
  2202. # TODO: Reactivate function
  2203. # if not self.presenter.getConfig().getboolean("main",
  2204. # "process_autogenerated_areas"):
  2205. # return text
  2206. #
  2207. # return WikiFormatting.AutoGenAreaRE.sub(ur"\1\2\4", text)
  2208. def _agaReplace(self, match):
  2209. try:
  2210. result = unicode(eval(match.group(2), self.evalScope))
  2211. except Exception, e:
  2212. s = StringIO()
  2213. traceback.print_exc(file=s)
  2214. result = unicode(s.getvalue())
  2215. if len(result) == 0 or result[-1] != u"\n":
  2216. result += u"\n"
  2217. return match.group(1) + match.group(2) + result + match.group(4)
  2218. def updateAutoGenAreas(self, text):
  2219. """
  2220. Update content of the autogenerated areas and return
  2221. updated text. Call this before loading the text in the editor
  2222. and on user request. The original text is returned if
  2223. option "process_autogenerated_areas" is False.
  2224. """
  2225. return text
  2226. # TODO: Reactivate function
  2227. # if not self.presenter.getConfig().getboolean("main",
  2228. # "process_autogenerated_areas"):
  2229. # return text
  2230. #
  2231. # # So the text can be referenced from an AGA function
  2232. # self.agatext = text
  2233. #
  2234. # return WikiFormatting.AutoGenAreaRE.sub(self._agaReplace, text)
  2235. def getAgaCleanedText(self):
  2236. """
  2237. Get editor text after cleaning of autogenerated area content
  2238. if configuration option is set appropriately, otherwise, the
  2239. text is not modified
  2240. """
  2241. return self.cleanAutoGenAreas(self.GetText())
  2242. def setTextAgaUpdated(self, text):
  2243. """
  2244. Set editor text after updating of autogenerated area content
  2245. if configuration option is set appropriately, otherwise, the
  2246. text is not modified
  2247. """
  2248. self.SetText(self.updateAutoGenAreas(text))
  2249. # TODO Reflect possible changes in WikidPadParser.py
  2250. AGACONTENTTABLERE = re.compile(ur"^(\+{1,4})([^\n\+][^\n]*)", re.DOTALL | re.LOCALE | re.MULTILINE)
  2251. def agaContentTable(self, omitfirst = False):
  2252. """
  2253. Can be called by an aga to present the content table of the current page.
  2254. The text is assumed to be in self.agatext variable(see updateAutoGenAreas()).
  2255. If omitfirst is true, the first entry (normally the title) is not shown.
  2256. """
  2257. allmatches = map(lambda m: m.group(0), self.AGACONTENTTABLERE.finditer(self.agatext))
  2258. if omitfirst and len(allmatches) > 0:
  2259. allmatches = allmatches[1:]
  2260. return u"\n".join(allmatches)
  2261. # TODO Multi column support
  2262. def agaFormatList(self, l):
  2263. """
  2264. Format a list l of strings in a nice way for an aga content
  2265. """
  2266. return u"\n".join(l)
  2267. def agaParentsTable(self):
  2268. """
  2269. Can be called by an aga to present all parents of the current page.
  2270. """
  2271. relations = self.getLoadedDocPage().getParentRelationships()[:]
  2272. # Apply sort order
  2273. relations.sort(key=string.lower) # sort alphabetically
  2274. return self.agaFormatList(relations)
  2275. def ensureTextRangeByBytePosExpanded(self, byteStart, byteEnd):
  2276. self.repairFoldingVisibility()
  2277. startLine = self.LineFromPosition(byteStart)
  2278. endLine = self.LineFromPosition(byteEnd)
  2279. # Just to be sure, shouldn't happen normally
  2280. if endLine < startLine:
  2281. startLine, endLine = endLine, startLine
  2282. for checkLine in xrange(endLine, startLine - 1, -1):
  2283. if not self.GetLineVisible(checkLine):
  2284. line = checkLine
  2285. while True:
  2286. line = self.GetFoldParent(line)
  2287. if line == -1:
  2288. break
  2289. if not self.GetFoldExpanded(line):
  2290. self.ToggleFold(line)
  2291. def ensureSelectionExpanded(self):
  2292. """
  2293. Ensure that the selection is visible and not in a folded area
  2294. """
  2295. byteStart = self.GetSelectionStart()
  2296. byteEnd = self.GetSelectionEnd()
  2297. self.ensureTextRangeByBytePosExpanded(byteStart, byteEnd)
  2298. self.SetSelection(byteStart, byteEnd)
  2299. def setSelectionForIncSearchByCharPos(self, start, end):
  2300. """
  2301. Overwrites SearchableScintillaControl.setSelectionForIncSearchByCharPos
  2302. Called during incremental search to select text. Will be called with
  2303. start=-1 if nothing is found to select.
  2304. This variant handles showing/hiding of folded lines
  2305. """
  2306. # Hide lines which were previously shown
  2307. if self.incSearchPreviousHiddenLines is not None:
  2308. line = self.incSearchPreviousHiddenStartLine
  2309. for state in self.incSearchPreviousHiddenLines:
  2310. if state:
  2311. self.ShowLines(line, line)
  2312. else:
  2313. self.HideLines(line, line)
  2314. line += 1
  2315. self.incSearchPreviousHiddenLines = None
  2316. self.incSearchPreviousHiddenStartLine = -1
  2317. if start == -1:
  2318. # self.SetSelection(-1, -1)
  2319. self.SetSelection(self.GetSelectionStart(), self.GetSelectionStart())
  2320. return
  2321. text = self.GetText()
  2322. byteStart = self.bytelenSct(text[:start])
  2323. byteEnd = byteStart + self.bytelenSct(text[start:end])
  2324. startLine = self.LineFromPosition(byteStart)
  2325. endLine = self.LineFromPosition(byteEnd)
  2326. # Store current show/hide state of lines to show
  2327. shownList = []
  2328. for i in xrange(startLine, endLine + 1):
  2329. shownList.append(self.GetLineVisible(i))
  2330. self.incSearchPreviousHiddenLines = shownList
  2331. self.incSearchPreviousHiddenStartLine = startLine
  2332. # Show lines
  2333. self.ShowLines(startLine, endLine)
  2334. self.SetSelection(byteStart, byteEnd)
  2335. self.EnsureCaretVisible()
  2336. def startIncrementalSearch(self, initSearch=None):
  2337. self.incSearchPreviousHiddenLines = None
  2338. self.incSearchPreviousHiddenStartLine = -1
  2339. super(WikiTxtCtrl, self).startIncrementalSearch(initSearch)
  2340. def endIncrementalSearch(self):
  2341. super(WikiTxtCtrl, self).endIncrementalSearch()
  2342. self.ensureSelectionExpanded()
  2343. def rewrapText(self):
  2344. wrapType = "word" if self.GetWrapMode() != wx.stc.STC_WRAP_CHAR else "char"
  2345. return self.wikiLanguageHelper.handleRewrapText(self, {})
  2346. def getNearestWordPositions(self, bytepos=None):
  2347. if not bytepos:
  2348. bytepos = self.GetCurrentPos()
  2349. return (self.WordStartPosition(bytepos, 1), self.WordEndPosition(bytepos, 1))
  2350. def autoComplete(self):
  2351. """
  2352. Called when user wants autocompletion.
  2353. """
  2354. text = self.GetText()
  2355. wikiDocument = self.presenter.getWikiDocument()
  2356. closingBracket = self.presenter.getConfig().getboolean("main",
  2357. "editor_autoComplete_closingBracket", False)
  2358. bytePos = self.GetCurrentPos()
  2359. lineStartBytePos = self.PositionFromLine(self.LineFromPosition(bytePos))
  2360. lineStartCharPos = len(self.GetTextRange(0, lineStartBytePos))
  2361. charPos = lineStartCharPos + len(self.GetTextRange(lineStartBytePos,
  2362. bytePos))
  2363. acResultTuples = self.wikiLanguageHelper.prepareAutoComplete(self, text,
  2364. charPos, lineStartCharPos, wikiDocument, self.getLoadedDocPage(),
  2365. {"closingBracket": closingBracket, "builtinAttribs": True})
  2366. if len(acResultTuples) > 0:
  2367. self.presenter.getWikiDocument().getCollator().sortByFirst(
  2368. acResultTuples)
  2369. self.autoCompBackBytesMap = dict( (
  2370. (art[1], self.bytelenSct(text[charPos - art[2]:charPos]))
  2371. for art in acResultTuples) )
  2372. self.UserListShow(1, u"\x01".join(
  2373. [art[1] for art in acResultTuples]))
  2374. def OnModified(self, evt):
  2375. if not self.ignoreOnChange:
  2376. if evt.GetModificationType() & \
  2377. (wx.stc.STC_MOD_INSERTTEXT | wx.stc.STC_MOD_DELETETEXT):
  2378. self.presenter.informEditorTextChanged(self)
  2379. # docPage = self.getLoadedDocPage()
  2380. def OnCharAdded(self, evt):
  2381. "When the user presses enter reindent to the previous level"
  2382. # currPos = self.GetScrollPos(wxVERTICAL)
  2383. evt.Skip()
  2384. key = evt.GetKey()
  2385. if key == 10:
  2386. text = self.GetText()
  2387. wikiDocument = self.presenter.getWikiDocument()
  2388. bytePos = self.GetCurrentPos()
  2389. lineStartBytePos = self.PositionFromLine(self.LineFromPosition(bytePos))
  2390. lineStartCharPos = len(self.GetTextRange(0, lineStartBytePos))
  2391. charPos = lineStartCharPos + len(self.GetTextRange(lineStartBytePos,
  2392. bytePos))
  2393. autoUnbullet = self.presenter.getConfig().getboolean("main",
  2394. "editor_autoUnbullets", False)
  2395. settings = {
  2396. "autoUnbullet": autoUnbullet,
  2397. "autoBullets": self.autoBullets,
  2398. "autoIndent": self.autoIndent
  2399. }
  2400. self.wikiLanguageHelper.handleNewLineAfterEditor(self, text,
  2401. charPos, lineStartCharPos, wikiDocument, settings)
  2402. def _getExpandedByteSelectionToLine(self, extendOverChildren):
  2403. """
  2404. Move the start of current selection to start of the line it's in and
  2405. move end of selection to end of its line.
  2406. """
  2407. selByteStart = self.GetSelectionStart();
  2408. selByteEnd = self.GetSelectionEnd();
  2409. firstLine = self.LineFromPosition(selByteStart)
  2410. lastLine = self.LineFromPosition(selByteEnd)
  2411. selByteStart = self.PositionFromLine(self.LineFromPosition(selByteStart))
  2412. selByteEnd = self.PositionFromLine(lastLine + 1)
  2413. if extendOverChildren:
  2414. # Extend over all lines which are more indented than the first line
  2415. firstLineDeep = StringOps.splitIndentDeepness(self.GetLine(firstLine))[0]
  2416. testLine = lastLine + 1
  2417. while True:
  2418. testLineContent = self.GetLine(testLine)
  2419. if len(testLineContent) == 0:
  2420. # End of text reached
  2421. break
  2422. if StringOps.splitIndentDeepness(testLineContent)[0] <= firstLineDeep:
  2423. break
  2424. testLine += 1
  2425. selByteEnd = self.PositionFromLine(testLine)
  2426. self.SetSelectionMode(0)
  2427. self.SetSelectionStart(selByteStart)
  2428. self.SetSelectionMode(0)
  2429. self.SetSelectionEnd(selByteEnd)
  2430. return selByteStart, selByteEnd
  2431. def moveSelectedLinesOneUp(self, extendOverChildren):
  2432. """
  2433. Extend current selection to full logical lines and move selected lines
  2434. upward one line.
  2435. extendOverChildren -- iff true, extend selection over lines more indented
  2436. below the initial selection. It should only be set True for first move
  2437. in a sequence of moves (until all modifier keys are released) otherwise:
  2438. If you have e.g.
  2439. A
  2440. B
  2441. C
  2442. D
  2443. and move up C with children two times then B would be moved above A
  2444. as well which is not intended
  2445. """
  2446. self.BeginUndoAction()
  2447. try:
  2448. selByteStart, selByteEnd = self._getExpandedByteSelectionToLine(
  2449. extendOverChildren)
  2450. firstLine = self.LineFromPosition(selByteStart)
  2451. if firstLine > 0:
  2452. content = self.GetSelectedText()
  2453. if len(content) > 0:
  2454. if content[-1] == u"\n":
  2455. selByteEnd -= 1
  2456. else:
  2457. content += u"\n"
  2458. # Now content ends with \n and selection end points
  2459. # before this newline
  2460. self.ReplaceSelection("")
  2461. target = self.PositionFromLine(firstLine - 1)
  2462. self.InsertText(target, content)
  2463. self.SetSelectionMode(0)
  2464. self.SetSelectionStart(target)
  2465. self.SetSelectionMode(0)
  2466. self.SetSelectionEnd(target + (selByteEnd - selByteStart))
  2467. finally:
  2468. self.EndUndoAction()
  2469. def moveSelectedLinesOneDown(self, extendOverChildren):
  2470. """
  2471. Extend current selection to full logical lines and move selected lines
  2472. upward one line.
  2473. extendOverChildren -- iff true, extend selection over lines more indented
  2474. below the initial selection
  2475. """
  2476. self.BeginUndoAction()
  2477. try:
  2478. selByteStart, selByteEnd = self._getExpandedByteSelectionToLine(
  2479. extendOverChildren)
  2480. lastLine = self.LineFromPosition(selByteEnd)
  2481. lineCount = self.GetLineCount() - 1
  2482. if lastLine <= lineCount:
  2483. content = self.GetSelectedText()
  2484. if len(content) > 0:
  2485. # Now content ends with \n and selection end points
  2486. # before this newline
  2487. target = self.PositionFromLine(lastLine + 1)
  2488. target -= selByteEnd - selByteStart
  2489. if content[-1] == u"\n": # Necessary for downward move?
  2490. selByteEnd -= 1
  2491. else:
  2492. content += u"\n"
  2493. self.ReplaceSelection("")
  2494. if self.GetTextRange(target - 1,
  2495. target) != u"\n":
  2496. self.InsertText(target, u"\n")
  2497. target += 1
  2498. self.InsertText(target, content)
  2499. self.SetSelectionMode(0)
  2500. self.SetSelectionStart(target)
  2501. self.SetSelectionMode(0)
  2502. self.SetSelectionEnd(target + (selByteEnd - selByteStart))
  2503. finally:
  2504. self.EndUndoAction()
  2505. def OnKeyUp(self, evt):
  2506. evt.Skip()
  2507. if not self.modifiersPressedSinceExtLogLineMove:
  2508. return
  2509. if wxHelper.isAllModKeysReleased(evt):
  2510. self.modifiersPressedSinceExtLogLineMove = False
  2511. def OnKeyDown(self, evt):
  2512. key = evt.GetKeyCode()
  2513. self.lastKeyPressed = time()
  2514. accP = getAccelPairFromKeyDown(evt)
  2515. matchesAccelPair = self.presenter.getMainControl().keyBindings.\
  2516. matchesAccelPair
  2517. # if self.pageType == u"texttree":
  2518. # if accP in ( (wx.ACCEL_ALT, wx.WXK_NUMPAD_UP),
  2519. # (wx.ACCEL_ALT, wx.WXK_UP),
  2520. # (wx.ACCEL_SHIFT | wx.ACCEL_ALT, wx.WXK_NUMPAD_UP),
  2521. # (wx.ACCEL_SHIFT | wx.ACCEL_ALT, wx.WXK_UP) ):
  2522. #
  2523. # self.moveSelectedLinesOneUp(accP[0] & wx.ACCEL_SHIFT)
  2524. # return
  2525. # elif accP in ( (wx.ACCEL_ALT, wx.WXK_NUMPAD_DOWN),
  2526. # (wx.ACCEL_ALT, wx.WXK_DOWN),
  2527. # (wx.ACCEL_SHIFT | wx.ACCEL_ALT, wx.WXK_NUMPAD_DOWN),
  2528. # (wx.ACCEL_SHIFT | wx.ACCEL_ALT, wx.WXK_DOWN) ):
  2529. #
  2530. # self.moveSelectedLinesOneDown(accP[0] & wx.ACCEL_SHIFT)
  2531. # return
  2532. #
  2533. # evt.Skip()
  2534. if matchesAccelPair("AutoComplete", accP):
  2535. # AutoComplete is normally Ctrl-Space
  2536. # Handle autocompletion
  2537. self.autoComplete()
  2538. elif matchesAccelPair("ActivateLink2", accP):
  2539. # ActivateLink2 is normally Ctrl-Return
  2540. self.activateLink()
  2541. elif matchesAccelPair("ActivateLinkBackground", accP):
  2542. # ActivateLink2 is normally Ctrl-Return
  2543. self.activateLink(tabMode=3)
  2544. elif matchesAccelPair("ActivateLink", accP):
  2545. # ActivateLink is normally Ctrl-L
  2546. # There is also a shortcut for it. This can only happen
  2547. # if OnKeyDown is called indirectly
  2548. # from IncrementalSearchDialog.OnKeyDownInput
  2549. self.activateLink()
  2550. elif matchesAccelPair("ActivateLinkNewTab", accP):
  2551. # ActivateLinkNewTab is normally Ctrl-Alt-L
  2552. # There is also a shortcut for it. This can only happen
  2553. # if OnKeyDown is called indirectly
  2554. # from IncrementalSearchDialog.OnKeyDownInput
  2555. self.activateLink(tabMode=2)
  2556. elif matchesAccelPair("ActivateLinkNewWindow", accP):
  2557. self.activateLink(tabMode=6)
  2558. # elif matchesAccelPair("LogLineUp", accP):
  2559. # # LogLineUp is by default undefined
  2560. # self.moveSelectedLinesOneUp(False)
  2561. # elif matchesAccelPair("LogLineUpWithIndented", accP):
  2562. # # LogLineUp is by default undefined
  2563. # self.moveSelectedLinesOneUp(
  2564. # not self.modifiersPressedSinceExtLogLineMove)
  2565. # self.modifiersPressedSinceExtLogLineMove = True
  2566. # elif matchesAccelPair("LogLineDown", accP):
  2567. # # LogLineUp is by default undefined
  2568. # self.moveSelectedLinesOneDown(False)
  2569. # elif matchesAccelPair("LogLineDownWithIndented", accP):
  2570. # # LogLineUp is by default undefined
  2571. # self.moveSelectedLinesOneDown(
  2572. # not self.modifiersPressedSinceExtLogLineMove)
  2573. # self.modifiersPressedSinceExtLogLineMove = True
  2574. elif not evt.ControlDown() and not evt.ShiftDown(): # TODO Check all modifiers
  2575. if key == wx.WXK_TAB:
  2576. if self.pageType == u"form":
  2577. if not self._goToNextFormField():
  2578. self.presenter.getMainControl().showStatusMessage(
  2579. _(u"No more fields in this 'form' page"), -1)
  2580. return
  2581. evt.Skip()
  2582. elif key == wx.WXK_RETURN and not self.AutoCompActive():
  2583. text = self.GetText()
  2584. wikiDocument = self.presenter.getWikiDocument()
  2585. bytePos = self.GetCurrentPos()
  2586. lineStartBytePos = self.PositionFromLine(self.LineFromPosition(bytePos))
  2587. lineStartCharPos = len(self.GetTextRange(0, lineStartBytePos))
  2588. charPos = lineStartCharPos + len(self.GetTextRange(lineStartBytePos,
  2589. bytePos))
  2590. autoUnbullet = self.presenter.getConfig().getboolean("main",
  2591. "editor_autoUnbullets", False)
  2592. settings = {
  2593. "autoUnbullet": autoUnbullet,
  2594. "autoBullets": self.autoBullets,
  2595. "autoIndent": self.autoIndent
  2596. }
  2597. if self.wikiLanguageHelper.handleNewLineBeforeEditor(self, text,
  2598. charPos, lineStartCharPos, wikiDocument, settings):
  2599. evt.Skip()
  2600. return
  2601. else:
  2602. super(WikiTxtCtrl, self).OnKeyDown(evt)
  2603. else:
  2604. super(WikiTxtCtrl, self).OnKeyDown(evt)
  2605. # CallAfter is used as otherwise we seem to lose a mouseup
  2606. # evt. TODO: check what happens on windows
  2607. wx.CallAfter(self.presenter.makeCurrent)
  2608. def OnChar_ImeWorkaround(self, evt):
  2609. """
  2610. Workaround for problem of Scintilla with some input method editors,
  2611. e.g. UniKey vietnamese IME.
  2612. """
  2613. key = evt.GetKeyCode()
  2614. # Return if this doesn't seem to be a real character input
  2615. if evt.ControlDown() or (0 < key < 32):
  2616. evt.Skip()
  2617. return
  2618. if key >= wx.WXK_START and (not isUnicode() or evt.GetUnicodeKey() != key):
  2619. evt.Skip()
  2620. return
  2621. if isUnicode():
  2622. unichar = unichr(evt.GetUnicodeKey())
  2623. else:
  2624. unichar = StringOps.mbcsDec(chr(key))[0]
  2625. self.ReplaceSelection(unichar)
  2626. def OnMouseWheel(self, evt):
  2627. # Scintilla's wheel zoom behavior is unusual (upward=zoom out)
  2628. # So the sign of rotation value must be changed if wheel zoom is NOT
  2629. # reversed by option
  2630. if evt.ControlDown() and not self.presenter.getConfig().getboolean(
  2631. "main", "mouse_reverseWheelZoom", False):
  2632. evt.m_wheelRotation = -evt.m_wheelRotation
  2633. evt.Skip()
  2634. def OnLogicalLineMove(self, evt):
  2635. evtId = evt.GetId()
  2636. if evtId == GUI_ID.CMD_LOGICAL_LINE_UP:
  2637. self.moveSelectedLinesOneUp(False)
  2638. elif evtId == GUI_ID.CMD_LOGICAL_LINE_UP_WITH_INDENT:
  2639. self.moveSelectedLinesOneUp(
  2640. not self.modifiersPressedSinceExtLogLineMove)
  2641. self.modifiersPressedSinceExtLogLineMove = \
  2642. not wxHelper.isAllModKeysReleased(None)
  2643. elif evtId == GUI_ID.CMD_LOGICAL_LINE_DOWN:
  2644. self.moveSelectedLinesOneDown(False)
  2645. elif evtId == GUI_ID.CMD_LOGICAL_LINE_DOWN_WITH_INDENT:
  2646. self.moveSelectedLinesOneDown(
  2647. not self.modifiersPressedSinceExtLogLineMove)
  2648. self.modifiersPressedSinceExtLogLineMove = \
  2649. not wxHelper.isAllModKeysReleased(None)
  2650. if isLinux():
  2651. def OnSetFocus(self, evt):
  2652. #self.presenter.makeCurrent()
  2653. # We need to make sure makeCurrent uses CallAfter overwise we
  2654. # get a selection bug if the mouse is moved quickly after
  2655. # clicking on the TxtCtrl (not sure if this is required for
  2656. # windows)
  2657. wx.CallAfter(self.presenter.makeCurrent)
  2658. evt.Skip()
  2659. wikiPage = self.getLoadedDocPage()
  2660. if wikiPage is None:
  2661. return
  2662. if not isinstance(wikiPage,
  2663. (DocPages.DataCarryingPage, DocPages.AliasWikiPage)):
  2664. return
  2665. try:
  2666. wikiPage.checkFileSignatureAndMarkDirty()
  2667. except (IOError, OSError, DbAccessError), e:
  2668. self.presenter.getMainControl().lostAccess(e)
  2669. evt.Skip()
  2670. else:
  2671. def OnSetFocus(self, evt):
  2672. self.presenter.makeCurrent()
  2673. evt.Skip()
  2674. wikiPage = self.getLoadedDocPage()
  2675. if wikiPage is None:
  2676. return
  2677. if not isinstance(wikiPage,
  2678. (DocPages.DataCarryingPage, DocPages.AliasWikiPage)):
  2679. return
  2680. try:
  2681. wikiPage.checkFileSignatureAndMarkDirty()
  2682. except (IOError, OSError, DbAccessError), e:
  2683. self.presenter.getMainControl().lostAccess(e)
  2684. def OnUserListSelection(self, evt):
  2685. text = evt.GetText()
  2686. toerase = self.autoCompBackBytesMap[text]
  2687. self.SetSelection(self.GetCurrentPos() - toerase, self.GetCurrentPos())
  2688. self.ReplaceSelection(text)
  2689. def OnClick(self, evt):
  2690. if evt.ControlDown():
  2691. x = evt.GetX()
  2692. y = evt.GetY()
  2693. if not self.activateLink(wx.Point(x, y)):
  2694. evt.Skip()
  2695. else:
  2696. evt.Skip()
  2697. def OnMiddleDown(self, evt):
  2698. if not evt.ControlDown():
  2699. middleConfig = self.presenter.getConfig().getint("main",
  2700. "mouse_middleButton_withoutCtrl", 2)
  2701. else:
  2702. middleConfig = self.presenter.getConfig().getint("main",
  2703. "mouse_middleButton_withCtrl", 3)
  2704. tabMode = Configuration.MIDDLE_MOUSE_CONFIG_TO_TABMODE[middleConfig]
  2705. if not self.activateLink(evt.GetPosition(), tabMode=tabMode):
  2706. evt.Skip()
  2707. def OnDoubleClick(self, evt):
  2708. x = evt.GetX()
  2709. y = evt.GetY()
  2710. if not self.activateLink(wx.Point(x, y)):
  2711. evt.Skip()
  2712. # def OnMouseMove(self, evt):
  2713. # if (not evt.ControlDown()) or evt.Dragging():
  2714. # self.SetCursor(WikiTxtCtrl.CURSOR_IBEAM)
  2715. # evt.Skip()
  2716. # return
  2717. # else:
  2718. # textPos = self.PositionFromPoint(evt.GetPosition())
  2719. #
  2720. # if (self.isPositionInWikiWord(textPos) or
  2721. # self.isPositionInLink(textPos)):
  2722. # self.SetCursor(WikiTxtCtrl.CURSOR_HAND)
  2723. # return
  2724. # else:
  2725. # # self.SetCursor(WikiTxtCtrl.CURSOR_IBEAM)
  2726. # evt.Skip()
  2727. # return
  2728. # def OnStyleDone(self, evt):
  2729. # if evt.stylebytes:
  2730. # self.applyStyling(evt.stylebytes)
  2731. #
  2732. # if evt.foldingseq:
  2733. # self.applyFolding(evt.foldingseq)
  2734. #
  2735. def onIdleVisible(self, miscevt):
  2736. if (self.IsEnabled() and (wx.Window.FindFocus() is self)):
  2737. if self.presenter.isCurrent():
  2738. # fix the line, pos and col numbers
  2739. currentLine = self.GetCurrentLine()+1
  2740. currentPos = self.GetCurrentPos()
  2741. currentCol = self.GetColumn(currentPos)
  2742. self.presenter.SetStatusText(_(u"Line: %d Col: %d Pos: %d") %
  2743. (currentLine, currentCol, currentPos), 2)
  2744. def OnDestroy(self, evt):
  2745. # This is how the clipboard contents can be preserved after
  2746. # the app has exited.
  2747. wx.TheClipboard.Flush()
  2748. evt.Skip()
  2749. def OnMarginClick(self, evt):
  2750. if evt.GetMargin() == self.FOLD_MARGIN:
  2751. pos = evt.GetPosition()
  2752. line = self.LineFromPosition(pos)
  2753. modifiers = evt.GetModifiers() #?
  2754. self.ToggleFold(line)
  2755. self.repairFoldingVisibility()
  2756. evt.Skip()
  2757. def _threadShowCalltip(self, wikiDocument, charPos, bytePos, mouseCoords,
  2758. windowSize, threadstop=DUMBTHREADSTOP):
  2759. try:
  2760. docPage = self.getLoadedDocPage()
  2761. if docPage is None:
  2762. return
  2763. pageAst = docPage.getLivePageAst(threadstop=threadstop)
  2764. astNodes = pageAst.findNodesForCharPos(charPos)
  2765. if charPos > 0:
  2766. # Maybe a token left to the cursor was meant, so check
  2767. # one char to the left
  2768. astNodes += pageAst.findNodesForCharPos(charPos - 1)
  2769. callTip = None
  2770. for astNode in astNodes:
  2771. if astNode.name == "wikiWord":
  2772. threadstop.testValidThread()
  2773. wikiWord = wikiDocument.getWikiPageNameForLinkTerm(
  2774. astNode.wikiWord)
  2775. # Set status to wikipage
  2776. callInMainThread(
  2777. self.presenter.getMainControl().showStatusMessage,
  2778. _(u"Link to page: %s") % wikiWord, 0, "linkToPage")
  2779. if wikiWord is not None:
  2780. propList = wikiDocument.getAttributeTriples(
  2781. wikiWord, u"short_hint", None)
  2782. if len(propList) > 0:
  2783. callTip = propList[-1][2]
  2784. break
  2785. elif astNode.name == "urlLink":
  2786. # Should we show image preview tooltips for local URLs?
  2787. if not self.presenter.getConfig().getboolean("main",
  2788. "editor_imageTooltips_localUrls", True):
  2789. continue
  2790. # Decision code taken from HtmlExporter.HtmlExporter._processUrlLink
  2791. if astNode.appendixNode is None:
  2792. appendixDict = {}
  2793. else:
  2794. appendixDict = dict(astNode.appendixNode.entries)
  2795. # Decide if this is an image link
  2796. if appendixDict.has_key("l"):
  2797. urlAsImage = False
  2798. # If we have an external link prevent its attemped render
  2799. elif astNode.url.lower().startswith("http"):
  2800. urlAsImage = False
  2801. callTip = _(u"External (http) link")
  2802. elif appendixDict.has_key("i"):
  2803. urlAsImage = True
  2804. # elif self.asHtmlPreview and \
  2805. # self.mainControl.getConfig().getboolean(
  2806. # "main", "html_preview_pics_as_links"):
  2807. # urlAsImage = False
  2808. # elif not self.asHtmlPreview and self.addOpt[0]:
  2809. # urlAsImage = False
  2810. elif astNode.url.lower().split(".")[-1] in \
  2811. ("jpg", "jpeg", "gif", "png", "tif", "bmp"):
  2812. urlAsImage = True
  2813. else:
  2814. urlAsImage = False
  2815. # If link is a picture display it as a tooltip
  2816. if urlAsImage:
  2817. path = self.presenter.getWikiDocument()\
  2818. .makeFileUrlAbsPath(astNode.url)
  2819. if path is not None and isfile(path):
  2820. if imghdr.what(path):
  2821. config = self.presenter.getConfig()
  2822. maxWidth = config.getint("main",
  2823. "editor_imageTooltips_maxWidth", 200)
  2824. maxHeight = config.getint("main",
  2825. "editor_imageTooltips_maxHeight", 200)
  2826. def SetImageTooltip(path):
  2827. self.tooltip_image = ImageTooltipPanel(self,
  2828. path, maxWidth, maxHeight)
  2829. threadstop.testValidThread()
  2830. callInMainThread(SetImageTooltip, path)
  2831. else:
  2832. callTip = _(u"Not a valid image")
  2833. break
  2834. else:
  2835. callTip = _(u"Image does not exist")
  2836. if callTip:
  2837. threadstop.testValidThread()
  2838. # Position and format CallTip
  2839. # try and display CallTip without reformating
  2840. callTipLen = max([len(i) for i in callTip.split("\n")])
  2841. colPos = self.GetColumn(bytePos)
  2842. mouseX, mouseY = mouseCoords
  2843. # There is probably a better way to do this without making a
  2844. # call back to the main thread...
  2845. callTipWidth = callInMainThread(self.TextWidth, 0, callTip)
  2846. x, y = windowSize
  2847. # first see if we can just reposition the calltip
  2848. if x <= callTipWidth + mouseX:
  2849. # if this fails wrap the calltip to a more reasonable size
  2850. if x < callTipWidth:
  2851. # Split the CallTip
  2852. ratio = x / float(callTipWidth)
  2853. maxTextLen = int(ratio * callTipLen * 0.8)
  2854. lines = callTip.split("\n")
  2855. formatedLines = []
  2856. # By default wrap ignores newlines so rewrap each line
  2857. # seperately
  2858. for line in lines:
  2859. formatedLines.append(
  2860. "\n".join(textwrap.wrap(line, maxTextLen)))
  2861. callTip = "\n".join(formatedLines)
  2862. bytePos = bytePos - self.GetColumn(bytePos)
  2863. callInMainThread(self.CallTipShow, bytePos, callTip)
  2864. except NotCurrentThreadException:
  2865. pass
  2866. @contextlib.contextmanager
  2867. def dwellLock(self):
  2868. if self.dwellLockCounter == 0:
  2869. self.OnDwellEnd(None)
  2870. self.dwellLockCounter += 1
  2871. yield
  2872. self.dwellLockCounter -= 1
  2873. def OnDwellStart(self, evt):
  2874. if self.dwellLockCounter > 0:
  2875. return
  2876. elif self.vi is not None and self.vi.KeyCommandInProgress():
  2877. # Otherwise calltips (etc..) will break a command input
  2878. return
  2879. wikiDocument = self.presenter.getWikiDocument()
  2880. if wikiDocument is None:
  2881. return
  2882. bytePos = evt.GetPosition()
  2883. charPos = len(self.GetTextRange(0, bytePos))
  2884. mouseCoords = evt.GetX(), evt.GetY()
  2885. windowSize = self.GetClientSizeTuple()
  2886. thread = threading.Thread(target=self._threadShowCalltip,
  2887. args=(wikiDocument, charPos, bytePos, mouseCoords, windowSize),
  2888. kwargs={"threadstop": self.calltipThreadHolder})
  2889. self.calltipThreadHolder.setThread(thread)
  2890. thread.setDaemon(True)
  2891. thread.start()
  2892. def OnDwellEnd(self, evt):
  2893. if self.dwellLockCounter > 0:
  2894. return
  2895. self.calltipThreadHolder.setThread(None)
  2896. self.CallTipCancel()
  2897. # Set status back to previous
  2898. callInMainThread(self.presenter.getMainControl().dropStatusMessageByKey,
  2899. "linkToPage")
  2900. # And close any shown pic
  2901. if self.tooltip_image:
  2902. self.tooltip_image.Close()
  2903. self.tooltip_image = None
  2904. @staticmethod
  2905. def userActionPasteFiles(unifActionName, paramDict):
  2906. """
  2907. User action to handle pasting or dropping of files into editor.
  2908. """
  2909. editor = paramDict.get("editor")
  2910. if editor is None:
  2911. return
  2912. filenames = paramDict.get("filenames")
  2913. x = paramDict.get("x")
  2914. y = paramDict.get("y")
  2915. dlgParams = WikiTxtDialogs.FilePasteParams()
  2916. # config = editor.presenter.getMainControl().getConfig()
  2917. dlgParams.readOptionsFromConfig(
  2918. editor.presenter.getMainControl().getConfig())
  2919. if unifActionName == u"action/editor/this/paste/files/insert/url/ask":
  2920. # Ask user
  2921. if not paramDict.get("processDirectly", False):
  2922. # If files are drag&dropped, at least on Windows the dragging
  2923. # source (e.g. Windows Explorer) is halted until the drop
  2924. # event returns.
  2925. # So do an idle call to open dialog later
  2926. paramDict["processDirectly"] = True
  2927. wx.CallAfter(WikiTxtCtrl.userActionPasteFiles, unifActionName,
  2928. paramDict)
  2929. return
  2930. dlgParams = WikiTxtDialogs.FilePasteDialog.runModal(
  2931. editor.presenter.getMainControl(), -1, dlgParams)
  2932. if dlgParams is None:
  2933. # User abort
  2934. return
  2935. unifActionName = dlgParams.unifActionName
  2936. moveToStorage = False
  2937. if unifActionName == u"action/editor/this/paste/files/insert/url/absolute":
  2938. modeToStorage = False
  2939. modeRelativeUrl = False
  2940. elif unifActionName == u"action/editor/this/paste/files/insert/url/relative":
  2941. modeToStorage = False
  2942. modeRelativeUrl = True
  2943. elif unifActionName == u"action/editor/this/paste/files/insert/url/tostorage":
  2944. modeToStorage = True
  2945. modeRelativeUrl = False
  2946. elif unifActionName == u"action/editor/this/paste/files/insert/url/movetostorage":
  2947. modeToStorage = True
  2948. modeRelativeUrl = False
  2949. moveToStorage = True
  2950. else:
  2951. return
  2952. try:
  2953. prefix = StringOps.strftimeUB(dlgParams.rawPrefix)
  2954. except:
  2955. traceback.print_exc()
  2956. prefix = u"" # TODO Error message?
  2957. try:
  2958. middle = StringOps.strftimeUB(dlgParams.rawMiddle)
  2959. except:
  2960. traceback.print_exc()
  2961. middle = u" " # TODO Error message?
  2962. try:
  2963. suffix = StringOps.strftimeUB(dlgParams.rawSuffix)
  2964. except:
  2965. traceback.print_exc()
  2966. suffix = u"" # TODO Error message?
  2967. urls = []
  2968. for fn in filenames:
  2969. protocol = None
  2970. if fn.endswith(u".wiki"):
  2971. protocol = "wiki"
  2972. toStorage = False
  2973. if modeToStorage and protocol is None:
  2974. # Copy file into file storage
  2975. fs = editor.presenter.getWikiDocument().getFileStorage()
  2976. try:
  2977. fn = fs.createDestPath(fn, move=moveToStorage)
  2978. toStorage = True
  2979. except Exception, e:
  2980. traceback.print_exc()
  2981. editor.presenter.getMainControl().displayErrorMessage(
  2982. _(u"Couldn't copy file"), e)
  2983. return
  2984. urls.append(editor.wikiLanguageHelper.createUrlLinkFromPath(
  2985. editor.presenter.getWikiDocument(), fn,
  2986. relative=modeRelativeUrl or toStorage,
  2987. bracketed=dlgParams.bracketedUrl, protocol=protocol))
  2988. editor.handleDropText(x, y, prefix + middle.join(urls) + suffix)
  2989. def GetEOLChar(self):
  2990. """
  2991. Gets the end of line char currently being used
  2992. """
  2993. m_id = self.GetEOLMode()
  2994. if m_id == wx.stc.STC_EOL_CR:
  2995. return u'\r'
  2996. elif m_id == wx.stc.STC_EOL_CRLF:
  2997. return u'\r\n'
  2998. else:
  2999. return u'\n'
  3000. def GetScrollAndCaretPosition(self):
  3001. return self.GetCurrentPos(), self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
  3002. def SetScrollAndCaretPosition(self, pos, x, y):
  3003. self.GotoPos(pos)
  3004. self.scrollXY(x, y)
  3005. def GetViHandler(self):
  3006. return self.vi
  3007. # TODO
  3008. # def setMouseCursor(self):
  3009. # """
  3010. # Set the right mouse cursor depending on some circumstances.
  3011. # Returns True iff a special cursor was choosen.
  3012. # """
  3013. # mousePos = wxGetMousePosition()
  3014. # mouseBtnPressed = wxGetKeyState(WXK_LBUTTON) or \
  3015. # wxGetKeyState(WXK_MBUTTON) or \
  3016. # wxGetKeyState(WXK_RBUTTON)
  3017. #
  3018. # ctrlPressed = wxGetKeyState(WXK_CONTROL)
  3019. #
  3020. # if (not ctrlPressed) or mouseBtnPressed:
  3021. # self.SetCursor(WikiTxtCtrl.CURSOR_IBEAM)
  3022. # return False
  3023. # else:
  3024. # linkPos = self.PositionFromPoint(wxPoint(*self.ScreenToClientXY(*mousePos)))
  3025. #
  3026. # if (self.isPositionInWikiWord(linkPos) or
  3027. # self.isPositionInLink(linkPos)):
  3028. # self.SetCursor(WikiTxtCtrl.CURSOR_HAND)
  3029. # return True
  3030. # else:
  3031. # self.SetCursor(WikiTxtCtrl.CURSOR_IBEAM)
  3032. # return False
  3033. class WikiTxtCtrlDropTarget(wx.PyDropTarget):
  3034. def __init__(self, editor):
  3035. wx.PyDropTarget.__init__(self)
  3036. self.editor = editor
  3037. self.resetDObject()
  3038. def resetDObject(self):
  3039. """
  3040. (Re)sets the dataobject at init and after each drop
  3041. """
  3042. dataob = wx.DataObjectComposite()
  3043. self.tobj = wx.TextDataObject() # Char. size depends on wxPython build!
  3044. dataob.Add(self.tobj)
  3045. self.fobj = wx.FileDataObject()
  3046. dataob.Add(self.fobj)
  3047. self.dataob = dataob
  3048. self.SetDataObject(dataob)
  3049. def OnDragOver(self, x, y, defresult):
  3050. return self.editor.DoDragOver(x, y, defresult)
  3051. def OnData(self, x, y, defresult):
  3052. try:
  3053. if self.GetData():
  3054. fnames = self.fobj.GetFilenames()
  3055. text = self.tobj.GetText()
  3056. if fnames:
  3057. self.OnDropFiles(x, y, fnames)
  3058. elif text:
  3059. text = StringOps.lineendToInternal(text)
  3060. self.OnDropText(x, y, text)
  3061. return defresult
  3062. finally:
  3063. self.resetDObject()
  3064. def OnDropText(self, x, y, text):
  3065. text = StringOps.lineendToInternal(text)
  3066. self.editor.handleDropText(x, y, text)
  3067. def OnDropFiles(self, x, y, filenames):
  3068. urls = []
  3069. # Necessary because key state may change during the loop
  3070. controlPressed = wx.GetKeyState(wx.WXK_CONTROL)
  3071. shiftPressed = wx.GetKeyState(wx.WXK_SHIFT)
  3072. if isLinux():
  3073. # On Linux, at least Ubuntu, fn may be a UTF-8 encoded unicode(!?)
  3074. # string
  3075. try:
  3076. filenames = [StringOps.utf8Dec(fn.encode("latin-1"))[0]
  3077. for fn in filenames]
  3078. except (UnicodeEncodeError, UnicodeDecodeError):
  3079. pass
  3080. mc = self.editor.presenter.getMainControl()
  3081. paramDict = {"editor": self.editor, "filenames": filenames,
  3082. "x": x, "y": y, "main control": mc}
  3083. if controlPressed:
  3084. suffix = u"/modkeys/ctrl"
  3085. elif shiftPressed:
  3086. suffix = u"/modkeys/shift"
  3087. else:
  3088. suffix = u""
  3089. mc.getUserActionCoord().reactOnUserEvent(
  3090. u"mouse/leftdrop/editor/files" + suffix, paramDict)
  3091. # User actions to register
  3092. # _ACTION_EDITOR_PASTE_FILES_ABSOLUTE = UserActionCoord.SimpleAction("",
  3093. # u"action/editor/this/paste/files/insert/url/absolute",
  3094. # WikiTxtCtrl.userActionPasteFiles)
  3095. #
  3096. # _ACTION_EDITOR_PASTE_FILES_RELATIVE = UserActionCoord.SimpleAction("",
  3097. # u"action/editor/this/paste/files/insert/url/relative",
  3098. # WikiTxtCtrl.userActionPasteFiles)
  3099. #
  3100. # _ACTION_EDITOR_PASTE_FILES_TOSTORAGE = UserActionCoord.SimpleAction("",
  3101. # u"action/editor/this/paste/files/insert/url/tostorage",
  3102. # WikiTxtCtrl.userActionPasteFiles)
  3103. #
  3104. # _ACTION_EDITOR_PASTE_FILES_ASK = UserActionCoord.SimpleAction("",
  3105. # u"action/editor/this/paste/files/insert/url/ask",
  3106. # WikiTxtCtrl.userActionPasteFiles)
  3107. #
  3108. #
  3109. # _ACTIONS = (
  3110. # _ACTION_EDITOR_PASTE_FILES_ABSOLUTE, _ACTION_EDITOR_PASTE_FILES_RELATIVE,
  3111. # _ACTION_EDITOR_PASTE_FILES_TOSTORAGE, _ACTION_EDITOR_PASTE_FILES_ASK)
  3112. # Register paste actions
  3113. _ACTIONS = tuple( UserActionCoord.SimpleAction("", unifName,
  3114. WikiTxtCtrl.userActionPasteFiles) for unifName in (
  3115. u"action/editor/this/paste/files/insert/url/absolute",
  3116. u"action/editor/this/paste/files/insert/url/relative",
  3117. u"action/editor/this/paste/files/insert/url/tostorage",
  3118. u"action/editor/this/paste/files/insert/url/movetostorage",
  3119. u"action/editor/this/paste/files/insert/url/ask") )
  3120. UserActionCoord.registerActions(_ACTIONS)
  3121. _CONTEXT_MENU_INTEXT_SPELLING = \
  3122. u"""
  3123. -
  3124. Ignore;CMD_ADD_THIS_SPELLING_SESSION
  3125. Add Globally;CMD_ADD_THIS_SPELLING_GLOBAL
  3126. Add Locally;CMD_ADD_THIS_SPELLING_LOCAL
  3127. """
  3128. _CONTEXT_MENU_INTEXT_BASE = \
  3129. u"""
  3130. -
  3131. Undo;CMD_UNDO
  3132. Redo;CMD_REDO
  3133. -
  3134. Cut;CMD_CLIPBOARD_CUT
  3135. Copy;CMD_CLIPBOARD_COPY
  3136. Paste;CMD_CLIPBOARD_PASTE
  3137. Delete;CMD_TEXT_DELETE
  3138. -
  3139. Select All;CMD_SELECT_ALL
  3140. """
  3141. _CONTEXT_MENU_INTEXT_ACTIVATE = \
  3142. u"""
  3143. -
  3144. Follow Link;CMD_ACTIVATE_THIS
  3145. Follow Link New Tab;CMD_ACTIVATE_NEW_TAB_THIS
  3146. Follow Link New Tab Backgrd.;CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS
  3147. Follow Link New Window;CMD_ACTIVATE_NEW_WINDOW_THIS
  3148. """
  3149. _CONTEXT_MENU_INTEXT_ACTIVATE_DIRECTION = {
  3150. u"left" : u"""
  3151. -
  3152. Follow Link in pane|Left;CMD_ACTIVATE_THIS_LEFT
  3153. Follow Link in pane|Left New Tab;CMD_ACTIVATE_NEW_TAB_THIS_LEFT
  3154. Follow Link in pane|Left New Tab Backgrd.;CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_LEFT
  3155. """,
  3156. u"right" : u"""
  3157. -
  3158. Follow Link in pane|Right;CMD_ACTIVATE_THIS_RIGHT
  3159. Follow Link in pane|Right New Tab;CMD_ACTIVATE_NEW_TAB_THIS_RIGHT
  3160. Follow Link in pane|Right New Tab Backgrd.;CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_RIGHT
  3161. """,
  3162. u"above" : u"""
  3163. -
  3164. Follow Link in pane|Above;CMD_ACTIVATE_THIS_ABOVE
  3165. Follow Link in pane|Above New Tab;CMD_ACTIVATE_NEW_TAB_THIS_ABOVE
  3166. Follow Link in pane|Above New Tab Backgrd.;CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_ABOVE
  3167. """,
  3168. u"below" : u"""
  3169. -
  3170. Follow Link in pane|Below;CMD_ACTIVATE_THIS_BELOW
  3171. Follow Link in pane|Below New Tab;CMD_ACTIVATE_NEW_TAB_THIS_BELOW
  3172. Follow Link in pane|Below New Tab Backgrd.;CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS_BELOW
  3173. """,
  3174. }
  3175. _CONTEXT_MENU_INTEXT_WIKI_URL = \
  3176. u"""
  3177. -
  3178. Convert Absolute/Relative File URL;CMD_CONVERT_URL_ABSOLUTE_RELATIVE_THIS
  3179. Open Containing Folder;CMD_OPEN_CONTAINING_FOLDER_THIS
  3180. """
  3181. _CONTEXT_MENU_INTEXT_FILE_URL = \
  3182. u"""
  3183. -
  3184. Convert Absolute/Relative File URL;CMD_CONVERT_URL_ABSOLUTE_RELATIVE_THIS
  3185. Open Containing Folder;CMD_OPEN_CONTAINING_FOLDER_THIS
  3186. Rename file;CMD_RENAME_FILE
  3187. Delete file;CMD_DELETE_FILE
  3188. """
  3189. _CONTEXT_MENU_INTEXT_URL_TO_CLIPBOARD = \
  3190. u"""
  3191. -
  3192. Copy Anchor URL to Clipboard;CMD_CLIPBOARD_COPY_URL_TO_THIS_ANCHOR
  3193. """
  3194. _CONTEXT_MENU_SELECT_TEMPLATE_IN_TEMPLATE_MENU = \
  3195. u"""
  3196. -
  3197. Other...;CMD_SELECT_TEMPLATE
  3198. """
  3199. _CONTEXT_MENU_SELECT_TEMPLATE = \
  3200. u"""
  3201. -
  3202. Use Template...;CMD_SELECT_TEMPLATE
  3203. """
  3204. _CONTEXT_MENU_INTEXT_BOTTOM = \
  3205. u"""
  3206. -
  3207. Close Tab;CMD_CLOSE_CURRENT_TAB
  3208. """
  3209. FOLD_MENU = \
  3210. u"""
  3211. +Show folding;CMD_CHECKBOX_SHOW_FOLDING;Show folding marks and allow folding;*ShowFolding
  3212. &Toggle current folding;CMD_TOGGLE_CURRENT_FOLDING;Toggle folding of the current line;*ToggleCurrentFolding
  3213. &Unfold All;CMD_UNFOLD_ALL_IN_CURRENT;Unfold everything in current editor;*UnfoldAll
  3214. &Fold All;CMD_FOLD_ALL_IN_CURRENT;Fold everything in current editor;*FoldAll
  3215. """
  3216. # Entries to support i18n of context menus
  3217. if False:
  3218. N_(u"Ignore")
  3219. N_(u"Add Globally")
  3220. N_(u"Add Locally")
  3221. N_(u"Undo")
  3222. N_(u"Redo")
  3223. N_(u"Cut")
  3224. N_(u"Copy")
  3225. N_(u"Paste")
  3226. N_(u"Delete")
  3227. N_(u"Select All")
  3228. N_(u"Follow Link")
  3229. N_(u"Follow Link New Tab")
  3230. N_(u"Follow Link New Tab Backgrd.")
  3231. N_(u"Follow Link New Window")
  3232. N_(u"Convert Absolute/Relative File URL")
  3233. N_(u"Open Containing Folder")
  3234. N_(u"Rename file")
  3235. N_(u"Delete file")
  3236. N_(u"Copy anchor URL to clipboard")
  3237. N_(u"Other...")
  3238. N_(u"Use Template...")
  3239. N_(u"Close Tab")
  3240. N_(u"Show folding")
  3241. N_(u"Show folding marks and allow folding")
  3242. N_(u"&Toggle current folding")
  3243. N_(u"Toggle folding of the current line")
  3244. N_(u"&Unfold All")
  3245. N_(u"Unfold everything in current editor")
  3246. N_(u"&Fold All")
  3247. N_(u"Fold everything in current editor")
  3248. # I will move this to wxHelper later (MB)
  3249. try:
  3250. class wxPopupOrFrame(wx.PopupWindow):
  3251. def __init__(self, parent, id=-1, style=None):
  3252. wx.PopupWindow.__init__(self, parent)
  3253. except AttributeError:
  3254. class wxPopupOrFrame(wx.Frame):
  3255. def __init__(self, parent, id=-1,
  3256. style=wx.NO_BORDER|wx.FRAME_NO_TASKBAR|wx.FRAME_FLOAT_ON_PARENT):
  3257. wx.Frame.__init__(self, parent, id, style=style)
  3258. class ImageTooltipPanel(wxPopupOrFrame):
  3259. """Quick panel for image tooltips"""
  3260. def __init__(self, pWiki, filePath, maxWidth=200, maxHeight=200):
  3261. wxPopupOrFrame.__init__(self, pWiki, -1)
  3262. self.url = filePath
  3263. self.pWiki = pWiki
  3264. self.firstMove = True
  3265. img = wx.Image(filePath, wx.BITMAP_TYPE_ANY)
  3266. origWidth = img.GetWidth()
  3267. origHeight = img.GetHeight()
  3268. # Set defaults for invalid values
  3269. if maxWidth <= 0:
  3270. maxWidth = 200
  3271. if maxHeight <= 0:
  3272. maxHeight = 200
  3273. if origWidth > 0 and origHeight > 0:
  3274. self.width, self.height = calcResizeArIntoBoundingBox(origWidth,
  3275. origHeight, maxWidth, maxHeight)
  3276. img.Rescale(self.width, self.height, quality = wx.IMAGE_QUALITY_HIGH)
  3277. else:
  3278. self.width = origWidth
  3279. self.height = origHeight
  3280. img = img.ConvertToBitmap()
  3281. self.SetSize((self.width, self.height))
  3282. self.bmp = wx.StaticBitmap(self, -1, img, (0, 0), (img.GetWidth(),
  3283. img.GetHeight()))
  3284. self.bmp.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick)
  3285. self.bmp.Bind(wx.EVT_RIGHT_DOWN, self.OnRightClick)
  3286. self.bmp.Bind(wx.EVT_MOTION, self.OnMouseMotion)
  3287. self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  3288. mousePos = wx.GetMousePosition()
  3289. # If possible the frame shouldn't be exactly under mouse pointer
  3290. # so doubleclicking on link works
  3291. mousePos.x += 1
  3292. mousePos.y += 1
  3293. WindowLayout.setWindowPos(self, mousePos, fullVisible=True)
  3294. self.Show()
  3295. # Works for Windows (but not for GTK), maybe also for Mac (MB)
  3296. self.GetParent().SetFocus()
  3297. def Close(self, event=None):
  3298. self.Destroy()
  3299. def OnLeftClick(self, event=None):
  3300. # scrPos = self.ClientToScreen(evt.GetPosition())
  3301. self.Close()
  3302. # wnd = wx.FindWindowAtPoint(scrPos)
  3303. # print "--OnLeftClick1", repr((self, wnd))
  3304. # if wnd is not None:
  3305. # cliPos = wnd.ScreenToClient(scrPos)
  3306. # evt.m_x = cliPos.x
  3307. # evt.m_y = cliPos.y
  3308. # wnd.ProcessEvent(evt)
  3309. def OnRightClick(self, event=None):
  3310. self.Close()
  3311. def OnMouseMotion(self, evt):
  3312. if self.firstMove:
  3313. self.firstMove = False
  3314. evt.Skip()
  3315. return
  3316. self.Close()
  3317. def OnKeyDown(self, event):
  3318. kc = event.GetKeyCode()
  3319. if kc == wx.WXK_ESCAPE:
  3320. self.Close()
  3321. class ViHandler(ViHelper):
  3322. # TODO: Add search commands
  3323. # repeat visual actions
  3324. # scroll cursor on mousewheel at viewport top/bottom
  3325. def __init__(self, stc):
  3326. ViHelper.__init__(self, stc)
  3327. self._anchor = None
  3328. wx.CallAfter(self.SetDefaultCaretColour)
  3329. # Set default mode
  3330. wx.CallAfter(self.SetMode, ViHelper.NORMAL)
  3331. wx.CallAfter(self.Setup)
  3332. self.text_object_map = {
  3333. "w" : (False, self.SelectInWord),
  3334. "W" : (False, self.SelectInWORD),
  3335. "s" : (False, self.SelectInSentence),
  3336. "p" : (False, self.SelectInParagraph),
  3337. "[" : (True, self.SelectInSquareBracket),
  3338. "]" : (True, self.SelectInSquareBracket),
  3339. "r" : (True, self.SelectInSquareBracketIgnoreStartingSlash),
  3340. "(" : (True, self.SelectInRoundBracket),
  3341. ")" : (True, self.SelectInRoundBracket),
  3342. "b" : (True, self.SelectInRoundBracket),
  3343. # < will select double blocks, << ... >> as these
  3344. # are commonly used in the default wikidpad parser
  3345. "<" : (True, self.SelectInDoubleInequalitySigns),
  3346. ">" : (True, self.SelectInInequalitySigns),
  3347. "t" : (True, self.SelectInTagBlock),
  3348. # T for table?
  3349. "{" : (True, self.SelectInBlock),
  3350. "}" : (True, self.SelectInBlock),
  3351. "B" : (True, self.SelectInBlock),
  3352. '"' : (True, self.SelectInDoubleQuote),
  3353. "'" : (True, self.SelectInSingleQuote),
  3354. "`" : (True, self.SelectInTilde),
  3355. # The commands below are not present in vim but may
  3356. # be useful for quickly editing parser syntax
  3357. u"\xc2" : (True, self.SelectInPoundSigns),
  3358. u"$" : (True, self.SelectInDollarSigns),
  3359. u"^" : (True, self.SelectInHats),
  3360. u"%" : (True, self.SelectInPercentSigns),
  3361. u"&" : (True, self.SelectInAmpersands),
  3362. u"*" : (True, self.SelectInStars),
  3363. u"-" : (True, self.SelectInHyphens),
  3364. u"_" : (True, self.SelectInUnderscores),
  3365. u"=" : (True, self.SelectInEqualSigns),
  3366. u"+" : (True, self.SelectInPlusSigns),
  3367. u"!" : (True, self.SelectInExclamationMarks),
  3368. u"?" : (True, self.SelectInQuestionMarks),
  3369. u"@" : (True, self.SelectInAtSigns),
  3370. u"#" : (True, self.SelectInHashs),
  3371. u"~" : (True, self.SelectInApproxSigns),
  3372. u"|" : (True, self.SelectInVerticalBars),
  3373. u";" : (True, self.SelectInSemicolons),
  3374. u":" : (True, self.SelectInColons),
  3375. u"\\" : (True, self.SelectInBackslashes),
  3376. u"/" : (True, self.SelectInForwardslashes),
  3377. }
  3378. self.LoadKeybindings()
  3379. self.LoadPlugins(u"editor")
  3380. self.GenerateKeyBindings()
  3381. self.SINGLE_LINE_WHITESPACE = [9, 11, 12, 32]
  3382. self.WORD_BREAK = u'!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
  3383. self.WORD_BREAK_INCLUDING_WHITESPACE = \
  3384. u'!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~ \n\r'
  3385. self.SENTENCE_ENDINGS = (".", "!", "?", "\n\n")
  3386. self.SENTENCE_ENDINGS_SUFFIXS = '\'")]'
  3387. self.BRACES = {
  3388. u"(" : u")",
  3389. u"[" : u"]",
  3390. u"{" : u"}",
  3391. u"<" : u">",
  3392. u"<<" : u">>",
  3393. }
  3394. self.REVERSE_BRACES = dict((v,k) for k, v in self.BRACES.iteritems())
  3395. self.SURROUND_REPLACEMENTS = {
  3396. ")" : ("(", ")"),
  3397. "b" : ("(", ")"),
  3398. "}" : ("{", "}"),
  3399. "B" : ("{", "}"),
  3400. "]" : ("[", "]"),
  3401. "r" : ("[", "]"),
  3402. ">" : ("<", ">"),
  3403. "a" : ("<", ">"),
  3404. "(" : ("( ", " )"),
  3405. "{" : ("{ ", " }"),
  3406. "[" : ("[ ", " ]"),
  3407. # TODO
  3408. #"t" : ("<{0}>", "</{0}>"),
  3409. #"<" : ("<{0}>", "</{0}>"),
  3410. "'" : ("'", "'"),
  3411. '"' : ('"', '"'),
  3412. "`" : ("`", "`"),
  3413. }
  3414. self._undo_state = 0
  3415. self._undo_pos = -1
  3416. self._undo_start_position = None
  3417. self._undo_positions = []
  3418. self._line_column_pos = 0
  3419. self.ctrl.SendMsg(2472, 1)
  3420. # Autoenlarge autcomplete box
  3421. self.ctrl.AutoCompSetMaxWidth(200)
  3422. def LoadKeybindings(self):
  3423. """
  3424. Function called to load keybindings.
  3425. Must call GenerateKeyBindings after this.
  3426. """
  3427. # Format
  3428. # key code : (command type, (function, arguments), repeatable, selection_type)
  3429. # command type -
  3430. # 0 : Normal
  3431. # 1 : Motion (inclusive)
  3432. # 2 : Motion (exclusive)
  3433. # 3 : Command ends in visual mode
  3434. # 4 : Command alters visual anchor pos
  3435. # function : function to call on keypress
  3436. # arguments : arguments can be None, a single argument or a dictionary
  3437. # repeatable - repeat types
  3438. # 0 : Not repeatable
  3439. # 1 : Normal repeat
  3440. # 2 : Repeat with insertion (i.e. i/a/etc)
  3441. # 3 : Replace
  3442. # selection_type : how does the cmd select text
  3443. # 0 : Normal
  3444. # 1 : Always selects full lines
  3445. # Note:
  3446. # The repeats provided by ; and , are managed within the FindChar function
  3447. ######## TODO: some of these are duplicated in WikiHtmlView*, they should
  3448. # be moved to ViHelper.
  3449. k = self.KEY_BINDINGS
  3450. self.keys = {
  3451. 0 : {
  3452. # Normal mode
  3453. (k[":"],) : (0, (self.StartCmdInput, None), 0, 0), # :
  3454. (k["&"],) : (0, (self.RepeatLastSubCmd, False), 0, 0), # &
  3455. # TODO: convert from dialog so search can be used as a motion
  3456. (k["/"],) : (0, (self.StartForwardSearch, None), 0, 0), # /
  3457. (k["?"],) : (0, (self.StartReverseSearch, None), 0, 0), # ?
  3458. (k["<"], "m") : (0, (self.DedentText, None), 1, 0), # <motion
  3459. (k[">"], "m") : (0, (self.IndentText, None), 1, 0), # >motion
  3460. (k["c"], "m") : (0, (self.EndDeleteInsert, None), 2, 0), # cmotion
  3461. (k["d"], "m") : (0, (self.EndDelete, None), 1, 0), # dmotion
  3462. (k["d"], k["s"], "*") : (0, (self.DeleteSurrounding, None), 1, 0), # ds
  3463. (k["y"], "m") : (0, (self.Yank, None), 1, 0), # ymotion
  3464. (k["c"], k["s"], "*", "*") : (0, (self.ChangeSurrounding, None), 1, 0), # cs**
  3465. # cas**??ca**?? add surrounding
  3466. (k["y"], k["s"], "m", "*") : (0, (self.PreSurround, None), 1, 0), # ysmotion*
  3467. # TODO: yS and ySS (indentation on new line)
  3468. # ? yss
  3469. (k["y"], k["S"], "m", "*") : (0, (self.PreSurroundOnNewLine, None), 1, 0), # yS**
  3470. (k["y"], k["s"], k["s"], "*") : (0, (self.PreSurroundLine, None), 1, 0), # yss*
  3471. (k["g"], k["u"], "m") : (0, (self.PreLowercase, None), 1, 0), # gu
  3472. (k["g"], k["U"], "m") : (0, (self.PreUppercase, None), 1, 0), # gU
  3473. # TODO: gugu / guu and gUgU / gUU
  3474. (k["g"], k["s"], "m") : (0, (self.SubscriptMotion, None), 1, 0), # gs
  3475. (k["g"], k["S"], "m") : (0, (self.SuperscriptMotion, None), 1, 0), # gS
  3476. (k["`"], "*") : (1, (self.GotoMark, None), 0, 0), # `
  3477. # TODO: ' is linewise
  3478. (k["'"], "*") : (1, (self.GotoMarkIndent, None), 0, 0), # '
  3479. (k["m"], "*") : (0, (self.Mark, None), 0, 0), # m
  3480. (k["f"], "*") : (1, (self.FindNextChar, None), 4, 0), # f*
  3481. (k["F"], "*") : (2, (self.FindNextCharBackwards, None), 4, 0), # F*
  3482. (k["t"], "*") : (1, (self.FindUpToNextChar, None), 5, 0), # t*
  3483. (k["T"], "*") : (2, (self.FindUpToNextCharBackwards, None), 5, 0), # T*
  3484. (k["r"], "*") : (0, (self.ReplaceChar, None), 0, 0), # r*
  3485. (k["d"], k["c"], u"m", u"*") : (0, (self.DeleteCharMotion,
  3486. False), 1, 0), # dcmotion*
  3487. (k["i"],) : (0, (self.Insert, None), 2, 0), # i
  3488. (k["a"],) : (0, (self.Append, None), 2, 0), # a
  3489. (k["I"],) : (0, (self.InsertAtLineStart, None), 2, 0), # I
  3490. (k["A"],) : (0, (self.AppendAtLineEnd, None), 2, 0), # A
  3491. (k["o"],) : (0, (self.OpenNewLine, False), 2, 0), # o
  3492. (k["O"],) : (0, (self.OpenNewLine, True), 2, 0), # O
  3493. (k["C"],) : (0, (self.TruncateLineAndInsert, None), 2, 0), # C
  3494. (k["D"],) : (0, (self.TruncateLine, None), 1, 0), # D
  3495. (k["x"],) : (0, (self.DeleteRight, None), 1, 0), # x
  3496. (k["X"],) : (0, (self.DeleteLeft, None), 1, 0), # X
  3497. (k["s"],) : (0, (self.DeleteRightAndInsert, None), 2, 0), # s
  3498. (k["S"],) : (0, (self.DeleteLinesAndIndentInsert, None), 2, 0), # S
  3499. (k["c"], k["c"]) : (0, (self.DeleteLinesAndIndentInsert, None), 2, 1), # cc
  3500. (k["w"],) : (2, (self.MoveCaretNextWord, None), 0, 0), # w
  3501. (k["W"],) : (2, (self.MoveCaretNextWORD, None), 0, 0), # W
  3502. (k["g"], k["e"]) : (1, (self.MoveCaretPreviousWordEnd, None), 0, 0), # ge
  3503. # TODO: gE
  3504. (k["e"],) : (1, (self.MoveCaretWordEnd, None), 0, 0), # e
  3505. (k["E"],) : (1, (self.MoveCaretWordEND, None), 0, 0), # E
  3506. (k["b"],) : (2, (self.MoveCaretBackWord, None), 0, 0), # b
  3507. (k["B"],) : (2, (self.MoveCaretBackWORD, None), 0, 0), # B
  3508. (k["{"],) : (2, (self.MoveCaretParaUp, None), 0, 0), # {
  3509. (k["}"],) : (2, (self.MoveCaretParaDown, None), 0, 0), # }
  3510. (k["n"],) : (1, (self.Repeat, self.ContinueLastSearchSameDirection), 0, 0), # n
  3511. (k["N"],) : (1, (self.Repeat, self.ContinueLastSearchReverseDirection), 0, 0), # N
  3512. (k["*"],) : (2, (self.Repeat, self.SearchCaretWordForwards), 0, 0), # *
  3513. (k["#"],) : (2, (self.Repeat, self.SearchCaretWordBackwards), 0, 0), # #
  3514. (k["g"], k["*"]) : (2, (self.Repeat, self.SearchPartialCaretWordForwards), 0, 0), # g*
  3515. (k["g"], k["#"]) : (2, (self.Repeat, self.SearchPartialCaretWordBackwards), 0, 0), # g#
  3516. # Basic movement
  3517. (k["h"],) : (2, (self.MoveCaretLeft, None), 0, 0), # h
  3518. (k["k"],) : (2, (self.MoveCaretUp, None), 0, 0), # k
  3519. (k["l"],) : (2, (self.MoveCaretRight, False), 0, 0), # l
  3520. (k["j"],) : (2, (self.MoveCaretDown, None), 0, 0), # j
  3521. # TODO: ctrl-h / ctrl-l - goto headings?
  3522. (k["g"], k["k"]) : (2, (self.MoveCaretUp, {"visual" : True}), 0, 0), # gk
  3523. (k["g"], k["j"]) : (2, (self.MoveCaretDown, {"visual" : True}), 0, 0), # gj
  3524. (k["g"], k["0"]) : (2, (self.GotoVisualLineStart, None), 1, 0), # g0
  3525. (k["g"], k["$"]) : (2, (self.GotoVisualLineEnd, None), 1, 0), # g$
  3526. # Arrow keys
  3527. (wx.WXK_LEFT,) : (2, (self.MoveCaretLeft, None), 0, 0), # left
  3528. (wx.WXK_UP,) : (2, (self.MoveCaretUp, None), 0, 0), # up
  3529. (wx.WXK_RIGHT,) : (2, (self.MoveCaretRight, False), 0, 0), # right
  3530. (wx.WXK_DOWN,) : (2, (self.MoveCaretDown, None), 0, 0), # down
  3531. (wx.WXK_RETURN,) : (1, (self.MoveCaretDownAndIndent, None), 0, 0), # enter
  3532. (wx.WXK_NUMPAD_ENTER,) : (1, (self.MoveCaretDownAndIndent, None), 0, 0), # return
  3533. # Line movement
  3534. (k["$"],) : (1, (self.GotoLineEnd, False), 0, 0), # $
  3535. (wx.WXK_END,) : (1, (self.GotoLineEnd, False), 0, 0), # end
  3536. (k["0"],) : (2, (self.GotoLineStart, None), 0, 0), # 0
  3537. (wx.WXK_HOME,) : (2, (self.GotoLineStart, None), 0, 0), # home
  3538. (k["-"],) : (1, (self.GotoLineIndentPreviousLine, None), 0, 0), # -
  3539. (k["+"],) : (1, (self.GotoLineIndentNextLine, None), 0, 0), # +
  3540. (k["^"],) : (2, (self.GotoLineIndent, None), 0, 0), # ^
  3541. (k["|"],) : (2, (self.GotoColumn, None), 0, 0), # |
  3542. (k["("],) : (2, (self.GotoSentenceStart, True), 0, 0), # (
  3543. (k[")"],) : (1, (self.GotoNextSentence, True), 0, 0), # )
  3544. # Page scroll control
  3545. (k["g"], k["g"]) : (1, (self.DocumentNavigation, (k["g"], k["g"])), 0, 0), # gg
  3546. (k["G"],) : (1, (self.DocumentNavigation, k["G"]), 0, 0), # G
  3547. (k["%"],) : (1, (self.DocumentNavigation, k["%"]), 0, 0), # %
  3548. (k["H"],) : (1, (self.GotoViewportTop, None), 0, 0), # H
  3549. (k["L"],) : (1, (self.GotoViewportBottom, None), 0, 0), # L
  3550. (k["M"],) : (1, (self.GotoViewportMiddle, None), 0, 0), # M
  3551. (k["z"], k["z"]) : (0, (self.ScrollViewportMiddle, None), 0, 0), # zz
  3552. (k["z"], k["t"]) : (0, (self.ScrollViewportTop, None), 0, 0), # zt
  3553. (k["z"], k["b"]) : (0, (self.ScrollViewportBottom, None), 0, 0), # zb
  3554. (("Ctrl", k["u"]),) : (0, (self.ScrollViewportUpHalfScreen,
  3555. None), 0, 0), # <c-u>
  3556. (("Ctrl", k["d"]),) : (0, (self.ScrollViewportDownHalfScreen,
  3557. None), 0, 0), # <c-d>
  3558. (("Ctrl", k["b"]),) : (0, (self.ScrollViewportUpFullScreen,
  3559. None), 0, 0), # <c-b>
  3560. (("Ctrl", k["f"]),) : (0, (self.ScrollViewportDownFullScreen,
  3561. None), 0, 0), # <c-f>
  3562. (("Ctrl", k["e"]),) : (0, (self.ctrl.LineScrollDown,
  3563. None), 0, 0), # <c-e>
  3564. (("Ctrl", k["y"]),) : (0, (self.ctrl.LineScrollUp,
  3565. None), 0, 0), # <c-y>
  3566. # (("Ctrl", k["e"]),) : (0, (self.ScrollViewportLineDown,
  3567. # None), 0, 0), # <c-e>
  3568. # (("Ctrl", k["y"]),) : (0, (self.ScrollViewportLineUp,
  3569. # None), 0, 0), # <c-y>
  3570. (k["Z"], k["Z"]) : (0, (self.ctrl.presenter.getMainControl().\
  3571. exitWiki, None), 0, 0), # ZZ
  3572. (k["u"],) : (0, (self.Undo, None), 0, 0), # u
  3573. (("Ctrl", k["r"]),) : (0, (self.Redo, None), 0, 0), # <c-r>
  3574. (("Ctrl", k["i"]),) : (0, (self.GotoNextJump, None), 0, 0), # <c-i>
  3575. (wx.WXK_TAB,) : (0, (self.GotoNextJump, None), 0, 0), # Tab
  3576. (("Ctrl", k["o"]),) : (0, (self.GotoPreviousJump, None), 0, 0), # <c-o>
  3577. # These two are motions
  3578. (k[";"],) : (1, (self.RepeatLastFindCharCmd, None), 0, 0), # ;
  3579. (k[","],) : (1, (self.RepeatLastFindCharCmdReverse, None), 0, 0), # ,
  3580. # Replace ?
  3581. # repeatable?
  3582. (k["R"],) : (0, (self.StartReplaceMode, None), 0, 0), # R
  3583. (k["v"],) : (3, (self.EnterVisualMode, None), 0, 0), # v
  3584. (k["V"],) : (3, (self.EnterLineVisualMode, None), 0, 0), # V
  3585. #(("Ctrl", k["v"]),) : (3, (self.EnterBlockVisualMode, None), 0, 0), # <c-v>
  3586. (k["J"],) : (0, (self.JoinLines, None), 1, 1), # J
  3587. (k["~"],) : (0, (self.SwapCase, None), 0, 0), # ~
  3588. (k["y"], k["y"]) : (0, (self.YankLine, None), 0, 0), # yy
  3589. (k["Y"],) : (0, (self.YankLine, None), 0, 0), # Y
  3590. (k["p"],) : (0, (self.Put, False), 0, 0), # p
  3591. (k["P"],) : (0, (self.Put, True), 0, 0), # P
  3592. (("Ctrl", k["v"]),) : (0, (self.PutClipboard, False), 0, 0), # <c-v>
  3593. (k["d"], k["d"]) : (0, (self.DeleteLine, None), 1, 0), # dd
  3594. (k[">"], k[">"]) : (0, (self.Indent, True), 1, 0), # >>
  3595. (k["<"], k["<"]) : (0, (self.Indent, False), 1, 0), # <<
  3596. (k["."],) : (0, (self.RepeatCmd, None), 0, 0), # .
  3597. # Wikipage navigation
  3598. # As some command (e.g. HL) are already being used in most cases
  3599. # these navigation commands have been prefixed by "g".
  3600. # TODO: different repeat command for these?
  3601. (k["g"], k["f"]) : (0, (self.ctrl.activateLink, { "tabMode" : 0 }), 0, 0), # gf
  3602. (k["\\"], k["g"], k["f"]) : (0, (self.ctrl.findSimilarWords, None), 0, 0), # gf
  3603. (k["g"], k["c"]) : (0, (self.PseudoActivateLink, 0), 0, 0), # gc
  3604. (k["g"], k["C"]) : (0, (self.PseudoActivateLink, 2), 0, 0), # gC
  3605. (("Ctrl", k["w"]), k["g"], k["f"]) : (0, (self.ctrl.activateLink, { "tabMode" : 2 }), 0, 0), # <c-w>gf
  3606. (k["g"], k["F"]) : (0, (self.ctrl.activateLink, { "tabMode" : 2 }), 0, 0), # gF
  3607. (k["g"], k["b"]) : (0, (self.ctrl.activateLink, { "tabMode" : 3 }), 0, 0), # gb
  3608. # This might be going a bit overboard with history nagivaiton!
  3609. (k["g"], k["H"]) : (0, (self.GoBackwardInHistory, None), 0, 0), # gH
  3610. (k["g"], k["L"]) : (0, (self.GoForwardInHistory, None), 0, 0), # gL
  3611. (k["g"], k["h"]) : (0, (self.GoBackwardInHistory, None), 0, 0), # gh
  3612. (k["g"], k["l"]) : (0, (self.GoForwardInHistory, None), 0, 0), # gl
  3613. (k["["],) : (0, (self.GoBackwardInHistory, None), 0, 0), # [
  3614. (k["]"],) : (0, (self.GoForwardInHistory, None), 0, 0), # ]
  3615. (k["g"], k["t"]) : (0, (self.SwitchTabs, None), 0, 0), # gt
  3616. (k["g"], k["T"]) : (0, (self.SwitchTabs, True), 0, 0), # gT
  3617. (k["g"], k["r"]) : (0, (self.OpenHomePage, False), 0, 0), # gr
  3618. (k["g"], k["R"]) : (0, (self.OpenHomePage, True), 0, 0), # gR
  3619. (k["\\"], k["o"]) : (0, (self.StartCmdInput, "open "), 0, 0), # \o
  3620. (k["\\"], k["t"]) : (0, (self.StartCmdInput, "tabopen "), 0, 0), # \t
  3621. # TODO: rewrite open dialog so it can be opened with new tab as default
  3622. (k["\\"], k["O"]): (0, (self.ctrl.presenter.getMainControl(). \
  3623. showWikiWordOpenDialog, None), 0, 0), # \O
  3624. #(k["g"], k["o"]) : (0, (self.ctrl.presenter.getMainControl(). \
  3625. # showWikiWordOpenDialog, None), 0), # go
  3626. (k["g"], k["o"]) : (0, (self.StartCmdInput, "open "), 0, 0), # go
  3627. (k["g"], k["O"]): (0, (self.ctrl.presenter.getMainControl(). \
  3628. showWikiWordOpenDialog, None), 0, 0), # gO
  3629. (k["\\"], k["u"]) : (0, (self.ViewParents, False), 0, 0), # \u
  3630. (k["\\"], k["U"]) : (0, (self.ViewParents, True), 0, 0), # \U
  3631. (k["\\"], k["h"], "*") : (0, (self.SetHeading, None), 1, 0), # \h{level}
  3632. (k["\\"], k["s"]) : (0, (self.CreateShortHint, None), 2, 0), # \s
  3633. (("Alt", k["g"]),) : (0, (self.GoogleSelection, None), 1, 0), # <a-g>
  3634. (("Alt", k["e"]),) : (0, (self.ctrl.evalScriptBlocks, None), 1, 0), # <a-e>
  3635. (("Ctrl", k["w"]), k["l"]) : (0, (self.ctrl.presenter.getMainControl().getMainAreaPanel().switchPresenterByPosition, "right"), 0, 0), # <c-w>l
  3636. (("Ctrl", k["w"]), k["h"]) : (0, (self.ctrl.presenter.getMainControl().getMainAreaPanel().switchPresenterByPosition, "left"), 0, 0), # <c-w>l
  3637. (("Ctrl", k["w"]), k["j"]) : (0, (self.ctrl.presenter.getMainControl().getMainAreaPanel().switchPresenterByPosition, "below"), 0, 0), # <c-w>l
  3638. (("Ctrl", k["w"]), k["k"]) : (0, (self.ctrl.presenter.getMainControl().getMainAreaPanel().switchPresenterByPosition, "above"), 0, 0), # <c-w>l
  3639. #(k["g"], k["s"]) : (0, (self.SwitchEditorPreview, None), 0), # gs
  3640. # TODO: think of suitable commands for the following
  3641. (wx.WXK_F3,) : (0, (self.SwitchEditorPreview, "textedit"), 0, 0), # F3
  3642. (wx.WXK_F4,) : (0, (self.SwitchEditorPreview, "preview"), 0, 0), # F4
  3643. }
  3644. }
  3645. # Could be changed to use a wildcard
  3646. for i in self.text_object_map:
  3647. self.keys[0][(k["i"], ord(i))] = (4, (self.SelectInTextObject, i), 0, 0)
  3648. self.keys[0][(k["a"], ord(i))] = (4, (self.SelectATextObject, i), 0, 0)
  3649. # INSERT MODE
  3650. # Shortcuts available in insert mode (need to be repeatable by ".",
  3651. # i.e. must work with EmulateKeypresses)
  3652. self.keys[1] = {
  3653. # TODO:
  3654. #(("Ctrl", 64),) : (0, (self.InsertPreviousText, None), 0), # Ctrl-@
  3655. #(("Ctrl", k["a"]),) : (0, (self.InsertPreviousTextLeaveInsertMode,
  3656. # None), 0), # Ctrl-a
  3657. (("Ctrl", k["n"]),) : (0, (self.Autocomplete, True), 0, 0), # Ctrl-n
  3658. (("Ctrl", k["p"]),) : (0, (self.Autocomplete, False), 0, 0), # Ctrl-p
  3659. # Unlike vim we these are case sensitive
  3660. (("Ctrl", k["w"]),) : (0, (self.DeleteBackword, False), 0, 0), # Ctrl-w
  3661. (("Ctrl", k["W"]),) : (0, (self.DeleteBackword, True), 0, 0), # Ctrl-W
  3662. (("Ctrl", k["v"]),) : (0, (self.PutClipboard, True), 0, 0), # <c-v>
  3663. # Ctrl-t and -d indent / deindent respectively
  3664. (("Ctrl", k["t"]),) : (0, (self.ctrl.Tab, None), 0, 0), # <c-t>
  3665. (("Ctrl", k["d"]),) : (0, (self.ctrl.BackTab, None), 0, 0), # <c-d>
  3666. # F1 and F2 in insert mode will still switch between editor and preview
  3667. # Should it revert back to normal mode?
  3668. (wx.WXK_F1,) : (0, (self.SwitchEditorPreview, "textedit"), 0, 0), # F1
  3669. (wx.WXK_F2,) : (0, (self.SwitchEditorPreview, "preview"), 0, 0), # F2
  3670. }
  3671. # Rather than rewrite all the keys for other modes it is easier just
  3672. # to modify those that need to be changed
  3673. # VISUAL MODE
  3674. self.keys[2] = self.keys[0].copy()
  3675. self.keys[2].update({
  3676. # In visual mode the caret must be able to select the last char
  3677. (k["l"],) : (2, (self.MoveCaretRight, True), 0, 0), # l
  3678. (wx.WXK_RIGHT,) : (2, (self.MoveCaretRight, True), 0, 0), # right
  3679. (k["'"], "*") : (1, (self.GotoMark, None), 0, 0), # '
  3680. (k["`"], "*") : (1, (self.GotoMarkIndent, None), 0, 0), # `
  3681. (k["m"], "*") : (0, (self.Mark, None), 0, 0), # m
  3682. (k["f"], "*") : (1, (self.FindNextChar, None), 0, 0), # f
  3683. (k["F"], "*") : (2, (self.FindNextCharBackwards, None), 0, 0), # F
  3684. (k["t"], "*") : (1, (self.FindUpToNextChar, None), 0, 0), # t
  3685. (k["T"], "*") : (2, (self.FindUpToNextCharBackwards, None), 0, 0), # T
  3686. (k["r"], "*") : (0, (self.ReplaceChar, None), 0, 0), # r*
  3687. (k["S"], "*") : (0, (self.SurroundSelection, None), 1, 0), # S
  3688. (k["o"],) : (4, (self.SwitchSelection, None), 0, 0), # o
  3689. (k["c"],) : (0, (self.DeleteSelectionAndInsert, None), 2, 0), # c
  3690. (k["d"],) : (0, (self.DeleteSelection, None), 1, 0), # d
  3691. (wx.WXK_DELETE,) : (0, (self.DeleteSelection, None), 1, 0), # del key
  3692. (k["D"],) : (0, (self.DeleteSelectionLines, None), 2, 0), # D
  3693. (k["x"],) : (0, (self.DeleteSelection, None), 1, 0), # x
  3694. (k["y"],) : (0, (self.Yank, False), 0, 0), # y
  3695. (k["Y"],) : (0, (self.Yank, True), 0, 0), # Y
  3696. (k["<"],) : (0, (self.Indent, {"forward":False, "visual":True}), 1, 0), # <
  3697. (k[">"],) : (0, (self.Indent, {"forward":True, "visual":True}), 1, 0), # >
  3698. (k["u"],) : (0, (self.SelectionToLowerCase, None), 1, 0), # u
  3699. (k["U"],) : (0, (self.SelectionToUpperCase, None), 1, 0), # U
  3700. (k["g"], k["u"]) : (0, (self.SelectionToLowerCase, None), 1, 0), # gu
  3701. (k["g"], k["U"]) : (0, (self.SelectionToUpperCase, None), 1, 0), # gU
  3702. (k["g"], k["s"]) : (0, (self.SelectionToSubscript, None), 1, 0), # gs
  3703. (k["g"], k["S"]) : (0, (self.SelectionToSuperscript, None), 1, 0), # gS
  3704. (k["\\"], k["d"], k["c"], u"*") : (0, (self.DeleteCharFromSelection,
  3705. None), 1), # \dc*
  3706. # Use Ctrl-r in visual mode to start a replace
  3707. (("Ctrl", k["r"]),) : (0, (self.StartReplaceOnSelection, None), 1, 0), # <c-r>
  3708. (("Alt", k["g"]),) : (0, (self.GoogleSelection, None), 1, 0), # <a-g>
  3709. })
  3710. # And delete a few so our key mods are correct
  3711. # These are keys that who do not serve the same function in visual mode
  3712. # as in normal mode (in most cases they are replaced by other function)
  3713. del self.keys[2][(k["d"], k["d"])] # dd
  3714. del self.keys[2][(k["y"], k["y"])] # yy
  3715. del self.keys[2][(k["i"],)] # i
  3716. del self.keys[2][(k["a"],)] # a
  3717. del self.keys[2][(k["S"],)] # S
  3718. del self.keys[2][(k["<"], k["<"])] # <<
  3719. del self.keys[2][(k[">"], k[">"])] # <<
  3720. self.keys[3] = {}
  3721. def GenerateKeyBindings(self):
  3722. #self._motion_chains = self.GenerateMotionKeyChains(self.keys)
  3723. self.key_mods = self.GenerateKeyModifiers(self.keys)
  3724. self.motion_keys = self.GenerateMotionKeys(self.keys)
  3725. self.motion_key_mods = self.GenerateKeyModifiers(self.motion_keys)
  3726. # Used for rewriting menu shortcuts
  3727. self.GenerateKeyAccelerators(self.keys)
  3728. def ApplySettings(self):
  3729. # Set wrap indent mode
  3730. self.ctrl.SendMsg(2472, self.settings["set_wrap_indent_mode"])
  3731. #self.ctrl.SendMsg(, self.settings["set_wrap_start_indent"])
  3732. def Setup(self):
  3733. self.AddJumpPosition(self.ctrl.GetCurrentPos())
  3734. def EndInsertMode(self):
  3735. # If switching from insert mode vi does a few things
  3736. if self.mode == ViHelper.INSERT:
  3737. # Move back one pos if not at the start of a line
  3738. if self.ctrl.GetCurrentPos() != \
  3739. self.GetLineStartPos(self.ctrl.GetCurrentLine()):
  3740. self.ctrl.CharLeft()
  3741. # If current line only contains whitespace remove it
  3742. current_line = self.ctrl.GetCurLine()[0]
  3743. if len(current_line) > 1 and current_line.strip() == u"":
  3744. self.ctrl.LineDelete()
  3745. self.ctrl.AddText(self.ctrl.GetEOLChar())
  3746. self.ctrl.CharLeft()
  3747. self.SetMode(ViHelper.NORMAL)
  3748. self.EndUndo()
  3749. def SetMode(self, mode):
  3750. """
  3751. It would be nice to set caret alpha but i don't think its
  3752. possible at the moment
  3753. """
  3754. if mode is None:
  3755. mode = self.mode
  3756. else:
  3757. self.mode = mode
  3758. # ! there may be some situations in which we want to do this
  3759. # but it is probably better handled in the calling function
  3760. ## Save caret position
  3761. #self.SetLineColumnPos()
  3762. if mode == ViHelper.NORMAL:
  3763. # Set block caret (Not in wxpython < 2.9)
  3764. self.SetCaretStyle("block")
  3765. #self.ctrl.SetCaretWidth(40)
  3766. self.ctrl.SetCaretPeriod(800)
  3767. #self.ctrl.SetSelectionMode(0)
  3768. self.RemoveSelection()
  3769. self.SetCaretColour(self.settings['caret_colour_normal'])
  3770. self.ctrl.SetOvertype(False)
  3771. self.SetSelMode("NORMAL")
  3772. # Vim never goes right to the end of the line
  3773. self.CheckLineEnd()
  3774. elif mode == ViHelper.VISUAL:
  3775. self.SetCaretStyle("line")
  3776. #self.ctrl.SetCaretWidth(1)
  3777. self.SetCaretColour(self.settings['caret_colour_visual'])
  3778. self.ctrl.SetOvertype(False)
  3779. elif mode == ViHelper.INSERT:
  3780. self.insert_action = []
  3781. self.SetCaretStyle("line")
  3782. #self.ctrl.SetCaretWidth(1)
  3783. self.SetCaretColour(self.settings['caret_colour_insert'])
  3784. self.ctrl.SetOvertype(False)
  3785. elif mode == ViHelper.REPLACE:
  3786. self.SetCaretStyle("block")
  3787. #self.ctrl.SetCaretWidth(1)
  3788. self.SetCaretColour(self.settings['caret_colour_replace'])
  3789. self.ctrl.SetOvertype(True)
  3790. def SetCaretStyle(self, style):
  3791. """
  3792. Helper to set the caret style
  3793. @param style: Caret style
  3794. """
  3795. # default caret style is line
  3796. sci_style = 1
  3797. if style == "block":
  3798. sci_style = 2
  3799. elif style == "invisible":
  3800. sci_style = 0
  3801. self.ctrl.SendMsg(2512, sci_style)
  3802. def SetCaretColour(self, colour):
  3803. """
  3804. Helper to set the caret colour
  3805. @param colour: wx colour
  3806. """
  3807. self.ctrl.SetCaretForeground(colour)
  3808. # Starting code to allow correct postitioning when undoing and redoing
  3809. # actions
  3810. # Need to overide Undo and Redo to goto positions
  3811. # TODO: Tidy up
  3812. def BeginUndo(self, use_start_pos=True, force=False):
  3813. if self._undo_state == 0:
  3814. self.ctrl.BeginUndoAction()
  3815. print "START UNDO"
  3816. self._undo_positions = \
  3817. self._undo_positions[:self._undo_pos + 1]
  3818. if use_start_pos:
  3819. if self.HasSelection():
  3820. self._undo_start_position = self._GetSelectionRange()[0]
  3821. else:
  3822. self._undo_start_position = self.ctrl.GetCurrentPos()
  3823. self._undo_state += 1
  3824. print "BEGIN", self._undo_state, inspect.getframeinfo(inspect.currentframe().f_back)[2]
  3825. def EndUndo(self, force=False):
  3826. if self._undo_state == 1:
  3827. self.ctrl.EndUndoAction()
  3828. if self._undo_start_position is not None:
  3829. self._undo_positions.append(self._undo_start_position)
  3830. self._undo_start_position = None
  3831. elif self.HasSelection:
  3832. self._undo_positions.append(self.ctrl.GetSelectionStart())
  3833. else:
  3834. self._undo_positions.append(self.ctrl.GetCurrentPos())
  3835. self._undo_pos += 1
  3836. print "END UNDO"
  3837. self._undo_state -= 1
  3838. print self._undo_state, inspect.getframeinfo(inspect.currentframe().f_back)[2]
  3839. def EndBeginUndo(self):
  3840. # TODO: shares code with EndUndo and BeginUndo
  3841. self.ctrl.EndUndoAction()
  3842. if self._undo_start_position is not None:
  3843. self._undo_positions.append(self._undo_start_position)
  3844. self._undo_start_position = None
  3845. elif self.HasSelection:
  3846. self._undo_positions.append(self.ctrl.GetSelectionStart())
  3847. else:
  3848. self._undo_positions.append(self.ctrl.GetCurrentPos())
  3849. self._undo_pos += 1
  3850. self.ctrl.BeginUndoAction()
  3851. self._undo_positions = \
  3852. self._undo_positions[:self._undo_pos + 1]
  3853. if self.HasSelection():
  3854. self._undo_start_position = self._GetSelectionRange()[0]
  3855. else:
  3856. self._undo_start_position = self.ctrl.GetCurrentPos()
  3857. def _Undo(self):
  3858. if self._undo_pos < 0:
  3859. return False
  3860. self.ctrl.Undo()
  3861. self.ctrl.GotoPos(self._undo_positions[self._undo_pos])
  3862. self.SetLineColumnPos()
  3863. self._undo_pos -= 1
  3864. def _Redo(self):
  3865. # NOTE: the position may be off on some redo's
  3866. if self._undo_pos > len(self._undo_positions):
  3867. return False
  3868. self.ctrl.Redo()
  3869. self._undo_pos += 1
  3870. def SetLineColumnPos(self):
  3871. currentPos = self.ctrl.GetCurrentPos()
  3872. currentCol = self.ctrl.GetColumn(currentPos)
  3873. self._line_column_pos = currentCol
  3874. self.ctrl.ChooseCaretX()
  3875. def GetLineColumnPos(self):
  3876. return self._line_column_pos
  3877. def GotoFirstVisibleLine(self):
  3878. # GetFirstVisibleLine does not take into account word wrapping
  3879. line = self.GetFirstVisibleLine()
  3880. # Correct for word wrapping
  3881. pos = self.ctrl.GetLineIndentPosition(line)
  3882. text = self.ctrl.GetTextRange(0, pos)
  3883. self.ctrl.GotoLine(line)
  3884. def GotoLastVisibleLine(self):
  3885. line = self.GetLastVisibleLine()
  3886. if line > self.ctrl.GetCurrentLine():
  3887. return
  3888. self.ctrl.GotoLine(line)
  3889. def FlushBuffersExtra(self):
  3890. #self.SetCaretColour(self.settings['caret_colour_normal'])
  3891. self.SetMode(None)
  3892. self.register.SelectRegister(None)
  3893. def OnMouseScroll(self, evt):
  3894. # TODO: check if it would be better to move the caret once scrolling has
  3895. # finished (It would but there is no way to detect where to move
  3896. # it to...)
  3897. # Not the best solution possible but until GetFirstVisibleLine
  3898. # is fixed appears to be the best.
  3899. current_line = self.ctrl.GetCurrentLine()
  3900. if evt.GetWheelRotation() < 0:
  3901. if current_line <= self.GetFirstVisibleLine():
  3902. for i in range(evt.GetLinesPerAction()):
  3903. #self.ctrl.LineDown()
  3904. self.ctrl.LineScrollDown()
  3905. return
  3906. else:
  3907. if current_line >= self.GetLastVisibleLine() - \
  3908. evt.GetLinesPerAction() - 1:
  3909. for i in range(evt.GetLinesPerAction()):
  3910. #self.ctrl.LineUp()
  3911. self.ctrl.LineScrollUp()
  3912. return
  3913. #if self.ctrl.GetLineVisible(current_line):
  3914. # top_line = self.GetFirstVisibleLine() + 1
  3915. # bottom_line = self.GetLastVisibleLine()
  3916. # if current_line < top_line:
  3917. # wx.CallAfter(self.GotoFirstVisibleLine)
  3918. # elif current_line > bottom_line:
  3919. # wx.CallAfter(self.GotoLastVisibleLine)
  3920. # #offset = evt.GetWheelRotation() / 40
  3921. #print offset
  3922. #if offset < 0:
  3923. # func = self.ctrl.LineDown
  3924. #else:
  3925. # func = self.ctrl.LineUp
  3926. #
  3927. #for i in range(abs(offset)):
  3928. # print "i", i
  3929. # func()
  3930. evt.Skip()
  3931. # def OnScroll(self, evt):
  3932. # """
  3933. # Vim never lets the caret out of the viewport so track any
  3934. # viewport movements
  3935. # """
  3936. # # NOTE: may be inefficient?
  3937. # # perhaps use EVT_SCROLLWIN_LINEUP
  3938. # current_line = self.ctrl.GetCurrentLine()
  3939. # top_line = self.GetFirstVisibleLine()+1
  3940. # bottom_line = self.GetLastVisibleLine()-1
  3941. #
  3942. # if current_line < top_line:
  3943. # self.MoveCaretToLine(top_line)
  3944. # elif current_line > bottom_line:
  3945. # self.MoveCaretToLine(bottom_line)
  3946. # evt.Skip()
  3947. def OnMouseClick(self, evt):
  3948. # Vim runs the command after changing the mouse position
  3949. self.ctrl.OnClick(evt)
  3950. wx.CallAfter(self.SetLineColumnPos)
  3951. # TODO::
  3952. # Get Mouse Pos and save it as motion cmd
  3953. #if self.NextKeyCommandCanBeMotion:
  3954. # self.StartSelection()
  3955. # evt = wx.KeyEvent(wx.wxEVT_KEY_DOWN)
  3956. # evt.m_keyCode = -999 # Should this be done?
  3957. # wx.PostEvent(self.ctrl, evt)
  3958. def OnLeftMouseUp(self, evt):
  3959. """Enter visual mode if text is selected by mouse"""
  3960. # Prevent the end of line character from being selected as per vim
  3961. # This will cause a slight delay, there may be a better solution
  3962. # May be possible to override MOUSE_DOWN event.
  3963. # If we are in insert mode we clear our insert buffer and reset
  3964. # and undo's
  3965. if self.mode == ViHelper.INSERT:
  3966. self.EndBeginUndo()
  3967. self.insert_action = []
  3968. wx.CallAfter(self.EnterVisualModeIfRequired)
  3969. wx.CallAfter(self.CheckLineEnd)
  3970. evt.Skip()
  3971. def EnterVisualModeIfRequired(self):
  3972. if len(self.ctrl.GetSelectedText()) > 0:
  3973. self.EnterVisualMode(True)
  3974. else:
  3975. self.LeaveVisualMode()
  3976. def OnAutocompleteKeyDown(self, evt):
  3977. if evt.GetRawKeyCode() in (65505, 65507, 65513):
  3978. return
  3979. if evt.GetKeyCode() in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_RETURN):
  3980. return
  3981. # Messy
  3982. if evt.ControlDown():
  3983. if evt.GetKeyCode() == 80:
  3984. evt = wx.KeyEvent(wx.wxEVT_KEY_DOWN)
  3985. evt.m_keyCode = wx.WXK_UP
  3986. wx.PostEvent(self.ctrl, evt)
  3987. return
  3988. elif evt.GetKeyCode() == 78:
  3989. evt = wx.KeyEvent(wx.wxEVT_KEY_DOWN)
  3990. evt.m_keyCode = wx.WXK_DOWN
  3991. wx.PostEvent(self.ctrl, evt)
  3992. return
  3993. evt.Skip()
  3994. def TurnOff(self):
  3995. self._enableMenuShortcuts(True)
  3996. self.ctrl.SetCaretWidth(1)
  3997. def GetChar(self, length=1):
  3998. """
  3999. Retrieves text from under caret
  4000. @param length: the number of characters to get
  4001. """
  4002. pos = self.ctrl.GetCurrentPos()
  4003. start, end = self.minmax(pos, pos + length)
  4004. start = max(0, start)
  4005. end = min(end, self.ctrl.GetLength())
  4006. return self.ctrl.GetTextRange(start, end)
  4007. def EmulateKeypresses(self, actions):
  4008. # NOTE: wxstyledtextctrl supports macros which would probably be a
  4009. # better solution for much of this.
  4010. if len(actions) > 0:
  4011. eol = self.ctrl.GetEOLChar()
  4012. for i in actions:
  4013. # TODO: handle modifier keys, e.g. ctrl
  4014. if i == wx.WXK_LEFT:
  4015. self.ctrl.CharLeft()
  4016. elif i == wx.WXK_RIGHT:
  4017. self.ctrl.CharRight()
  4018. elif i == wx.WXK_BACK:
  4019. self.ctrl.DeleteBackNotLine()
  4020. elif i in [wx.WXK_DELETE]:# 65439????
  4021. self.ctrl.CharRight()
  4022. self.ctrl.DeleteBack()
  4023. elif i in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: # enter, return
  4024. self.ctrl.InsertText(self.ctrl.GetCurrentPos(), eol)
  4025. self.ctrl.CharRight()
  4026. elif i == wx.WXK_TAB: # tab
  4027. self.ctrl.InsertText(self.ctrl.GetCurrentPos(), "\t")
  4028. self.ctrl.CharRight()
  4029. else:
  4030. self.ctrl.InsertText(self.ctrl.GetCurrentPos(), unichr(i))
  4031. self.ctrl.CharRight()
  4032. def _RepeatCmdHelper(self):
  4033. self.visualBell("GREEN")
  4034. #self.BeginUndo()
  4035. cmd_type, key, count, motion, motion_wildcards, wildcards, \
  4036. text_to_select = self.last_cmd
  4037. # If no count has been specified use saved count
  4038. if not self.true_count:
  4039. self.count = count
  4040. # overwise save the new count
  4041. else:
  4042. self.last_cmd[2] = self.count
  4043. self._motion = motion
  4044. actions = self.insert_action
  4045. # NOTE: Is "." only going to repeat editable commands as in vim?
  4046. if cmd_type == 1:
  4047. self.RunFunction(key, motion, motion_wildcards, wildcards,
  4048. text_to_select, repeat=True)
  4049. # If a command ends in insertion mode we also repeat any changes
  4050. # made up until the next mode change.
  4051. elif cmd_type == 2: # + insertion
  4052. self.RunFunction(key, motion, motion_wildcards, wildcards,
  4053. text_to_select, repeat=True)
  4054. # Emulate keypresses
  4055. # Return to normal mode
  4056. self.EmulateKeypresses(actions)
  4057. self.EndInsertMode()
  4058. elif cmd_type == 3:
  4059. self.ReplaceChar(key)
  4060. #self.EndUndo()
  4061. self.insert_action = actions
  4062. def RepeatCmd(self):
  4063. # TODO: move to ViHelper?
  4064. if self.last_cmd is not None:
  4065. self.BeginUndo()
  4066. # If a count is specified the count in the previous cmd is changed
  4067. #self.Repeat(self._RepeatCmdHelper)
  4068. self._RepeatCmdHelper()
  4069. self.EndUndo()
  4070. else:
  4071. self.visualBell("RED")
  4072. #--------------------------------------------------------------------
  4073. # Misc stuff
  4074. #--------------------------------------------------------------------
  4075. def PseudoActivateLink(self, tab_mode):
  4076. """
  4077. A custom way to follow links.
  4078. Useful for a number of reasons
  4079. # It works on the text (so does not need a parsed page)
  4080. # It capitalises the first letter of the link
  4081. """
  4082. self.SelectInSquareBracket()
  4083. self.SelectSelection(1)
  4084. wikiword = self.ctrl.GetSelectedText()
  4085. # Arbitrary limits are fun
  4086. if len(wikiword) < 2:
  4087. return
  4088. if "|" in wikiword:
  4089. wikiword = wikiword.split("|")[0]
  4090. if wikiword.startswith("//"):
  4091. wikiword = wikiword[2:]
  4092. elif wikiword.startswith("/"):
  4093. wikiword = self.ctrl.presenter.getWikiWord()+wikiword
  4094. wikiword = wikiword[:1].upper() + wikiword[1:]
  4095. self.ctrl.presenter.getMainControl().activatePageByUnifiedName(
  4096. u"wikipage/" + wikiword, tabMode=tab_mode)
  4097. def Autocomplete(self, forwards):
  4098. """
  4099. Basic autocomplete
  4100. Will search in current page for completions to basic words
  4101. Also supports completing relative links (may not work on windows)
  4102. """
  4103. # TODO: fix for single length words.
  4104. if not self.ctrl.AutoCompActive():
  4105. pos = self.ctrl.GetCurrentPos()
  4106. # First check if we are working with a link
  4107. link = self.GetLinkAtCaret(link_type="rel://")
  4108. if link is not None:
  4109. word = None
  4110. else:
  4111. self.ctrl.GotoPos(pos)
  4112. # NOTE: list order is not correct
  4113. if pos - 1 > 0 and self.GetUnichrAt(pos-1) in \
  4114. self.WORD_BREAK_INCLUDING_WHITESPACE:
  4115. word = u"[a-zA-Z0-9_]"
  4116. comcompletion_length = 0
  4117. # Select in word fails if it is a single char, may be better
  4118. # to fix it there
  4119. elif pos - 2 > 0 and self.GetUnichrAt(pos-2) in \
  4120. self.WORD_BREAK_INCLUDING_WHITESPACE:
  4121. self.ctrl.CharLeftExtend()
  4122. word = self.ctrl.GetSelectedText()
  4123. completion_length = 1
  4124. else:
  4125. self.ctrl.CharLeft()
  4126. # Vim only selects backwards
  4127. # this will select the entire word
  4128. self.SelectInWord()
  4129. self.ctrl.CharRightExtend()
  4130. #self.ExtendSelectionIfRequired()
  4131. word = self.ctrl.GetSelectedText()
  4132. completion_length = len(word)
  4133. # Remove duplicates
  4134. completions = set()
  4135. if word is None:
  4136. abs_link = self.ctrl.presenter.getWikiDocument()\
  4137. .makeRelUrlAbsolute(link)
  4138. # Check if link is a dir
  4139. link_is_dir = False
  4140. if isdir(abs_link[5:]):
  4141. link_is_dir = True
  4142. link_dirname = dirname(abs_link)
  4143. link_basename = basename(abs_link)
  4144. # Search for matching files within the current directory
  4145. files = listdir("{0}".format(link_dirname[5:]))
  4146. completion_list = [self.ctrl.presenter.getWikiDocument()\
  4147. .makeAbsUrlRelative(join(link_dirname, i)) \
  4148. for i in files if link_basename == "" \
  4149. or i.startswith(link_basename)]
  4150. # If the link is a directory we need to add all files from
  4151. # that directory
  4152. if link_is_dir:
  4153. completion_list.extend(
  4154. [self.ctrl.presenter.getWikiDocument()\
  4155. .makeAbsUrlRelative(join(abs_link, i)) \
  4156. for i in listdir(abs_link[5:])])
  4157. completion_length = len(link)
  4158. else:
  4159. # Search for possible autocompletions
  4160. # Bad ordering at the moment
  4161. text = self.ctrl.GetTextRange(
  4162. self.ctrl.GetCurrentPos(), self.ctrl.GetLength())
  4163. completion_list = re.findall(ur"\b{0}.*?\b".format(re.escape(word)),
  4164. text, re.U)
  4165. text = self.ctrl.GetTextRange(0, self.ctrl.GetCurrentPos())
  4166. completion_list.extend(re.findall(ur"\b{0}.*?\b".format(word),
  4167. text, re.U))
  4168. completions.add(word)
  4169. unique_completion_list = [i for i in completion_list if i not in completions \
  4170. and not completions.add(i)]
  4171. # No completions found
  4172. if len(unique_completion_list) < 1:
  4173. if self.HasSelection():
  4174. self.ctrl.CharRight()
  4175. self.visualBell("RED")
  4176. return
  4177. completion_list_prepped = "\x01".join(unique_completion_list)
  4178. if self.HasSelection():
  4179. self.ctrl.CharRight()
  4180. self.ctrl.AutoCompShow(completion_length, completion_list_prepped)
  4181. def GetFirstVisibleLine(self):
  4182. return self.ctrl.GetFirstVisibleLine()
  4183. #line = self.ctrl.GetFirstVisibleLine()
  4184. #pos = self.ctrl.GetLineIdentPosition(line)
  4185. #text = self.ctrl.GetTextRange(0, pos)
  4186. #move = 0
  4187. #print line, self.ctrl.GetLineVisible(line)
  4188. #while self.ctrl.GetLineVisible(line - 1) and line - 1 > 0:
  4189. # line -= 1
  4190. #
  4191. #return line
  4192. def GetLastVisibleLine(self):
  4193. """
  4194. Returns line number of the first visible line in viewport
  4195. """
  4196. return self.GetFirstVisibleLine() + self.ctrl.LinesOnScreen() - 1
  4197. def GetMiddleVisibleLine(self):
  4198. """
  4199. Returns line number of the middle visible line in viewport
  4200. """
  4201. # TODO: Fix this for long lines
  4202. fl = self.GetFirstVisibleLine()
  4203. ll = self.GetLastVisibleLine()
  4204. lines = ll - fl
  4205. mid = fl + lines // 2
  4206. #if self.LinesOnScreen() < self.GetLineCount():
  4207. # # This may return a float with .5 Really wanted? (MB)
  4208. # mid = (fl + (self.LinesOnScreen() / 2))
  4209. #else:
  4210. # mid = (fl + (self.GetLineCount() / 2))
  4211. return mid
  4212. def GotoSelectionStart(self):
  4213. self.ctrl.GotoPos(self.ctrl.GetSelectionStart())
  4214. def InsertPreviousText(self):
  4215. self.EmulateKeypresses(self.insert_action)
  4216. def InsertPreviousTextLeaveInsertMode(self):
  4217. self.InsertPreviousText()
  4218. self.EndInsertMode()
  4219. def ChangeSurrounding(self, keycodes):
  4220. self.BeginUndo(force=True)
  4221. r = self.DeleteSurrounding(keycodes[0])
  4222. if r:
  4223. self.StartSelection(r[0])
  4224. self.ctrl.GotoPos(r[1])
  4225. self.SelectSelection(2)
  4226. self.SurroundSelection(keycodes[1])
  4227. self.EndUndo(force=True)
  4228. return
  4229. # TODO: should work on the diff between selecting A and In text block
  4230. char_to_change = self.GetCharFromCode(keycodes[0])
  4231. if char_to_change in self.text_object_map:
  4232. self.SelectATextObject(char_to_change)
  4233. if self.HasSelection():
  4234. #pos = self.ExtendSelectionIfRequired()
  4235. pos = self.ctrl.GetSelectionStart()
  4236. self.BeginUndo(force=True)
  4237. self.ctrl.CharRightExtend()
  4238. text = self.ctrl.GetSelectedText()[1:-1]
  4239. self.ctrl.ReplaceSelection(text)
  4240. #self.ctrl.CharLeft()
  4241. self.ctrl.SetSelection(pos, pos + len(bytes(text)))
  4242. self.SurroundSelection(keycodes[1])
  4243. self.ctrl.GotoPos(pos)
  4244. self.EndUndo(force=True)
  4245. self.visualBell("GREEN")
  4246. return
  4247. self.visualBell("RED")
  4248. def PreSurround(self, code):
  4249. self.BeginUndo(use_start_pos=True, force=True)
  4250. self.SurroundSelection(code)
  4251. self.EndUndo()
  4252. def PreSurroundOnNewLine(self, code):
  4253. self.BeginUndo(use_start_pos=True, force=True)
  4254. self.SurroundSelection(code, new_line=True)
  4255. self.EndUndo()
  4256. def PreSurroundLine(self, code):
  4257. self.SelectCurrentLine()
  4258. self.BeginUndo(use_start_pos=True)
  4259. self.SurroundSelection(code)
  4260. self.EndUndo()
  4261. def GetUnichrAt(self, pos):
  4262. """
  4263. Returns the character under the caret.
  4264. Works with unicode characters.
  4265. """
  4266. return self.ctrl.GetTextRange(pos, self.ctrl.PositionAfter(pos))
  4267. def SelectInTextObject(self, ob):
  4268. self.SelectTextObject(ob, False)
  4269. def SelectATextObject(self, ob):
  4270. self.SelectTextObject(ob, True)
  4271. def SelectTextObject(self, ob=None, extra=False):
  4272. """
  4273. Selects specified text object
  4274. See vim help -> *text-objects*
  4275. Two different selection methods are supported. They are given
  4276. the names "In" and "A" corresponding to vim's "i(n)" and "a(n)"
  4277. commands respectively.
  4278. The differences between these is dependent on the type of text
  4279. to be selected, it can be either a selection (e.g. words,
  4280. sentences, etc...) or a block (e.g. text within [ ] or " " or
  4281. ( ) etc...).
  4282. """
  4283. if ob in self.text_object_map:
  4284. between, cmd = self.text_object_map[ob]
  4285. if between:
  4286. cmd(extra)
  4287. else:
  4288. self.SelectForwardStream(cmd, extra)
  4289. #self.text_object_map[ob][1](extra)
  4290. #self.ExtendSelectionIfRequired()
  4291. #if extra: # extra corresponds to a
  4292. # if self.text_object_map[ob][0]: # text block
  4293. # pass # Select surrouding chars
  4294. # else:
  4295. # self.SelectTrailingWhitespace(sel_start, sel_end)
  4296. #if self.mode == ViHelper.VISUAL:
  4297. # self.ctrl.CharRightExtend()
  4298. def SelectTrailingWhitespace(self, sel_start, sel_end):
  4299. self.StartSelection(sel_start)
  4300. true_end = self.ctrl.GetCurrentPos()
  4301. # Vim defaults to selecting trailing whitespace
  4302. if self.ctrl.GetCharAt(sel_end+1) in \
  4303. self.SINGLE_LINE_WHITESPACE or \
  4304. self.ctrl.GetCharAt(sel_start) in \
  4305. self.SINGLE_LINE_WHITESPACE:
  4306. self.MoveCaretWordEndCountWhitespace(1)
  4307. # or if not found it selects preceeding whitespace
  4308. elif self.ctrl.GetCharAt(sel_start-1) \
  4309. in self.SINGLE_LINE_WHITESPACE:
  4310. pos = sel_start-1
  4311. while self.ctrl.GetCharAt(pos-1) \
  4312. in self.SINGLE_LINE_WHITESPACE:
  4313. pos -= 1
  4314. if pos == 0:
  4315. break
  4316. self.ctrl.GotoPos(pos)
  4317. self.StartSelection()
  4318. self.ctrl.GotoPos(true_end)
  4319. #self.SelectSelection()
  4320. def SelectForwardStream(self, cmd, extra):
  4321. start_anchor = False
  4322. if self.HasSelection():
  4323. start_anchor = self.ctrl.GetSelectionStart()
  4324. cmd(extra)
  4325. if start_anchor and self._anchor > start_anchor:
  4326. self._anchor = start_anchor
  4327. self.SelectSelection(2)
  4328. def SelectInWord(self, extra=False):
  4329. """
  4330. Selects n words where n is the count. Whitespace between words
  4331. is counted.
  4332. """
  4333. self._SelectInWords(False, extra=extra)
  4334. def SelectInWORD(self, extra=False):
  4335. self._SelectInWords(True, extra=extra)
  4336. def _SelectInWords(self, WORD=False, extra=False):
  4337. # NOTE: in VIM direction depends on selection stream direction
  4338. # there are still a number of difference between this
  4339. # and vims implementation
  4340. # NOTE: Does not select single characters
  4341. # TODO: Does not select the word if on final character
  4342. pos = self.ctrl.GetCurrentPos()
  4343. if not WORD:
  4344. back_word = self.MoveCaretBackWord
  4345. move_caret_word_end_count_whitespace = \
  4346. self.MoveCaretWordEndCountWhitespace
  4347. else:
  4348. back_word = self.MoveCaretBackWORD
  4349. move_caret_word_end_count_whitespace = \
  4350. self.MoveCaretWordENDCountWhitespace
  4351. # If the caret is in whitespace the whitespace is selected
  4352. if self.ctrl.GetCharAt(pos) in self.SINGLE_LINE_WHITESPACE:
  4353. self.MoveCaretToWhitespaceStart()
  4354. else:
  4355. if pos > 0:
  4356. if self.GetUnichrAt(pos-1) in string.whitespace:
  4357. pass
  4358. elif ((self.GetUnichrAt(pos) in self.WORD_BREAK) is not \
  4359. (self.GetUnichrAt(pos-1) in self.WORD_BREAK)) \
  4360. and not WORD:
  4361. pass
  4362. else:
  4363. back_word(1)
  4364. sel_start = self.StartSelection(None)
  4365. move_caret_word_end_count_whitespace(1)
  4366. sel_end = self.ctrl.GetCurrentPos()
  4367. if extra:
  4368. self.SelectTrailingWhitespace(sel_start, sel_end)
  4369. move_caret_word_end_count_whitespace(self.count-1)
  4370. self.SelectSelection(2)
  4371. def SelectInSentence(self, extra=False):
  4372. """ Selects current sentence """
  4373. # First check if we are at the start of a sentence already
  4374. pos = start_pos = self.ctrl.GetCurrentPos()
  4375. if pos > 0 and self.GetUnichrAt(pos-1) in string.whitespace:
  4376. pos -= 1
  4377. char = self.GetUnichrAt(pos-1)
  4378. while pos > 0 and char in string.whitespace:
  4379. pos -= 1
  4380. char = self.GetUnichrAt(pos-1)
  4381. if char in self.SENTENCE_ENDINGS_SUFFIXS:
  4382. pos -= 1
  4383. char = self.GetUnichrAt(pos-1)
  4384. while pos > 0 and char in self.SENTENCE_ENDINGS_SUFFIXS:
  4385. pos -= 1
  4386. char = self.GetUnichrAt(pos-1)
  4387. if char not in self.SENTENCE_ENDINGS:
  4388. self.GotoSentenceStart(1)
  4389. elif pos > 0 and self.GetUnichrAt(pos-1) in self.SENTENCE_ENDINGS:
  4390. pass
  4391. else:
  4392. self.GotoSentenceStart(1)
  4393. sel_start = self.StartSelection()
  4394. #self.GotoSentenceEnd(1)
  4395. if extra:
  4396. self.count += 1
  4397. self.GotoSentenceEndCountWhitespace()
  4398. self.SelectSelection(2)
  4399. def SelectInParagraph(self, extra=False):
  4400. """ Selects current paragraph. """
  4401. # TODO: fix for multiple counts
  4402. # should track back to whitespace start
  4403. self.MoveCaretParaDown(1)
  4404. self.MoveCaretParaUp(1)
  4405. self.StartSelection(None)
  4406. self.MoveCaretParaDown()
  4407. self.ctrl.CharLeft()
  4408. self.ctrl.CharLeft()
  4409. self.SelectSelection(2)
  4410. def _SelectInBracket(self, bracket, extra=False, start_pos=None,
  4411. count=None, linewise=False, ignore_forewardslashes=False):
  4412. # TODO: config option to "delete preceeding whitespace"
  4413. if start_pos is None: start_pos = self.ctrl.GetCurrentPos()
  4414. if count is None: count = self.count
  4415. if self.SearchBackwardsForChar(bracket, count):
  4416. pos = self.ctrl.GetCurrentPos()
  4417. pre_text = self.ctrl.GetTextRange(pos, start_pos)
  4418. while pre_text.count(bracket) - \
  4419. pre_text.count(self.BRACES[bracket]) != self.count:
  4420. self.ctrl.CharLeft()
  4421. if self.SearchBackwardsForChar(bracket, 1):
  4422. pos = self.ctrl.GetCurrentPos()
  4423. pre_text = self.ctrl.GetTextRange(pos, start_pos)
  4424. else:
  4425. break
  4426. if self.MatchBraceUnderCaret(brace=bracket):
  4427. self.StartSelection(pos)
  4428. self.SelectSelection(2)
  4429. sel_start, sel_end = self._GetSelectionRange()
  4430. if not sel_start <= start_pos <= sel_end:
  4431. self.ctrl.GotoPos(sel_start-len(bracket))
  4432. self._SelectInBracket(bracket, extra, start_pos, count)
  4433. else:
  4434. # Only select the brackets if required
  4435. if not extra:
  4436. sel_start = self.StartSelection(sel_start+len(bracket))
  4437. if ignore_forewardslashes:
  4438. if self.GetUnichrAt(sel_start) == u"/":
  4439. sel_start = self.StartSelection(sel_start+1)
  4440. if self.GetUnichrAt(sel_start) == u"/":
  4441. sel_start = self.StartSelection(sel_start+1)
  4442. self.ctrl.GotoPos(sel_end - len(bracket))
  4443. #self.ctrl.SetSelection(sel_start+len(bracket), sel_end-len(bracket))
  4444. #self.ctrl.CharLeftExtend()
  4445. else:
  4446. sel_start = self.StartSelection(sel_start)
  4447. self.ctrl.GotoPos(sel_end - len(bracket) + 1)
  4448. if linewise:
  4449. self.ctrl.CharRightExtend()
  4450. else:
  4451. self.ctrl.GotoPos(start_pos)
  4452. self.SelectSelection(2)
  4453. def SelectInSquareBracket(self, extra=False):
  4454. """ Selects text in [ ] block """
  4455. self._SelectInBracket("[", extra)
  4456. def SelectInSquareBracketIgnoreStartingSlash(self, extra=False):
  4457. """ Selects text in [ ] block ignoring an starting /'s """
  4458. self._SelectInBracket("[", extra, ignore_forewardslashes=True)
  4459. def SelectInRoundBracket(self, extra=False):
  4460. """ Selects text in ( ) block """
  4461. self._SelectInBracket("(", extra)
  4462. def SelectInInequalitySigns(self, extra=False):
  4463. """ Selects text in < > block """
  4464. self._SelectInBracket("<", extra)
  4465. def SelectInDoubleInequalitySigns(self, extra=False):
  4466. """ Selects text in < > block """
  4467. self._SelectInBracket("<<", extra, linewise=True)
  4468. # TODO: rewrite so input is handled more generally
  4469. # (and handled at the time of keypress)
  4470. def SelectInTagBlock(self, extra=False):
  4471. """ selects text in <aaa> </aaa> block """
  4472. # Catch key inputs
  4473. if not self.tag_input:
  4474. try:
  4475. dialog = wx.TextEntryDialog(self.ctrl.presenter.getMainControl(), "Enter tag")
  4476. if dialog.ShowModal() == wx.ID_OK:
  4477. tag_content = dialog.GetValue()
  4478. self.tag_input = tag_content
  4479. r = self._SelectInChars("<{0}>".format(tag_content), forward_char="</{0}>".format(tag_content), extra=extra)
  4480. return r
  4481. return False
  4482. finally:
  4483. dialog.Destroy()
  4484. else:
  4485. tag_content = self.tag_input
  4486. return self._SelectInChars("<{0}>".format(tag_content), forward_char="</{0}>".format(tag_content), extra=extra)
  4487. def SelectInBlock(self, extra=False):
  4488. """ selects text in { } block """
  4489. self._SelectInBracket("{", extra)
  4490. def _SelectInChars(self, char, extra=False, forward_char=None):
  4491. """
  4492. Select in between "char" blocks.
  4493. If "forward_char" is not None will select between "char" and
  4494. "forward_char".
  4495. Note:
  4496. char / forward_char must not be unicode
  4497. """
  4498. if forward_char is None:
  4499. forward_char = char
  4500. pos = self.ctrl.GetCurrentPos()
  4501. if self.SearchBackwardsForChar(char):
  4502. start_pos = self.ctrl.GetCurrentPos()
  4503. if self.SearchForwardsForChar(forward_char, start_offset=0):
  4504. self.StartSelection(start_pos)
  4505. self.SelectSelection(2)
  4506. if not extra:
  4507. self.ctrl.CharLeftExtend()
  4508. self.StartSelection(start_pos+len(char))
  4509. self.SelectSelection(2)
  4510. else:
  4511. for i in range(1, len(forward_char)):
  4512. self.ctrl.CharRightExtend()
  4513. self.SelectSelection(2)
  4514. return True
  4515. self.ctrl.GotoPos(pos)
  4516. return False
  4517. def SelectInDoubleQuote(self, extra=False):
  4518. """ selects text in " " block """
  4519. self._SelectInChars('"', extra)
  4520. def SelectInSingleQuote(self, extra=False):
  4521. """ selects text in ' ' block """
  4522. self._SelectInChars("'", extra)
  4523. def SelectInTilde(self, extra=False):
  4524. """ selects text in ` ` block """
  4525. self._SelectInChars("`", extra)
  4526. # ---------------------------------------
  4527. def SelectInPoundSigns(self, extra=False):
  4528. self._SelectInChars("\xc2", extra)
  4529. def SelectInDollarSigns(self, extra=False):
  4530. self._SelectInChars("$", extra)
  4531. def SelectInHats(self, extra=False):
  4532. self._SelectInChars("^", extra)
  4533. def SelectInPercentSigns(self, extra=False):
  4534. self._SelectInChars("%", extra)
  4535. def SelectInAmpersands(self, extra=False):
  4536. self._SelectInChars("&", extra)
  4537. def SelectInStars(self, extra=False):
  4538. self._SelectInChars("*", extra)
  4539. def SelectInHyphens(self, extra=False):
  4540. self._SelectInChars("-", extra)
  4541. def SelectInUnderscores(self, extra=False):
  4542. self._SelectInChars("_", extra)
  4543. def SelectInEqualSigns(self, extra=False):
  4544. self._SelectInChars("=", extra)
  4545. def SelectInPlusSigns(self, extra=False):
  4546. self._SelectInChars("+", extra)
  4547. def SelectInExclamationMarks(self, extra=False):
  4548. self._SelectInChars("!", extra)
  4549. def SelectInQuestionMarks(self, extra=False):
  4550. self._SelectInChars("?", extra)
  4551. def SelectInAtSigns(self, extra=False):
  4552. self._SelectInChars("@", extra)
  4553. def SelectInHashs(self, extra=False):
  4554. self._SelectInChars("#", extra)
  4555. def SelectInApproxSigns(self, extra=False):
  4556. self._SelectInChars("~", extra)
  4557. def SelectInVerticalBars(self, extra=False):
  4558. self._SelectInChars("|", extra)
  4559. def SelectInSemicolons(self, extra=False):
  4560. self._SelectInChars(";", extra)
  4561. def SelectInColons(self, extra=False):
  4562. self._SelectInChars(":", extra)
  4563. def SelectInBackslashes(self, extra=False):
  4564. self._SelectInChars("\\", extra)
  4565. def SelectInForwardslashes(self, extra=False):
  4566. self._SelectInChars("/", extra)
  4567. def SurroundSelection(self, keycode, new_line=False):
  4568. #start = self.ExtendSelectionIfRequired()
  4569. start = self.ctrl.GetSelectionStart()
  4570. text = self.ctrl.GetSelectedText()
  4571. if len(text) < 1:
  4572. return # Should never happen
  4573. self.BeginUndo()
  4574. # Fix for EOL
  4575. if text[-1] == self.ctrl.GetEOLChar():
  4576. sel_start, sel_end = self._GetSelectionRange()
  4577. self.ctrl.SetSelection(sel_start, sel_end-1)
  4578. text = self.ctrl.GetSelectedText()
  4579. replacements = self.SURROUND_REPLACEMENTS
  4580. if new_line:
  4581. text = "\n{0}\n".format(text)
  4582. uni_chr = unichr(keycode)
  4583. if uni_chr in replacements:
  4584. # Use .join([]) ?
  4585. REWRITE_SUB_SUP_LINKS = True
  4586. new_text = text
  4587. # TODO: add option to rewrite links containing <sup> and <sub> tags
  4588. tags = [u"<sup>", u"</sup>", u"<sub>", u"</sub>"]
  4589. if uni_chr == u"r" and REWRITE_SUB_SUP_LINKS and \
  4590. True in [tag in text for tag in tags]:
  4591. stripped_text = text
  4592. for tag in tags:
  4593. stripped_text = stripped_text.replace(tag, u"")
  4594. new_text = "{0}|{1}".format(stripped_text, text)
  4595. # If r is used on a subpage we will add // for ease of use
  4596. if uni_chr == u"r" and u"/" in self.ctrl.presenter.getWikiWord():
  4597. new_text = "{0}//{1}{2}".format(replacements[uni_chr][0], new_text, replacements[uni_chr][1])
  4598. else:
  4599. new_text = "{0}{1}{2}".format(replacements[uni_chr][0], new_text, replacements[uni_chr][1])
  4600. else:
  4601. new_text = "{0}{1}{2}".format(uni_chr, text, uni_chr)
  4602. self.ctrl.ReplaceSelection(new_text)
  4603. self.LeaveVisualMode()
  4604. self.ctrl.GotoPos(start)
  4605. self.SetLineColumnPos()
  4606. self.EndUndo()
  4607. def CheckLineEnd(self):
  4608. if self.mode not in [ViHelper.VISUAL, ViHelper.INSERT]:
  4609. line_text, line_pos = self.ctrl.GetCurLine()
  4610. if len(line_text) > 1 and line_text != self.ctrl.GetEOLChar():
  4611. if self.OnLastLine():
  4612. if line_pos >= len(bytes(line_text)):
  4613. self.ctrl.CharLeft()
  4614. else:
  4615. if line_pos >= len(bytes(line_text)) - 1:
  4616. self.ctrl.CharLeft()
  4617. def OnLastLine(self):
  4618. return self.ctrl.GetCurrentLine() + 1 == self.ctrl.GetLineCount()
  4619. def SelectCurrentLine(self, include_eol=True):
  4620. line_no = self.ctrl.GetCurrentLine()
  4621. max_line = min(line_no+self.count-1, self.ctrl.GetLineCount())
  4622. start = self.GetLineStartPos(line_no)
  4623. end = self.ctrl.GetLineEndPosition(max_line)
  4624. # If we are deleting the last line we also need to delete
  4625. # the eol char at the end of the new last line.
  4626. if max_line + 1 == self.ctrl.GetLineCount():
  4627. start -= 1
  4628. if include_eol:
  4629. end += 1
  4630. #self.ctrl.SetSelection(end, start)
  4631. self.ctrl.SetSelection(start, end)
  4632. def SelectFullLines(self, include_eol=False):
  4633. """
  4634. Could probably be replaced by SetSectionMode,
  4635. if it can be made to work. (retest with wx >= 2.9)
  4636. """
  4637. start_line, end_line = self._GetSelectedLines(exclusive=True)
  4638. if self.ctrl.GetCurrentPos() >= self.GetSelectionAnchor():
  4639. reverse = False
  4640. # If selection is not on an empty line
  4641. if self.ctrl.GetLine(start_line) != self.ctrl.GetEOLChar():
  4642. end_line -= 1
  4643. if self.GetUnichrAt(self.GetSelectionAnchor()) == \
  4644. self.ctrl.GetEOLChar():
  4645. start_line += 1
  4646. if self.ctrl.GetCurrentPos() > \
  4647. self.ctrl.GetLineEndPosition(end_line):
  4648. end_line += 1
  4649. else:
  4650. reverse = True
  4651. end_line -= 1
  4652. self.SelectLines(start_line, end_line, reverse=reverse,
  4653. include_eol=include_eol)
  4654. def JoinLines(self):
  4655. self.BeginUndo(use_start_pos=True)
  4656. text = self.ctrl.GetSelectedText()
  4657. start_line = self.ctrl.GetCurrentLine()
  4658. eol_char = self.ctrl.GetEOLChar()
  4659. if len(text) < 1:
  4660. # We need at least 2 lines to be able to join
  4661. count = self.count if self.count > 1 else 2
  4662. self.SelectLines(start_line, start_line - 1 + count)
  4663. else:
  4664. self.SelectFullLines()
  4665. text = self.ctrl.GetSelectedText()
  4666. # Probably not the most efficient way to do this
  4667. # We need to lstrip every line except the first
  4668. lines = text.split(eol_char)
  4669. new_text = []
  4670. for i in range(len(lines)):
  4671. line = lines[i]
  4672. if line.strip() == u"": # Leave out empty lines
  4673. continue
  4674. # Strip one space from line end (if it exists)
  4675. elif line.endswith(" "):
  4676. line = line[:-1]
  4677. if i == 0:
  4678. new_text.append(line)
  4679. else:
  4680. if ViHelper.STRIP_BULLETS_ON_LINE_JOIN:
  4681. # TODO: roman numerals
  4682. # It may be better to avoid using a regex here?
  4683. line = re.sub(r"^ *(\*|#|\d\.) ", r"", line)
  4684. new_text.append(line.lstrip())
  4685. self.ctrl.ReplaceSelection(" ".join(new_text))
  4686. self.CheckLineEnd()
  4687. self.EndUndo()
  4688. #def DeleteCharMotion(self, key_code):
  4689. # if not self.HasSelection():
  4690. # self.visualBell("RED")
  4691. # return
  4692. def DeleteCharMotion(self, key_code):
  4693. self.DeleteCharFromSelection(key_code)
  4694. def DeleteCharFromSelection(self, key_code):
  4695. """
  4696. Remove key_codes corresponding char from selection
  4697. """
  4698. if not self.HasSelection():
  4699. self.visualBell("RED")
  4700. return
  4701. self.BeginUndo(use_start_pos=True)
  4702. char = self.GetCharFromCode(key_code)
  4703. text = self.ctrl.GetSelectedText()
  4704. new_text = text.replace(char, u"")
  4705. self.ctrl.ReplaceSelection(new_text)
  4706. self.SetLineColumnPos()
  4707. self.EndUndo()
  4708. def DeleteBackword(self, word=False):
  4709. if word:
  4710. move_word = self.MoveCaretBackWORD
  4711. else:
  4712. move_word = self.MoveCaretBackWord
  4713. self.StartSelection()
  4714. move_word()
  4715. self.SelectSelection(2)
  4716. self.DeleteSelection(yank=False)
  4717. def DeleteSelectionAndInsert(self):
  4718. self.DeleteSelection()
  4719. self.Insert()
  4720. def RemoveSelection(self, pos=None):
  4721. """
  4722. Removes the selection.
  4723. """
  4724. if pos is None:
  4725. pos = self.ctrl.GetAnchor()
  4726. self.ctrl.SetSelection(pos,pos)
  4727. # TODO: Clean up selection names
  4728. def GetSelectionAnchor(self):
  4729. return self._anchor
  4730. def SelectEOLCharIfRequired(self):
  4731. """
  4732. Select the end of line character if current selection spans multiple
  4733. lines and selects and selects all the text. Will select the EOL char
  4734. if required.
  4735. @return: True if multiple lines are selected in their entirety.
  4736. """
  4737. sel_start, sel_end = self._GetSelectionRange()
  4738. eol_char = self.ctrl.GetEOLChar()
  4739. if self.GetUnichrAt(sel_start - 1) != eol_char or \
  4740. self.ctrl.GetEOLChar() not in self.ctrl.GetSelectedText():
  4741. return False
  4742. if self.GetUnichrAt(sel_end - 1) == eol_char:
  4743. return True
  4744. elif self.GetUnichrAt(sel_end) == eol_char:
  4745. self.ctrl.CharRightExtend()
  4746. return True
  4747. return False
  4748. def GetSelectionDetails(self, selection_type):
  4749. """
  4750. Returns the type of selection
  4751. """
  4752. # Test if selection is lines
  4753. if selection_type == 1 or self.GetSelMode() == u"LINE" or \
  4754. (self.GetLineStartPos(self.ctrl.LineFromPosition(
  4755. self.ctrl.GetSelectionStart())) == self.ctrl.GetSelectionStart() and \
  4756. self.ctrl.GetLineEndPosition(self.ctrl.LineFromPosition(
  4757. self.ctrl.GetSelectionEnd())) == self.ctrl.GetSelectionEnd()):
  4758. start, end = self._GetSelectedLines()
  4759. return (True, end-start)
  4760. else:
  4761. return (False, len(self.ctrl.GetSelectedText()))
  4762. def StartSelection(self, pos=None):
  4763. """ Saves the current position to be used for selection start """
  4764. if pos is None:
  4765. pos = self.ctrl.GetCurrentPos()
  4766. self._anchor = pos
  4767. return pos
  4768. def StartSelectionAtAnchor(self):
  4769. """
  4770. Saves the current position to be used for selection start using
  4771. the anchor as the selection start.
  4772. """
  4773. if len(self.ctrl.GetSelectedText()) > 0:
  4774. self._anchor = self.ctrl.GetAnchor()
  4775. else:
  4776. self._anchor = self.ctrl.GetCurrentPos()
  4777. def SwitchSelection(self, com_type=0):
  4778. """
  4779. Goes to the other end of the selected text (switches the cursor and
  4780. anchor positions)
  4781. """
  4782. anchor = self._anchor
  4783. self._anchor = self.ctrl.GetCurrentPos()
  4784. if self.SelectionIsForward():
  4785. self._anchor = self._anchor - 1
  4786. anchor = anchor - 1
  4787. else:
  4788. self._anchor = self._anchor + 1
  4789. anchor = anchor - 1
  4790. self.ctrl.GotoPos(anchor)
  4791. self.SelectSelection(2)
  4792. def SelectionIsForward(self):
  4793. """
  4794. Check what direction the selection is going in.
  4795. @return True if anchor is behind current position
  4796. """
  4797. return self.ctrl.GetCurrentPos() > self.ctrl.GetSelectionStart()
  4798. def SelectSelection(self, com_type=0):
  4799. if com_type < 1:
  4800. print "Select selection called incorrectly", inspect.getframeinfo(inspect.currentframe().f_back)[2]
  4801. return
  4802. current_pos = self.ctrl.GetCurrentPos()
  4803. if current_pos > self._anchor:
  4804. self.ctrl.SetSelectionStart(self._anchor)
  4805. self.ctrl.SetSelectionEnd(current_pos)
  4806. else:
  4807. self.ctrl.SetAnchor(self._anchor)
  4808. #self.SetSelMode(u"LINE")
  4809. if self.GetSelMode() == u"LINE":
  4810. self.SelectFullLines()
  4811. # Inclusive motion commands select the last character as well
  4812. elif com_type != 2:
  4813. self.ctrl.CharRightExtend()
  4814. def SelectionOnSingleLine(self):
  4815. """
  4816. Assume that if an EOL char is present we have mutiple lines
  4817. """
  4818. if self.ctrl.GetEOLChar() in self.ctrl.GetSelectedText():
  4819. return False
  4820. else:
  4821. return True
  4822. #def ExtendSelectionIfRequired(self):
  4823. # """
  4824. # If selection is positive the last character is not actually
  4825. # selected and so a correction must be applied
  4826. # """
  4827. # start, end = self._GetSelectionRange()
  4828. # if self.ctrl.GetCurrentPos() == end:
  4829. # self.ctrl.CharRightExtend()
  4830. # return start
  4831. def DeleteSelectionLines(self, yank=True):
  4832. self.SelectFullLines(include_eol=True)
  4833. self.DeleteSelection(yank)
  4834. def DeleteSelection(self, yank=True):
  4835. """Yank selection and delete it"""
  4836. self.BeginUndo(use_start_pos=True)
  4837. # Hack so that when deleting blocks that consit of entire lines
  4838. # the eol char is copied as well (allows pasting of line block)
  4839. self.SelectEOLCharIfRequired()
  4840. if yank:
  4841. self.YankSelection()
  4842. self.ctrl.Clear()
  4843. self.EndUndo()
  4844. self.LeaveVisualMode()
  4845. self.SetLineColumnPos()
  4846. def _GetSelectionRange(self):
  4847. """
  4848. Get the range of selection such that the start is the visual start
  4849. of the selection, not the logical start.
  4850. """
  4851. start, end = self.minmax(self.ctrl.GetSelectionStart(),
  4852. self.ctrl.GetSelectionEnd())
  4853. return start, end
  4854. #def _GetSelectedLines(self):
  4855. # # why exclusive?
  4856. # """Get the first and last line (exclusive) of selection"""
  4857. # start, end = self._GetSelectionRange()
  4858. # start_line, end_line = (self.ctrl.LineFromPosition(start),
  4859. # self.ctrl.LineFromPosition(end - 1)+ 1)
  4860. # return start_line, end_line
  4861. def _GetSelectedLines(self, exclusive=False):
  4862. """Get the first and last line of selection"""
  4863. start, end = self._GetSelectionRange()
  4864. if exclusive:
  4865. end -= 1
  4866. start_line, end_line = (self.ctrl.LineFromPosition(start),
  4867. self.ctrl.LineFromPosition(end) + 1)
  4868. return start_line, end_line
  4869. def HasSelection(self):
  4870. """
  4871. Detects if there's anything selected
  4872. @rtype: bool
  4873. """
  4874. return len(self.ctrl.GetSelectedText()) > 0
  4875. def InsertText(self, text):
  4876. self.ctrl.InsertText(self.ctrl.GetCurrentPos(), text)
  4877. self.MoveCaretPos(len(text))
  4878. def SelectLines(self, start, end, reverse=False, include_eol=False):
  4879. """
  4880. Selects lines
  4881. @param start: start line
  4882. @param end: end line
  4883. @param reverse: if true selection is reversed
  4884. """
  4885. start = max(start, 0)
  4886. max_line_count = self.ctrl.GetLineCount()
  4887. end = min(end, max_line_count)
  4888. start_pos = self.GetLineStartPos(start)
  4889. end_pos = self.ctrl.GetLineEndPosition(end)
  4890. if include_eol or end == max_line_count:
  4891. end_pos += 1
  4892. if reverse:
  4893. self.ctrl.GotoPos(start_pos)
  4894. self.ctrl.SetAnchor(end_pos)
  4895. else:
  4896. self.ctrl.SetSelection(start_pos, end_pos)
  4897. def PreUppercase(self):
  4898. """Convert selected text to uppercase"""
  4899. start = self.ctrl.GetSelectionStart()
  4900. self.BeginUndo(use_start_pos=True)
  4901. self.ctrl.ReplaceSelection(self.ctrl.GetSelectedText().upper())
  4902. self.ctrl.GotoPos(start)
  4903. self.EndUndo()
  4904. def PreLowercase(self):
  4905. """Convert selected text to lowercase"""
  4906. start = self.ctrl.GetSelectionStart()
  4907. self.BeginUndo(use_start_pos=True)
  4908. self.ctrl.ReplaceSelection(self.ctrl.GetSelectedText().lower())
  4909. self.ctrl.GotoPos(start)
  4910. self.EndUndo()
  4911. def SubscriptMotion(self):
  4912. start = self.ctrl.GetSelectionStart()
  4913. self.BeginUndo(use_start_pos=True)
  4914. self.ctrl.ReplaceSelection("<sub>{0}</sub>".format(self.ctrl.GetSelectedText()))
  4915. self.ctrl.GotoPos(start)
  4916. self.EndUndo()
  4917. def SuperscriptMotion(self):
  4918. start = self.ctrl.GetSelectionStart()
  4919. self.BeginUndo(use_start_pos=True)
  4920. self.ctrl.ReplaceSelection("<sup>{0}</sup>".format(self.ctrl.GetSelectedText()))
  4921. self.ctrl.GotoPos(start)
  4922. self.EndUndo()
  4923. def CreateShortHint(self):
  4924. """Add short hint template"""
  4925. self.BeginUndo()
  4926. self.ctrl.AddText('[short_hint:""]')
  4927. self.ctrl.CharLeft()
  4928. self.ctrl.CharLeft()
  4929. self.EndUndo()
  4930. def SetHeading(self, code):
  4931. # TODO: make more vim-like (multiple line support)
  4932. try:
  4933. level = int(self.GetCharFromCode(code))
  4934. except ValueError:
  4935. self.visualBell("RED")
  4936. return
  4937. self.BeginUndo()
  4938. self.SelectCurrentLine()
  4939. # Check if heading needs line padding above
  4940. extra = u""
  4941. if self.settings["blank_line_above_headings"]:
  4942. start = self.ctrl.GetSelectionStart()
  4943. if self.GetUnichrAt(start-2) != self.ctrl.GetEOLChar():
  4944. extra = self.ctrl.GetEOLChar()
  4945. line = self.ctrl.GetSelectedText()
  4946. if line.lstrip().startswith("* "):
  4947. line = line.lstrip().lstrip("* ")
  4948. # The heading still has its EOL character present
  4949. if self.settings["strip_headings"]:
  4950. if line[0] == "*" and line[-2] == "*":
  4951. line = line[1:-2] + line[-1]
  4952. if line[0] == "_" and line[-2] == "_":
  4953. line = line[1:-2] + line[-1]
  4954. new_line = "".join([extra, level * u"+", self.ctrl.vi.settings["pad_headings"] * u" ", line.lstrip("+")])
  4955. self.ctrl.ReplaceSelection(new_line)
  4956. self.EndUndo()
  4957. self.MoveCaretUp(1)
  4958. def SwapCase(self):
  4959. """
  4960. Swap case of selected text. If no text selected swap case of
  4961. character under caret.
  4962. """
  4963. self.BeginUndo(force=True)
  4964. text = self.ctrl.GetSelectedText()
  4965. if len(text) < 1:
  4966. self.StartSelection()
  4967. self.MoveCaretRight(allow_last_char=True)
  4968. self.SelectSelection(2)
  4969. text = self.ctrl.GetSelectedText()
  4970. self.ctrl.ReplaceSelection(text.swapcase())
  4971. self.EndUndo()
  4972. def SelectionToSubscript(self):
  4973. """Surround selected text with <sub> </sub> tags"""
  4974. self.BeginUndo(force=True)
  4975. #self.ExtendSelectionIfRequired()
  4976. self.ctrl.ReplaceSelection("<sub>{0}</sub>".format(
  4977. self.ctrl.GetSelectedText()))
  4978. self.RemoveSelection()
  4979. self.EndUndo()
  4980. def SelectionToSuperscript(self):
  4981. """Surround selected text with <sup> </sup> tags"""
  4982. self.BeginUndo(force=True)
  4983. #self.ExtendSelectionIfRequired()
  4984. self.ctrl.ReplaceSelection("<sup>{0}</sup>".format(
  4985. self.ctrl.GetSelectedText()))
  4986. self.RemoveSelection()
  4987. self.EndUndo()
  4988. def SelectionToUpperCase(self):
  4989. self.BeginUndo(force=True)
  4990. #self.ExtendSelectionIfRequired()
  4991. self.ctrl.ReplaceSelection(self.ctrl.GetSelectedText().upper())
  4992. self.RemoveSelection()
  4993. self.EndUndo()
  4994. def SelectionToLowerCase(self):
  4995. self.BeginUndo(force=True)
  4996. #self.ExtendSelectionIfRequired()
  4997. self.ctrl.ReplaceSelection(self.ctrl.GetSelectedText().lower())
  4998. self.RemoveSelection()
  4999. self.EndUndo()
  5000. def Indent(self, forward=True, repeat=1, visual=False):
  5001. # TODO: fix - call SelectSelection?
  5002. if visual == True:
  5003. repeat = self.count
  5004. self.BeginUndo(force=True)
  5005. # If no selected text we work on lines as specified by count
  5006. if not self.HasSelection():
  5007. start_line = self.ctrl.GetCurrentLine()
  5008. if self.count > 1:
  5009. self.SelectLines(start_line, min(self.ctrl.GetLineCount(), \
  5010. start_line - 1 + self.count))
  5011. else:
  5012. start_line, end = self._GetSelectedLines()
  5013. if self.SelectionOnSingleLine():
  5014. self.GotoLineIndent()
  5015. for i in range(repeat):
  5016. if forward:
  5017. self.ctrl.Tab()
  5018. else:
  5019. self.ctrl.BackTab()
  5020. self.ctrl.GotoLine(start_line)
  5021. self.GotoLineIndent()
  5022. self.EndUndo()
  5023. def _PositionViewport(self, n):
  5024. """
  5025. Helper function for ScrollViewport* functions.
  5026. Positions the viewport around caret position
  5027. """
  5028. lines = self.ctrl.LinesOnScreen() - 1
  5029. current = self.ctrl.GetCurrentLine()
  5030. diff = int(lines * n)
  5031. self.ctrl.ScrollToLine(current - diff)
  5032. def GetViewportPosition(self):
  5033. lines = self.ctrl.LinesOnScreen() - 1
  5034. current = self.ctrl.GetCurrentLine()
  5035. first_visible_line = self.GetFirstVisibleLine()
  5036. n = current - first_visible_line
  5037. return n / float(lines)
  5038. def _ScrollViewportByLines(self, n):
  5039. # TODO: should not always move cursor position
  5040. first_visible_line = self.GetFirstVisibleLine()
  5041. lines_on_screen = self.ctrl.LinesOnScreen()
  5042. line = max(0, first_visible_line + n)
  5043. line = min(line, self.ctrl.GetLineCount() - lines_on_screen)
  5044. self.ctrl.ScrollToLine(line)
  5045. if self.ctrl.GetCurrentLine() < line:
  5046. self.ctrl.LineDown()
  5047. elif self.ctrl.GetCurrentLine() > first_visible_line + lines_on_screen-2:
  5048. self.ctrl.LineUp()
  5049. def _ScrollViewport(self, n):
  5050. view_pos = self.GetViewportPosition()
  5051. lines = self.ctrl.LinesOnScreen()
  5052. current_line = self.ctrl.GetCurrentLine()
  5053. new_line = max(0, current_line + n * lines)
  5054. new_line = min(new_line, self.ctrl.GetLineCount())
  5055. self.GotoLineIndent(new_line)
  5056. self._PositionViewport(view_pos)
  5057. def IndentText(self):
  5058. """
  5059. Post motion function. Select text and indent it
  5060. """
  5061. self.SelectSelection()
  5062. self.Indent(True)
  5063. def DedentText(self):
  5064. """
  5065. Post motion function. Select text and deindent it
  5066. """
  5067. self.SelectSelection()
  5068. self.Indent(False)
  5069. #--------------------------------------------------------------------
  5070. # Visual mode
  5071. #--------------------------------------------------------------------
  5072. #def SetSelMode(self, mode):
  5073. # if mode == "LINE":
  5074. # self.ctrl.SetSelectionMode(wx.stc.STC_SEL_LINES)
  5075. # else:
  5076. # self.ctrl.SetSelectionMode(wx.stc.STC_SEL_STREAM)
  5077. def EnterBlockVisualMode(self):
  5078. pass
  5079. def EnterLineVisualMode(self):
  5080. """
  5081. Enter line visual mode
  5082. Sets a special type of visual mode in which only full lines can
  5083. be selected.
  5084. NOTE:
  5085. Should be possible using StyledTextCtrl.SetSelectionType() but
  5086. for some reason I can't get it to work so a SetSelMode() has
  5087. been implemented.
  5088. """
  5089. self.SetSelMode("LINE")
  5090. text, pos = self.ctrl.GetCurLine()
  5091. if pos == 0:
  5092. self.MoveCaretRight()
  5093. if self.mode != ViHelper.VISUAL:
  5094. self.SetMode(ViHelper.VISUAL)
  5095. self.StartSelectionAtAnchor()
  5096. def LeaveVisualMode(self):
  5097. """Helper function to end visual mode"""
  5098. if self.mode == ViHelper.VISUAL:
  5099. self.ctrl.GotoPos(self.ctrl.GetSelectionStart())
  5100. self.SetSelMode("NORMAL")
  5101. self.SetMode(ViHelper.NORMAL)
  5102. def EnterVisualMode(self, mouse=False):
  5103. """
  5104. Change to visual (selection) mode
  5105. Will do nothing if already in visual mode
  5106. @param mouse: Visual mode was started by mouse action
  5107. """
  5108. if self.mode == ViHelper.INSERT:
  5109. self.EndInsertMode()
  5110. if self.mode != ViHelper.VISUAL:
  5111. self.SetMode(ViHelper.VISUAL)
  5112. if not mouse:
  5113. self.StartSelectionAtAnchor()
  5114. else:
  5115. pos = self.ctrl.GetSelectionStart() \
  5116. + self.ctrl.GetSelectionEnd() \
  5117. - self.ctrl.GetCurrentPos()
  5118. self.StartSelection(pos)
  5119. #--------------------------------------------------------------------
  5120. # Searching
  5121. #--------------------------------------------------------------------
  5122. def SearchForwardsForChar(self, search_char, count=None,
  5123. wrap_lines=True, start_offset=0):
  5124. """
  5125. Search for "char".
  5126. Note:
  5127. Position is incorrect if search_char is unicode (and there are unicode
  5128. strings in the document text)
  5129. """
  5130. if count is None: count = self.count
  5131. pos = start_pos = self.ctrl.GetCurrentPos() + start_offset
  5132. text = self.ctrl.GetTextRaw()
  5133. #text_to_search = text[pos:]
  5134. n = 0
  5135. for i in range(count):
  5136. pos = text.find(search_char, pos + 1)
  5137. if pos > -1:
  5138. if not wrap_lines:
  5139. text_to_check = self.ctrl.GetTextRange(start_pos, pos)
  5140. if self.ctrl.GetEOLChar() in text_to_check:
  5141. return False
  5142. self.ctrl.GotoPos(pos)
  5143. return True
  5144. self.visualBell("RED")
  5145. return False
  5146. def SearchBackwardsForChar(self, search_char, count=None,
  5147. wrap_lines=True, start_offset=0):
  5148. """
  5149. Searches backwards in text for character.
  5150. Cursor is positioned at the start of the text if found
  5151. @param search_char: Character to search for.
  5152. @param count: Number of characeters to find.
  5153. @param wrap_lines: Should search occur on multiple lines.
  5154. @param start_offset: Start offset for searching. Should be
  5155. zero if character under the caret should be included in
  5156. the search, -1 if not.
  5157. @rtype: bool
  5158. @return: True if successful, False if not.
  5159. Note:
  5160. Position is incorrect if search_char is unicode (and there are unicode
  5161. strings in the document text)
  5162. """
  5163. if count is None: count = self.count
  5164. pos = start_pos = self.ctrl.GetCurrentPos() + start_offset
  5165. text = self.ctrl.GetTextRaw()
  5166. text_to_search = text[:pos+1]
  5167. for i in range(count):
  5168. pos = text_to_search.rfind(search_char)
  5169. text_to_search = text[:pos]
  5170. if pos > -1:
  5171. if not wrap_lines:
  5172. text_to_check = self.ctrl.GetTextRangeRaw(start_pos, pos)
  5173. if self.ctrl.GetEOLChar() in text_to_check:
  5174. return False
  5175. self.ctrl.GotoPos(pos)
  5176. return True
  5177. self.visualBell("RED")
  5178. return False
  5179. def FindMatchingBrace(self, brace):
  5180. if brace in self.BRACES:
  5181. forward = True
  5182. b = self.BRACES
  5183. elif brace in self.REVERSE_BRACES:
  5184. forward = False
  5185. b = self.REVERSE_BRACES
  5186. else:
  5187. return False
  5188. start_pos = self.ctrl.GetCurrentPos()
  5189. if forward:
  5190. text = self.ctrl.GetTextRaw()[start_pos+1:]
  5191. else:
  5192. text = self.ctrl.GetTextRaw()[0:start_pos:][::-1]
  5193. brace_count = 1
  5194. pos = -1
  5195. search_brace = b[brace]
  5196. brace_length = len(bytes(search_brace))
  5197. # It is probably unnecessary to convert to bytes, but it will
  5198. # prevent any unicode warnings that may otherwise occur
  5199. brace_bytes = bytes(brace)
  5200. search_brace_bytes = bytes(search_brace)
  5201. for j in range(len(text)-brace_length + 1):
  5202. i = bytes(text[j:j+brace_length])
  5203. if i == brace_bytes:
  5204. brace_count += 1
  5205. elif i == search_brace_bytes:
  5206. brace_count -= 1
  5207. # brace_count will be 0 when we have found our matching brace
  5208. if brace_count < 1:
  5209. if forward:
  5210. pos = start_pos + j + len(search_brace)
  5211. else:
  5212. pos = start_pos - j - len(search_brace)
  5213. break
  5214. if pos > -1:
  5215. self.ctrl.GotoPos(pos)
  5216. return True
  5217. else:
  5218. self.visualBell("RED")
  5219. return False
  5220. def FindChar(self, code=None, reverse=False, offset=0, count=1, \
  5221. repeat=True):
  5222. """
  5223. Searches current *line* for specified character.
  5224. Will move the caret to this place (+/- any offset supplied).
  5225. @param code: keycode of character to search for.
  5226. @param reverse: If True will search backwards.
  5227. @param offset: Offset to move caret post search.
  5228. @param count: Number of characters to find. If not all found,
  5229. i.e. count is 3 but only 2 characters on current line, will
  5230. not move caret.
  5231. @param repeat: Should the search be saved so it will be
  5232. repeated (by "," and ";")
  5233. @rtype: bool
  5234. @return: True if successful, False if not.
  5235. """
  5236. if code is None:
  5237. return False
  5238. # Weird stuff happens when searching for a unicode string
  5239. char = bytes(self.GetCharFromCode(code))
  5240. pos = self.ctrl.GetCurrentPos()
  5241. if repeat:
  5242. # First save cmd so it can be repeated later
  5243. # Vim doesn't save the count so a new one can be used next time
  5244. self.last_find_cmd = {
  5245. "code": code,
  5246. "reverse": reverse,
  5247. "offset": offset,
  5248. "repeat": False
  5249. }
  5250. if reverse: # Search backwards
  5251. search_cmd = self.SearchBackwardsForChar
  5252. start_offset = -1
  5253. offset = - offset
  5254. else: # Search forwards
  5255. search_cmd = self.SearchForwardsForChar
  5256. start_offset = 0
  5257. if search_cmd(char, count, False, start_offset):
  5258. self.MoveCaretPos(offset)
  5259. self.SetLineColumnPos()
  5260. return True
  5261. return False
  5262. def FindNextChar(self, keycode):
  5263. return self.FindChar(keycode, count=self.count)
  5264. def FindNextCharBackwards(self, keycode):
  5265. return self.FindChar(keycode, count=self.count, reverse=True)
  5266. def FindUpToNextChar(self, keycode):
  5267. return self.FindChar(keycode, count=self.count, offset=-1)
  5268. def FindUpToNextCharBackwards(self, keycode):
  5269. return self.FindChar(keycode, count=self.count, reverse=True, offset=-1)
  5270. def GetLastFindCharCmd(self):
  5271. return self.last_find_cmd
  5272. def RepeatLastFindCharCmd(self):
  5273. args = self.GetLastFindCharCmd()
  5274. if args is not None:
  5275. # Set the new count
  5276. args["count"] = self.count
  5277. return self.FindChar(**args)
  5278. def RepeatLastFindCharCmdReverse(self):
  5279. args = self.GetLastFindCharCmd()
  5280. if args is not None:
  5281. args["count"] = self.count
  5282. args["reverse"] = not args["reverse"]
  5283. self.FindChar(**args)
  5284. args["reverse"] = not args["reverse"]
  5285. def MatchBraceUnderCaret(self, brace=None):
  5286. # TODO: << and >>
  5287. if brace is None:
  5288. return self.FindMatchingBrace(self.GetUnichrAt(
  5289. self.ctrl.GetCurrentPos()))
  5290. else:
  5291. return self.FindMatchingBrace(brace)
  5292. # TODO: vim like searching
  5293. def _SearchText(self, text, forward=True, match_case=True, wrap=True,
  5294. whole_word=True, regex=False, word_start=False, select_text=False,
  5295. repeat_search=False):
  5296. """
  5297. Searches for next occurance of 'text'
  5298. @param text: text to search for
  5299. @param forward: if true searches forward in text, else
  5300. search in reverse
  5301. @param match_case: should search be case sensitive?
  5302. """
  5303. if repeat_search:
  5304. offset = 2 if forward else -1
  5305. self.MoveCaretPos(offset)
  5306. if not forward and self.HasSelection():
  5307. self.ctrl.GotoPos(self.ctrl.GetSelectionEnd()+1)
  5308. #elif forward:
  5309. # self.ctrl.CharRight()
  5310. self.ctrl.SearchAnchor()
  5311. self.AddJumpPosition(self.ctrl.GetCurrentPos() - len(text))
  5312. search_cmd = self.ctrl.SearchNext if forward else self.ctrl.SearchPrev
  5313. flags = 0
  5314. if whole_word:
  5315. flags = flags | wx.stc.STC_FIND_WHOLEWORD
  5316. if match_case:
  5317. flags = flags | wx.stc.STC_FIND_MATCHCASE
  5318. if word_start:
  5319. flags = flags | wx.stc.STC_FIND_WORDSTART
  5320. if regex:
  5321. flags = flags | wx.stc.STC_FIND_REGEXP
  5322. pos = search_cmd(flags, text)
  5323. if pos == -1 and wrap:
  5324. if forward:
  5325. self.ctrl.GotoLine(0)
  5326. else:
  5327. self.ctrl.GotoLine(self.ctrl.GetLineCount())
  5328. self.ctrl.SearchAnchor()
  5329. pos = search_cmd(flags, text)
  5330. if pos != -1:
  5331. self.ctrl.GotoPos(pos)
  5332. if select_text:
  5333. # Unicode conversion?
  5334. self.ctrl.SetSelection(pos, pos + len(text))
  5335. return True
  5336. return False
  5337. def _SearchCaretWord(self, forward=True, match_case=True, whole_word=True):
  5338. """
  5339. Searches for next occurance of word currently under
  5340. the caret
  5341. @param forward: if true searches forward in text, else
  5342. search in reverse
  5343. @param match_case: should search be case sensitive?
  5344. @param whole_word: must the entire string match as a word
  5345. """
  5346. self.SelectInWord()
  5347. # this should probably be rewritten
  5348. self.ctrl.CharRightExtend()
  5349. #self.ExtendSelectionIfRequired()
  5350. text = self.ctrl.GetSelectedText()
  5351. #offset = 1 if forward else -1
  5352. #self.MoveCaretPos(offset)
  5353. if forward:
  5354. self.ctrl.CharRight()
  5355. else:
  5356. self.ctrl.CharLeft()
  5357. self.ctrl.SearchAnchor()
  5358. self._SearchText(text, forward, match_case=match_case, wrap=True, whole_word=whole_word)
  5359. self.last_search_args = {'text' : text, 'forward' : forward,
  5360. 'match_case' : match_case,
  5361. 'whole_word' : whole_word}
  5362. def SearchCaretWordForwards(self):
  5363. """Helper function to allow repeats"""
  5364. self._SearchCaretWord(True, True, True)
  5365. def SearchPartialCaretWordForwards(self):
  5366. self._SearchCaretWord(True, True, False)
  5367. def SearchCaretWordBackwards(self):
  5368. """Helper function to allow repeats"""
  5369. self._SearchCaretWord(False, True, True)
  5370. def SearchPartialCaretWordBackwards(self):
  5371. self._SearchCaretWord(False, True, False)
  5372. #--------------------------------------------------------------------
  5373. # Replace
  5374. #--------------------------------------------------------------------
  5375. def StartReplaceOnSelection(self):
  5376. """
  5377. Starts a search and replace cmd using the currently selected text
  5378. """
  5379. if not self.HasSelection():
  5380. return
  5381. text = self.ctrl.GetSelectedText()
  5382. # \V is used as we want a direct text replace
  5383. self.StartCmdInput("%s/\V{0}/".format(text))
  5384. def ReplaceChar(self, keycode):
  5385. """
  5386. Replaces character under caret
  5387. Contains some custom code to allow repeating
  5388. """
  5389. # TODO: visual indication
  5390. try:
  5391. char = unichr(keycode)
  5392. except:
  5393. return
  5394. selected_text_len = None
  5395. # If in visual mode use the seletion we have (not the count)
  5396. if self.mode == ViHelper.VISUAL:
  5397. sel_start, sel_end = self._GetSelectionRange()
  5398. count = sel_end - sel_start
  5399. self.ctrl.GotoPos(sel_start)
  5400. selected_text_len = count
  5401. else:
  5402. count = self.count
  5403. # Replace does not wrap lines and fails if you try and replace
  5404. # non existent chars
  5405. line, pos = self.ctrl.GetCurLineRaw()
  5406. line_length = len(line)
  5407. # If we are on the last line we need to increase the line
  5408. # length by 1 (as the last line has no eol char)
  5409. if self.ctrl.GetLineCount() == self.ctrl.GetCurrentLine() + 1:
  5410. line_length += 1
  5411. if pos + count > line_length:
  5412. return
  5413. self.last_cmd = \
  5414. [3, keycode, count, None, None, None, selected_text_len]
  5415. self.BeginUndo(use_start_pos=True, force=True)
  5416. self.StartSelection()
  5417. # Use movecaretright so it works correctly with unicode characters
  5418. #self.ctrl.GotoPos(self.ctrl.GetCurrentPos()+count)
  5419. #self.DeleteRight()
  5420. #self.ctrl.CharRight()
  5421. self.MoveCaretRight(allow_last_char=True)
  5422. #self.EndDelete()
  5423. self.SelectSelection(2)
  5424. self.ctrl.ReplaceSelection(count * char)
  5425. #self.Repeat(self.InsertText, arg=char)
  5426. #if pos + count != line_length:
  5427. # self.MoveCaretPos(-1)
  5428. self.ctrl.CharLeft()
  5429. self.SetLineColumnPos()
  5430. self.EndUndo()
  5431. def StartReplaceMode(self):
  5432. # TODO: visual indication
  5433. self.BeginUndo(use_start_pos=True, force=True)
  5434. self.SetMode(ViHelper.REPLACE)
  5435. def EndReplaceMode(self):
  5436. if self.mode == ViHelper.REPLACE:
  5437. self.EndUndo()
  5438. self.SetMode(ViHelper.NORMAL)
  5439. #--------------------------------------------------------------------
  5440. # Marks
  5441. #--------------------------------------------------------------------
  5442. def _SetMark(self, code):
  5443. """
  5444. Not called directly (call self.Mark instead)
  5445. """
  5446. page = self.ctrl.presenter.getWikiWord()
  5447. self.marks[page][code] = self.ctrl.GetCurrentPos()
  5448. def GotoMark(self, char):
  5449. # TODO '' and `` goto previous jump
  5450. page = self.ctrl.presenter.getWikiWord()
  5451. if char in self.marks[page]:
  5452. self.AddJumpPosition()
  5453. pos = self.marks[page][char]
  5454. # If mark is set past the end of the document just
  5455. # go to the end
  5456. pos = min(self.ctrl.GetLength(), pos)
  5457. self.ctrl.GotoPos(pos)
  5458. self.visualBell("GREEN")
  5459. return True
  5460. self.visualBell("RED")
  5461. return False
  5462. def GotoMarkIndent(self, char):
  5463. if self.GotoMark(char):
  5464. self.GotoLineIndent()
  5465. #--------------------------------------------------------------------
  5466. # Copy and Paste commands
  5467. #--------------------------------------------------------------------
  5468. def YankLine(self):
  5469. """Copy the current line text to the clipboard"""
  5470. line_no = self.ctrl.GetCurrentLine()
  5471. max_line = min(line_no+self.count-1, self.ctrl.GetLineCount())
  5472. start = self.GetLineStartPos(line_no)
  5473. end = self.ctrl.GetLineEndPosition(max_line)
  5474. self.ctrl.SetSelection(start, end + 1)
  5475. self.YankSelection(yank_register=True)
  5476. self.GotoSelectionStart()
  5477. def YankSelection(self, lines=False, yank_register=False):
  5478. """
  5479. Copy the current selection to the clipboard
  5480. Sets the currently selected register and either the yank ("0)
  5481. or other number register depending on the value of yank_register
  5482. """
  5483. if lines:
  5484. self.SelectFullLines()
  5485. self.ctrl.CharRightExtend()
  5486. elif self.GetSelMode() == "LINE":
  5487. # Selection needs to be the correct way round
  5488. start, end = self._GetSelectionRange()
  5489. self.ctrl.SetSelection(start, end)
  5490. self.SelectEOLCharIfRequired()
  5491. #self.ctrl.Copy()
  5492. text = self.ctrl.GetSelectedText()
  5493. self.register.SetCurrentRegister(text, yank_register)
  5494. def Yank(self, lines=False):
  5495. self.YankSelection(lines, yank_register=True)
  5496. self.GotoSelectionStart()
  5497. def PutClipboard(self, before, count=None):
  5498. self.register.SelectRegister("+")
  5499. self.Put(before=False, count=count)
  5500. def Put(self, before, count=None):
  5501. count = count if count is not None else self.count
  5502. #text = getTextFromClipboard()
  5503. current_reg = self.register.GetSelectedRegister()
  5504. text = self.register.GetCurrentRegister()
  5505. #if text is None:
  5506. # self.visualBell("RED")
  5507. # return
  5508. self.BeginUndo(True, force=True)
  5509. # If its not text paste as normal for now
  5510. if not text:
  5511. self.ctrl.Paste()
  5512. self.EndUndo()
  5513. return
  5514. # If the text to copy ends with an eol char we treat the text
  5515. # as a line(s) (only for internal pastes)
  5516. # TODO: fix for cross tab pasting
  5517. is_line = False
  5518. if current_reg != "+":
  5519. eol = self.ctrl.GetEOLChar()
  5520. eol_len = len(eol)
  5521. if len(text) > eol_len:
  5522. if text[-len(eol):] == eol:
  5523. is_line = True
  5524. text_to_paste = count * text
  5525. if self.HasSelection():
  5526. if is_line:
  5527. text_to_paste = "".join(["\n", text_to_paste])
  5528. self.ctrl.ReplaceSelection(text_to_paste)
  5529. else:
  5530. if is_line:
  5531. if not before:
  5532. # If pasting a line we have to goto the end before moving caret
  5533. # down to handle long lines correctly
  5534. #self.ctrl.LineEnd()
  5535. self.MoveCaretDown(1)
  5536. self.GotoLineStart()
  5537. else:
  5538. if not before:
  5539. self.MoveCaretRight(allow_last_char=True)
  5540. #line_text, pos = self.ctrl.GetCurLine()
  5541. #if len(line_text) != pos + 1:
  5542. # self.ctrl.CharRight()
  5543. #self.Repeat(self.InsertText, arg=text)
  5544. self.InsertText(text_to_paste)
  5545. if is_line:
  5546. #if before:
  5547. # self.MoveCaretUp(1)
  5548. self.GotoLineIndent()
  5549. self.EndUndo()
  5550. #--------------------------------------------------------------------
  5551. # Deletion commands
  5552. #--------------------------------------------------------------------
  5553. def DeleteSurrounding(self, code):
  5554. char = self.GetCharFromCode(code)
  5555. pos = self.ctrl.GetCurrentPos()
  5556. if char in self.text_object_map:
  5557. self.SelectInTextObject(char)
  5558. self.ctrl.CharRightExtend()
  5559. text = self.ctrl.GetSelectedText()
  5560. self.ctrl.GotoPos(pos)
  5561. self.SelectATextObject(char)
  5562. self.ctrl.CharRightExtend()
  5563. pos = self._GetSelectionRange()
  5564. self.ctrl.ReplaceSelection(text)
  5565. self.ctrl.GotoPos(pos[0])
  5566. return ((pos[0], pos[0]+len(bytes(text))))
  5567. return False
  5568. def EndDelete(self):
  5569. self.SelectSelection(2)
  5570. self.DeleteSelection()
  5571. self.CheckLineEnd()
  5572. self.SetLineColumnPos()
  5573. def EndDeleteInsert(self):
  5574. self.BeginUndo(use_start_pos=True)
  5575. self.SelectSelection(2)
  5576. self.DeleteSelection()
  5577. self.Insert()
  5578. self.EndUndo()
  5579. def DeleteRight(self):
  5580. self.BeginUndo()
  5581. self.StartSelection()
  5582. self.MoveCaretRight(allow_last_char=True)
  5583. self.SelectSelection(2)
  5584. ## If the selection len is less than the count we need to select
  5585. ## the last character on the line
  5586. #if len(self.ctrl.GetSelectedText()) < self.count:
  5587. # self.ctrl.CharRightExtend()
  5588. self.DeleteSelection()
  5589. self.EndUndo()
  5590. self.CheckLineEnd()
  5591. def DeleteLeft(self):
  5592. self.BeginUndo(force=True)
  5593. self.StartSelection()
  5594. self.MoveCaretLeft()
  5595. self.SelectSelection(2)
  5596. self.DeleteSelection()
  5597. self.CheckLineEnd()
  5598. self.EndUndo()
  5599. def DeleteRightAndInsert(self):
  5600. self.BeginUndo(use_start_pos=True)
  5601. self.DeleteRight()
  5602. self.Insert()
  5603. self.EndUndo()
  5604. def DeleteLinesAndIndentInsert(self):
  5605. self.BeginUndo(force=True)
  5606. indent = self.ctrl.GetLineIndentation(self.ctrl.GetCurrentLine())
  5607. self.DeleteLine()
  5608. self.OpenNewLine(True, indent=indent)
  5609. self.EndUndo()
  5610. def DeleteLine(self):
  5611. self.BeginUndo()
  5612. self.SelectCurrentLine()
  5613. self.DeleteSelection()
  5614. self.EndUndo()
  5615. #--------------------------------------------------------------------
  5616. # Movement commands
  5617. #--------------------------------------------------------------------
  5618. def GotoSelectionStart(self):
  5619. if not self.HasSelection():
  5620. return False
  5621. self.ctrl.GotoPos(self.ctrl.GetSelectionStart())
  5622. def GetLineStartPos(self, line):
  5623. return self.ctrl.PositionFromLine(line)
  5624. #if line == 0:
  5625. # return 0
  5626. #return self.ctrl.GetLineIndentPosition(line) - \
  5627. # self.ctrl.GetLineIndentation(line)
  5628. def GotoLineStart(self):
  5629. self.ctrl.Home()
  5630. self.SetLineColumnPos()
  5631. def GotoLineEnd(self, true_end=True):
  5632. line, pos = self.ctrl.GetCurLine()
  5633. if line == self.ctrl.GetEOLChar():
  5634. return
  5635. self.ctrl.LineEnd()
  5636. if not true_end:
  5637. self.ctrl.CharLeft()
  5638. def GotoLineIndentPreviousLine(self):
  5639. line = max(0, self.ctrl.GetCurrentLine()-1)
  5640. self.GotoLineIndent(line)
  5641. def GotoLineIndentNextLine(self):
  5642. line = min(self.ctrl.GetLineCount(), self.ctrl.GetCurrentLine()+1)
  5643. self.GotoLineIndent(line)
  5644. def GotoLineIndent(self, line=None):
  5645. """
  5646. Moves caret to first non-whitespace character on "line".
  5647. If "line" is None current line is used.
  5648. @param line: Line number
  5649. """
  5650. if line is None: line = self.ctrl.GetCurrentLine()
  5651. self.ctrl.GotoPos(self.ctrl.GetLineIndentPosition(line))
  5652. self.SetLineColumnPos()
  5653. def GotoColumn(self, pos=None, save_position=True):
  5654. """
  5655. Moves caret to "pos" on current line. If no pos specified use "count".
  5656. @param pos: Column position to move caret to.
  5657. """
  5658. if pos is None: pos = self.count
  5659. line = self.ctrl.GetCurrentLine()
  5660. line_text = self.ctrl.GetLine(line)
  5661. if len(line_text) < 2:
  5662. return
  5663. lstart = self.ctrl.PositionFromLine(line)
  5664. # Use CharRight for correct handling of unicode chars
  5665. self.MoveCaretPos(pos, allow_last_char=False, save_column_pos=False)
  5666. #lend = self.ctrl.GetLineEndPosition(line)
  5667. #line_len = lend - lstart
  5668. #column = min(line_len, pos)
  5669. #self.ctrl.GotoPos(lstart + column)
  5670. if save_position:
  5671. self.SetLineColumnPos()
  5672. def GotoSentenceStart(self, count=None, save_jump_pos=False):
  5673. if save_jump_pos:
  5674. self.AddJumpPosition()
  5675. self.Repeat(self._MoveCaretSentenceStart, count)
  5676. def _MoveCaretSentenceStart(self, pos=None, start_pos=None):
  5677. """
  5678. Internal function to move caret to sentence start.
  5679. Call GotoSentenceStart instead.
  5680. """
  5681. if pos is None:
  5682. pos = self.ctrl.GetCurrentPos()-1
  5683. if start_pos is None:
  5684. start_pos = pos
  5685. char = self.GetUnichrAt(pos)
  5686. page_length = self.ctrl.GetLength()
  5687. text = self.ctrl.GetTextRaw()[:pos]
  5688. n = -1
  5689. for i in self.SENTENCE_ENDINGS:
  5690. index = text.rfind(i)
  5691. if index != -1 and index > n:
  5692. n = index
  5693. pos = n
  5694. if pos < 1:
  5695. self.ctrl.GotoPos(0)
  5696. return
  5697. sentence_end_pos = pos
  5698. forward_char = self.GetUnichrAt(pos+1)
  5699. if forward_char in self.SENTENCE_ENDINGS_SUFFIXS:
  5700. pos += 1
  5701. while forward_char in self.SENTENCE_ENDINGS_SUFFIXS:
  5702. pos += 1
  5703. forward_char = self.GetUnichrAt(pos)
  5704. if pos == page_length:
  5705. break
  5706. if forward_char in string.whitespace:
  5707. while forward_char in string.whitespace:
  5708. pos += 1
  5709. forward_char = self.GetUnichrAt(pos)
  5710. if pos == page_length:
  5711. break
  5712. else:
  5713. self._MoveCaretSentenceStart(pos-1, start_pos)
  5714. return
  5715. if start_pos >= pos:
  5716. self.ctrl.GotoPos(pos)
  5717. else:
  5718. self._MoveCaretSentenceStart(sentence_end_pos-1, start_pos)
  5719. def GotoNextSentence(self, count=None, save_jump_pos=False):
  5720. if save_jump_pos:
  5721. self.AddJumpPosition()
  5722. self.Repeat(self._MoveCaretNextSentence, count)
  5723. def GotoSentenceEnd(self, count=None, save_jump_pos=False):
  5724. if save_jump_pos:
  5725. self.AddJumpPosition()
  5726. self.Repeat(self._MoveCaretNextSentence, count, False)
  5727. def GotoSentenceEndCountWhitespace(self, count=None):
  5728. if count is None: count = self.count
  5729. if count % 2:
  5730. include_whitespace = False
  5731. count = count / 2 + 1
  5732. move_left = False
  5733. else:
  5734. include_whitespace = True
  5735. count = count / 2
  5736. move_left = True
  5737. self.Repeat(self._MoveCaretNextSentence, count, include_whitespace)
  5738. if move_left:
  5739. self.ctrl.CharLeftExtend()
  5740. #self.ctrl.CharLeftExtend()
  5741. def _MoveCaretNextSentence(self, include_whitespace=True,
  5742. pos=None, start_pos=None):
  5743. # Could be combined with _MoveCaretBySentence func
  5744. if pos is None:
  5745. pos = self.ctrl.GetCurrentPos()+1
  5746. if start_pos is None:
  5747. start_pos = pos
  5748. char = self.GetUnichrAt(pos)
  5749. page_length = self.ctrl.GetLength()
  5750. text = self.ctrl.GetTextRaw()[pos:]
  5751. n = page_length
  5752. for i in self.SENTENCE_ENDINGS:
  5753. index = text.find(i)
  5754. if index != -1 and index < n:
  5755. n = index
  5756. pos = pos + n
  5757. if pos+1 >= page_length:
  5758. self.ctrl.GotoPos(page_length)
  5759. return
  5760. sentence_end_pos = pos
  5761. forward_char = self.GetUnichrAt(pos+1)
  5762. if forward_char in self.SENTENCE_ENDINGS_SUFFIXS:
  5763. pos += 1
  5764. while forward_char in self.SENTENCE_ENDINGS_SUFFIXS:
  5765. pos += 1
  5766. forward_char = self.GetUnichrAt(pos)
  5767. if pos == page_length:
  5768. break
  5769. sentence_end_pos = pos-1
  5770. if forward_char in string.whitespace:
  5771. while forward_char in string.whitespace:
  5772. pos += 1
  5773. forward_char = self.GetUnichrAt(pos)
  5774. if pos == page_length:
  5775. break
  5776. else:
  5777. self._MoveCaretNextSentence(include_whitespace, pos+1, start_pos)
  5778. return
  5779. if start_pos <= pos:
  5780. if include_whitespace:
  5781. self.ctrl.GotoPos(pos)
  5782. else:
  5783. self.ctrl.GotoPos(sentence_end_pos)
  5784. else:
  5785. self._MoveCaretNextSentence(include_whitespace,
  5786. sentence_end_pos, start_pos)
  5787. def MoveCaretRight(self, allow_last_char=True):
  5788. self.MoveCaretPos(self.count, allow_last_char=allow_last_char)
  5789. def MoveCaretVertically(self, count):
  5790. line_no = self.ctrl.GetCurrentLine()
  5791. self.ctrl.GotoPos(self.ctrl.PositionFromLine(line_no + count))
  5792. self.GotoColumn(self.GetLineColumnPos(), save_position=False)
  5793. def MoveCaretUp(self, count=None, visual=False):
  5794. if count is None: count = self.count
  5795. if visual:
  5796. self.Repeat(self.ctrl.LineUp, count)
  5797. self.CheckLineEnd()
  5798. #self.SetLineColumnPos()
  5799. else:
  5800. self.MoveCaretVertically(-count)
  5801. def MoveCaretDown(self, count=None, visual=False):
  5802. """
  5803. Moves caret down
  5804. @param visual: If False ignore linewrap
  5805. """
  5806. if count is None: count = self.count
  5807. if visual:
  5808. self.Repeat(self.ctrl.LineDown, count)
  5809. self.CheckLineEnd()
  5810. #self.SetLineColumnPos()
  5811. else:
  5812. self.MoveCaretVertically(count)
  5813. def MoveCaretToLine(self, line):
  5814. current_line = self.ctrl.GetCurrentLine()
  5815. if line == current_line:
  5816. return
  5817. if line < current_line:
  5818. scroll_func = self.ctrl.LineUp
  5819. else:
  5820. scroll_func = self.ctrl.LineDown
  5821. to_move = abs(current_line - line)
  5822. for i in range(to_move):
  5823. scroll_func()
  5824. def MoveCaretDownAndIndent(self, count=None):
  5825. #self.Repeat(self.ctrl.LineDown, count)
  5826. self.MoveCaretDown()
  5827. self.GotoLineIndent()
  5828. def MoveCaretLeft(self):
  5829. self.MoveCaretPos(-self.count)
  5830. def MoveCaretPos(self, offset, allow_last_char=False, save_column_pos=True):
  5831. """
  5832. Move caret by a given offset along the current line.
  5833. """
  5834. line, line_pos = self.ctrl.GetCurLine()
  5835. line_no = self.ctrl.GetCurrentLine()
  5836. # Last line doesn't have an EOL char which the code below requires
  5837. if self.OnLastLine():
  5838. line = line + self.ctrl.GetEOLChar()
  5839. end_offset = 1
  5840. if not allow_last_char and len(line) > 2:
  5841. end_offset = end_offset + len(bytes(line[-2]))
  5842. if offset > 0:
  5843. if line_pos + end_offset == len(bytes(line)):
  5844. return
  5845. bytes_to_move = len(bytes(
  5846. unicode(bytes(line)[line_pos:-end_offset])[:offset]))
  5847. elif offset < 0:
  5848. if line_pos == 0:
  5849. return
  5850. bytes_to_move = -len(bytes(unicode(bytes(line)[:line_pos])[offset:]))
  5851. else:
  5852. # Offset is 0, do nothing
  5853. return
  5854. self.ctrl.GotoPos(self.ctrl.GetCurrentPos() + bytes_to_move)
  5855. if save_column_pos:
  5856. self.SetLineColumnPos()
  5857. return
  5858. # Code below is left as a reminder that trying to improve this function
  5859. # is probably more trouble than its worth.
  5860. #
  5861. # pos = self.ctrl.GetCurrentPos()
  5862. #
  5863. # if offset > 0:
  5864. #
  5865. # end_offset = 0
  5866. # if allow_last_char and len(line) > 2:
  5867. # end_offset = len(bytes(line[-2]))
  5868. #
  5869. # line_end = self.ctrl.GetLineEndPosition(line_no)
  5870. #
  5871. # if pos + offset >= line_end + end_offset:
  5872. # self.ctrl.GotoPos(line_end - 1)
  5873. # else:
  5874. # self.ctrl.GotoPos(pos + offset + end_offset)
  5875. #
  5876. # else:
  5877. # line_start = self.ctrl.PositionFromLine(line_no)
  5878. #
  5879. # if pos + offset < line_start:
  5880. # self.ctrl.GotoPos(line_start)
  5881. # else:
  5882. # self.ctrl.GotoPos(pos + offset)
  5883. #
  5884. # if save_column_pos:
  5885. # self.SetLineColumnPos()
  5886. #
  5887. # return
  5888. #
  5889. #
  5890. # # The code below works but is slower than the above implementation (i think)
  5891. #
  5892. # # TODO: Speedup
  5893. # line, line_pos = self.ctrl.GetCurLine()
  5894. # line_no = self.ctrl.GetCurrentLine()
  5895. #
  5896. # if self.mode == ViHelper.VISUAL:
  5897. # if offset > 0:
  5898. # move_right = True
  5899. # move = self.ctrl.CharRightExtend
  5900. # stop_pos = self.GetLineStartPos(line_no) + \
  5901. # self.ctrl.LineLength(line_no)-1
  5902. # else:
  5903. # move_right = False
  5904. # move = self.ctrl.CharLeftExtend
  5905. # stop_pos = self.GetLineStartPos(line_no)
  5906. # else:
  5907. # if offset > 0:
  5908. # move_right = True
  5909. # move = self.ctrl.CharRight
  5910. # stop_pos = self.GetLineStartPos(line_no) + \
  5911. # self.ctrl.LineLength(line_no)-2
  5912. #
  5913. # # Fix for last line (no EOL char present)
  5914. # if line_no+1 == self.ctrl.GetLineCount():
  5915. # stop_pos += 1
  5916. # else:
  5917. # move_right = False
  5918. # move = self.ctrl.CharLeft
  5919. # stop_pos = self.GetLineStartPos(line_no)
  5920. #
  5921. # if allow_last_char:
  5922. # stop_pos += 1
  5923. #
  5924. # for i in range(abs(offset)):
  5925. # if (move_right and self.ctrl.GetCurrentPos() < stop_pos) or \
  5926. # (not move_right and self.ctrl.GetCurrentPos() > stop_pos):
  5927. # move()
  5928. # else:
  5929. # break
  5930. #
  5931. # if save_column_pos:
  5932. # self.SetLineColumnPos()
  5933. #
  5934. # ## The code below is faster but does not handle
  5935. # ## unicode charcters nicely
  5936. # #line, line_pos = self.ctrl.GetCurLine()
  5937. # #line_no = self.ctrl.GetCurrentLine()
  5938. # #pos = max(line_pos + offset, 0)
  5939. # #if self.mode == ViHelper.VISUAL:
  5940. # # pos = min(pos, self.ctrl.LineLength(line_no)-1)
  5941. # #else:
  5942. # # pos = min(pos, self.ctrl.LineLength(line_no)-2)
  5943. # #self.ctrl.GotoPos(self.GetLineStartPos(line_no) + pos)
  5944. # #self.SetLineColumnPos()
  5945. def MoveCaretLinePos(self, offset):
  5946. """
  5947. Move caret line position by a given offset
  5948. Faster but does not maintain line position
  5949. """
  5950. self.SetLineColumnPos()
  5951. line = max(self.ctrl.GetCurrentLine() + offset, 0)
  5952. line = min(line, self.ctrl.GetLineCount())
  5953. self.ctrl.GotoLine(line)
  5954. line_start_pos = self.ctrl.GetCurrentPos()
  5955. pos = max(index, 0)
  5956. pos = min(pos, self.ctrl.GetLineEndPosition(line)-line_start_pos)
  5957. self.ctrl.GotoPos(line_start_pos+pos)
  5958. #self.SetLineColumnPos()
  5959. def MoveCaretToLinePos(self, line, index):
  5960. line = max(line, 0)
  5961. line = min(line, self.ctrl.GetLineCount())
  5962. self.ctrl.GotoLine(line)
  5963. line_start_pos = self.ctrl.GetCurrentPos()
  5964. pos = max(index, 0)
  5965. pos = min(pos, self.ctrl.GetLineEndPosition(line)-line_start_pos)
  5966. self.ctrl.GotoPos(line_start_pos+pos)
  5967. # word-motions
  5968. def MoveCaretWordEndCountWhitespace(self, count=None):
  5969. self.Repeat(self._MoveCaretWord, count,
  5970. { "recursion" : False, "count_whitespace" : True, \
  5971. "only_whitespace" : False })
  5972. def MoveCaretNextWord(self, count=None):
  5973. # TODO: should probably use _MoveCaretWord
  5974. self.Repeat(self.ctrl.WordRight, count)
  5975. self.SetLineColumnPos()
  5976. def MoveCaretPreviousWordEnd(self, count=None):
  5977. # TODO: complete
  5978. self.Repeat(self._MoveCaretWord, count, {
  5979. "recursion" : False,
  5980. "count_whitespace" : False,
  5981. "only_whitespace" : False,
  5982. "reverse" : True
  5983. })
  5984. def MoveCaretWordEnd(self, count=None):
  5985. self.Repeat(self._MoveCaretWord, count)
  5986. def _MoveCaretWord(self, recursion=False, count_whitespace=False,
  5987. only_whitespace=False, reverse=False):
  5988. """
  5989. wxStyledTextCtrl's WordEnd function behaves differently to
  5990. vims so it need to be replaced to get equivalent function
  5991. """
  5992. pos = start_pos = self.ctrl.GetCurrentPos()
  5993. char = self.GetUnichrAt(pos)
  5994. # At the end of the file
  5995. if char is None:
  5996. self.ctrl.CharLeft()
  5997. self.SetLineColumnPos()
  5998. return
  5999. text_length = self.ctrl.GetTextLength()
  6000. if reverse:
  6001. offset = -1
  6002. move = self.ctrl.CharLeft
  6003. move_extend = self.ctrl.CharLeftExtend
  6004. else:
  6005. offset = 1
  6006. move = self.ctrl.CharRight
  6007. move_extend = self.ctrl.CharRightExtend
  6008. # If the current char is whitespace we either skip it or count
  6009. # it depending on "count_whitespace"
  6010. if char in string.whitespace:
  6011. char = self.GetUnichrAt(pos + offset)
  6012. if char is not None:
  6013. while char is not None and char in string.whitespace:
  6014. pos = pos + offset
  6015. char = self.GetUnichrAt(pos + offset)
  6016. if not count_whitespace:
  6017. self.GotoPosAndSave(pos)
  6018. move()
  6019. self._MoveCaretWord(recursion=True,
  6020. count_whitespace=count_whitespace,
  6021. only_whitespace=only_whitespace,
  6022. reverse=reverse)
  6023. return
  6024. # If we want a minor word end and start in punctuation we goto
  6025. # end of the punctuation
  6026. elif not only_whitespace and char in self.WORD_BREAK:
  6027. char = self.GetUnichrAt(pos + offset)
  6028. if char is not None:
  6029. while char is not None and char in self.WORD_BREAK:
  6030. pos = pos + offset
  6031. char = self.GetUnichrAt(pos + offset)
  6032. # Else offset forwards to first punctuation or whitespace char
  6033. # (or just whitespace char if only_whitespace = True)
  6034. else:
  6035. char = self.GetUnichrAt(pos + offset)
  6036. if char is not None:
  6037. while char is not None and \
  6038. (((only_whitespace or char not in self.WORD_BREAK) and \
  6039. char not in string.whitespace) \
  6040. or char in ("_")) \
  6041. and pos <= text_length:
  6042. pos = pos + offset
  6043. char = self.GetUnichrAt(pos + offset)
  6044. # We need to correct the position if using a unicode character
  6045. if len(bytes(char)) > 2:
  6046. pos = self.ctrl.PositionAfter(pos)
  6047. if pos != start_pos or recursion:
  6048. self.GotoPosAndSave(pos)
  6049. else:
  6050. move_extend()
  6051. self._MoveCaretWord(True, count_whitespace=count_whitespace,
  6052. only_whitespace=only_whitespace, reverse=reverse)
  6053. return
  6054. self.SetLineColumnPos()
  6055. def MoveCaretToWhitespaceStart(self):
  6056. start = self.ctrl.GetCurrentPos()
  6057. while start > 0 and \
  6058. self.ctrl.GetCharAt(start-1) in self.SINGLE_LINE_WHITESPACE:
  6059. start -= 1
  6060. self.ctrl.GotoPos(start)
  6061. def MoveCaretBackWord(self, count=None):
  6062. self.Repeat(self._MoveCaretWord, count, {
  6063. "recursion" : False,
  6064. "count_whitespace" : False,
  6065. "only_whitespace" : False,
  6066. "reverse" : True
  6067. })
  6068. def MoveCaretBackWORD(self, count=None):
  6069. self.Repeat(self._MoveCaretWord, count, {
  6070. "recursion" : False,
  6071. "count_whitespace" : False,
  6072. "only_whitespace" : True,
  6073. "reverse" : True
  6074. })
  6075. def MoveCaretNextWORD(self, count=None):
  6076. """Wordbreaks are spaces"""
  6077. def func():
  6078. text_length = self.ctrl.GetLength()
  6079. self.ctrl.WordRight()
  6080. while self.GetChar(-1) and not self.GetChar(-1).isspace():
  6081. if self.ctrl.GetCurrentPos() == text_length:
  6082. return
  6083. self.ctrl.WordRight()
  6084. self.Repeat(func, count)
  6085. self.SetLineColumnPos()
  6086. def MoveCaretWordEND(self, count=None):
  6087. self.Repeat(self._MoveCaretWord, count, {
  6088. "recursion" : False,
  6089. "count_whitespace" : False,
  6090. "only_whitespace" : True
  6091. })
  6092. def MoveCaretWordENDCountWhitespace(self, count=None):
  6093. self.Repeat(self._MoveCaretWord, count, {
  6094. "recursion" : False,
  6095. "count_whitespace" : True,
  6096. "only_whitespace" : True
  6097. })
  6098. def MoveCaretParaUp(self, count=None):
  6099. self.AddJumpPosition()
  6100. self.Repeat(self.ctrl.ParaUp, count)
  6101. self.ctrl.CharLeft()
  6102. def MoveCaretParaDown(self, count=None):
  6103. # TODO: headings as paragraphs?
  6104. self.AddJumpPosition()
  6105. self.ctrl.CharRight()
  6106. self.Repeat(self.ctrl.ParaDown, count)
  6107. self.ctrl.CharLeft()
  6108. def GotoPosAndSave(self, pos):
  6109. """
  6110. Helper for GotoPos. Saves current position.
  6111. """
  6112. self.ctrl.GotoPos(pos)
  6113. self.SetLineColumnPos()
  6114. def GotoVisualLineStart(self):
  6115. """
  6116. Move caret to start of the visual line
  6117. """
  6118. self.ctrl.HomeDisplay()
  6119. def GotoVisualLineEnd(self):
  6120. """
  6121. Move caret to end of the visual line
  6122. """
  6123. self.ctrl.LineEndDisplay()
  6124. def DocumentNavigation(self, key):
  6125. """
  6126. It may be better to seperate this into multiple functions
  6127. """
  6128. k = self.KEY_BINDINGS
  6129. if key in [k["G"], (k["g"], k["g"]), k["%"]]:
  6130. self.AddJumpPosition()
  6131. # %, G or gg
  6132. if self.true_count:
  6133. if key in [k["G"], (k["g"], k["g"])]:
  6134. # Correct for line 0
  6135. self.MoveCaretToLinePos(
  6136. self.count-1, self.ctrl.GetCurLine()[1])
  6137. elif key == k["%"]: # %
  6138. max_lines = self.ctrl.GetLineCount()
  6139. # Same as int(self.count / 100 * max_lines) but needs only
  6140. # integer arithmetic
  6141. line_percentage = (self.count * max_lines) // 100
  6142. self.MoveCaretToLinePos(
  6143. line_percentage, self.ctrl.GetCurLine()[1])
  6144. elif key == k["%"]:
  6145. # If key is % but no count it is used for brace matching
  6146. self.MatchBraceUnderCaret()
  6147. elif key == (k["g"], k["g"]):
  6148. self.ctrl.GotoLine(0)
  6149. elif key == (k["G"]):
  6150. # As with vim "G" goes to the first nonwhitespace of the
  6151. # character on the bottom line
  6152. self.GotoLineIndent(self.ctrl.GetLineCount())
  6153. def GotoViewportTop(self):
  6154. self.GotoLineIndent(self.GetFirstVisibleLine())
  6155. def GotoViewportMiddle(self):
  6156. self.GotoLineIndent(self.GetMiddleVisibleLine())
  6157. def GotoViewportBottom(self):
  6158. self.GotoLineIndent(self.GetLastVisibleLine())
  6159. def ScrollViewportTop(self):
  6160. self._PositionViewport(0)
  6161. def ScrollViewportMiddle(self):
  6162. self._PositionViewport(0.5)
  6163. def ScrollViewportBottom(self):
  6164. self._PositionViewport(1)
  6165. def ScrollViewportUpHalfScreen(self):
  6166. self._ScrollViewport(-0.5)
  6167. def ScrollViewportUpFullScreen(self):
  6168. # vim has a 2 line offset
  6169. # see *03.7*
  6170. self._ScrollViewport(-1)
  6171. def ScrollViewportDownHalfScreen(self):
  6172. self._ScrollViewport(0.5)
  6173. def ScrollViewportDownFullScreen(self):
  6174. self._ScrollViewport(1)
  6175. # def ScrollViewportLineDown(self):
  6176. # self._ScrollViewportByLines(1)
  6177. #
  6178. # def ScrollViewportLineUp(self):
  6179. # self._ScrollViewportByLines(-1)
  6180. # TODO: FIX UNDO / REDO
  6181. def CanUndo(self):
  6182. return not self._undo_pos < 0
  6183. def CanRedo(self):
  6184. return not self._undo_pos > len(self._undo_positions)
  6185. def Undo(self, count=None):
  6186. if self.CanUndo():
  6187. self.visualBell("GREEN")
  6188. self.Repeat(self._Undo, count)
  6189. else:
  6190. self.visualBell("RED")
  6191. def Redo(self, count=None):
  6192. if self.CanRedo():
  6193. self.visualBell("GREEN")
  6194. self.Repeat(self._Redo, count)
  6195. else:
  6196. self.visualBell("RED")
  6197. # The following commands are basic ways to enter insert mode
  6198. def Insert(self):
  6199. self.BeginUndo(use_start_pos=True)
  6200. self.SetMode(ViHelper.INSERT)
  6201. def Append(self):
  6202. if self.ctrl.GetCurrentPos() != self.ctrl.GetLineEndPosition(
  6203. self.ctrl.GetCurrentLine()):
  6204. self.ctrl.CharRight()
  6205. self.Insert()
  6206. def InsertAtLineStart(self):
  6207. # Goto line places the caret at the start of the line
  6208. self.GotoLineIndent(self.ctrl.GetCurrentLine())
  6209. self.SetLineColumnPos()
  6210. self.Insert()
  6211. def AppendAtLineEnd(self):
  6212. self.ctrl.GotoPos(self.ctrl.GetLineEndPosition(
  6213. self.ctrl.GetCurrentLine()))
  6214. self.Append()
  6215. def OpenNewLine(self, above, indent=None):
  6216. self.BeginUndo(True)
  6217. # Set to True if opening a line above the first line.
  6218. create_first_line = False
  6219. if indent is None:
  6220. indent = self.ctrl.GetLineIndentation(self.ctrl.GetCurrentLine())
  6221. # This code is independent of the wikidpad syntax used
  6222. line_prefix = False
  6223. line_text = self.ctrl.GetCurLine()[0].strip()
  6224. if line_text.startswith(u"* "):
  6225. line_prefix = u"* "
  6226. if above:
  6227. if self.ctrl.GetCurrentLine() == 0:
  6228. create_first_line = True
  6229. self.MoveCaretUp(1)
  6230. if create_first_line:
  6231. self.GotoLineStart()
  6232. else:
  6233. self.GotoLineEnd()
  6234. self.ctrl.AddText(self.ctrl.GetEOLChar())
  6235. if line_prefix:
  6236. self.ctrl.AddText(line_prefix)
  6237. if create_first_line:
  6238. self.MoveCaretUp(1)
  6239. self.ctrl.SetLineIndentation(self.ctrl.GetCurrentLine(), indent)
  6240. self.AppendAtLineEnd()
  6241. self.EndUndo()
  6242. def TruncateLine(self, check_line_end=True):
  6243. text, pos = self.ctrl.GetCurLine()
  6244. # If line is empty do nothing (blank line has eol char)
  6245. if len(text) < 2:
  6246. return
  6247. self.ctrl.LineEndExtend()
  6248. self.ctrl.CharLeftExtend()
  6249. self.ctrl.CharRightExtend()
  6250. #self.ExtendSelectionIfRequired()
  6251. self.DeleteSelection()
  6252. ## replace with function
  6253. #if self.mode == ViHelper.INSERT:
  6254. if check_line_end:
  6255. self.CheckLineEnd()
  6256. def TruncateLineAndInsert(self):
  6257. self.TruncateLine(check_line_end=False)
  6258. self.Insert()
  6259. def GetLinkAtCaret(self, link_type=("rel://", "abs://"), extensions=""):
  6260. """
  6261. Helper for checking if the caret is currently within a link
  6262. """
  6263. pos = self.ctrl.GetCurrentPos()
  6264. # First check if we are working with a link
  6265. self.SelectInWORD()
  6266. self.ctrl.CharRightExtend()
  6267. link = self.ctrl.GetSelectedText()
  6268. self.RemoveSelection(pos)
  6269. if link.startswith(link_type) and link.endswith(extensions):
  6270. return link
  6271. return None