codekingpro/portable-devtools
114k
1import codecs2import re3import string4 5import win32con6import win32ui7from pywin import default_scintilla_encoding8from pywin.mfc import docview9 10from . import scintillacon11 12crlf_bytes = "\r\n".encode("ascii")13lf_bytes = "\n".encode("ascii")14 15# re from pep263 - but we use it both on bytes and strings.16re_encoding_bytes = re.compile("coding[:=]\s*([-\w.]+)".encode("ascii"))17re_encoding_text = re.compile("coding[:=]\s*([-\w.]+)")18 19ParentScintillaDocument = docview.Document20 21 22class CScintillaDocument(ParentScintillaDocument):23 "A SyntEdit document."24 25 def __init__(self, *args):26 self.bom = None # the BOM, if any, read from the file.27 # the encoding we detected from the source. Might have28 # detected via the BOM or an encoding decl. Note that in29 # the latter case (ie, while self.bom is None), it can't be30 # trusted - the user may have edited the encoding decl between31 # open and save.32 self.source_encoding = None33 ParentScintillaDocument.__init__(self, *args)34 35 def DeleteContents(self):36 pass37 38 def OnOpenDocument(self, filename):39 # init data members40 # print "Opening", filename41 self.SetPathName(filename) # Must set this early!42 try:43 # load the text as binary we can get smart44 # about detecting any existing EOL conventions.45 f = open(filename, "rb")46 try:47 self._LoadTextFromFile(f)48 finally:49 f.close()50 except IOError:51 rc = win32ui.MessageBox(52 "Could not load the file from %s\n\nDo you want to create a new file?"53 % filename,54 "Pythonwin",55 win32con.MB_YESNO | win32con.MB_ICONWARNING,56 )57 if rc == win32con.IDNO:58 return 059 assert rc == win32con.IDYES, rc60 try:61 f = open(filename, "wb+")62 try:63 self._LoadTextFromFile(f)64 finally:65 f.close()66 except IOError as e:67 rc = win32ui.MessageBox("Cannot create the file %s" % filename)68 return 169 70 def SaveFile(self, fileName, encoding=None):71 view = self.GetFirstView()72 ok = view.SaveTextFile(fileName, encoding=encoding)73 if ok:74 view.SCISetSavePoint()75 return ok76 77 def ApplyFormattingStyles(self):78 self._ApplyOptionalToViews("ApplyFormattingStyles")79 80 # #####################81 # File related functions82 # Helper to transfer text from the MFC document to the control.83 def _LoadTextFromFile(self, f):84 # detect EOL mode - we don't support \r only - so find the85 # first '\n' and guess based on the char before.86 l = f.readline()87 l2 = f.readline()88 # If line ends with \r\n or has no line ending, use CRLF.89 if l.endswith(crlf_bytes) or not l.endswith(lf_bytes):90 eol_mode = scintillacon.SC_EOL_CRLF91 else:92 eol_mode = scintillacon.SC_EOL_LF93 94 # Detect the encoding - first look for a BOM, and if not found,95 # look for a pep263 encoding declaration.96 for bom, encoding in (97 (codecs.BOM_UTF8, "utf8"),98 (codecs.BOM_UTF16_LE, "utf_16_le"),99 (codecs.BOM_UTF16_BE, "utf_16_be"),100 ):101 if l.startswith(bom):102 self.bom = bom103 self.source_encoding = encoding104 l = l[len(bom) :] # remove it.105 break106 else:107 # no bom detected - look for pep263 encoding decl.108 for look in (l, l2):109 # Note we are looking at raw bytes here: so110 # both the re itself uses bytes and the result111 # is bytes - but we need the result as a string.112 match = re_encoding_bytes.search(look)113 if match is not None:114 self.source_encoding = match.group(1).decode("ascii")115 break116 117 # reading by lines would be too slow? Maybe we can use the118 # incremental encoders? For now just stick with loading the119 # entire file in memory.120 text = l + l2 + f.read()121 122 # Translate from source encoding to UTF-8 bytes for Scintilla123 source_encoding = self.source_encoding124 # If we don't know an encoding, try utf-8 - if that fails we will125 # fallback to latin-1 to treat it as bytes...126 if source_encoding is None:127 source_encoding = "utf-8"128 # we could optimize this by avoiding utf8 to-ing and from-ing,129 # but then we would lose the ability to handle invalid utf8130 # (and even then, the use of encoding aliases makes this tricky)131 # To create an invalid utf8 file:132 # >>> open(filename, "wb").write(codecs.BOM_UTF8+"bad \xa9har\r\n")133 try:134 dec = text.decode(source_encoding)135 except UnicodeError:136 print(137 "WARNING: Failed to decode bytes from '%s' encoding - treating as latin1"138 % source_encoding139 )140 dec = text.decode("latin1")141 except LookupError:142 print(143 "WARNING: Invalid encoding '%s' specified - treating as latin1"144 % source_encoding145 )146 dec = text.decode("latin1")147 # and put it back as utf8 - this shouldn't fail.148 text = dec.encode(default_scintilla_encoding)149 150 view = self.GetFirstView()151 if view.IsWindow():152 # Turn off undo collection while loading153 view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 0, 0)154 # Make sure the control isnt read-only155 view.SetReadOnly(0)156 view.SendScintilla(scintillacon.SCI_CLEARALL)157 view.SendMessage(scintillacon.SCI_ADDTEXT, text)158 view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 1, 0)159 view.SendScintilla(win32con.EM_EMPTYUNDOBUFFER, 0, 0)160 # set EOL mode161 view.SendScintilla(scintillacon.SCI_SETEOLMODE, eol_mode)162 163 def _SaveTextToFile(self, view, filename, encoding=None):164 s = view.GetTextRange() # already decoded from scintilla's encoding165 source_encoding = encoding166 if source_encoding is None:167 if self.bom:168 source_encoding = self.source_encoding169 else:170 # no BOM - look for an encoding.171 bits = re.split("[\r\n]+", s, 3)172 for look in bits[:-1]:173 match = re_encoding_text.search(look)174 if match is not None:175 source_encoding = match.group(1)176 self.source_encoding = source_encoding177 break178 179 if source_encoding is None:180 source_encoding = "utf-8"181 182 ## encode data before opening file so script is not lost if encoding fails183 file_contents = s.encode(source_encoding)184 # Open in binary mode as scintilla itself ensures the185 # line endings are already appropriate186 f = open(filename, "wb")187 try:188 if self.bom:189 f.write(self.bom)190 f.write(file_contents)191 finally:192 f.close()193 self.SetModifiedFlag(0)194 195 def FinalizeViewCreation(self, view):196 pass197 198 def HookViewNotifications(self, view):199 parent = view.GetParentFrame()200 parent.HookNotify(201 ViewNotifyDelegate(self, "OnBraceMatch"), scintillacon.SCN_CHECKBRACE202 )203 parent.HookNotify(204 ViewNotifyDelegate(self, "OnMarginClick"), scintillacon.SCN_MARGINCLICK205 )206 parent.HookNotify(207 ViewNotifyDelegate(self, "OnNeedShown"), scintillacon.SCN_NEEDSHOWN208 )209 210 parent.HookNotify(211 DocumentNotifyDelegate(self, "OnSavePointReached"),212 scintillacon.SCN_SAVEPOINTREACHED,213 )214 parent.HookNotify(215 DocumentNotifyDelegate(self, "OnSavePointLeft"),216 scintillacon.SCN_SAVEPOINTLEFT,217 )218 parent.HookNotify(219 DocumentNotifyDelegate(self, "OnModifyAttemptRO"),220 scintillacon.SCN_MODIFYATTEMPTRO,221 )222 # Tell scintilla what characters should abort auto-complete.223 view.SCIAutoCStops(string.whitespace + "()[]:;+-/*=\\?'!#@$%^&,<>\"'|")224 225 if view != self.GetFirstView():226 view.SCISetDocPointer(self.GetFirstView().SCIGetDocPointer())227 228 def OnSavePointReached(self, std, extra):229 self.SetModifiedFlag(0)230 231 def OnSavePointLeft(self, std, extra):232 self.SetModifiedFlag(1)233 234 def OnModifyAttemptRO(self, std, extra):235 self.MakeDocumentWritable()236 237 # All Marker functions are 1 based.238 def MarkerAdd(self, lineNo, marker):239 self.GetEditorView().SCIMarkerAdd(lineNo - 1, marker)240 241 def MarkerCheck(self, lineNo, marker):242 v = self.GetEditorView()243 lineNo = lineNo - 1 # Make 0 based244 markerState = v.SCIMarkerGet(lineNo)245 return markerState & (1 << marker) != 0246 247 def MarkerToggle(self, lineNo, marker):248 v = self.GetEditorView()249 if self.MarkerCheck(lineNo, marker):250 v.SCIMarkerDelete(lineNo - 1, marker)251 else:252 v.SCIMarkerAdd(lineNo - 1, marker)253 254 def MarkerDelete(self, lineNo, marker):255 self.GetEditorView().SCIMarkerDelete(lineNo - 1, marker)256 257 def MarkerDeleteAll(self, marker):258 self.GetEditorView().SCIMarkerDeleteAll(marker)259 260 def MarkerGetNext(self, lineNo, marker):261 return self.GetEditorView().SCIMarkerNext(lineNo - 1, 1 << marker) + 1262 263 def MarkerAtLine(self, lineNo, marker):264 markerState = self.GetEditorView().SCIMarkerGet(lineNo - 1)265 return markerState & (1 << marker)266 267 # Helper for reflecting functions to views.268 def _ApplyToViews(self, funcName, *args):269 for view in self.GetAllViews():270 func = getattr(view, funcName)271 func(*args)272 273 def _ApplyOptionalToViews(self, funcName, *args):274 for view in self.GetAllViews():275 func = getattr(view, funcName, None)276 if func is not None:277 func(*args)278 279 def GetEditorView(self):280 # Find the first frame with a view,281 # then ask it to give the editor view282 # as it knows which one is "active"283 try:284 frame_gev = self.GetFirstView().GetParentFrame().GetEditorView285 except AttributeError:286 return self.GetFirstView()287 return frame_gev()288 289 290# Delegate to the correct view, based on the control that sent it.291class ViewNotifyDelegate:292 def __init__(self, doc, name):293 self.doc = doc294 self.name = name295 296 def __call__(self, std, extra):297 (hwndFrom, idFrom, code) = std298 for v in self.doc.GetAllViews():299 if v.GetSafeHwnd() == hwndFrom:300 return getattr(v, self.name)(*(std, extra))301 302 303# Delegate to the document, but only from a single view (as each view sends it seperately)304class DocumentNotifyDelegate:305 def __init__(self, doc, name):306 self.doc = doc307 self.delegate = getattr(doc, name)308 309 def __call__(self, std, extra):310 (hwndFrom, idFrom, code) = std311 if hwndFrom == self.doc.GetEditorView().GetSafeHwnd():312 self.delegate(*(std, extra))313 