import win32ui from pywin.mfc import docview from pywin import default_scintilla_encoding from . import scintillacon import win32con import string import os import codecs import re 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<