Scintilla教程(2): 文本检索与修改

整体介绍

Scintilla主要用于对文本的编辑,因此该部分的功能是Scintilla的核心所在。Scintilla以字节为基础单元处理整个文本。对于不同的编码格式,每个字符所占的字节个数并不相同。

比如常用的UTF-8编码,一个中文等于三个字节,中文标点占三个字节。一个英文字符等于一个字节,英文标点占一个字节。而对于Unicode编码,一个英文等于两个字节,一个中文(含繁体)等于两个字节。

在实际开发中,选择UTF-8编码,对于文本"abc",调用Scintilla获取文档字节总长的方法(SCI_GETLENGTH),返回值为3;对于文本"测试",调用Scintilla获取文档字节总长的方法(SCI_GETLENGTH),返回值为6

SCI_GETTEXT

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编码,如下文本内容为例:

Scintilla教程(2): 文本检索与修改_第1张图片

 

如果对该文本发送如下消息:

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=15text所获取的文本内容为“Scintilla是一”

SCI_SETTEXT

SCI_SETTEXT(, const char *text)

该消息可替换当前编辑器中的所有文本内容(即先清空后载入text的内容),text最后必须以\0作为结尾。

以UTF-8编码,如下文本内容为例:

如果对该文本发送如下消息:

SCI_SETTEXT(0,"Scintilla完全开放源代码,并且提供一个license允许用户自由地将它用在开源软件或是商业软件中") 

Scintilla教程(2): 文本检索与修改_第2张图片

则文本内容为如下:

Scintilla教程(2): 文本检索与修改_第3张图片

注:本消息(SCI_SETTEXT)经常用来载入并显示文本文件。而对于打开文本文件的操作,正常而言是不允许有回退(undo)操作的,因此在完成本消息调用后,通常会调用消息SCI_EMPTYUNDOBUFFER,用来清空回退(undo)操作历史序列。

SCI_SETSAVEPOINT

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

SCI_GETLINE(line line, char *text) → position

该消息用于获取某一行(如果有换行情况,则换行符也会被获取)的文本内容。形参line的起始为序号0,所获取的文本内容被存入在形参text中。text的大小可以通过调用消息获取:

SCI_LINELENGTH(line line)

对于消息SCI_GETLINE而言,如果其形参line的值大于文本的行数,则text存入的字节为空。

以UTF-8编码,如下文本内容为例(注意第一行最后有换行):

Scintilla教程(2): 文本检索与修改_第4张图片 

如果对该文本执行如下代码:

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

SCI_REPLACESEL(, const char *text)

该消息用于替换选中的文本内容,其中形参text为待替入的字符串,以\0结尾。如果当前没有任何选中的内容,则该消息实际上执行的是插入操作(插入点为光标所在位置)。

完成本消息的调用后,光标位置位于插入字符串的末端。

以UTF-8编码,如下文本内容为例(注意选中的文本):

Scintilla教程(2): 文本检索与修改_第5张图片 

如果对该文本发送如下消息:

SCI_REPLACESEL(0,"123abc")

则结果为:

Scintilla教程(2): 文本检索与修改_第6张图片 

注意:执行该消息后,光标位置为所插入字符串"123abc"的末端。

SCI_SETREADONLY,SCI_GETREADONLY

SCI_SETREADONLY(bool readOnly), SCI_GETREADONLY → bool

SCI_SETREADONLYSCI_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_GETTEXTRANGE(, Sci_TextRange *tr) → position

该消息类似SCI_GETTEXT消息,用于获得文档中的文本内容。区别在于SCI_GETTEXT是从文档头部开始获取,而本消息SCI_GETTEXTRANGE可以设定获取范围。

Sci_TextRangeScintilla定义的一个用于存储文本范围的数据结构,如下为其定义:

typedef long Sci_PositionCR;
​
struct Sci_CharacterRange {
    Sci_PositionCR cpMin;
    Sci_PositionCR cpMax;
};
​
struct Sci_TextRange {
    struct Sci_CharacterRange chrg; //文本范围: 起始点与结束点
    char *lpstrText;                //保存所选文本范围内的内容
};

以UTF-8编码,如下文本内容为例:Scintilla教程(2): 文本检索与修改_第7张图片

如果对该文本执行如下代码:

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_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):

Scintilla教程(2): 文本检索与修改_第8张图片 

如果对该文本执行如下代码:

#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

SCI_ALLOCATE(position bytes)

本消息用于分配一个足够大的文档缓冲区,从而避免了在文本内容修改时所造成的频繁文本内存扩容以及拷贝。

Scintilla的文本内容存放在CellBuffer的成员变量substance中:

