Scintilla开源库使用指南
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Scintilla开源库使⽤指南
Scintilla是⼀个免费、跨平台、⽀持语法⾼亮的编辑控件。
它完整⽀持源代码的编辑和调试,包括语法⾼亮、错误指⽰、代码完成
(code completion)和调⽤提⽰(call tips)。
能包含标记(marker)的页边(margin)可⽤于标记断点、折叠和⾼亮当前⾏。
Scintilla是⼀个免费、跨平台、⽀持语法⾼亮的编辑控件。
它完整⽀持源代码的编辑和调试,包括语法⾼亮、错误指⽰、代码完成(code completion)和调⽤提⽰(call tips)。
能包含标记(marker)的页边(margin)可⽤于标记断点、折叠和⾼亮当前⾏。
另外,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++代码的话,可以:
1. 进到src⽬录⾥,移除除LexCPP.cxx以外的所有Lex*.cxx⽂件
2. 执⾏LexGen.py重建make⽂件和KeyWords.cxx⽂件(需要安装Python)。
3. 重新按前⾯的⽅法编译,这样⽣成的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的两种⽅法
有两种⽅法来发送配置命令,⼀种是直接使⽤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控件。
#include <Scintilla.h>
#include <SciLexer.h>
class TForm1 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
SciFnDirect m_fnDirect;
sptr_t m_ptrDirect;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
sptr_t SendEditor(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0)
{
return m_fnDirect(m_ptrDirect, iMessage, wParam, lParam);
}
};
#define SCINT_ID 1010
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
HWND hwndEditor = ::CreateWindow(_T("Scintilla"),
NULL, WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_VISIBLE,
0,0,ClientWidth,ClientHeight,
Handle,
(HMENU)SCINT_ID, HInstance, NULL);
m_fnDirect = (SciFnDirect)SendMessage(hwndEditor,SCI_GETDIRECTFUNCTION,0,0);
m_ptrDirect = (sptr_t)SendMessage(hwndEditor,SCI_GETDIRECTPOINTER,0,0);
}
让Scintilla⽀持语法⾼亮
有了前⾯的SendEditor控制函数,我们就可以配置语法⾼亮了,下⾯这段代码可以使我们的Scintilla控件显⽰C++语法⾼亮代码:
const char* g_szKeywords=
"asm auto bool break case catch char class const "
"const_cast continue default delete do double "
"dynamic_cast else enum explicit extern false finally "
"float for friend goto if inline int long mutable "
"namespace new operator private protected public "
"register reinterpret_cast register return short signed "
"sizeof static static_cast struct switch template "
"this throw true try typedef typeid typename "
"union unsigned using virtual void volatile "
"wchar_t while";
...
SendEditor(SCI_SETLEXER, SCLEX_CPP); //C++语法解析SendEditor(SCI_SETKEYWORDS, 0, (sptr_t)g_szKeywords);//设置关键字// 下⾯设置各种语法元素前景⾊SendEditor(SCI_STYLESETFORE, SCE_C_WORD, 0x00FF0000); //关键字SendEditor(SCI_STYLESETFORE, SCE_C_STRING, 0x001515A3); //字符串SendEditor(SCI_STYLESETFORE, SCE_C_CHARACTER, 0x001515A3); //字符SendEditor(SCI_STYLESETFORE, SCE_C_PREPROCESSOR, 0x00808080);//预编译开关SendEditor(SCI_STYLESETFORE, SCE_C_COMMENT, 0x00008000);//块注释SendEditor(SCI_STYLESETFORE, SCE_C_COMMENTLINE, 0x00008000);//⾏注释SendEditor(SCI_STYLESETFORE, SCE_C_COMMENTDOC, 0x00008000);//⽂档注释(/**开头)
要⽀持语法⾼亮,要做三件事:
⼀、选定语法解析器
语法解析器⽤于把⼀⼤段代码分解成⼀个个的单词(token),另外还⽤于代码折叠的控制(后⾯会说到)。
选定语法解析器的命令是SCI_SETLEXER,如:
SendEditor(SCI_SETLEXER, SCLEX_CPP);
除了SCLEX_CPP以外,还有SCLEX_HTML、SCLEX_PERL、SCLEX_SQL、SCLEX_VB等⼀⼤堆,定义在SciLexer.h⾥。
现代的IDE 应该可以定位SCLEX_CPP定义,它周围的SCLEX_XXX就是其它的语法解析器。
另外,也可以⽤SCI_SETLEXERLANGUAGE命令,如:
SendEditor(SCI_SETLEXERLANGUAGE, 0, (sptr_t)"cpp");
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_STYLESETBACK(int styleNumber, int colour) //设置背景⾊SCI_STYLESETFORE(int styleNumber, int colour) //设置前景⾊SCI_STYLESETFONT(int styleNumber, char *fontName) //设置字体SCI_STYLESETSIZE(int styleNumber, int sizeInPoints)//设置字号SCI_STYLESETBOLD(int styleNumber, bool bold) //设置粗体
这⾥的styleNumber是指⽂本元素,如关键字、⾏号、控制字串等。
前⾯代码中的SCE_C_XXXX是C++解析器分解出的语法相关的元素。
另外还有STYLE_DEFAULT(默认)、STYLE_LINENUMBER(⾏号)、STYLE_BRACELIGHT(括号匹配)、
STYLE_BRACEBAD(括号失配)、STYLE_CONTROLCHAR(控制字符)、STYLE_INDENTGUIDE(缩进线)、
STYLE_CALLTIP(调⽤提⽰)。
SCI_STYLECLEARALL //把所有⽂本元素设置成与STYLE_DEFAULT相同的风格
Scintilla⽂档建议的顺序是先向STYLE_DEFAULT设置⼀些通⽤风格,然后再⽤SCI_STYLECLEARALL 把所有元素风格重置成与STYLE_DEFAULT⼀致,最后单独设置其它元素。
演⽰,我们的编辑器⽀持C++⾼亮啦!
#include <Scintilla.h>
#include <SciLexer.h>
...
void TForm1::setCppStyle()
{
const char* szKeywords1=
"asm auto break case catch class const "
"const_cast continue default delete do double "
"dynamic_cast else enum explicit extern false "
"for friend goto if inline mutable "
"namespace new operator private protected public "
"register reinterpret_cast return signed "
"sizeof static static_cast struct switch template "
"this throw true try typedef typeid typename "
"union unsigned using virtual volatile while";
const char* szKeywords2=
"bool char float int long short void wchar_t";
// 设置全局风格
SendEditor(SCI_STYLESETFONT, STYLE_DEFAULT,(sptr_t)"Courier New");
SendEditor(SCI_STYLESETSIZE, STYLE_DEFAULT,10);
SendEditor(SCI_STYLECLEARALL);
//C++语法解析
SendEditor(SCI_SETLEXER, SCLEX_CPP);
SendEditor(SCI_SETKEYWORDS, 0, (sptr_t)szKeywords1);//设置关键字
SendEditor(SCI_SETKEYWORDS, 1, (sptr_t)szKeywords2);//设置关键字
// 下⾯设置各种语法元素风格
SendEditor(SCI_STYLESETFORE, SCE_C_WORD, 0x00FF0000); //关键字
SendEditor(SCI_STYLESETFORE, SCE_C_WORD2, 0x00800080); //关键字
SendEditor(SCI_STYLESETBOLD, SCE_C_WORD2, TRUE); //关键字
SendEditor(SCI_STYLESETFORE, SCE_C_STRING, 0x001515A3); //字符串
SendEditor(SCI_STYLESETFORE, SCE_C_CHARACTER, 0x001515A3); //字符
SendEditor(SCI_STYLESETFORE, SCE_C_PREPROCESSOR, 0x00808080);//预编译开关
SendEditor(SCI_STYLESETFORE, SCE_C_COMMENT, 0x00008000);//块注释
SendEditor(SCI_STYLESETFORE, SCE_C_COMMENTLINE, 0x00008000);//⾏注释
SendEditor(SCI_STYLESETFORE, SCE_C_COMMENTDOC, 0x00008000);//⽂档注释(/**开头)
SendEditor(SCI_SETCARETLINEVISIBLE, TRUE);
SendEditor(SCI_SETCARETLINEBACK, 0xb0ffff);
}
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
...
setCppStyle();
}
看上去不错,如果你愿意,还可以加上当前⾏⾼亮功能:
SendEditor(SCI_SETCARETLINEVISIBLE, TRUE);
SendEditor(SCI_SETCARETLINEBACK, 0xb0ffff);
最后,建议把TAB宽度由默认的8改为4(依个⼈习惯~~)
SendEditor(SCI_SETTABWIDTH, 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号标记指定背景⾊
// 标记和页边演⽰
void TForm1::example()
{
// 先写10⾏⽂本上去
for(int i=0; i<10; i++)
SendEditor(SCI_APPENDTEXT, 12, (sptr_t)"hello world ");
// 0号页边,宽度为9,显⽰0号标记(0..0001B)
SendEditor(SCI_SETMARGINTYPEN,0,SC_MARGIN_SYMBOL);
SendEditor(SCI_SETMARGINWIDTHN,0, 9);
SendEditor(SCI_SETMARGINMASKN,0, 0x01);
// 1号页边,宽度为9,显⽰1,2号标记(0..0110B)
SendEditor(SCI_SETMARGINTYPEN,1, SC_MARGIN_SYMBOL);
SendEditor(SCI_SETMARGINWIDTHN,1, 9);
SendEditor(SCI_SETMARGINMASKN,1, 0x06);
// 2号页边,宽度为20,显⽰⾏号
SendEditor(SCI_SETMARGINTYPEN,2, SC_MARGIN_NUMBER);
SendEditor(SCI_SETMARGINWIDTHN,2, 20);
for(int i=0; i<10; i++)
{
// 前10⾏分别加⼊0~2号标记
SendEditor(SCI_MARKERADD, i, i%3);
}
// 设置标记的前景⾊
SendEditor(SCI_MARKERSETFORE,0,0x0000ff);//0-红⾊
SendEditor(SCI_MARKERSETFORE,1,0x00ff00);//1-绿⾊
SendEditor(SCI_MARKERSETFORE,2,0xff0000);//2-蓝⾊
}
显⽰效果是:
如果你不喜欢这些圆圈,可以⽤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");
#define MARGIN_FOLD_INDEX 2
void TForm1::setFold()
{
SendEditor(SCI_SETPROPERTY,(sptr_t)"fold",(sptr_t)"1");
SendEditor(SCI_SETMARGINTYPEN, MARGIN_FOLD_INDEX, SC_MARGIN_SYMBOL);//页边类型
SendEditor(SCI_SETMARGINMASKN, MARGIN_FOLD_INDEX, SC_MASK_FOLDERS); //页边掩码
SendEditor(SCI_SETMARGINWIDTHN, MARGIN_FOLD_INDEX, 11); //页边宽度
SendEditor(SCI_SETMARGINSENSITIVEN, MARGIN_FOLD_INDEX, TRUE); //响应⿏标消息
// 折叠标签样式
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_CIRCLEPLUS);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_CIRCLEMINUS);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_CIRCLEPLUSCONNECTED);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_CIRCLEMINUSCONNECTED);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE);
// 折叠标签颜⾊
SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERSUB, 0xa0a0a0);
SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERMIDTAIL, 0xa0a0a0);
SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERTAIL, 0xa0a0a0);
SendEditor(SCI_SETFOLDFLAGS, 16|4, 0); //如果折叠就在折叠⾏的上下各画⼀条横线
}
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
...
setFold();
}
void__fastcall TForm1::WndProc(Messages::TMessage &Message)
{
TForm::WndProc(Message);
if(Message.Msg == WM_NOTIFY){
SCNotification* notify = (SCNotification*)Message.LParam;
if(notify->nmhdr.code == SCN_MARGINCLICK &&
notify->nmhdr.idFrom == SCINT_ID){
// 确定是页边点击事件
const int line_number = SendEditor(SCI_LINEFROMPOSITION,notify->position);
SendEditor(SCI_TOGGLEFOLD, line_number);
}
}
}
这⾥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允许我们⾃⼰定义标记的样式,⽅法是:
1. ⽤SCI_MARKERDEFINE命令设置标记的样式为SC_MARK_PIXMAP
2. ⽤SCI_MARKERDEFINEPIXMAP命令设置标记使⽤的图形,这⾥的图形要求是xpm格式。
怎样得到xpm格式图形
xpm在linux系统下⽤得⽐较多,它和BMP、jpg⼀样也是⼀种图⽚格式,有不少⼯具可以把图⽚转换成xpm格式的,⽐如我喜欢的免费看图软件就可以,想必ACDSee也⾏吧。
xpm⽐较特殊的地⽅是它可以作为头⽂件直接被C语⾔调⽤,吃惊吧?⽤⽂本编辑器打开它看看,呵呵,其实它就是⼀个数组定义。
如下⾯这个数据(代码)就是后⾯马上就要⽤到的minus.xpm和plus.xpm图⽚⽂件的内容(从eclipse⾥挖出来的):
/* XPM */
static const char*minus_xpm[] = {
/* width height num_colors chars_per_pixel */
" 9 9 16 1",
/* colors */
"` c #8c96ac",
". c #c4d0da",
"# c #daecf4",
"a c #ccdeec",
"b c #eceef4",
"c c #e0e5eb",
"d c #a7b7c7",
"e c #e4ecf0",
"f c #d0d8e2",
"g c #b7c5d4",
"h c #fafdfc",
"i c #b4becc",
"j c #d1e6f1",
"k c #e4f2fb",
"l c #ecf6fc",
"m c #d4dfe7",
/* pixels */
"hbc.i.cbh",
"bffeheffb",
"mfllkllfm",
"gjkkkkkji",
"da`````jd",
"ga#j##jai",
"f.k##k#.a",
"#..jkj..#",
"hemgdgc#h"
};
/* XPM */
static const char*plus_xpm[] = {
/* width height num_colors chars_per_pixel */
" 9 9 16 1",
/* colors */
"` c #242e44",
". c #8ea0b5",
"# c #b7d5e4",
"a c #dcf2fc",
"b c #a2b8c8",
"c c #ccd2dc",
"d c #b8c6d4",
"e c #f4f4f4",
"f c #accadc",
"g c #798fa4",
"h c #a4b0c0",
"i c #cde5f0",
"j c #bcdeec",
"k c #ecf1f6",
"l c #acbccc",
"m c #fcfefc",
/* pixels */
"mech.hcem",
"eldikille",
"dlaa`akld",
".#ii`ii#.",
"g#`````fg",
".fjj`jjf.",
"lbji`ijbd",
"khb#idlhk",
"mkd.ghdkm"
};
演⽰,使⽤⾃定义图形:
#include "minus.xpm"
#include "plus.xpm"
void TForm1::setFold()
{
...
// 折叠标签样式
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_PIXMAP);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_PIXMAP);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_PIXMAP);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_PIXMAP);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE);
//
SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDER, (sptr_t)plus_xpm);
SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEROPEN, (sptr_t)minus_xpm);
SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEREND, (sptr_t)plus_xpm);
SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEROPENMID, (sptr_t)minus_xpm);
...
}
现在,我们的成果是这样的(与VS有⼀拼了吧^_^):
怎样⽀持⾃动缩进
在VS⾥编写C++代码时,输⼊回车换⾏后会保持和上⼀⾏的缩进⼀致,输⼊"{'字符后回车还会帮我们多缩进⼀次,输⼊'}'后⼜能⾃动退回。
我们的编辑器也要实现这个功能。
实现⾃动缩进功能我们要关⼼的事件通知是SCN_CHARADDED和SCN_UPDATEUI。
当⽤户输⼊⼀个字符时,SCN_CHARADDED事件触发,SCNotification的ch成员保存了输⼊的字符。
当更新⽂档界⾯时,SCN_UPDATEUI事件触发。
输⼊字符,改变字体风格,改变选区都会引起界⾯更新
演⽰代码
改写TForm1::WndProc,处理这两个事件,我们的编辑器⽀持⾃动缩进啦
void__fastcall TForm1::WndProc(Messages::TMessage &Message)
{
TForm::WndProc(Message);
if(Message.Msg == WM_NOTIFY)
{
...
// 处理⾃动缩进
static int LastProcessedChar = 0;
//在CharAdded事件中记录最后输⼊的字符
if(notify->nmhdr.code == SCN_CHARADDED)
{
LastProcessedChar = notify->ch;
}
// 在UpdateUI事件中处理缩进
if(notify->nmhdr.code == SCN_UPDATEUI && LastProcessedChar!=0)
{
int pos = SendEditor(SCI_GETCURRENTPOS); //取得当前位置
int line = SendEditor(SCI_LINEFROMPOSITION,pos); //取得当前⾏
//如果最后输⼊的字符是右括号的话就⾃动让当前⾏缩进和它匹配的左括号所在⾏⼀致
if( strchr("})>]",LastProcessedChar) &&
isspace(SendEditor(SCI_GETCHARAT,pos-2)) && //要求右括号左边是空⽩字符
LastProcessedChar!=0)
{
//找前⼀个单词起始位置,这⾥⽤它来确定右括号左边是否全是空⽩字符
int startpos = SendEditor(SCI_WORDSTARTPOSITION,pos-1,false);
int linepos = SendEditor(SCI_POSITIONFROMLINE,line); //当前⾏起始位置
if(startpos == linepos) //这样相当于判断右括号左边是否全是空⽩字符
{
int othpos = SendEditor(SCI_BRACEMATCH,pos-1); //得到对应的左括号所在的位置
int othline = SendEditor(SCI_LINEFROMPOSITION,othpos); //左括号所在⾏
int nIndent = SendEditor(SCI_GETLINEINDENTATION,othline);//左括号所在⾏的缩进值
// 替换右括号前⾯的空⽩字符,使之与左括号缩进⼀致
char space[1024];
memset(space,' ',1024);
SendEditor(SCI_SETTARGETSTART, startpos);
SendEditor(SCI_SETTARGETEND, pos-1);
SendEditor(SCI_REPLACETARGET,nIndent,(sptr_t)space);
}
}
// 如果输⼊的是回车,则保持与上⼀⾏缩进⼀致
// 如果上⼀⾏最后有效字符为左括号,就多缩进四个空格
if(LastProcessedChar == ' ')
{
if(line > 0)
{
// 得到上⼀⾏缩进设置
int nIndent = SendEditor(SCI_GETLINEINDENTATION,line-1);
// 查找上⼀⾏最后⼀个有效字符(⾮空⽩字符)
int nPrevLinePos = SendEditor(SCI_POSITIONFROMLINE,line-1);
int c = ' ';
for(int p = pos-2;
p>=nPrevLinePos && isspace(c);
p--, c=SendEditor(SCI_GETCHARAT,p));
// 如果是左括号,就多缩进四格
if(c && strchr("{([<",c)) nIndent+=4;
// 缩进...
char space[1024];
memset(space,' ',1024);
space[nIndent] = 0;
SendEditor(SCI_REPLACESEL, 0, (sptr_t)space);
}
}
LastProcessedChar = 0;
}
}
}
下⾯是代码中⽤到的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_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的函数提⽰
//我们要⾼亮的两个函数
const size_t FUNCSIZE=2;
char* g_szFuncList[FUNCSIZE]={ //函数名
"CreateWindow(",
"MoveWindow("
};
char* g_szFuncDesc[FUNCSIZE]={ //函数信息
"HWND CreateWindow("
"LPCTSTR lpClassName,"
" LPCTSTR lpWindowName,"
" DWORD dwStyle, "
" int x,"
" int y,"
" int nWidth,"
" int nHeight, "
" HWND hWndParent,"
" HMENU hMenu,"
" HANDLE hInstance,"
" PVOID lpParam"
")",
"BOOL MoveWindow("
"HWND hWnd,"
" int X,"
" int Y,"
" int nWidth,"
" int nHeight,"
" BOOL bRepaint"
")"
};
void__fastcall TForm1::WndProc(Messages::TMessage &Message)
{
TForm::WndProc(Message);
if(Message.Msg == WM_NOTIFY)
{
SCNotification* notify = (SCNotification*)Message.LParam;
...
if(notify->nmhdr.code == SCN_CHARADDED)
{
...
// 函数提⽰功能
static const char* pCallTipNextWord = NULL;//下⼀个⾼亮位置
static const char* pCallTipCurDesc = NULL;//当前提⽰的函数信息
if(notify->ch == '(') //如果输⼊了左括号,显⽰函数提⽰
{
char word[1000]; //保存当前光标下的单词(函数名)
TextRange tr; //⽤于SCI_GETTEXTRANGE命令
int pos = SendEditor(SCI_GETCURRENTPOS); //取得当前位置(括号的位置)
int startpos = SendEditor(SCI_WORDSTARTPOSITION,pos-1);//当前单词起始位置 int endpos = SendEditor(SCI_WORDENDPOSITION,pos-1);//当前单词终⽌位置
tr.chrg.cpMin = startpos; //设定单词区间,取出单词
tr.chrg.cpMax = endpos;
tr.lpstrText = word;
SendEditor(SCI_GETTEXTRANGE,0, sptr_t(&tr));
for(size_t i=0; i<FUNCSIZE; i++) //找找有没有我们认识的函数?
{
if(memcmp(g_szFuncList[i],word,sizeof(g_szFuncList[i])) == 0)
{ //找到啦,那么显⽰提⽰吧
pCallTipCurDesc = g_szFuncDesc[i]; //当前提⽰的函数信息
SendEditor(SCI_CALLTIPSHOW,pos,sptr_t(pCallTipCurDesc));//显⽰这个提⽰ const char*pStart = strchr(pCallTipCurDesc,'(')+1; //⾼亮第⼀个参数
const char*pEnd = strchr(pStart,',');//参数列表以逗号分隔
if(pEnd == NULL) pEnd = strchr(pStart,')');//若是最后⼀个参数,后⾯是右括号 SendEditor(SCI_CALLTIPSETHLT,
pStart-pCallTipCurDesc, pEnd-pCallTipCurDesc);
pCallTipNextWord = pEnd+1;//指向下⼀参数位置
break;
}
}
}
else if(notify->ch == ')') //如果输⼊右括号,就关闭函数提⽰
{
SendEditor(SCI_CALLTIPCANCEL);
pCallTipCurDesc = NULL;
pCallTipNextWord = NULL;
}
else if(notify->ch == ','&& SendEditor(SCI_CALLTIPACTIVE) && pCallTipCurDesc) {
//输⼊的是逗号,⾼亮下⼀个参数
const char*pStart = pCallTipNextWord;
const char*pEnd = s。