# An Python interface to the Scintilla control. # # Exposes Python classes that allow you to use Scintilla as # a "standard" MFC edit control (eg, control.GetTextLength(), control.GetSel() # plus many Scintilla specific features (eg control.SCIAddStyledText()) import array import os import struct import win32api import win32con import win32ui from pywin import default_scintilla_encoding from pywin.mfc import window from . import scintillacon # Load Scintilla.dll to get access to the control. # We expect to find this in the same directory as win32ui.pyd dllid = None if win32ui.debug: # If running _d version of Pythonwin... try: dllid = win32api.LoadLibrary( os.path.join(os.path.split(win32ui.__file__)[0], "Scintilla_d.DLL") ) except ( win32api.error ): # Not there - we dont _need_ a debug ver, so ignore this error. pass if dllid is None: try: dllid = win32api.LoadLibrary( os.path.join(os.path.split(win32ui.__file__)[0], "Scintilla.DLL") ) except win32api.error: pass if dllid is None: # Still not there - lets see if Windows can find it by searching? dllid = win32api.LoadLibrary("Scintilla.DLL") # null_byte is str in py2k, bytes on py3k null_byte = "\0".encode("ascii") ## These are from Richedit.h - need to add to win32con or commctrl EM_GETTEXTRANGE = 1099 EM_EXLINEFROMCHAR = 1078 EM_FINDTEXTEX = 1103 EM_GETSELTEXT = 1086 EM_EXSETSEL = win32con.WM_USER + 55 class ScintillaNotification: def __init__(self, **args): self.__dict__.update(args) class ScintillaControlInterface: def SCIUnpackNotifyMessage(self, msg): format = "iiiiPiiiPPiiii" bytes = win32ui.GetBytes(msg, struct.calcsize(format)) ( position, ch, modifiers, modificationType, text_ptr, length, linesAdded, msg, wParam, lParam, line, foldLevelNow, foldLevelPrev, margin, ) = struct.unpack(format, bytes) return ScintillaNotification( position=position, ch=ch, modifiers=modifiers, modificationType=modificationType, text_ptr=text_ptr, length=length, linesAdded=linesAdded, msg=msg, wParam=wParam, lParam=lParam, line=line, foldLevelNow=foldLevelNow, foldLevelPrev=foldLevelPrev, margin=margin, ) def SCIAddText(self, text): self.SendMessage( scintillacon.SCI_ADDTEXT, text.encode(default_scintilla_encoding) ) def SCIAddStyledText(self, text, style=None): # If style is None, text is assumed to be a "native" Scintilla buffer. # If style is specified, text is a normal string, and the style is # assumed to apply to the entire string. if style is not None: text = list(map(lambda char, style=style: char + chr(style), text)) text = "".join(text) self.SendMessage( scintillacon.SCI_ADDSTYLEDTEXT, text.encode(default_scintilla_encoding) ) def SCIInsertText(self, text, pos=-1): # SCIInsertText allows unicode or bytes - but if they are bytes, # the caller must ensure it is encoded correctly. if isinstance(text, str): text = text.encode(default_scintilla_encoding) self.SendScintilla(scintillacon.SCI_INSERTTEXT, pos, text + null_byte) def SCISetSavePoint(self): self.SendScintilla(scintillacon.SCI_SETSAVEPOINT) def SCISetUndoCollection(self, collectFlag): self.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, collectFlag) def SCIBeginUndoAction(self): self.SendScintilla(scintillacon.SCI_BEGINUNDOACTION) def SCIEndUndoAction(self): self.SendScintilla(scintillacon.SCI_ENDUNDOACTION) def SCIGetCurrentPos(self): return self.SendScintilla(scintillacon.SCI_GETCURRENTPOS) def SCIGetCharAt(self, pos): # Must ensure char is unsigned! return chr(self.SendScintilla(scintillacon.SCI_GETCHARAT, pos) & 0xFF) def SCIGotoLine(self, line): self.SendScintilla(scintillacon.SCI_GOTOLINE, line) def SCIBraceMatch(self, pos, maxReStyle): return self.SendScintilla(scintillacon.SCI_BRACEMATCH, pos, maxReStyle) def SCIBraceHighlight(self, pos, posOpposite): return self.SendScintilla(scintillacon.SCI_BRACEHIGHLIGHT, pos, posOpposite) def SCIBraceBadHighlight(self, pos): return self.SendScintilla(scintillacon.SCI_BRACEBADLIGHT, pos) #################################### # Styling # def SCIColourise(self, start=0, end=-1): # NOTE - dependent on of we use builtin lexer, so handled below. def SCIGetEndStyled(self): return self.SendScintilla(scintillacon.SCI_GETENDSTYLED) def SCIStyleSetFore(self, num, v): return self.SendScintilla(scintillacon.SCI_STYLESETFORE, num, v) def SCIStyleSetBack(self, num, v): return self.SendScintilla(scintillacon.SCI_STYLESETBACK, num, v) def SCIStyleSetEOLFilled(self, num, v): return self.SendScintilla(scintillacon.SCI_STYLESETEOLFILLED, num, v) def SCIStyleSetFont(self, num, name, characterset=0): buff = (name + "\0").encode(default_scintilla_encoding) self.SendScintilla(scintillacon.SCI_STYLESETFONT, num, buff) self.SendScintilla(scintillacon.SCI_STYLESETCHARACTERSET, num, characterset) def SCIStyleSetBold(self, num, bBold): self.SendScintilla(scintillacon.SCI_STYLESETBOLD, num, bBold) def SCIStyleSetItalic(self, num, bItalic): self.SendScintilla(scintillacon.SCI_STYLESETITALIC, num, bItalic) def SCIStyleSetSize(self, num, size): self.SendScintilla(scintillacon.SCI_STYLESETSIZE, num, size) def SCIGetViewWS(self): return self.SendScintilla(scintillacon.SCI_GETVIEWWS) def SCISetViewWS(self, val): self.SendScintilla(scintillacon.SCI_SETVIEWWS, not (val == 0)) self.InvalidateRect() def SCISetIndentationGuides(self, val): self.SendScintilla(scintillacon.SCI_SETINDENTATIONGUIDES, val) def SCIGetIndentationGuides(self): return self.SendScintilla(scintillacon.SCI_GETINDENTATIONGUIDES) def SCISetIndent(self, val): self.SendScintilla(scintillacon.SCI_SETINDENT, val) def SCIGetIndent(self, val): return self.SendScintilla(scintillacon.SCI_GETINDENT) def SCIGetViewEOL(self): return self.SendScintilla(scintillacon.SCI_GETVIEWEOL) def SCISetViewEOL(self, val): self.SendScintilla(scintillacon.SCI_SETVIEWEOL, not (val == 0)) self.InvalidateRect() def SCISetTabWidth(self, width): self.SendScintilla(scintillacon.SCI_SETTABWIDTH, width, 0) def SCIStartStyling(self, pos, mask): self.SendScintilla(scintillacon.SCI_STARTSTYLING, pos, mask) def SCISetStyling(self, pos, attr): self.SendScintilla(scintillacon.SCI_SETSTYLING, pos, attr) def SCISetStylingEx(self, ray): # ray is an array. address, length = ray.buffer_info() self.SendScintilla(scintillacon.SCI_SETSTYLINGEX, length, address) def SCIGetStyleAt(self, pos): return self.SendScintilla(scintillacon.SCI_GETSTYLEAT, pos) def SCISetMarginWidth(self, width): self.SendScintilla(scintillacon.SCI_SETMARGINWIDTHN, 1, width) def SCISetMarginWidthN(self, n, width): self.SendScintilla(scintillacon.SCI_SETMARGINWIDTHN, n, width) def SCISetFoldFlags(self, flags): self.SendScintilla(scintillacon.SCI_SETFOLDFLAGS, flags) # Markers def SCIMarkerDefineAll(self, markerNum, markerType, fore, back): self.SCIMarkerDefine(markerNum, markerType) self.SCIMarkerSetFore(markerNum, fore) self.SCIMarkerSetBack(markerNum, back) def SCIMarkerDefine(self, markerNum, markerType): self.SendScintilla(scintillacon.SCI_MARKERDEFINE, markerNum, markerType) def SCIMarkerSetFore(self, markerNum, fore): self.SendScintilla(scintillacon.SCI_MARKERSETFORE, markerNum, fore) def SCIMarkerSetBack(self, markerNum, back): self.SendScintilla(scintillacon.SCI_MARKERSETBACK, markerNum, back) def SCIMarkerAdd(self, lineNo, markerNum): self.SendScintilla(scintillacon.SCI_MARKERADD, lineNo, markerNum) def SCIMarkerDelete(self, lineNo, markerNum): self.SendScintilla(scintillacon.SCI_MARKERDELETE, lineNo, markerNum) def SCIMarkerDeleteAll(self, markerNum=-1): self.SendScintilla(scintillacon.SCI_MARKERDELETEALL, markerNum) def SCIMarkerGet(self, lineNo): return self.SendScintilla(scintillacon.SCI_MARKERGET, lineNo) def SCIMarkerNext(self, lineNo, markerNum): return self.SendScintilla(scintillacon.SCI_MARKERNEXT, lineNo, markerNum) def SCICancel(self): self.SendScintilla(scintillacon.SCI_CANCEL) # AutoComplete def SCIAutoCShow(self, text): if type(text) in [type([]), type(())]: text = " ".join(text) buff = (text + "\0").encode(default_scintilla_encoding) return self.SendScintilla(scintillacon.SCI_AUTOCSHOW, 0, buff) def SCIAutoCCancel(self): self.SendScintilla(scintillacon.SCI_AUTOCCANCEL) def SCIAutoCActive(self): return self.SendScintilla(scintillacon.SCI_AUTOCACTIVE) def SCIAutoCComplete(self): return self.SendScintilla(scintillacon.SCI_AUTOCCOMPLETE) def SCIAutoCStops(self, stops): buff = (stops + "\0").encode(default_scintilla_encoding) self.SendScintilla(scintillacon.SCI_AUTOCSTOPS, 0, buff) def SCIAutoCSetAutoHide(self, hide): self.SendScintilla(scintillacon.SCI_AUTOCSETAUTOHIDE, hide) def SCIAutoCSetFillups(self, fillups): self.SendScintilla(scintillacon.SCI_AUTOCSETFILLUPS, fillups) # Call tips def SCICallTipShow(self, text, pos=-1): if pos == -1: pos = self.GetSel()[0] buff = (text + "\0").encode(default_scintilla_encoding) self.SendScintilla(scintillacon.SCI_CALLTIPSHOW, pos, buff) def SCICallTipCancel(self): self.SendScintilla(scintillacon.SCI_CALLTIPCANCEL) def SCICallTipActive(self): return self.SendScintilla(scintillacon.SCI_CALLTIPACTIVE) def SCICallTipPosStart(self): return self.SendScintilla(scintillacon.SCI_CALLTIPPOSSTART) def SCINewline(self): self.SendScintilla(scintillacon.SCI_NEWLINE) # Lexer etc def SCISetKeywords(self, keywords, kw_list_no=0): buff = (keywords + "\0").encode(default_scintilla_encoding) self.SendScintilla(scintillacon.SCI_SETKEYWORDS, kw_list_no, buff) def SCISetProperty(self, name, value): name_buff = array.array("b", (name + "\0").encode(default_scintilla_encoding)) val_buff = array.array( "b", (str(value) + "\0").encode(default_scintilla_encoding) ) address_name_buffer = name_buff.buffer_info()[0] address_val_buffer = val_buff.buffer_info()[0] self.SendScintilla( scintillacon.SCI_SETPROPERTY, address_name_buffer, address_val_buffer ) def SCISetStyleBits(self, nbits): self.SendScintilla(scintillacon.SCI_SETSTYLEBITS, nbits) # Folding def SCIGetFoldLevel(self, lineno): return self.SendScintilla(scintillacon.SCI_GETFOLDLEVEL, lineno) def SCIToggleFold(self, lineno): return self.SendScintilla(scintillacon.SCI_TOGGLEFOLD, lineno) def SCIEnsureVisible(self, lineno): self.SendScintilla(scintillacon.SCI_ENSUREVISIBLE, lineno) def SCIGetFoldExpanded(self, lineno): return self.SendScintilla(scintillacon.SCI_GETFOLDEXPANDED, lineno) # right edge def SCISetEdgeColumn(self, edge): self.SendScintilla(scintillacon.SCI_SETEDGECOLUMN, edge) def SCIGetEdgeColumn(self): return self.SendScintilla(scintillacon.SCI_GETEDGECOLUMN) def SCISetEdgeMode(self, mode): self.SendScintilla(scintillacon.SCI_SETEDGEMODE, mode) def SCIGetEdgeMode(self): return self.SendScintilla(scintillacon.SCI_GETEDGEMODE) def SCISetEdgeColor(self, color): self.SendScintilla(scintillacon.SCI_SETEDGECOLOUR, color) def SCIGetEdgeColor(self): return self.SendScintilla(scintillacon.SCI_GETEDGECOLOR) # Multi-doc def SCIGetDocPointer(self): return self.SendScintilla(scintillacon.SCI_GETDOCPOINTER) def SCISetDocPointer(self, p): return self.SendScintilla(scintillacon.SCI_SETDOCPOINTER, 0, p) def SCISetWrapMode(self, mode): return self.SendScintilla(scintillacon.SCI_SETWRAPMODE, mode) def SCIGetWrapMode(self): return self.SendScintilla(scintillacon.SCI_GETWRAPMODE) class CScintillaEditInterface(ScintillaControlInterface): def close(self): self.colorizer = None def Clear(self): self.SendScintilla(win32con.WM_CLEAR) def FindText(self, flags, range, findText): """LPARAM for EM_FINDTEXTEX: typedef struct _findtextex { CHARRANGE chrg; LPCTSTR lpstrText; CHARRANGE chrgText;} FINDTEXTEX; typedef struct _charrange { LONG cpMin; LONG cpMax;} CHARRANGE; """ findtextex_fmt = "llPll" ## Scintilla does not handle unicode in EM_FINDTEXT msg (FINDTEXTEX struct) txt_buff = (findText + "\0").encode(default_scintilla_encoding) txt_array = array.array("b", txt_buff) ft_buff = struct.pack( findtextex_fmt, range[0], range[1], txt_array.buffer_info()[0], 0, 0 ) ft_array = array.array("b", ft_buff) rc = self.SendScintilla(EM_FINDTEXTEX, flags, ft_array.buffer_info()[0]) ftUnpacked = struct.unpack(findtextex_fmt, ft_array) return rc, (ftUnpacked[3], ftUnpacked[4]) def GetSel(self): currentPos = self.SendScintilla(scintillacon.SCI_GETCURRENTPOS) anchorPos = self.SendScintilla(scintillacon.SCI_GETANCHOR) if currentPos < anchorPos: return (currentPos, anchorPos) else: return (anchorPos, currentPos) return currentPos def GetSelText(self): start, end = self.GetSel() txtBuf = array.array("b", null_byte * (end - start + 1)) addressTxtBuf = txtBuf.buffer_info()[0] # EM_GETSELTEXT is documented as returning the number of chars # not including the NULL, but scintilla includes the NULL. A # quick glance at the scintilla impl doesn't make this # obvious - the NULL is included in the 'selection' object # and reflected in the length of that 'selection' object. # I expect that is a bug in scintilla and may be fixed by now, # but we just blindly assume that the last char is \0 and # strip it. self.SendScintilla(EM_GETSELTEXT, 0, addressTxtBuf) return txtBuf.tobytes()[:-1].decode(default_scintilla_encoding) def SetSel(self, start=0, end=None): if type(start) == type(()): assert ( end is None ), "If you pass a point in the first param, the second must be None" start, end = start elif end is None: end = start if start < 0: start = self.GetTextLength() if end < 0: end = self.GetTextLength() assert start <= self.GetTextLength(), "The start postion is invalid (%d/%d)" % ( start, self.GetTextLength(), ) assert end <= self.GetTextLength(), "The end postion is invalid (%d/%d)" % ( end, self.GetTextLength(), ) cr = struct.pack("ll", start, end) crBuff = array.array("b", cr) addressCrBuff = crBuff.buffer_info()[0] rc = self.SendScintilla(EM_EXSETSEL, 0, addressCrBuff) def GetLineCount(self): return self.SendScintilla(win32con.EM_GETLINECOUNT) def LineFromChar(self, charPos=-1): if charPos == -1: charPos = self.GetSel()[0] assert ( charPos >= 0 and charPos <= self.GetTextLength() ), "The charPos postion (%s) is invalid (max=%s)" % ( charPos, self.GetTextLength(), ) # return self.SendScintilla(EM_EXLINEFROMCHAR, charPos) # EM_EXLINEFROMCHAR puts charPos in lParam, not wParam return self.SendScintilla(EM_EXLINEFROMCHAR, 0, charPos) def LineIndex(self, line): return self.SendScintilla(win32con.EM_LINEINDEX, line) def ScrollCaret(self): return self.SendScintilla(win32con.EM_SCROLLCARET) def GetCurLineNumber(self): return self.LineFromChar(self.SCIGetCurrentPos()) def GetTextLength(self): return self.SendScintilla(scintillacon.SCI_GETTEXTLENGTH) def GetTextRange(self, start=0, end=-1, decode=True): if end == -1: end = self.SendScintilla(scintillacon.SCI_GETTEXTLENGTH) assert end >= start, "Negative index requested (%d/%d)" % (start, end) assert ( start >= 0 and start <= self.GetTextLength() ), "The start postion is invalid" assert end >= 0 and end <= self.GetTextLength(), "The end postion is invalid" initer = null_byte * (end - start + 1) buff = array.array("b", initer) addressBuffer = buff.buffer_info()[0] tr = struct.pack("llP", start, end, addressBuffer) trBuff = array.array("b", tr) addressTrBuff = trBuff.buffer_info()[0] num_bytes = self.SendScintilla(EM_GETTEXTRANGE, 0, addressTrBuff) ret = buff.tobytes()[:num_bytes] if decode: ret = ret.decode(default_scintilla_encoding) return ret def ReplaceSel(self, str): buff = (str + "\0").encode(default_scintilla_encoding) self.SendScintilla(scintillacon.SCI_REPLACESEL, 0, buff) def GetLine(self, line=-1): if line == -1: line = self.GetCurLineNumber() start = self.LineIndex(line) end = self.LineIndex(line + 1) return self.GetTextRange(start, end) def SetReadOnly(self, flag=1): return self.SendScintilla(win32con.EM_SETREADONLY, flag) def LineScroll(self, lines, cols=0): return self.SendScintilla(win32con.EM_LINESCROLL, cols, lines) def GetFirstVisibleLine(self): return self.SendScintilla(win32con.EM_GETFIRSTVISIBLELINE) def SetWordWrap(self, mode): if mode != win32ui.CRichEditView_WrapNone: raise ValueError("We dont support word-wrap (I dont think :-)") class CScintillaColorEditInterface(CScintillaEditInterface): ################################ # Plug-in colorizer support def _GetColorizer(self): if not hasattr(self, "colorizer"): self.colorizer = self._MakeColorizer() return self.colorizer def _MakeColorizer(self): # Give parent a chance to hook. parent_func = getattr(self.GetParentFrame(), "_MakeColorizer", None) if parent_func is not None: return parent_func() from . import formatter ## return formatter.PythonSourceFormatter(self) return formatter.BuiltinPythonSourceFormatter(self) def Colorize(self, start=0, end=-1): c = self._GetColorizer() if c is not None: c.Colorize(start, end) def ApplyFormattingStyles(self, bReload=1): c = self._GetColorizer() if c is not None: c.ApplyFormattingStyles(bReload) # The Parent window will normally hook def HookFormatter(self, parent=None): c = self._GetColorizer() if c is not None: # No need if we have no color! c.HookFormatter(parent) class CScintillaEdit(window.Wnd, CScintillaColorEditInterface): def __init__(self, wnd=None): if wnd is None: wnd = win32ui.CreateWnd() window.Wnd.__init__(self, wnd) def SendScintilla(self, msg, w=0, l=0): return self.SendMessage(msg, w, l) def CreateWindow(self, style, rect, parent, id): self._obj_.CreateWindow("Scintilla", "Scintilla", style, rect, parent, id, None)