You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pythonwin/pywin/framework/editor/document.py

333 lines
12 KiB

# We no longer support the old, non-colour editor!
from pywin.mfc import docview, object
from pywin.framework.editor import GetEditorOption
import win32ui
import os
import win32con
import string
import traceback
import win32api
import shutil
BAK_NONE=0
BAK_DOT_BAK=1
BAK_DOT_BAK_TEMP_DIR=2
BAK_DOT_BAK_BAK_DIR=3
MSG_CHECK_EXTERNAL_FILE = win32con.WM_USER+1999 ## WARNING: Duplicated in editor.py and coloreditor.py
import pywin.scintilla.document
ParentEditorDocument=pywin.scintilla.document.CScintillaDocument
class EditorDocumentBase(ParentEditorDocument):
def __init__(self, template):
self.bAutoReload = GetEditorOption("Auto Reload", 1)
self.bDeclinedReload = 0 # Has the user declined to reload.
self.fileStat = None
self.bReportedFileNotFound = 0
# what sort of bak file should I create.
# default to write to %temp%/bak/filename.ext
self.bakFileType=GetEditorOption("Backup Type", BAK_DOT_BAK_BAK_DIR)
self.watcherThread = FileWatchingThread(self)
self.watcherThread.CreateThread()
# Should I try and use VSS integration?
self.scModuleName=GetEditorOption("Source Control Module", "")
self.scModule = None # Loaded when first used.
ParentEditorDocument.__init__(self, template, template.CreateWin32uiDocument())
def OnCloseDocument(self ):
self.watcherThread.SignalStop()
return self._obj_.OnCloseDocument()
# def OnOpenDocument(self, name):
# rc = ParentEditorDocument.OnOpenDocument(self, name)
# self.GetFirstView()._SetLoadedText(self.text)
# self._DocumentStateChanged()
# return rc
def OnSaveDocument( self, fileName ):
win32ui.SetStatusText("Saving file...",1)
# rename to bak if required.
dir, basename = os.path.split(fileName)
if self.bakFileType==BAK_DOT_BAK:
bakFileName=dir+'\\'+os.path.splitext(basename)[0]+'.bak'
elif self.bakFileType==BAK_DOT_BAK_TEMP_DIR:
bakFileName=win32api.GetTempPath()+'\\'+os.path.splitext(basename)[0]+'.bak'
elif self.bakFileType==BAK_DOT_BAK_BAK_DIR:
tempPath=os.path.join(win32api.GetTempPath(),'bak')
try:
os.mkdir(tempPath,0)
except os.error:
pass
bakFileName=os.path.join(tempPath,basename)
try:
os.unlink(bakFileName) # raise NameError if no bakups wanted.
except (os.error, NameError):
pass
try:
# Do a copy as it might be on different volumes,
# and the file may be a hard-link, causing the link
# to follow the backup.
shutil.copy2(fileName, bakFileName)
except (os.error, NameError, IOError):
pass
try:
self.SaveFile(fileName)
except IOError as details:
win32ui.MessageBox("Error - could not save file\r\n\r\n%s"%details)
return 0
except (UnicodeEncodeError, LookupError) as details:
rc = win32ui.MessageBox("Encoding failed: \r\n%s"%details +
'\r\nPlease add desired source encoding as first line of file, eg \r\n' +
'# -*- coding: mbcs -*-\r\n\r\n' +
'If you continue, the file will be saved as binary and will\r\n' +
'not be valid in the declared encoding.\r\n\r\n' +
'Save the file as binary with an invalid encoding?',
"File save failed",
win32con.MB_YESNO | win32con.MB_DEFBUTTON2)
if rc==win32con.IDYES:
try:
self.SaveFile(fileName, encoding="latin-1")
except IOError as details:
win32ui.MessageBox("Error - could not save file\r\n\r\n%s"%details)
return 0
else:
return 0
self.SetModifiedFlag(0) # No longer dirty
self.bDeclinedReload = 0 # They probably want to know if it changes again!
win32ui.AddToRecentFileList(fileName)
self.SetPathName(fileName)
win32ui.SetStatusText("Ready")
self._DocumentStateChanged()
return 1
def FinalizeViewCreation(self, view):
ParentEditorDocument.FinalizeViewCreation(self, view)
if view == self.GetFirstView():
self._DocumentStateChanged()
if view.bFolding and GetEditorOption("Fold On Open", 0):
view.FoldTopLevelEvent()
def HookViewNotifications(self, view):
ParentEditorDocument.HookViewNotifications(self, view)
# Support for reloading the document from disk - presumably after some
# external application has modified it (or possibly source control has
# checked it out.
def ReloadDocument(self):
"""Reloads the document from disk. Assumes the file has
been saved and user has been asked if necessary - it just does it!
"""
win32ui.SetStatusText("Reloading document. Please wait...", 1)
self.SetModifiedFlag(0)
# Loop over all views, saving their state, then reload the document
views = self.GetAllViews()
states = []
for view in views:
try:
info = view._PrepareUserStateChange()
except AttributeError: # Not our editor view?
info = None
states.append(info)
self.OnOpenDocument(self.GetPathName())
for view, info in zip(views, states):
if info is not None:
view._EndUserStateChange(info)
self._DocumentStateChanged()
win32ui.SetStatusText("Document reloaded.")
# Reloading the file
def CheckExternalDocumentUpdated(self):
if self.bDeclinedReload or not self.GetPathName():
return
try:
newstat = os.stat(self.GetPathName())
except os.error as exc:
if not self.bReportedFileNotFound:
print("The file '%s' is open for editing, but\nchecking it for changes caused the error: %s" % (self.GetPathName(), exc.strerror))
self.bReportedFileNotFound = 1
return
if self.bReportedFileNotFound:
print("The file '%s' has re-appeared - continuing to watch for changes..." % (self.GetPathName(),))
self.bReportedFileNotFound = 0 # Once found again we want to start complaining.
changed = (self.fileStat is None) or \
self.fileStat[0] != newstat[0] or \
self.fileStat[6] != newstat[6] or \
self.fileStat[8] != newstat[8] or \
self.fileStat[9] != newstat[9]
if changed:
question = None
if self.IsModified():
question = "%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?" % self.GetPathName()
mbStyle = win32con.MB_YESNO | win32con.MB_DEFBUTTON2 # Default to "No"
else:
if not self.bAutoReload:
question = "%s\r\n\r\nThis file has been modified outside of the source editor.\r\nDo you want to reload it?" % self.GetPathName()
mbStyle = win32con.MB_YESNO # Default to "Yes"
if question:
rc = win32ui.MessageBox(question, None, mbStyle)
if rc!=win32con.IDYES:
self.bDeclinedReload = 1
return
self.ReloadDocument()
def _DocumentStateChanged(self):
"""Called whenever the documents state (on disk etc) has been changed
by the editor (eg, as the result of a save operation)
"""
if self.GetPathName():
try:
self.fileStat = os.stat(self.GetPathName())
except os.error:
self.fileStat = None
else:
self.fileStat = None
self.watcherThread._DocumentStateChanged()
self._UpdateUIForState()
self._ApplyOptionalToViews("_UpdateUIForState")
self._ApplyOptionalToViews("SetReadOnly", self._IsReadOnly())
self._ApplyOptionalToViews("SCISetSavePoint")
# Allow the debugger to reset us too.
import pywin.debugger
if pywin.debugger.currentDebugger is not None:
pywin.debugger.currentDebugger.UpdateDocumentLineStates(self)
# Read-only document support - make it obvious to the user
# that the file is read-only.
def _IsReadOnly(self):
return self.fileStat is not None and (self.fileStat[0] & 128)==0
def _UpdateUIForState(self):
"""Change the title to reflect the state of the document -
eg ReadOnly, Dirty, etc
"""
filename = self.GetPathName()
if not filename: return # New file - nothing to do
try:
# This seems necessary so the internal state of the window becomes
# "visible". without it, it is still shown, but certain functions
# (such as updating the title) dont immediately work?
self.GetFirstView().ShowWindow(win32con.SW_SHOW)
title = win32ui.GetFileTitle(filename)
except win32ui.error:
title = filename
if self._IsReadOnly():
title = title + " (read-only)"
self.SetTitle(title)
def MakeDocumentWritable(self):
pretend_ss = 0 # Set to 1 to test this without source safe :-)
if not self.scModuleName and not pretend_ss: # No Source Control support.
win32ui.SetStatusText("Document is read-only, and no source-control system is configured")
win32api.MessageBeep()
return 0
# We have source control support - check if the user wants to use it.
msg = "Would you like to check this file out?"
defButton = win32con.MB_YESNO
if self.IsModified():
msg = msg + "\r\n\r\nALL CHANGES IN THE EDITOR WILL BE LOST"
defButton = win32con.MB_YESNO
if win32ui.MessageBox(msg, None, defButton)!=win32con.IDYES:
return 0
if pretend_ss:
print("We are only pretending to check it out!")
win32api.SetFileAttributes(self.GetPathName(), win32con.FILE_ATTRIBUTE_NORMAL)
self.ReloadDocument()
return 1
# Now call on the module to do it.
if self.scModule is None:
try:
self.scModule = __import__(self.scModuleName)
for part in self.scModuleName.split('.')[1:]:
self.scModule = getattr(self.scModule, part)
except:
traceback.print_exc()
print("Error loading source control module.")
return 0
if self.scModule.CheckoutFile(self.GetPathName()):
self.ReloadDocument()
return 1
return 0
def CheckMakeDocumentWritable(self):
if self._IsReadOnly():
return self.MakeDocumentWritable()
return 1
def SaveModified(self):
# Called as the document is closed. If we are about
# to prompt for a save, bring the document to the foreground.
if self.IsModified():
frame = self.GetFirstView().GetParentFrame()
try:
frame.MDIActivate()
frame.AutoRestore()
except:
print("Could not bring document to foreground")
return self._obj_.SaveModified()
# NOTE - I DONT use the standard threading module,
# as this waits for all threads to terminate at shutdown.
# When using the debugger, it is possible shutdown will
# occur without Pythonwin getting a complete shutdown,
# so we deadlock at the end - threading is waiting for
import pywin.mfc.thread
import win32event
class FileWatchingThread(pywin.mfc.thread.WinThread):
def __init__(self, doc):
self.doc = doc
self.adminEvent = win32event.CreateEvent(None, 0, 0, None)
self.stopEvent = win32event.CreateEvent(None, 0, 0, None)
self.watchEvent = None
pywin.mfc.thread.WinThread.__init__(self)
def _DocumentStateChanged(self):
win32event.SetEvent(self.adminEvent)
def RefreshEvent(self):
self.hwnd = self.doc.GetFirstView().GetSafeHwnd()
if self.watchEvent is not None:
win32api.FindCloseChangeNotification(self.watchEvent)
self.watchEvent = None
path = self.doc.GetPathName()
if path: path = os.path.dirname(path)
if path:
filter = win32con.FILE_NOTIFY_CHANGE_FILE_NAME | \
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES | \
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE
try:
self.watchEvent = win32api.FindFirstChangeNotification(path, 0, filter)
except win32api.error as exc:
print("Can not watch file", path, "for changes -", exc.strerror)
def SignalStop(self):
win32event.SetEvent(self.stopEvent)
def Run(self):
while 1:
handles = [self.stopEvent, self.adminEvent]
if self.watchEvent is not None:
handles.append(self.watchEvent)
rc = win32event.WaitForMultipleObjects(handles, 0, win32event.INFINITE)
if rc == win32event.WAIT_OBJECT_0:
break
elif rc == win32event.WAIT_OBJECT_0+1:
self.RefreshEvent()
else:
win32api.PostMessage(self.hwnd, MSG_CHECK_EXTERNAL_FILE, 0, 0)
try:
# If the directory has been removed underneath us, we get this error.
win32api.FindNextChangeNotification(self.watchEvent)
except win32api.error as exc:
print("Can not watch file", self.doc.GetPathName(), "for changes -", exc.strerror)
break
# close a circular reference
self.doc = None
if self.watchEvent:
win32api.FindCloseChangeNotification(self.watchEvent)