您的位置:首页 > 其它

Scintilla开源库使用指南

2012-11-07 20:10 519 查看
Scintilla是一个免费、跨平台、支持语法高亮的编辑控件。它完整支持源代码的编辑和调试,包括语法高亮、错误指示、代码完成(code completion)和调用提示(call tips)。能包含标记(marker)的页边(margin)可用于标记断点、折叠和高亮当前行。

Scintilla是一个免费、跨平台、支持语法高亮的编辑控件。它完整支持源代码的编辑和调试,包括语法高亮、错误指示、代码完成 (code completion)和调用提示(call tips)。能包含标记(marker)的页边(margin)可用于标记断点、折叠和高亮当前行。

可以从这里下载Scintilla库:http://scintilla.sourceforge.net/ScintillaDownload.html

这里有Scinilla相关的库下载,比如wxScintilla就是Scintilla的wxWidgets移植版。http://www.scintilla.org/ScintillaRelated.html

另外,Scintilla的作者为了演示这个东东的功能,编写了一个叫SciTE的演示程序。不过这个程序的功能已经强大到足以作为我 们的常用代码编辑器,很值得下载下来学习学习。

老规矩,还是从编译说起

偶只在Windows下编译过,所以只好说说Windows环境下的编译方法。对于Linux,没试过(丢人-_-)

下载、解压略过不提

首先进入scintilla的win32目录:

cd scintilla\win32


对于mingw,输入:

mingw32-make


对于VC6以上版本,输入:

nmake -f scintilla.mak


对于VC6(没试过,从Readme里看来的),输入:

nmake -f scintilla_vc6.mak


对于C++Builder,输入:

make -fscintilla.mak


编译完成后,在bin目录里会得到Scintilla.dll和SciLexer.dll文件,SciLexer.dll是包含了语法解析器 (Lexer)的Scintilla控件,一般来说我们只要用它就可以了。

需要说明的是,不管是用什么编译器生成的DLL文件,都可以供给其它编译器使用(就象系统DLL一样,任何编译器都能使用),所以不用为各种编译器都编译 一份。

如果觉得生成的SciLexer.dll太大的话,可以考虑去除自带的部分语法解析器。比如你打算只用它来高亮C++代码的话,可以:

进到src目录里,移除除LexCPP.cxx以外的所有Lex*.cxx文件
执行LexGen.py重建make文件和KeyWords.cxx文件(需要安装Python)。
重新按前面的方法编译,这样生成的SciLexer.dll就只带有C++语法解析器了,体积也大小减小了(我VC编译的结果是从1.4M减小到 206K)。

启用Scintilla作为编辑控件

要启用Scintilla,首先当然是要加载之前编译的DLL文件啦~~

::LoadLibrary(_T("SciLexer.dll"));

SciLexer.dll加载后会自动以"Scintilla"作为类名注册一个窗体类,我们只要直接用这个类名建立窗体就可以了:

::CreateWindow(_T("Scintilla"),...);

演示(在C++Builder下编写)

由于Scintilla主要是窗体操作,为了减少不必要的窗体代码(主要是偷懒外加推广一下C++Builder,呵呵),这里使用C++Builder 来写演示程序。对于一些C++Builder的VCL库特有的东东,后面会有解释的。

首先新建一个窗体应用程序,

然后在WinMain里载入SciLexer.dll:

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    LoadLibrary(_T("SciLexer.dll"));
    ...

最后,在TForm1的构造里建立Scintilla窗体:

#define SCINT_ID 1010

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    ::CreateWindow(_T("Scintilla"),
        NULL, WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_VISIBLE,
        0,0,ClientWidth,ClientHeight,
        Handle,
        (HMENU)SCINT_ID, HInstance, NULL);
}

很简单,是吧?对于Scintilla来说,没什么好解释的了。

这里主要给不了解C++Builder的童鞋介绍一下VCL的东东,以便于接下来的讲解和代码阅读(以及移植到其它编译器中)。

TForm1是一个C++Builder自动生成的窗体类,它继承自TForm,可以把它看成是WS_OVERLAPPEDWINDOW风格的HWND的 封装。
ClientWidth和ClientHeight是TForm的属性,看名字就知道它是客户区(ClientRect)的宽和高
Handle也是TForm的属性,就是该窗体的HWND
HInstance不用解释了吧,这是VCL的一个全局变量。

现在,我们的成果是这样的:



