123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- import codecs
- import re
- import string
-
- import win32con
- import win32ui
- from pywin import default_scintilla_encoding
- from pywin.mfc import docview
-
- from . import scintillacon
-
- crlf_bytes = "\r\n".encode("ascii")
- lf_bytes = "\n".encode("ascii")
-
- # re from pep263 - but we use it both on bytes and strings.
- re_encoding_bytes = re.compile("coding[:=]\s*([-\w.]+)".encode("ascii"))
- re_encoding_text = re.compile("coding[:=]\s*([-\w.]+)")
-
- ParentScintillaDocument = docview.Document
-
-
- class CScintillaDocument(ParentScintillaDocument):
- "A SyntEdit document."
-
- def __init__(self, *args):
- self.bom = None # the BOM, if any, read from the file.
- # the encoding we detected from the source. Might have
- # detected via the BOM or an encoding decl. Note that in
- # the latter case (ie, while self.bom is None), it can't be
- # trusted - the user may have edited the encoding decl between
- # open and save.
- self.source_encoding = None
- ParentScintillaDocument.__init__(self, *args)
-
- def DeleteContents(self):
- pass
-
- def OnOpenDocument(self, filename):
- # init data members
- # print "Opening", filename
- self.SetPathName(filename) # Must set this early!
- try:
- # load the text as binary we can get smart
- # about detecting any existing EOL conventions.
- f = open(filename, "rb")
- try:
- self._LoadTextFromFile(f)
- finally:
- f.close()
- except IOError:
- rc = win32ui.MessageBox(
- "Could not load the file from %s\n\nDo you want to create a new file?"
- % filename,
- "Pythonwin",
- win32con.MB_YESNO | win32con.MB_ICONWARNING,
- )
- if rc == win32con.IDNO:
- return 0
- assert rc == win32con.IDYES, rc
- try:
- f = open(filename, "wb+")
- try:
- self._LoadTextFromFile(f)
- finally:
- f.close()
- except IOError as e:
- rc = win32ui.MessageBox("Cannot create the file %s" % filename)
- return 1
-
- def SaveFile(self, fileName, encoding=None):
- view = self.GetFirstView()
- ok = view.SaveTextFile(fileName, encoding=encoding)
- if ok:
- view.SCISetSavePoint()
- return ok
-
- def ApplyFormattingStyles(self):
- self._ApplyOptionalToViews("ApplyFormattingStyles")
-
- # #####################
- # File related functions
- # Helper to transfer text from the MFC document to the control.
- def _LoadTextFromFile(self, f):
- # detect EOL mode - we don't support \r only - so find the
- # first '\n' and guess based on the char before.
- l = f.readline()
- l2 = f.readline()
- # If line ends with \r\n or has no line ending, use CRLF.
- if l.endswith(crlf_bytes) or not l.endswith(lf_bytes):
- eol_mode = scintillacon.SC_EOL_CRLF
- else:
- eol_mode = scintillacon.SC_EOL_LF
-
- # Detect the encoding - first look for a BOM, and if not found,
- # look for a pep263 encoding declaration.
- for bom, encoding in (
- (codecs.BOM_UTF8, "utf8"),
- (codecs.BOM_UTF16_LE, "utf_16_le"),
- (codecs.BOM_UTF16_BE, "utf_16_be"),
- ):
- if l.startswith(bom):
- self.bom = bom
- self.source_encoding = encoding
- l = l[len(bom) :] # remove it.
- break
- else:
- # no bom detected - look for pep263 encoding decl.
- for look in (l, l2):
- # Note we are looking at raw bytes here: so
- # both the re itself uses bytes and the result
- # is bytes - but we need the result as a string.
- match = re_encoding_bytes.search(look)
- if match is not None:
- self.source_encoding = match.group(1).decode("ascii")
- break
-
- # reading by lines would be too slow? Maybe we can use the
- # incremental encoders? For now just stick with loading the
- # entire file in memory.
- text = l + l2 + f.read()
-
- # Translate from source encoding to UTF-8 bytes for Scintilla
- source_encoding = self.source_encoding
- # If we don't know an encoding, try utf-8 - if that fails we will
- # fallback to latin-1 to treat it as bytes...
- if source_encoding is None:
- source_encoding = "utf-8"
- # we could optimize this by avoiding utf8 to-ing and from-ing,
- # but then we would lose the ability to handle invalid utf8
- # (and even then, the use of encoding aliases makes this tricky)
- # To create an invalid utf8 file:
- # >>> open(filename, "wb").write(codecs.BOM_UTF8+"bad \xa9har\r\n")
- try:
- dec = text.decode(source_encoding)
- except UnicodeError:
- print(
- "WARNING: Failed to decode bytes from '%s' encoding - treating as latin1"
- % source_encoding
- )
- dec = text.decode("latin1")
- except LookupError:
- print(
- "WARNING: Invalid encoding '%s' specified - treating as latin1"
- % source_encoding
- )
- dec = text.decode("latin1")
- # and put it back as utf8 - this shouldn't fail.
- text = dec.encode(default_scintilla_encoding)
-
- view = self.GetFirstView()
- if view.IsWindow():
- # Turn off undo collection while loading
- view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 0, 0)
- # Make sure the control isnt read-only
- view.SetReadOnly(0)
- view.SendScintilla(scintillacon.SCI_CLEARALL)
- view.SendMessage(scintillacon.SCI_ADDTEXT, text)
- view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 1, 0)
- view.SendScintilla(win32con.EM_EMPTYUNDOBUFFER, 0, 0)
- # set EOL mode
- view.SendScintilla(scintillacon.SCI_SETEOLMODE, eol_mode)
-
- def _SaveTextToFile(self, view, filename, encoding=None):
- s = view.GetTextRange() # already decoded from scintilla's encoding
- source_encoding = encoding
- if source_encoding is None:
- if self.bom:
- source_encoding = self.source_encoding
- else:
- # no BOM - look for an encoding.
- bits = re.split("[\r\n]+", s, 3)
- for look in bits[:-1]:
- match = re_encoding_text.search(look)
- if match is not None:
- source_encoding = match.group(1)
- self.source_encoding = source_encoding
- break
-
- if source_encoding is None:
- source_encoding = "utf-8"
-
- ## encode data before opening file so script is not lost if encoding fails
- file_contents = s.encode(source_encoding)
- # Open in binary mode as scintilla itself ensures the
- # line endings are already appropriate
- f = open(filename, "wb")
- try:
- if self.bom:
- f.write(self.bom)
- f.write(file_contents)
- finally:
- f.close()
- self.SetModifiedFlag(0)
-
- def FinalizeViewCreation(self, view):
- pass
-
- def HookViewNotifications(self, view):
- parent = view.GetParentFrame()
- parent.HookNotify(
- ViewNotifyDelegate(self, "OnBraceMatch"), scintillacon.SCN_CHECKBRACE
- )
- parent.HookNotify(
- ViewNotifyDelegate(self, "OnMarginClick"), scintillacon.SCN_MARGINCLICK
- )
- parent.HookNotify(
- ViewNotifyDelegate(self, "OnNeedShown"), scintillacon.SCN_NEEDSHOWN
- )
-
- parent.HookNotify(
- DocumentNotifyDelegate(self, "OnSavePointReached"),
- scintillacon.SCN_SAVEPOINTREACHED,
- )
- parent.HookNotify(
- DocumentNotifyDelegate(self, "OnSavePointLeft"),
- scintillacon.SCN_SAVEPOINTLEFT,
- )
- parent.HookNotify(
- DocumentNotifyDelegate(self, "OnModifyAttemptRO"),
- scintillacon.SCN_MODIFYATTEMPTRO,
- )
- # Tell scintilla what characters should abort auto-complete.
- view.SCIAutoCStops(string.whitespace + "()[]:;+-/*=\\?'!#@$%^&,<>\"'|")
-
- if view != self.GetFirstView():
- view.SCISetDocPointer(self.GetFirstView().SCIGetDocPointer())
-
- def OnSavePointReached(self, std, extra):
- self.SetModifiedFlag(0)
-
- def OnSavePointLeft(self, std, extra):
- self.SetModifiedFlag(1)
-
- def OnModifyAttemptRO(self, std, extra):
- self.MakeDocumentWritable()
-
- # All Marker functions are 1 based.
- def MarkerAdd(self, lineNo, marker):
- self.GetEditorView().SCIMarkerAdd(lineNo - 1, marker)
-
- def MarkerCheck(self, lineNo, marker):
- v = self.GetEditorView()
- lineNo = lineNo - 1 # Make 0 based
- markerState = v.SCIMarkerGet(lineNo)
- return markerState & (1 << marker) != 0
-
- def MarkerToggle(self, lineNo, marker):
- v = self.GetEditorView()
- if self.MarkerCheck(lineNo, marker):
- v.SCIMarkerDelete(lineNo - 1, marker)
- else:
- v.SCIMarkerAdd(lineNo - 1, marker)
-
- def MarkerDelete(self, lineNo, marker):
- self.GetEditorView().SCIMarkerDelete(lineNo - 1, marker)
-
- def MarkerDeleteAll(self, marker):
- self.GetEditorView().SCIMarkerDeleteAll(marker)
-
- def MarkerGetNext(self, lineNo, marker):
- return self.GetEditorView().SCIMarkerNext(lineNo - 1, 1 << marker) + 1
-
- def MarkerAtLine(self, lineNo, marker):
- markerState = self.GetEditorView().SCIMarkerGet(lineNo - 1)
- return markerState & (1 << marker)
-
- # Helper for reflecting functions to views.
- def _ApplyToViews(self, funcName, *args):
- for view in self.GetAllViews():
- func = getattr(view, funcName)
- func(*args)
-
- def _ApplyOptionalToViews(self, funcName, *args):
- for view in self.GetAllViews():
- func = getattr(view, funcName, None)
- if func is not None:
- func(*args)
-
- def GetEditorView(self):
- # Find the first frame with a view,
- # then ask it to give the editor view
- # as it knows which one is "active"
- try:
- frame_gev = self.GetFirstView().GetParentFrame().GetEditorView
- except AttributeError:
- return self.GetFirstView()
- return frame_gev()
-
-
- # Delegate to the correct view, based on the control that sent it.
- class ViewNotifyDelegate:
- def __init__(self, doc, name):
- self.doc = doc
- self.name = name
-
- def __call__(self, std, extra):
- (hwndFrom, idFrom, code) = std
- for v in self.doc.GetAllViews():
- if v.GetSafeHwnd() == hwndFrom:
- return getattr(v, self.name)(*(std, extra))
-
-
- # Delegate to the document, but only from a single view (as each view sends it seperately)
- class DocumentNotifyDelegate:
- def __init__(self, doc, name):
- self.doc = doc
- self.delegate = getattr(doc, name)
-
- def __call__(self, std, extra):
- (hwndFrom, idFrom, code) = std
- if hwndFrom == self.doc.GetEditorView().GetSafeHwnd():
- self.delegate(*(std, extra))
|