codekingpro/portable-devtools
114k
1# We no longer support the old, non-colour editor!2 3import os4import shutil5import traceback6 7import win32api8import win32con9import win32ui10from pywin.framework.editor import GetEditorOption11from pywin.mfc import docview, object12 13BAK_NONE = 014BAK_DOT_BAK = 115BAK_DOT_BAK_TEMP_DIR = 216BAK_DOT_BAK_BAK_DIR = 317 18MSG_CHECK_EXTERNAL_FILE = (19 win32con.WM_USER + 199920) ## WARNING: Duplicated in editor.py and coloreditor.py21 22import pywin.scintilla.document23 24ParentEditorDocument = pywin.scintilla.document.CScintillaDocument25 26 27class EditorDocumentBase(ParentEditorDocument):28 def __init__(self, template):29 self.bAutoReload = GetEditorOption("Auto Reload", 1)30 self.bDeclinedReload = 0 # Has the user declined to reload.31 self.fileStat = None32 self.bReportedFileNotFound = 033 34 # what sort of bak file should I create.35 # default to write to %temp%/bak/filename.ext36 self.bakFileType = GetEditorOption("Backup Type", BAK_DOT_BAK_BAK_DIR)37 38 self.watcherThread = FileWatchingThread(self)39 self.watcherThread.CreateThread()40 # Should I try and use VSS integration?41 self.scModuleName = GetEditorOption("Source Control Module", "")42 self.scModule = None # Loaded when first used.43 ParentEditorDocument.__init__(self, template, template.CreateWin32uiDocument())44 45 def OnCloseDocument(self):46 self.watcherThread.SignalStop()47 return self._obj_.OnCloseDocument()48 49 # def OnOpenDocument(self, name):50 # rc = ParentEditorDocument.OnOpenDocument(self, name)51 # self.GetFirstView()._SetLoadedText(self.text)52 # self._DocumentStateChanged()53 # return rc54 55 def OnSaveDocument(self, fileName):56 win32ui.SetStatusText("Saving file...", 1)57 # rename to bak if required.58 dir, basename = os.path.split(fileName)59 if self.bakFileType == BAK_DOT_BAK:60 bakFileName = dir + "\\" + os.path.splitext(basename)[0] + ".bak"61 elif self.bakFileType == BAK_DOT_BAK_TEMP_DIR:62 bakFileName = (63 win32api.GetTempPath() + "\\" + os.path.splitext(basename)[0] + ".bak"64 )65 elif self.bakFileType == BAK_DOT_BAK_BAK_DIR:66 tempPath = os.path.join(win32api.GetTempPath(), "bak")67 try:68 os.mkdir(tempPath, 0)69 except os.error:70 pass71 bakFileName = os.path.join(tempPath, basename)72 try:73 os.unlink(bakFileName) # raise NameError if no bakups wanted.74 except (os.error, NameError):75 pass76 try:77 # Do a copy as it might be on different volumes,78 # and the file may be a hard-link, causing the link79 # to follow the backup.80 shutil.copy2(fileName, bakFileName)81 except (os.error, NameError, IOError):82 pass83 try:84 self.SaveFile(fileName)85 except IOError as details:86 win32ui.MessageBox("Error - could not save file\r\n\r\n%s" % details)87 return 088 except (UnicodeEncodeError, LookupError) as details:89 rc = win32ui.MessageBox(90 "Encoding failed: \r\n%s" % details91 + "\r\nPlease add desired source encoding as first line of file, eg \r\n"92 + "# -*- coding: mbcs -*-\r\n\r\n"93 + "If you continue, the file will be saved as binary and will\r\n"94 + "not be valid in the declared encoding.\r\n\r\n"95 + "Save the file as binary with an invalid encoding?",96 "File save failed",97 win32con.MB_YESNO | win32con.MB_DEFBUTTON2,98 )99 if rc == win32con.IDYES:100 try:101 self.SaveFile(fileName, encoding="latin-1")102 except IOError as details:103 win32ui.MessageBox(104 "Error - could not save file\r\n\r\n%s" % details105 )106 return 0107 else:108 return 0109 self.SetModifiedFlag(0) # No longer dirty110 self.bDeclinedReload = 0 # They probably want to know if it changes again!111 win32ui.AddToRecentFileList(fileName)112 self.SetPathName(fileName)113 win32ui.SetStatusText("Ready")114 self._DocumentStateChanged()115 return 1116 117 def FinalizeViewCreation(self, view):118 ParentEditorDocument.FinalizeViewCreation(self, view)119 if view == self.GetFirstView():120 self._DocumentStateChanged()121 if view.bFolding and GetEditorOption("Fold On Open", 0):122 view.FoldTopLevelEvent()123 124 def HookViewNotifications(self, view):125 ParentEditorDocument.HookViewNotifications(self, view)126 127 # Support for reloading the document from disk - presumably after some128 # external application has modified it (or possibly source control has129 # checked it out.130 def ReloadDocument(self):131 """Reloads the document from disk. Assumes the file has132 been saved and user has been asked if necessary - it just does it!133 """134 win32ui.SetStatusText("Reloading document. Please wait...", 1)135 self.SetModifiedFlag(0)136 # Loop over all views, saving their state, then reload the document137 views = self.GetAllViews()138 states = []139 for view in views:140 try:141 info = view._PrepareUserStateChange()142 except AttributeError: # Not our editor view?143 info = None144 states.append(info)145 self.OnOpenDocument(self.GetPathName())146 for view, info in zip(views, states):147 if info is not None:148 view._EndUserStateChange(info)149 self._DocumentStateChanged()150 win32ui.SetStatusText("Document reloaded.")151 152 # Reloading the file153 def CheckExternalDocumentUpdated(self):154 if self.bDeclinedReload or not self.GetPathName():155 return156 try:157 newstat = os.stat(self.GetPathName())158 except os.error as exc:159 if not self.bReportedFileNotFound:160 print(161 "The file '%s' is open for editing, but\nchecking it for changes caused the error: %s"162 % (self.GetPathName(), exc.strerror)163 )164 self.bReportedFileNotFound = 1165 return166 if self.bReportedFileNotFound:167 print(168 "The file '%s' has re-appeared - continuing to watch for changes..."169 % (self.GetPathName(),)170 )171 self.bReportedFileNotFound = (172 0 # Once found again we want to start complaining.173 )174 changed = (175 (self.fileStat is None)176 or self.fileStat[0] != newstat[0]177 or self.fileStat[6] != newstat[6]178 or self.fileStat[8] != newstat[8]179 or self.fileStat[9] != newstat[9]180 )181 if changed:182 question = None183 if self.IsModified():184 question = (185 "%s\r\n\r\nThis file has been modified outside of the source editor.\r\nDo you want to reload it and LOSE THE CHANGES in the source editor?"186 % self.GetPathName()187 )188 mbStyle = win32con.MB_YESNO | win32con.MB_DEFBUTTON2 # Default to "No"189 else:190 if not self.bAutoReload:191 question = (192 "%s\r\n\r\nThis file has been modified outside of the source editor.\r\nDo you want to reload it?"193 % self.GetPathName()194 )195 mbStyle = win32con.MB_YESNO # Default to "Yes"196 if question:197 rc = win32ui.MessageBox(question, None, mbStyle)198 if rc != win32con.IDYES:199 self.bDeclinedReload = 1200 return201 self.ReloadDocument()202 203 def _DocumentStateChanged(self):204 """Called whenever the documents state (on disk etc) has been changed205 by the editor (eg, as the result of a save operation)206 """207 if self.GetPathName():208 try:209 self.fileStat = os.stat(self.GetPathName())210 except os.error:211 self.fileStat = None212 else:213 self.fileStat = None214 self.watcherThread._DocumentStateChanged()215 self._UpdateUIForState()216 self._ApplyOptionalToViews("_UpdateUIForState")217 self._ApplyOptionalToViews("SetReadOnly", self._IsReadOnly())218 self._ApplyOptionalToViews("SCISetSavePoint")219 # Allow the debugger to reset us too.220 import pywin.debugger221 222 if pywin.debugger.currentDebugger is not None:223 pywin.debugger.currentDebugger.UpdateDocumentLineStates(self)224 225 # Read-only document support - make it obvious to the user226 # that the file is read-only.227 def _IsReadOnly(self):228 return self.fileStat is not None and (self.fileStat[0] & 128) == 0229 230 def _UpdateUIForState(self):231 """Change the title to reflect the state of the document -232 eg ReadOnly, Dirty, etc233 """234 filename = self.GetPathName()235 if not filename:236 return # New file - nothing to do237 try:238 # This seems necessary so the internal state of the window becomes239 # "visible". without it, it is still shown, but certain functions240 # (such as updating the title) dont immediately work?241 self.GetFirstView().ShowWindow(win32con.SW_SHOW)242 title = win32ui.GetFileTitle(filename)243 except win32ui.error:244 title = filename245 if self._IsReadOnly():246 title = title + " (read-only)"247 self.SetTitle(title)248 249 def MakeDocumentWritable(self):250 pretend_ss = 0 # Set to 1 to test this without source safe :-)251 if not self.scModuleName and not pretend_ss: # No Source Control support.252 win32ui.SetStatusText(253 "Document is read-only, and no source-control system is configured"254 )255 win32api.MessageBeep()256 return 0257 258 # We have source control support - check if the user wants to use it.259 msg = "Would you like to check this file out?"260 defButton = win32con.MB_YESNO261 if self.IsModified():262 msg = msg + "\r\n\r\nALL CHANGES IN THE EDITOR WILL BE LOST"263 defButton = win32con.MB_YESNO264 if win32ui.MessageBox(msg, None, defButton) != win32con.IDYES:265 return 0266 267 if pretend_ss:268 print("We are only pretending to check it out!")269 win32api.SetFileAttributes(270 self.GetPathName(), win32con.FILE_ATTRIBUTE_NORMAL271 )272 self.ReloadDocument()273 return 1274 275 # Now call on the module to do it.276 if self.scModule is None:277 try:278 self.scModule = __import__(self.scModuleName)279 for part in self.scModuleName.split(".")[1:]:280 self.scModule = getattr(self.scModule, part)281 except:282 traceback.print_exc()283 print("Error loading source control module.")284 return 0285 286 if self.scModule.CheckoutFile(self.GetPathName()):287 self.ReloadDocument()288 return 1289 return 0290 291 def CheckMakeDocumentWritable(self):292 if self._IsReadOnly():293 return self.MakeDocumentWritable()294 return 1295 296 def SaveModified(self):297 # Called as the document is closed. If we are about298 # to prompt for a save, bring the document to the foreground.299 if self.IsModified():300 frame = self.GetFirstView().GetParentFrame()301 try:302 frame.MDIActivate()303 frame.AutoRestore()304 except:305 print("Could not bring document to foreground")306 return self._obj_.SaveModified()307 308 309# NOTE - I DONT use the standard threading module,310# as this waits for all threads to terminate at shutdown.311# When using the debugger, it is possible shutdown will312# occur without Pythonwin getting a complete shutdown,313# so we deadlock at the end - threading is waiting for314import pywin.mfc.thread315import win32event316 317 318class FileWatchingThread(pywin.mfc.thread.WinThread):319 def __init__(self, doc):320 self.doc = doc321 self.adminEvent = win32event.CreateEvent(None, 0, 0, None)322 self.stopEvent = win32event.CreateEvent(None, 0, 0, None)323 self.watchEvent = None324 pywin.mfc.thread.WinThread.__init__(self)325 326 def _DocumentStateChanged(self):327 win32event.SetEvent(self.adminEvent)328 329 def RefreshEvent(self):330 self.hwnd = self.doc.GetFirstView().GetSafeHwnd()331 if self.watchEvent is not None:332 win32api.FindCloseChangeNotification(self.watchEvent)333 self.watchEvent = None334 path = self.doc.GetPathName()335 if path:336 path = os.path.dirname(path)337 if path:338 filter = (339 win32con.FILE_NOTIFY_CHANGE_FILE_NAME340 | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES341 | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE342 )343 try:344 self.watchEvent = win32api.FindFirstChangeNotification(path, 0, filter)345 except win32api.error as exc:346 print("Can not watch file", path, "for changes -", exc.strerror)347 348 def SignalStop(self):349 win32event.SetEvent(self.stopEvent)350 351 def Run(self):352 while 1:353 handles = [self.stopEvent, self.adminEvent]354 if self.watchEvent is not None:355 handles.append(self.watchEvent)356 rc = win32event.WaitForMultipleObjects(handles, 0, win32event.INFINITE)357 if rc == win32event.WAIT_OBJECT_0:358 break359 elif rc == win32event.WAIT_OBJECT_0 + 1:360 self.RefreshEvent()361 else:362 win32api.PostMessage(self.hwnd, MSG_CHECK_EXTERNAL_FILE, 0, 0)363 try:364 # If the directory has been removed underneath us, we get this error.365 win32api.FindNextChangeNotification(self.watchEvent)366 except win32api.error as exc:367 print(368 "Can not watch file",369 self.doc.GetPathName(),370 "for changes -",371 exc.strerror,372 )373 break374 375 # close a circular reference376 self.doc = None377 if self.watchEvent:378 win32api.FindCloseChangeNotification(self.watchEvent)379 