codekingpro/portable-devtools
114k
1#####################################################################2#3# editor.py4#5# A general purpose text editor, built on top of the win32ui edit6# type, which is built on an MFC CEditView7#8#9# We now support reloading of externally modified documented10# (eg, presumably by some other process, such as source control or11# another editor.12# We also suport auto-loading of externally modified files.13# - if the current document has not been modified in this14# editor, but has been modified on disk, then the file15# can be automatically reloaded.16#17# Note that it will _always_ prompt you if the file in the editor has been modified.18 19 20import re21 22import regex23import win32api24import win32con25import win32ui26from pywin.framework.editor import (27 GetEditorFontOption,28 GetEditorOption,29 SetEditorFontOption,30 SetEditorOption,31 defaultCharacterFormat,32)33from pywin.mfc import afxres, dialog, docview34 35patImport = regex.symcomp("import \(<name>.*\)")36patIndent = regex.compile("^\\([ \t]*[~ \t]\\)")37 38ID_LOCATE_FILE = 0xE20039ID_GOTO_LINE = 0xE200140MSG_CHECK_EXTERNAL_FILE = (41 win32con.WM_USER + 199942) ## WARNING: Duplicated in document.py and coloreditor.py43 44# Key Codes that modify the bufffer when Ctrl or Alt are NOT pressed.45MODIFYING_VK_KEYS = [46 win32con.VK_BACK,47 win32con.VK_TAB,48 win32con.VK_RETURN,49 win32con.VK_SPACE,50 win32con.VK_DELETE,51]52for k in range(48, 91):53 MODIFYING_VK_KEYS.append(k)54 55# Key Codes that modify the bufffer when Ctrl is pressed.56MODIFYING_VK_KEYS_CTRL = [57 win32con.VK_BACK,58 win32con.VK_RETURN,59 win32con.VK_SPACE,60 win32con.VK_DELETE,61]62 63# Key Codes that modify the bufffer when Alt is pressed.64MODIFYING_VK_KEYS_ALT = [65 win32con.VK_BACK,66 win32con.VK_RETURN,67 win32con.VK_SPACE,68 win32con.VK_DELETE,69]70 71 72# The editor itself starts here.73# Using the MFC Document/View model, we have an EditorDocument, which is responsible for74# managing the contents of the file, and a view which is responsible for rendering it.75#76# Due to a limitation in the Windows edit controls, we are limited to one view77# per document, although nothing in this code assumes this (I hope!)78 79isRichText = 1 # We are using the Rich Text control. This has not been tested with value "0" for quite some time!80 81# ParentEditorDocument=docview.Document82from .document import EditorDocumentBase83 84ParentEditorDocument = EditorDocumentBase85 86 87class EditorDocument(ParentEditorDocument):88 #89 # File loading and saving operations90 #91 def OnOpenDocument(self, filename):92 #93 # handle Unix and PC text file format.94 #95 96 # Get the "long name" of the file name, as it may have been translated97 # to short names by the shell.98 self.SetPathName(filename) # Must set this early!99 # Now do the work!100 self.BeginWaitCursor()101 win32ui.SetStatusText("Loading file...", 1)102 try:103 f = open(filename, "rb")104 except IOError:105 win32ui.MessageBox(106 filename107 + "\nCan not find this file\nPlease verify that the correct path and file name are given"108 )109 self.EndWaitCursor()110 return 0111 raw = f.read()112 f.close()113 contents = self.TranslateLoadedData(raw)114 rc = 0115 try:116 self.GetFirstView().SetWindowText(contents)117 rc = 1118 except TypeError: # Null byte in file.119 win32ui.MessageBox("This file contains NULL bytes, and can not be edited")120 rc = 0121 122 self.EndWaitCursor()123 self.SetModifiedFlag(0) # No longer dirty124 self._DocumentStateChanged()125 return rc126 127 def TranslateLoadedData(self, data):128 """Given raw data read from a file, massage it suitable for the edit window"""129 # if a CR in the first 250 chars, then perform the expensive translate130 if data[:250].find("\r") == -1:131 win32ui.SetStatusText(132 "Translating from Unix file format - please wait...", 1133 )134 return re.sub("\r*\n", "\r\n", data)135 else:136 return data137 138 def SaveFile(self, fileName, encoding=None):139 if isRichText:140 view = self.GetFirstView()141 view.SaveTextFile(fileName, encoding=encoding)142 else: # Old style edit view window.143 self.GetFirstView().SaveFile(fileName)144 try:145 # Make sure line cache has updated info about me!146 import linecache147 148 linecache.checkcache()149 except:150 pass151 152 #153 # Color state stuff154 #155 def SetAllLineColors(self, color=None):156 for view in self.GetAllViews():157 view.SetAllLineColors(color)158 159 def SetLineColor(self, lineNo, color):160 "Color a line of all views"161 for view in self.GetAllViews():162 view.SetLineColor(lineNo, color)163 164 165# def StreamTextOut(self, data): ### This seems unreliable???166# self.saveFileHandle.write(data)167# return 1 # keep em coming!168 169# ParentEditorView=docview.EditView170ParentEditorView = docview.RichEditView171 172 173class EditorView(ParentEditorView):174 def __init__(self, doc):175 ParentEditorView.__init__(self, doc)176 if isRichText:177 self.SetWordWrap(win32ui.CRichEditView_WrapNone)178 179 self.addToMRU = 1180 self.HookHandlers()181 self.bCheckingFile = 0182 183 self.defCharFormat = GetEditorFontOption("Default Font", defaultCharacterFormat)184 185 # Smart tabs override everything else if context can be worked out.186 self.bSmartTabs = GetEditorOption("Smart Tabs", 1)187 188 self.tabSize = GetEditorOption("Tab Size", 8)189 self.indentSize = GetEditorOption("Indent Size", 8)190 # If next indent is at a tab position, and useTabs is set, a tab will be inserted.191 self.bUseTabs = GetEditorOption("Use Tabs", 1)192 193 def OnInitialUpdate(self):194 rc = self._obj_.OnInitialUpdate()195 self.SetDefaultCharFormat(self.defCharFormat)196 return rc197 198 def CutCurLine(self):199 curLine = self._obj_.LineFromChar()200 nextLine = curLine + 1201 start = self._obj_.LineIndex(curLine)202 end = self._obj_.LineIndex(nextLine)203 if end == 0: # must be last line.204 end = start + self.end.GetLineLength(curLine)205 self._obj_.SetSel(start, end)206 self._obj_.Cut()207 208 def _PrepareUserStateChange(self):209 "Return selection, lineindex, etc info, so it can be restored"210 self.SetRedraw(0)211 return self.GetModify(), self.GetSel(), self.GetFirstVisibleLine()212 213 def _EndUserStateChange(self, info):214 scrollOff = info[2] - self.GetFirstVisibleLine()215 if scrollOff:216 self.LineScroll(scrollOff)217 self.SetSel(info[1])218 self.SetModify(info[0])219 self.SetRedraw(1)220 self.InvalidateRect()221 self.UpdateWindow()222 223 def _UpdateUIForState(self):224 self.SetReadOnly(self.GetDocument()._IsReadOnly())225 226 def SetAllLineColors(self, color=None):227 if isRichText:228 info = self._PrepareUserStateChange()229 try:230 if color is None:231 color = self.defCharFormat[4]232 self.SetSel(0, -1)233 self.SetSelectionCharFormat((win32con.CFM_COLOR, 0, 0, 0, color))234 finally:235 self._EndUserStateChange(info)236 237 def SetLineColor(self, lineNo, color):238 "lineNo is the 1 based line number to set. If color is None, default color is used."239 if isRichText:240 info = self._PrepareUserStateChange()241 try:242 if color is None:243 color = self.defCharFormat[4]244 lineNo = lineNo - 1245 startIndex = self.LineIndex(lineNo)246 if startIndex != -1:247 self.SetSel(startIndex, self.LineIndex(lineNo + 1))248 self.SetSelectionCharFormat((win32con.CFM_COLOR, 0, 0, 0, color))249 finally:250 self._EndUserStateChange(info)251 252 def Indent(self):253 """Insert an indent to move the cursor to the next tab position.254 255 Honors the tab size and 'use tabs' settings. Assumes the cursor is already at the256 position to be indented, and the selection is a single character (ie, not a block)257 """258 start, end = self._obj_.GetSel()259 startLine = self._obj_.LineFromChar(start)260 line = self._obj_.GetLine(startLine)261 realCol = start - self._obj_.LineIndex(startLine)262 # Calulate the next tab stop.263 # Expand existing tabs.264 curCol = 0265 for ch in line[:realCol]:266 if ch == "\t":267 curCol = ((curCol / self.tabSize) + 1) * self.tabSize268 else:269 curCol = curCol + 1270 nextColumn = ((curCol / self.indentSize) + 1) * self.indentSize271 # print "curCol is", curCol, "nextColumn is", nextColumn272 ins = None273 if self.bSmartTabs:274 # Look for some context.275 if realCol == 0: # Start of the line - see if the line above can tell us276 lookLine = startLine - 1277 while lookLine >= 0:278 check = self._obj_.GetLine(lookLine)[0:1]279 if check in ("\t", " "):280 ins = check281 break282 lookLine = lookLine - 1283 else: # See if the previous char can tell us284 check = line[realCol - 1]285 if check in ("\t", " "):286 ins = check287 288 # Either smart tabs off, or not smart enough!289 # Use the "old style" settings.290 if ins is None:291 if self.bUseTabs and nextColumn % self.tabSize == 0:292 ins = "\t"293 else:294 ins = " "295 296 if ins == " ":297 # Calc the number of spaces to take us to the next stop298 ins = ins * (nextColumn - curCol)299 300 self._obj_.ReplaceSel(ins)301 302 def BlockDent(self, isIndent, startLine, endLine):303 "Indent/Undent all lines specified"304 if not self.GetDocument().CheckMakeDocumentWritable():305 return 0306 tabSize = self.tabSize # hard-code for now!307 info = self._PrepareUserStateChange()308 try:309 for lineNo in range(startLine, endLine):310 pos = self._obj_.LineIndex(lineNo)311 self._obj_.SetSel(pos, pos)312 if isIndent:313 self.Indent()314 else:315 line = self._obj_.GetLine(lineNo)316 try:317 noToDel = 0318 if line[0] == "\t":319 noToDel = 1320 elif line[0] == " ":321 for noToDel in range(0, tabSize):322 if line[noToDel] != " ":323 break324 else:325 noToDel = tabSize326 if noToDel:327 self._obj_.SetSel(pos, pos + noToDel)328 self._obj_.Clear()329 except IndexError:330 pass331 finally:332 self._EndUserStateChange(info)333 self.GetDocument().SetModifiedFlag(1) # Now dirty334 self._obj_.SetSel(self.LineIndex(startLine), self.LineIndex(endLine))335 336 def GotoLine(self, lineNo=None):337 try:338 if lineNo is None:339 lineNo = int(input("Enter Line Number"))340 except (ValueError, KeyboardInterrupt):341 return 0342 self.GetLineCount() # Seems to be needed when file first opened???343 charNo = self.LineIndex(lineNo - 1)344 self.SetSel(charNo)345 346 def HookHandlers(self): # children can override, but should still call me!347 # self.HookAllKeyStrokes(self.OnKey)348 self.HookMessage(self.OnCheckExternalDocumentUpdated, MSG_CHECK_EXTERNAL_FILE)349 self.HookMessage(self.OnRClick, win32con.WM_RBUTTONDOWN)350 self.HookMessage(self.OnSetFocus, win32con.WM_SETFOCUS)351 self.HookMessage(self.OnKeyDown, win32con.WM_KEYDOWN)352 self.HookKeyStroke(self.OnKeyCtrlY, 25) # ^Y353 self.HookKeyStroke(self.OnKeyCtrlG, 7) # ^G354 self.HookKeyStroke(self.OnKeyTab, 9) # TAB355 self.HookKeyStroke(self.OnKeyEnter, 13) # Enter356 self.HookCommand(self.OnCmdLocateFile, ID_LOCATE_FILE)357 self.HookCommand(self.OnCmdGotoLine, ID_GOTO_LINE)358 self.HookCommand(self.OnEditPaste, afxres.ID_EDIT_PASTE)359 self.HookCommand(self.OnEditCut, afxres.ID_EDIT_CUT)360 361 # Hook Handlers362 def OnSetFocus(self, msg):363 # Even though we use file change notifications, we should be very sure about it here.364 self.OnCheckExternalDocumentUpdated(msg)365 366 def OnRClick(self, params):367 menu = win32ui.CreatePopupMenu()368 369 # look for a module name370 line = self._obj_.GetLine().strip()371 flags = win32con.MF_STRING | win32con.MF_ENABLED372 if patImport.match(line) == len(line):373 menu.AppendMenu(374 flags, ID_LOCATE_FILE, "&Locate %s.py" % patImport.group("name")375 )376 menu.AppendMenu(win32con.MF_SEPARATOR)377 menu.AppendMenu(flags, win32ui.ID_EDIT_UNDO, "&Undo")378 menu.AppendMenu(win32con.MF_SEPARATOR)379 menu.AppendMenu(flags, win32ui.ID_EDIT_CUT, "Cu&t")380 menu.AppendMenu(flags, win32ui.ID_EDIT_COPY, "&Copy")381 menu.AppendMenu(flags, win32ui.ID_EDIT_PASTE, "&Paste")382 menu.AppendMenu(flags, win32con.MF_SEPARATOR)383 menu.AppendMenu(flags, win32ui.ID_EDIT_SELECT_ALL, "&Select all")384 menu.AppendMenu(flags, win32con.MF_SEPARATOR)385 menu.AppendMenu(flags, ID_GOTO_LINE, "&Goto line...")386 menu.TrackPopupMenu(params[5])387 return 0388 389 def OnCmdGotoLine(self, cmd, code):390 self.GotoLine()391 return 0392 393 def OnCmdLocateFile(self, cmd, code):394 modName = patImport.group("name")395 if not modName:396 return 0397 import pywin.framework.scriptutils398 399 fileName = pywin.framework.scriptutils.LocatePythonFile(modName)400 if fileName is None:401 win32ui.SetStatusText("Can't locate module %s" % modName)402 else:403 win32ui.GetApp().OpenDocumentFile(fileName)404 return 0405 406 # Key handlers407 def OnKeyEnter(self, key):408 if not self.GetDocument().CheckMakeDocumentWritable():409 return 0410 curLine = self._obj_.GetLine()411 self._obj_.ReplaceSel("\r\n") # insert the newline412 # If the current line indicates the next should be indented,413 # then copy the current indentation to this line.414 res = patIndent.match(curLine, 0)415 if res > 0 and curLine.strip():416 curIndent = patIndent.group(1)417 self._obj_.ReplaceSel(curIndent)418 return 0 # dont pass on419 420 def OnKeyCtrlY(self, key):421 if not self.GetDocument().CheckMakeDocumentWritable():422 return 0423 self.CutCurLine()424 return 0 # dont let him have it!425 426 def OnKeyCtrlG(self, key):427 self.GotoLine()428 return 0 # dont let him have it!429 430 def OnKeyTab(self, key):431 if not self.GetDocument().CheckMakeDocumentWritable():432 return 0433 start, end = self._obj_.GetSel()434 if start == end: # normal TAB key435 self.Indent()436 return 0 # we handled this.437 438 # Otherwise it is a block indent/dedent.439 if start > end:440 start, end = end, start # swap them.441 startLine = self._obj_.LineFromChar(start)442 endLine = self._obj_.LineFromChar(end)443 444 self.BlockDent(win32api.GetKeyState(win32con.VK_SHIFT) >= 0, startLine, endLine)445 return 0446 447 def OnEditPaste(self, id, code):448 # Return 1 if we can make the file editable.(or it already is!)449 return self.GetDocument().CheckMakeDocumentWritable()450 451 def OnEditCut(self, id, code):452 # Return 1 if we can make the file editable.(or it already is!)453 return self.GetDocument().CheckMakeDocumentWritable()454 455 def OnKeyDown(self, msg):456 key = msg[2]457 if win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000:458 modList = MODIFYING_VK_KEYS_CTRL459 elif win32api.GetKeyState(win32con.VK_MENU) & 0x8000:460 modList = MODIFYING_VK_KEYS_ALT461 else:462 modList = MODIFYING_VK_KEYS463 464 if key in modList:465 # Return 1 if we can make the file editable.(or it already is!)466 return self.GetDocument().CheckMakeDocumentWritable()467 return 1 # Pass it on OK468 469 # def OnKey(self, key):470 # return self.GetDocument().CheckMakeDocumentWritable()471 472 def OnCheckExternalDocumentUpdated(self, msg):473 if self._obj_ is None or self.bCheckingFile:474 return475 self.bCheckingFile = 1476 self.GetDocument().CheckExternalDocumentUpdated()477 self.bCheckingFile = 0478 479 480from .template import EditorTemplateBase481 482 483class EditorTemplate(EditorTemplateBase):484 def __init__(485 self, res=win32ui.IDR_TEXTTYPE, makeDoc=None, makeFrame=None, makeView=None486 ):487 if makeDoc is None:488 makeDoc = EditorDocument489 if makeView is None:490 makeView = EditorView491 EditorTemplateBase.__init__(self, res, makeDoc, makeFrame, makeView)492 493 def _CreateDocTemplate(self, resourceId):494 return win32ui.CreateRichEditDocTemplate(resourceId)495 496 def CreateWin32uiDocument(self):497 return self.DoCreateRichEditDoc()498 499 500def Create(fileName=None, title=None, template=None):501 return editorTemplate.OpenDocumentFile(fileName)502 503 504from pywin.framework.editor import GetDefaultEditorModuleName505 506prefModule = GetDefaultEditorModuleName()507# Initialize only if this is the "default" editor.508if __name__ == prefModule:509 # For debugging purposes, when this module may be reloaded many times.510 try:511 win32ui.GetApp().RemoveDocTemplate(editorTemplate)512 except (NameError, win32ui.error):513 pass514 515 editorTemplate = EditorTemplate()516 win32ui.GetApp().AddDocTemplate(editorTemplate)517 