Spaces:
Paused
Paused
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)) | |