Scintilla主要用于对文本的编辑,因此该部分的功能是Scintilla的核心所在。Scintilla以字节为基础单元处理整个文本。对于不同的编码格式,每个字符所占的字节个数并不相同。
比如常用的UTF-8编码,一个中文等于三个字节,中文标点占三个字节。一个英文字符等于一个字节,英文标点占一个字节。而对于Unicode编码,一个英文等于两个字节,一个中文(含繁体)等于两个字节。
在实际开发中,选择UTF-8编码,对于文本"abc",调用Scintilla获取文档字节总长的方法(SCI_GETLENGTH),返回值为3;对于文本"测试",调用Scintilla获取文档字节总长的方法(SCI_GETLENGTH),返回值为6。
SCI_GETTEXT(position length, char *text NUL-terminated) → position
该消息可获取从文档开头获取length-1长度的字节,所获取字节存入形参text内,因此text的空间应至少分配length的字节(最后一个字节为\0)。该消息返回值position为所获取字节的长度(注:改返回值通常等于length-1)。如果需要获取文档中的全部字节,可以先调用消息SCI_GETLENGTH,获取文档字节总长lenTotal,然后再调用方法:
SCI_GETTEXT(lenTotal+1, *text)
以UTF-8编码,如下文本内容为例:
如果对该文本发送如下消息:
SCI_GETTEXT(3, *text)
则返回的position=2,text所获取的文本内容为“Sc”。
如果对该文本发送如下消息:
SCI_GETTEXT(15, *text)
则返回的position=14,text所获取的文本内容为“Scintilla是��”。此处最后的乱码是由于UTF-8中文占三个字节,而传入的length=15,实际获取字节数为length-1=14,刚好少了一个字节。如果发送如下消息:
SCI_GETTEXT(16, *text)
则返回的position=15,text所获取的文本内容为“Scintilla是一”。
SCI_SETTEXT(
, const char *text)
该消息可替换当前编辑器中的所有文本内容(即先清空后载入text的内容),text最后必须以\0作为结尾。
以UTF-8编码,如下文本内容为例:
如果对该文本发送如下消息:
SCI_SETTEXT(0,"Scintilla完全开放源代码,并且提供一个license允许用户自由地将它用在开源软件或是商业软件中")
则文本内容为如下:
注:本消息(SCI_SETTEXT)经常用来载入并显示文本文件。而对于打开文本文件的操作,正常而言是不允许有回退(undo)操作的,因此在完成本消息调用后,通常会调用消息SCI_EMPTYUNDOBUFFER,用来清空回退(undo)操作历史序列。
SCI_SETSAVEPOINT()
该消息用于设置控件内文本内容的保存标记。通常本消息与外部保存操作联动(Ctrl+S)。当文本内容变化后,回退(undo)操作历史序列会有相应变化,此时如果调用SCI_GETMODIFY消息:
bool dirtyFlag = SCI_GETMODIFY()
返回的当前文本修改状态dirtyFlag = true。
该消息使用场景:
打开文本文件后,执行SCI_SETSAVEPOINT()
保存文本文件后,执行SCI_SETSAVEPOINT()
SCI_SETSAVEPOINT消息与如下两个事件相关:
SCN_SAVEPOINTREACHED :执行SCI_SETSAVEPOINT后,该事件触发。
SCN_SAVEPOINTLEFT :当文本内容有更改后,该事件触发。(此时外部容器可以做对应处理,比如可以将文件标题最后增加*号,表示需要保存)
SCI_GETLINE(line line, char *text) → position
该消息用于获取某一行(如果有换行情况,则换行符也会被获取)的文本内容。形参line的起始为序号0,所获取的文本内容被存入在形参text中。text的大小可以通过调用消息获取:
SCI_LINELENGTH(line line)
对于消息SCI_GETLINE而言,如果其形参line的值大于文本的行数,则text存入的字节为空。
以UTF-8编码,如下文本内容为例(注意第一行最后有换行):
如果对该文本执行如下代码:
int line = 0; int len = SCI_LINELENGTH(line); //伪代码 char* textLine = new char[len+1]; memset(textLine, 0, len + 1); SCI_GETLINE(line, textLine) //伪代码
执行过程中的变量值为(注意最后的\r\n):
len = 59; textLine = "Scintilla是一个强大和稳定的源代码编辑控件\r\n"
SCI_REPLACESEL(
, const char *text)
该消息用于替换选中的文本内容,其中形参text为待替入的字符串,以\0结尾。如果当前没有任何选中的内容,则该消息实际上执行的是插入操作(插入点为光标所在位置)。
完成本消息的调用后,光标位置位于插入字符串的末端。
以UTF-8编码,如下文本内容为例(注意选中的文本):
如果对该文本发送如下消息:
SCI_REPLACESEL(0,"123abc")
则结果为:
注意:执行该消息后,光标位置为所插入字符串"123abc"的末端。
SCI_SETREADONLY(bool readOnly), SCI_GETREADONLY → bool
SCI_SETREADONLY与SCI_GETREADONLY 用于设置与读取文本的只读属性。当发送如下消息:
SCI_SETREADONLY(true)
则文本内容不可编辑。
在Scintilla的源码中,与编辑相关的处理,都会判断当前文本的只读属性,如Document.cxx中的插入字符串操作,有如下判断:
Sci::Position Document::InsertString(Sci::Position position, const char *s, Sci::Position insertLength) { if (insertLength <= 0) { return 0; } CheckReadOnly(); // 判断当前文本的只读属性 if (cb.IsReadOnly()) { return 0; } ...... }
SCI_GETTEXTRANGE(
, Sci_TextRange *tr) → position
该消息类似SCI_GETTEXT消息,用于获得文档中的文本内容。区别在于SCI_GETTEXT是从文档头部开始获取,而本消息SCI_GETTEXTRANGE可以设定获取范围。
Sci_TextRange 是Scintilla定义的一个用于存储文本范围的数据结构,如下为其定义:
typedef long Sci_PositionCR; struct Sci_CharacterRange { Sci_PositionCR cpMin; Sci_PositionCR cpMax; }; struct Sci_TextRange { struct Sci_CharacterRange chrg; //文本范围: 起始点与结束点 char *lpstrText; //保存所选文本范围内的内容 };
如果对该文本执行如下代码:
char text[10000] = { 0 }; Sci_TextRange textRange; textRange.chrg.cpMin = 1; textRange.chrg.cpMax = 5; textRange.lpstrText = text; SCI_GETTEXTRANGE(&textRange); //伪代码
执行过程中的变量值为:
text = "cint"
如果对该文本执行如下代码:
char text[10000] = { 0 }; Sci_TextRange textRange; textRange.chrg.cpMin = 9; textRange.chrg.cpMax = 15; textRange.lpstrText = text; SCI_GETTEXTRANGE(&textRange); //伪代码
执行过程中的变量值为(注意textRange设定的范围包含字节数为6,是3的整倍数,刚好包含两个UTF-8中文字符。如果不是3的整倍数,则获取的文本末尾会是乱码):
text = "是一"
SCI_GETSTYLEDTEXT(
, Sci_TextRange *tr) → position
该消息用于获取每个字节的值以及其当前样式,具体所返回数据详见下面的样例。在Scintilla中,文本内容的样式控制(如设置字体、下划线等)是通过设定不同的样式类型完成的。如下为Scintilla预先定义好的样式类型:
#define STYLE_DEFAULT 32 //默认样式 #define STYLE_LINENUMBER 33 #define STYLE_BRACELIGHT 34 #define STYLE_BRACEBAD 35 #define STYLE_CONTROLCHAR 36 #define STYLE_INDENTGUIDE 37 #define STYLE_CALLTIP 38 #define STYLE_FOLDDISPLAYTEXT 39 #define STYLE_LASTPREDEFINED 39 #define STYLE_MAX 255 //最多可以设置255个样式
在Scintilla文本中的每一个字节都有其对应的样式,因此在显示文本时,Scintilla便可以根据每一个字节的样式,来进行特性化显示。具体样式的设定方法,在后面Styling相关说明时有详细展示。
本消息中的结构体Sci_TextRange在介绍SCI_GETTEXTRANGE时已经做过详细说明。
以UTF-8编码,如下文本内容为例(其中字符串"Sci"的样式做了单独定义,其样式值设定为STYLE_TEST=50):
如果对该文本执行如下代码:
#define STYLE_TEST 50 //自定义样式类型 STYLE_TEST=50 SCI_GETSTYLEDTEXT(SCI_STARTSTYLING,0); //伪代码,样式起始字节 SCI_GETSTYLEDTEXT(SCI_SETSTYLING, 3, STYLE_TEST); //伪代码,样式结束字节 char styledText[10000] = { 0 }; Sci_TextRange textRange; textRange.chrg.cpMin = 0; textRange.chrg.cpMax = 15; textRange.lpstrText = styledText; SCI_GETSTYLEDTEXT(&textRange); //伪代码
执行过程中的变量值styledText为:
- styledText,100 0x00000005a46fa950 <字符串中的字符无效。> char[100] [0] 83 'S' char [1] 50 '2' char [2] 99 'c' char [3] 50 '2' char [4] 105 'i' char [5] 50 '2' char [6] 110 'n' char [7] 0 '\0' char [8] 116 't' char [9] 0 '\0' char [10] 105 'i' char [11] 0 '\0' char [12] 108 'l' char [13] 0 '\0' char [14] 108 'l' char [15] 0 '\0' char [16] 97 'a' char [17] 0 '\0' char
如上可见,变量styledText的第0、2、4位是文本字节的值“Sci”,第1、3、5位是定义的样式类型50,后面没有定义,则样式类型为0。
SCI_ALLOCATE(position bytes)
本消息用于分配一个足够大的文档缓冲区,从而避免了在文本内容修改时所造成的频繁文本内存扩容以及拷贝。
Scintilla的文本内容存放在CellBuffer的成员变量substance中:
class CellBuffer { private: bool hasStyles; bool largeDocument; SplitVectorsubstance; //存放Scintilla的文本内容 SplitVector style; bool readOnly; bool utf8Substance; Scintilla::LineEndType utf8LineEnds; ...... }
在对Scintilla的文本做增加字符串的动作时(比如输入一些字符串),会调用到CellBuffer的插入字符串方法:
void CellBuffer::BasicInsertString(Sci::Position position, const char *s, Sci::Position insertLength)
在该方法内部,成员变量substance会根据当前空间大小进行动态扩容:
void RoomFor(ptrdiff_t insertionLength) { if (gapLength <= insertionLength) { while (growSize < static_cast(body.size() / 6)) growSize *= 2; ReAllocate(body.size() + insertionLength + growSize); } }
因此,提前给Scintilla设置一个缓存空间,可以提高性能。
SCI_ADDTEXT(position length, const char *text)
该消息用于在光标所在位置插入一定长度的字符串(形参const char *text),插入长度为(形参position length)。如果text="123abc",length=4,则插入字符串为“123a”。
以UTF-8编码,如下文本内容为例:
如果对该文本发送如下消息(注意当前光标在文字“是”后面):
SCI_ADDTEXT(4,"123abc")
则结果为:
注意当前光标在新增加的字符串“123a”后面。
SCI_ADDSTYLEDTEXT(position length, cell *c)
该消息类似于SCI_ADDTEXT,用于在光标所在位置插入一定长度的字符串,所不同的是,插入字符串带有样式信息。
如预定义样式如下(具体样式的设定方法,在后面Styling相关说明时有详细展示):
#define STYLE_TEST 50 //自定义样式类型 STYLE_TEST=50,字体大小:12,颜色:蓝色,带下划线
如果对该文本执行如下代码:
#define STYLE_TEST 50 //自定义样式类型 STYLE_TEST=50 char textWithStyle[1000] = {0}; textWithStyle[0] = '1'; textWithStyle[1] = STYLE_SEL; textWithStyle[2] = '2'; textWithStyle[3] = STYLE_SEL; textWithStyle[4] = '3'; textWithStyle[5] = STYLE_SEL; textWithStyle[6] = 'a'; textWithStyle[7] = STYLE_SEL; textWithStyle[8] = 'b'; textWithStyle[9] = STYLE_SEL; textWithStyle[10] = 'c'; textWithStyle[11] = STYLE_SEL; SCI_GETSTYLEDTEXT(12,textWithStyle); //伪代码
则结果为:
SCI_APPENDTEXT(position length, const char *text)
该消息类似于SCI_ADDTEXT,区别在于本消息用于在文档结尾位置插入一定长度的字符串,而SCI_ADDTEXT则是在光标所在位置插入。
以UTF-8编码,如下文本内容为例:
如果对该文本发送如下消息(注意当前光标在文字“是”后面):
SCI_APPENDTEXT(4,"123abc")
则结果为:
注意当前新增加的字符串位于文档末尾处,光标位置不变,仍然在文字“是”后面,另外,当前文档如果有选中字符串,其选中状态不变。
SCI_INSERTTEXT(position pos, const char *text)
该消息类似于SCI_ADDTEXT,区别在于本消息用于在文档指定位置处插入全部长度的字符串,入参position pos 即为指定插入位置处。
如果对该文本发送如下消息:
SCI_INSERTTEXT(12,"123abc")
则结果为:
SCI_CHANGEINSERTION(position length, const char *text)
当准备在文本内容中增加或插入字符串时(比如调用SCI_SETTEXT、SCI_ADDTEXT以及SCI_INSERTTEXT等消息),Scintilla会发送通知事件:SCN_MODIFIED,发送同时会携带通知数据:Scintilla::NotificationData。
当有增加或插入字符串的动作时,Scintilla::NotificationData数据中的成员变量modificationType的值被设为SC_MOD_INSERTCHECK。
此时,用户可以通过该消息SCI_CHANGEINSERTION对即将增加或插入字符串做修改。
对Scintilla的通知事件的处理可以通过继承Editor类的纯虚函数实现:
virtual void NotifyParent(Scintilla::NotificationData scn) = 0;
实现方法为(QT程序会采用信号与槽的实现方式,原理一样):
//MyTextEdit继承ScintillaBase类,而ScintillaBase类又继承Editor类,从而可以处理Scintilla的通知事件 void MyTextEdit::NotifyParent(Scintilla::NotificationData *scn) { int snCode = (int)scn->nmhdr.code; switch (snCode) { case SCN_MODIFIED: { int modificationType = (int)scn->modificationType; switch (modificationType) { case SC_MOD_INSERTCHECK: { //TODO: 此处就可以发送SCI_CHANGEINSERTION消息对即将增加或插入字符串做修改 break; } default: break; } break; } } }
以UTF-8编码,如下文本内容为例:
对该文本内容的通知事件做如下处理(如果插入的字符串为"123abc",则将其修改为字符串"change",之后再插入):
//MyTextEdit继承ScintillaBase类,而ScintillaBase类又继承Editor类,从而可以处理Scintilla的通知事件 void MyTextEdit::NotifyParent(Scintilla::NotificationData *scn) { int snCode = (int)scn->nmhdr.code; switch (snCode) { case SCN_MODIFIED: { int modificationType = (int)scn->modificationType; switch (modificationType) { case SC_MOD_INSERTCHECK: { //如果插入的字符串为"123abc",则将其修改为字符串"change",之后再插入。 if (strcmp(scn->text, "123abc")==0) { m_sciEditor->SendScintilla(SCI_CHANGEINSERTION, 6, "change"); } break; } default: break; } break; } } }
此时对该文本发送如下消息:
SCI_INSERTTEXT(12,"123abc")
则结果为:
SCI_CLEARALL()
该消息用于清空全部文本内容。注:当前文本处于非只读状态
以UTF-8编码,如下文本内容为例:
此时对该文本发送如下消息:
SCI_CLEARALL()
则结果为:
SCI_DELETERANGE(position start, position lengthDelete)
该消息用于清空设定起始位置与长度的文本内容。注:当前文本处于非只读状态
此时对该文本发送如下消息:
SCI_DELETERANGE(1,8)
则结果为:
SCI_CLEARDOCUMENTSTYLE()
该消息用于清除当前文本内容的所有样式以及重置当前的折叠状态(取消全部折叠)。
以UTF-8编码,如下文本内容为例(其中字符串"Sci"有独特样式):
此时对该文本发送如下消息:
SCI_CLEARDOCUMENTSTYLE()
SCI_GETCHARAT(position pos) → int
该消息用于获取所选位置的字节(注意不是字符,特别是对于中文,如“中”,该字符的UTF-8编码为“E4B8AD”,因此如果此时发送消息SCI_GETCHARAT(0),则返回值为0xE4)。
此时对该文本发送如下消息:
int res = SCI_GETCHARAT(9);
则结果res值为:0xffffffe6。
SCI_GETSTYLEAT(position pos) → int
该消息用于清除当前文本内容的所有样式以及重置当前的折叠状态(取消全部折叠)。
以UTF-8编码,如下文本内容为例(其中字符串"Sci"的样式值设定为50。具体样式的设定方法,在后面Styling相关说明时有详细展示):
此时对该文本发送如下消息:
int res = SCI_GETSTYLEAT(0);
则结果res值为:50。
SCI_RELEASEALLEXTENDEDSTYLES()
SCI_ALLOCATEEXTENDEDSTYLES(int numberStyles) → int
这两个消息应用于扩展样式。在Scintilla中,文本内容的样式控制(如设置字体、下划线等)是通过设定不同的样式类型完成的。如下为Scintilla预先定义好的样式类型:
#define STYLE_DEFAULT 32 //默认样式 #define STYLE_LINENUMBER 33 #define STYLE_BRACELIGHT 34 #define STYLE_BRACEBAD 35 #define STYLE_CONTROLCHAR 36 #define STYLE_INDENTGUIDE 37 #define STYLE_CALLTIP 38 #define STYLE_FOLDDISPLAYTEXT 39 #define STYLE_LASTPREDEFINED 39 #define STYLE_MAX 255 //最多可以设置255个样式
由上可见,样式的定义值最多到255。但是针对页边(通常位于编辑器左侧,可以设置断点、页码等)以及注解功能,各自也需要样式定义,因此就需要扩充样式值。在Scintilla中,这两种消息分别对应如下两个函数:
void ViewStyle::ReleaseAllExtendedStyles() noexcept { nextExtendedStyle = 256; //从256开始 } int ViewStyle::AllocateExtendedStyles(int numberStyles) {//numberStyles是扩充的数量 const int startRange = nextExtendedStyle; nextExtendedStyle += numberStyles; EnsureStyle(nextExtendedStyle); for (int i=startRange; i后续针对页边以及注解的样式扩充消息:SCI_MARGINSETSTYLEOFFSET(页边样式偏移) 以及 SCI_ANNOTATIONSETSTYLEOFFSET(注解样式偏移)。其调用之前需要应用消息SCI_ALLOCATEEXTENDEDSTYLES。
SCI_TARGETASUTF8
SCI_TARGETASUTF8(
, char *s) → position 目前这个消息在Scintilla的源码中没有做实现,Scintilla的API文档对其描述主要是用于检索编码为UTF-8的目标值。
SCI_ENCODEDFROMUTF8,SCI_SETLENGTHFORENCODE
SCI_ENCODEDFROMUTF8(const char *utf8, char *encoded) → position
SCI_SETLENGTHFORENCODE(position bytes)
上述两个消息作用在于将UTF8字符串转换成当前文档的编码格式,在ScintillaQt.cpp中有如下处理过程:
std::string ScintillaQt::EncodedFromUTF8(std::string_view utf8) const { if (IsUnicodeMode()) { return std::string(utf8); } else { QString text = QString::fromUtf8(utf8.data(), static_cast(utf8.length())); QTextCodec *codec = QTextCodec::codecForName( CharacterSetID(CharacterSetOfDocument())); QByteArray ba = codec->fromUnicode(text); return std::string(ba.data(), ba.length()); } } 但是该函数并没有实际被调用,因此上述两消息暂时也无法应用。