现在,看上去还比较土,接下来我们开始配置它,为使它成为可与VS媲美的代码编程器而战!

配置Scintilla的两种方法

配置Scintilla控件是通过向该控件发送配置命令实现的,各种配置命令可以在doc目录下找到(或者是这里http://scintilla.sourceforge.net/ScintillaDoc.html),后面的大部分事情都是在介绍这些配置命令。

有两种方法来发送配置命令,一种是直接使用SendMessage API。另一种是取得直接控制函数,通过函数来执行配置命令。

在Windows下,第二种方法要比第一种快得多。

直接控制函数的定义为:

typedef sptr_t (*SciFnDirect)(sptr_t ptr, unsigned int iMessage, uptr_t wParam, sptr_t lParam);

后三个参数和SendMessage的后三个参数一样。

SciFnDirect的第一个参数用于指定具体的Scintilla窗体,它类似于窗体的HWND又不完全相同,姑且也称之为句柄吧。它是用一个配置命 令取得的,下面马上就要讲到。

取得直接控制函数和句柄的方法是:

SciFnDirect fnDirect = (SciFnDirect)SendMessage(hwndEditor,SCI_GETDIRECTFUNCTION,0,0);
sptr_t ptrDirect = (sptr_t)SendMessage(hwndEditor,SCI_GETDIRECTPOINTER,0,0);

取得这两样东西以后,就可以直接执行配置命令了,如:

m_fnDirect(fnDirect, SCI_CLEARALL, 0, 0);

演示代码:编写成员函数SendEditor,用于配置之前建立的Scintilla控件。

?

让Scintilla支持语法高亮

有了前面的SendEditor控制函数,我们就可以配置语法高亮了,下面这段代码可以使我们的Scintilla控件显示C++语法高亮代码:

?

要支持语法高亮,要做三件事:

一、选定语法解析器
语法解析器用于把一大段代码分解成一个个的单词(token),另外还用于代码折叠的控制(后面会说到)。

选定语法解析器的命令是SCI_SETLEXER,如:

?
除了SCLEX_CPP以外,还有SCLEX_HTML、SCLEX_PERL、SCLEX_SQL、SCLEX_VB等一大堆,定 义在SciLexer.h里。现代的IDE应该可以定位SCLEX_CPP定义,它周围的SCLEX_XXX就是其它的语法解析器。

另外,也可以用SCI_SETLEXERLANGUAGE命令,如:

?
SCI_SETLEXERLANGUAGE接受的是一个字符串参数,这个字符串定义于代码解析器源代码(src\lex*.cxx) 最后面LexerModule开头的那行代码,那里的第三个参数就是。

二、设置关键字
语法解析只负责把代码拆分开,至于哪些是关键字,还得我们来指定。

这种方式带来了些许的灵活性,比如我们要高亮一种自定义的语言,这种语言的风格与C++类似(如Java、C#、php等),我们也 可以选定SCLEX_CPP作为语法解析器,然后定义自己的关键字。(所以不需要把各种解析器都编译进DLL文件里)

设置关键字的命令是SCI_SETKEYWORDS。它的wParam用于指定关键字种类,可以是0~8即9种类型,这样我们可以做 更细致的区分,如把关键字for if和int bool区分显示。lParam指定关键字,以空格分隔。

三、设置文本元素对应的字体风格
即字体、前景色、背景色、斜体粗体等

设置字体风格的命令以SCI_STYLE作为前缀,这组命令比较多,为了不浪费篇幅,偶这里只列举几个,其它的可以参考这里
(http://scintilla.sourceforge.net/ScintillaDoc.html#StyleDefinition)。

?
这里的styleNumber是指文本元素,如关键字、行号、控制字串等。前面代码中的SCE_C_XXXX是C++解析器分解出的 语法相关的元素。另外还有STYLE_DEFAULT(默认)、STYLE_LINENUMBER(行号)、STYLE_BRACELIGHT(括号匹 配)、STYLE_BRACEBAD(括号失配)、STYLE_CONTROLCHAR(控制字符)、STYLE_INDENTGUIDE(缩进线)、 STYLE_CALLTIP(调用提示)。

?
Scintilla文档建议的顺序是先向STYLE_DEFAULT设置一些通用风格,然后再用SCI_STYLECLEARALL 把所有元素风格重置成与STYLE_DEFAULT一致,最后单独设置其它元素。

演示,我们的编辑器支持C++高亮啦!

?
看上去不错,如果你愿意,还可以加上当前行高亮功能:

?
最后,建议把TAB宽度由默认的8改为4(依个人习惯~~)

?
现在,我们的成果是这样的:





这里继续学习Scintilla更多的控制命令和实现细节,完善我们的编辑器;

页边(Margins)和标记(Markers)

代码折叠是现代IDE和代码编辑器的必备功能,如果现在推出一个不支持折叠的编辑器,那是要被BS地~~。为了不被BS,很有必要先“研究”一下Scintilla的页边(Margins)和标记(Markers)功能。

页 边(Margins):页边是位于文本显示区左边的一竖条区域,它可以用于显示行号、书签、断点标记等东东。Scintilla最多可以有5个页边(从左 到右的编号为0~4),每个页边可以使用SCI_SETMARGINTYPEN命令确定是用于显示行号还是符号。我们可以用 SCI_SETMARGINWIDTHN命令控制一个页边的宽度,如果设置为0,则表示不显示该页边。默认是只显示宽度为16的1号页边。

标 记(Markers):标记,不用说也知道是用来标记文本位置(确切地说,是文本行)的。我们可以使用32种标记(编号0~31),我们可以自由决定这 32种标记的意义,如标记0用来表示断点、标记1~10表示书签、标记20表示语法错误行等等。不过,如果编辑器要支持代码折叠功能,我们得把标记 25~31留出来,把这7个标记作为代码折叠专用标记(后面还会讲到)。

告诉页边显示哪些标记

当页边不是设定为显示行号时(由SCI_SETMARGINTYPEN命令设置),那么它就会显示标记。刚才说过Scintilla有32种标记,一般来说不会让一个页边来显示所有的标记,而是只显示部分标记。

在一个页边里可以显示哪几种标记由SCI_SETMARGINMASKN命令设置,它的参数是一个32位掩码(mask)值,掩码值的第n位为1时表示该页边可显示n号标记。

所有页边相关的命令以SCI_SETMARGIN或SCI_GETMARGIN作为前缀,如:

SCI_SETMARGINTYPEN(int margin, int type) 设置页边显示行号还是符号,type可以是SC_MARGIN_SYMBOL或SC_MARGIN_NUMBER

SCI_SETMARGINWIDTHN(int margin, int pixelWidth) 设置页边宽度
SCI_SETMARGINMASKN(int margin, int mask) 设置页边掩码
SCI_SETMARGINSENSITIVEN(int margin, bool sensitive) 设置页边是否接受鼠标点击事件

所有标记相关的命令以SCI_MARKER作为前缀,如:

SCI_MARKERADD(int line, int markerNumber) 在指定行加入一个markerNumber号标记
SCI_MARKERDEFINE(int markerNumber, int markerSymbols) 定义markerNumber号标记的样式
SCI_MARKERDELETE(int line, int markerNumber) 在指定行上的删除markerNumber号标记
SCI_MARKERDELETEALL(int markerNumber) 删除文本中所有markerNumber号标记
SCI_MARKERSETFORE(int markerNumber, int colour) 为markerNumber号标记指定前景色
SCI_MARKERSETBACK(int markerNumber, int colour) 为markerNumber号标记指定背景色

?

显示效果是:



如果你不喜欢这些圆圈,可以用SCI_MARKERDEFINE命令改变标记的样式,可选的有:

SC_MARK_CIRCLE, SC_MARK_ROUNDRECT, SC_MARK_ARROW, SC_MARK_SMALLRECT,
SC_MARK_SHORTARROW, SC_MARK_EMPTY, SC_MARK_ARROWDOWN, SC_MARK_MINUS,
SC_MARK_PLUS, SC_MARK_VLINE, SC_MARK_LCORNER, SC_MARK_TCORNER, SC_MARK_BOXPLUS,
SC_MARK_BOXPLUSCONNECTED, SC_MARK_BOXMINUS, SC_MARK_BOXMINUSCONNECTED,
SC_MARK_LCORNERCURVE, SC_MARK_TCORNERCURVE, SC_MARK_CIRCLEPLUS,
SC_MARK_CIRCLEPLUSCONNECTED, SC_MARK_CIRCLEMINUS, SC_MARK_CIRCLEMINUSCONNECTED,
SC_MARK_BACKGROUND, SC_MARK_DOTDOTDOT, SC_MARK_ARROWS, SC_MARK_PIXMAP,
SC_MARK_FULLRECT, SC_MARK_LEFTRECT, SC_MARK_CHARACTER

默认是SC_MARK_CIRCLE,小圆圈。你可以试试其它的。(注意SC_MARK_CHARACTER比较特殊,它和一个ASCII码加起来决定标记显示为一个对应的ASCII字符)

有了这些基础,我们可以动手为Scintilla加入代码折叠功能了...

为Scintilla加入代码折叠功能

前面曾说过当编辑器有代码折叠功能时,25号到31号这7个标记是作为代码折叠专用标记的。在scintilla.h中,我们可以找到它们的定义:

#define SC_MARKNUM_FOLDEREND 25  //折叠状态(多级中间)
#define SC_MARKNUM_FOLDEROPENMID 26  //展开状态(多级中间)
#define SC_MARKNUM_FOLDERMIDTAIL 27  //被折叠代码块尾部(多级中间)
#define SC_MARKNUM_FOLDERTAIL 28  //被折叠代码块尾部
#define SC_MARKNUM_FOLDERSUB 29   //被折叠的代码块
#define SC_MARKNUM_FOLDER 30     //折叠状态
#define SC_MARKNUM_FOLDEROPEN 31 //展开状态
显示这些标记的掩码是0xFE000000,同样头文件里已经定义好了

#define SC_MASK_FOLDERS 0xFE000000
要加入代码折叠功能,还有一个最最关键的事情,就是要得到语法解析器(Lexer)的支持,上面的这些标记都是由语法解析器自动添加删除的。一般来说,只要用下面这条命令就可以了让语法解析器支持代码折叠了:
SendEditor(SCI_SETPROPERTY,(sptr_t)"fold",(sptr_t)"1");
?


这里TForm1::WndProc方法是Scintilla父窗体即我们的TForm1的窗口处理函数。

代码折叠以后我们要通过点击页边上的+和-标记来打开和折叠代码,所以需要页边接收鼠标点击事件:

这里TForm1::WndProc方法是Scintilla父窗体即我们的TForm1的窗口处理函数。

代码折叠以后我们要通过点击页边上的+和-标记来打开和折叠代码,所以需要页边接收鼠标点击事件:

SendEditor(SCI_SETMARGINSENSITIVEN, MARGIN_FOLD_INDEX, TRUE); //响应鼠标消息
这样,当有鼠标点击该页边后,Scintilla就会向它的父窗体发送代码为SCN_MARGINCLICK的WM_NOTIFY消息,其中的LParam为SCNotification*类型。关于SCNotification的详细信息请参考这里。SCNotification的position成员指出了点击位置对应的行号,最后我们用SCI_TOGGLEFOLD命令折叠或展开代码。

使用自定义图形

Scintilla自带的标记样式和VS比起来还有差距,反正偶是怎么调都觉得有点土。Scintilla允许我们自己定义标记的样式,方法是: 用SCI_MARKERDEFINE命令设置标记的样式为SC_MARK_PIXMAP 用SCI_MARKERDEFINEPIXMAP命令设置标记使用的图形,这里的图形要求是xpm格式。

怎样得到xpm格式图形

xpm在linux系统下用得比较多,它和BMP、jpg一样也是一种图片格式,有不少工具可以把图片转换成xpm格式的,比如我喜欢的免费看图软件XnView就可以,想必ACDSee也行吧。 xpm比较特殊的地方是它可以作为头文件直接被C语言调用,吃惊吧?用文本编辑器打开它看看,呵呵,其实它就是一个数组定义。 如下面这个数据(代码)就是后面马上就要用到的minus.xpm和plus.xpm图片文件的内容(从eclipse里挖出来的): ?


?

演示,使用自定义图形:

?

现在,我们的成果是这样的(与VS有一拼了吧^_^):



怎样支持自动缩进

在VS里编写C++代码时,输入回车换行后会保持和上一行的缩进一致,输入"{'字符后回车还会帮我们多缩进一次,输入'}'后又能自动退回。我们的编辑器也要实现这个功能。

现在再仔细了解一下Scintilla的通知消息(http://scintilla.sourceforge.net/ScintillaDoc.html#Notifications),除了前面用到的页边点击事件外,还有很多事件非常有用。

实现自动缩进功能我们要关心的事件通知是SCN_CHARADDEDSCN_UPDATEUI

当用户输入一个字符时,SCN_CHARADDED事件触发,SCNotification的ch成员保存了输入的字符。
当更新文档界面时,SCN_UPDATEUI事件触发。输入字符,改变字体风格,改变选区都会引起界面更新

演示代码

改写TForm1::WndProc,处理这两个事件,我们的编辑器支持自动缩进啦

?

下面是代码中用到的Scintilla命令的简单介绍

SCN_CHARADDED事件记录最后输入的字符,在SCN_UPDATEUI事件中处理缩进。
当输入回车时(LastProcessedChar == ' '),我们只需要保证新行和前一行的缩进相同就可以了。
SCI_GETLINEINDENTATION命令可以取得指定行的缩进数(即行首的空格数目)。
SCI_REPLACESEL命令用指定字符串替换选择区域
SCI_GETCURRENTPOS命令取得当前位置
SCI_GETCHARAT命令取得指定位置的字符
SCI_LINEFROMPOSITION命令取得指定位置所在的行号
SCI_POSITIONFROMLINE命令取得指定行号的起始位置
SCI_WORDSTARTPOSITION命令取得指定位置所在单词的起始位置,如xxx|xx,(|代表指定位置),那么它会返回|xxxxx的位置。同样还有SCI_WORDENDPOSITION命令。

SCI_BRACEMATCH取得括号的另一半位置,如指定位置的字符是'}'时,它返回匹配的'{'所在的位置。
SCI_SETTARGETSTART和SCI_SETTARGETEND设置TARGET的起始和始止位置,SCI_REPLACETARGET命令用指定字符串替换TARGET指定范围内的字符。

VS的代码完成和函数提示功能是很值得称道的,它们可以极大地提高我们的编程效率(造成我现在写代码时往往只记住前四个字母,如果在对象后面点了小数点后不出现提示就会心慌意乱的说-_-),尽管有时也会失效。

做为IDE这个功能是绝对不能少D。即使你只打算做个编辑器,如果有这个功能那也是一大亮点啊~~(目前很多代码编辑器都没这个功能的说)。

关于函数提示的几个命令以SCI_CALLTIP作为前缀,这里只介绍我们即将使用的几个命令(更多命令见:http://scintilla.sourceforge.net/ScintillaDoc.html#CallTips

SCI_CALLTIPSHOW(int posStart, const char *definition) 显示提示。posStart表示显示位置,definition是显示的内容

SCI_CALLTIPCANCEL 取消提示
SCI_CALLTIPACTIVE 如果当前编辑器中有提示信息,返回1,否则返回0
SCI_CALLTIPSETHLT(int highlightStart, int highlightEnd) 设置提示中的高亮位置,在VS里我们输入函数实参时函数提示会高亮当前输入的参数名。

在我们程序中加入提示的最佳时机是SCN_CHARADDED(见上一节)事件。当用户输入左圆括号'('时,取得括号左边的函数名,然后显示出该函数的完整定义。

下面的代码实现了CreateWindow和MoveWindow两个API的函数提示

?


当然,这个提示功能相当山寨啦。比如函数名和括号之间有空格提示就不出来了,函数嵌套调用时只会提示最后一个函数的参数。关于如果改进,大家各显神通吧。

另外,还有一个提外话,在实际的使用中,我们的函数信息不可能象这里一样是写死的,而是依据用户的输入动态生成的函数名及信息列表。这就涉及到一个C++代码解析的问题(还好,只要解析函数声明就行了),对于这一点,牛X的同学可以自己写解析代码;次牛X的可以考虑用W***E,Spirit,Regex等库帮助解析;象偶这种不牛的同学,则可以考虑利用CTAGS工具在后台线程中生成tag文件,我们从tag文件里取就可以了(当然,效率嘛~~可以参考C++Builder的函数提示效率-_-)。

代码完成和函数提示的用法类似,前缀是SCI_AUTOC,具体命令见:http://scintilla.sourceforge.net/ScintillaDoc.html#Autocompletion

?


SCI_AUTOCSHOW命令的第一个参数表示已经输入了多少个字符。这对于代码自动完成是很有帮助的,比如我们可以用它帮助用户输入长串的单词,如:

?

支持中文

Scintilla默认用的是ANSI编码,所以编辑中文之类的多字节编码时,会出错半个字符的问题。我们可以使用SCI_SETCODEPAGE命令设置使用的编码。

为了支持多语言,建议使用UTF8编码:

// UTF-8编码
SendEditor(SCI_SETCODEPAGE,SC_CP_UTF8);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: