上接
页边(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()
-
{
-
-
for(int i=0; i<10; i++)
-
SendEditor(SCI_APPENDTEXT, 12, (sptr_t)"hello world ");
-
-
SendEditor(SCI_SETMARGINTYPEN,0,SC_MARGIN_SYMBOL);
-
SendEditor(SCI_SETMARGINWIDTHN,0, 9);
-
SendEditor(SCI_SETMARGINMASKN,0, 0x01);
-
-
SendEditor(SCI_SETMARGINTYPEN,1, SC_MARGIN_SYMBOL);
-
SendEditor(SCI_SETMARGINWIDTHN,1, 9);
-
SendEditor(SCI_SETMARGINMASKN,1, 0x06);
-
-
SendEditor(SCI_SETMARGINTYPEN,2, SC_MARGIN_NUMBER);
-
SendEditor(SCI_SETMARGINWIDTHN,2, 20);
-
-
for(int i=0; i<10; i++)
-
{
-
-
SendEditor(SCI_MARKERADD, i, i%3);
-
}
-
-
-
SendEditor(SCI_MARKERSETFORE,0,0x0000ff);
-
SendEditor(SCI_MARKERSETFORE,1,0x00ff00);
-
SendEditor(SCI_MARKERSETFORE,2,0xff0000);
-
}
显示效果是:
如果你不喜欢这些圆圈,可以用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的窗口处理函数。
代码折叠以后我们要通过点击页边上的+和-标记来打开和折叠代码,所以需要页边接收鼠标点击事件:
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格式的,比如我喜欢的免费看图软件就可以,想必ACDSee也行吧。
xpm比较特殊的地方是它可以作为头文件直接被C语言调用,吃惊吧?用文本编辑器打开它看看,呵呵,其实它就是一个数组定义。
如下面这个数据(代码)就是后面马上就要用到的minus.xpm和plus.xpm图片文件的内容(从eclipse里挖出来的):
static const char *minus_xpm[] = { " 9 9 16 1", "` 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", "hbc.i.cbh", "bffeheffb", "mfllkllfm", "gjkkkkkji", "da`````jd", "ga#j##jai", "f.k##k#.a", "#..jkj..#", "hemgdgc#h" };
static const char *plus_xpm[] = { " 9 9 16 1", "` 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", "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++代码时,输入回车换行后会保持和上一行的缩进一致,输入"{'字符后回车还会帮我们多缩进一次,输入'}'后又能自动退回。我们的编辑器也要实现这个功能。
现在再仔细了解一下Scintilla的通知消息(),除了前面用到的页边点击事件外,还有很多事件非常有用。
实现自动缩进功能我们要关心的事件通知是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;
-
-
if(notify->nmhdr.code == SCN_CHARADDED)
-
{
-
LastProcessedChar = notify->ch;
-
}
-
-
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_CALLTIP作为前缀,这里只介绍我们即将使用的几个命令(更多命令见:)
-
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;
-
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
-
{
-
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 = strchr(pStart,',');
-
if(pEnd == NULL) pEnd = strchr(pStart,')');
-
if(pEnd == NULL)
-
SendEditor(SCI_CALLTIPCANCEL);
-
else
-
{
-
SendEditor(SCI_CALLTIPSETHLT,pStart-pCallTipCurDesc, pEnd-pCallTipCurDesc);
-
pCallTipNextWord = pEnd+1;
-
}
-
}
-
}
-
...
-
}
-
}
效果如下:
当然,这个提示功能相当山寨啦。比如函数名和括号之间有空格提示就不出来了,函数嵌套调用时只会提示最后一个函数的参数。关于如果改进,大家各显神通吧。
另外,还有一个提外话,在实际的使用中,我们的函数信息不可能象这里一样是写死的,而是依据用户的输入动态生成的函数名及信息列表。这就涉及到一个C++代码解析的问题(还好,只要解析函数声明就行了),对于这一点,牛X的同学可以自己写解析代码;次牛X的可以考虑用,,等库帮助解析;象偶这种不牛的同学,则可以考虑利用工具在后台线程中生成tag文件,我们从tag文件里取就可以了(当然,效率嘛~~可以参考C++Builder的函数提示效率-_-)。
代码完成和函数提示的用法类似,前缀是SCI_AUTOC,具体命令见:
直接上代码:
-
void __fastcall TForm1::WndProc(Messages::TMessage &Message)
-
{
-
TForm::WndProc(Message);
-
if(Message.Msg == WM_NOTIFY)
-
{
-
...
-
if(notify->nmhdr.code == SCN_CHARADDED)
-
{
-
...
-
if(notify->ch == '.')
-
{
-
char word[1000];
-
TextRange tr;
-
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));
-
if(strcmp(word,"file.") == 0)
-
{
-
SendEditor(SCI_AUTOCSHOW,0,
-
sptr_t(
-
"close "
-
"eof "
-
"good "
-
"open "
-
"rdbuf "
-
"size"
-
));
-
}
-
}
-
...
效果如下:
SCI_AUTOCSHOW命令的第一个参数表示已经输入了多少个字符。这对于代码自动完成是很有帮助的,比如我们可以用它帮助用户输入长串的单词,如:
-
if(strcmp(word,"Create") == 0)
-
{
-
SendEditor(SCI_AUTOCSHOW,6,
-
sptr_t(
-
"CreateBitmap "
-
"CreateDC "
-
"CreateHandle "
-
"CreateWindow "
-
"CreateWindowEx"
-
));
-
}
-
支持中文
Scintilla默认用的是ANSI编码,所以编辑中文之类的多字节编码时,会出错半个字符的问题。我们可以使用SCI_SETCODEPAGE命令设置使用的编码。
为了支持多语言,建议使用UTF8编码:
-
-
SendEditor(SCI_SETCODEPAGE,SC_CP_UTF8);
这样,我们就得用UTF8编码输入输出了。关于UTF8编码的转换,不在本文讨论范围之内,大家自由发挥吧^_^
与C++Builder更好地集成
好了,Scintilla的使用就讲到这里,同学们下课![班长:“起立!”;童鞋们(包括睡觉中的):“老...师...再...见...”;老师:“啊!对了,用C++Builder的同学请多留一会儿,哎~~小白,说你呢,别跑~~”]。
如果大家和我一样一直在用C++Builder照上面玩Scintilla的话,一定早就发现了这个Scintilla控件不接受TAB键-_-。咳...如果你坚持看到了这里,恭喜你,你马上就可以看到解决这个问题的“终级代码”啦:-P
VCL组件库的消息循环位于TApplication类里,要让我们的Scintilla完美地嫁接到VCL里,一个好办法就是把Scintilla也包装成一个VCL组件。
VCL组件的继承线路很清晰,要包装Scintilla,只要写一个TWinControl的超类就可以了:
-
class TScEdit : public TWinControl{
-
protected:
-
virtual void __fastcall CreateParams(Controls::TCreateParams &Params)
-
{
-
TWinControl::CreateParams(Params);
-
CreateSubClass(Params, "Scintilla");
-
}
-
virtual void __fastcall WndProc(Messages::TMessage &Message)
-
{
-
TWinControl::WndProc(Message);
-
if(Message.Msg == WM_GETDLGCODE)
-
Message.Result = DLGC_WANTALLKEYS|DLGC_WANTARROWS|DLGC_WANTTAB;
-
}
-
public:
-
__fastcall TScEdit(Classes::TComponent* AOwner)
-
:TWinControl(AOwner){;}
-
-
sptr_t SendEditor(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0)
-
{
-
return SendMessage(Handle, iMessage, wParam, lParam);
-
}
-
};
现在,我们可以用这个TScEdit代替之前的用CreateWindow建立的Scintilla了:
-
class TForm1 : public TForm
-
{
-
...
-
TScEdit *m_se;
-
sptr_t SendEditor(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0)
-
{
-
return m_se->SendEditor(iMessage, wParam, lParam);
-
}
-
};
-
__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);
-
-
setCppStyle();
-
setFold();
-
*/
-
-
m_se = new TScEdit(this);
-
m_se->Parent = this;
-
m_se->Align = alClient;
-
setCppStyle();
-
setFold();
-
}
<全文完>