class CellBuffer {
private:
    bool hasStyles;
    bool largeDocument;
    SplitVector substance;                //存放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

SCI_ADDTEXT(position length, const char *text)

该消息用于在光标所在位置插入一定长度的字符串(形参const char *text),插入长度为(形参position length)。如果text="123abc"length=4,则插入字符串为“123a”。

以UTF-8编码,如下文本内容为例:

Scintilla教程(2): 文本检索与修改_第9张图片 

如果对该文本发送如下消息(注意当前光标在文字“”后面):

SCI_ADDTEXT(4,"123abc")

则结果为:

Scintilla教程(2): 文本检索与修改_第10张图片 

注意当前光标在新增加的字符串“123a”后面。

SCI_ADDSTYLEDTEXT

SCI_ADDSTYLEDTEXT(position length, cell *c)

该消息类似于SCI_ADDTEXT,用于在光标所在位置插入一定长度的字符串,所不同的是,插入字符串带有样式信息。

如预定义样式如下(具体样式的设定方法,在后面Styling相关说明时有详细展示):

#define STYLE_TEST  50  //自定义样式类型 STYLE_TEST=50,字体大小:12,颜色:蓝色,带下划线

以UTF-8编码,如下文本内容为例:Scintilla教程(2): 文本检索与修改_第11张图片

如果对该文本执行如下代码:

#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);            //伪代码

则结果为:

Scintilla教程(2): 文本检索与修改_第12张图片

SCI_APPENDTEXT

SCI_APPENDTEXT(position length, const char *text)

该消息类似于SCI_ADDTEXT,区别在于本消息用于在文档结尾位置插入一定长度的字符串,而SCI_ADDTEXT则是在光标所在位置插入。

以UTF-8编码,如下文本内容为例:

如果对该文本发送如下消息(注意当前光标在文字“”后面):

Scintilla教程(2): 文本检索与修改_第13张图片 

SCI_APPENDTEXT(4,"123abc")

则结果为:

Scintilla教程(2): 文本检索与修改_第14张图片 

注意当前新增加的字符串位于文档末尾处,光标位置不变,仍然在文字“”后面,另外,当前文档如果有选中字符串,其选中状态不变。

SCI_INSERTTEXT

SCI_INSERTTEXT(position pos, const char *text)

该消息类似于SCI_ADDTEXT,区别在于本消息用于在文档指定位置处插入全部长度的字符串,入参position pos 即为指定插入位置处。

以UTF-8编码,如下文本内容为例:Scintilla教程(2): 文本检索与修改_第15张图片

如果对该文本发送如下消息:

SCI_INSERTTEXT(12,"123abc")

则结果为:

Scintilla教程(2): 文本检索与修改_第16张图片

 

SCI_CHANGEINSERTION

SCI_CHANGEINSERTION(position length, const char *text)

当准备在文本内容中增加或插入字符串时(比如调用SCI_SETTEXTSCI_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编码,如下文本内容为例:

Scintilla教程(2): 文本检索与修改_第17张图片 

对该文本内容的通知事件做如下处理(如果插入的字符串为"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")

则结果为:

Scintilla教程(2): 文本检索与修改_第18张图片

 

SCI_CLEARALL

SCI_CLEARALL()

该消息用于清空全部文本内容。注:当前文本处于非只读状态

以UTF-8编码,如下文本内容为例:

Scintilla教程(2): 文本检索与修改_第19张图片 

此时对该文本发送如下消息:

SCI_CLEARALL()

则结果为:

Scintilla教程(2): 文本检索与修改_第20张图片

SCI_DELETERANGE

SCI_DELETERANGE(position start, position lengthDelete)

该消息用于清空设定起始位置与长度的文本内容。注:当前文本处于非只读状态

以UTF-8编码,如下文本内容为例:Scintilla教程(2): 文本检索与修改_第21张图片

此时对该文本发送如下消息:

SCI_DELETERANGE(1,8)

则结果为:

Scintilla教程(2): 文本检索与修改_第22张图片

SCI_CLEARDOCUMENTSTYLE

SCI_CLEARDOCUMENTSTYLE()

该消息用于清除当前文本内容的所有样式以及重置当前的折叠状态(取消全部折叠)。

以UTF-8编码,如下文本内容为例(其中字符串"Sci"有独特样式):

Scintilla教程(2): 文本检索与修改_第23张图片 

此时对该文本发送如下消息:

SCI_CLEARDOCUMENTSTYLE()

则结果为:Scintilla教程(2): 文本检索与修改_第24张图片

SCI_GETCHARAT

SCI_GETCHARAT(position pos) → int

该消息用于获取所选位置的字节(注意不是字符,特别是对于中文,如“”,该字符的UTF-8编码为“E4B8AD”,因此如果此时发送消息SCI_GETCHARAT(0),则返回值为0xE4)。

以UTF-8编码,如下文本内容为例:Scintilla教程(2): 文本检索与修改_第25张图片

此时对该文本发送如下消息:

int res = SCI_GETCHARAT(9);

则结果res值为:0xffffffe6

SCI_GETSTYLEAT(position pos)

SCI_GETSTYLEAT(position pos) → int

该消息用于清除当前文本内容的所有样式以及重置当前的折叠状态(取消全部折叠)。

以UTF-8编码,如下文本内容为例(其中字符串"Sci"的样式值设定为50。具体样式的设定方法,在后面Styling相关说明时有详细展示):Scintilla教程(2): 文本检索与修改_第26张图片

此时对该文本发送如下消息:

int res = SCI_GETSTYLEAT(0);

则结果res值为:50

SCI_RELEASEALLEXTENDEDSTYLESSCI_ALLOCATEEXTENDEDSTYLES

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的源码中没有做实现,ScintillaAPI文档对其描述主要是用于检索编码为UTF-8的目标值。

SCI_ENCODEDFROMUTF8SCI_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());
    }
}

但是该函数并没有实际被调用,因此上述两消息暂时也无法应用。

你可能感兴趣的:(Scintilla,c++)