Scintilla是一个免费、跨平台、支持语法高亮的编辑控件。它完整支持源代码的编辑和调试,包括语法高亮、错误指示、代码完成 (code completion)和调用提示(call tips)。能包含标记(marker)的页边(margin)可用于标记断点、折叠和高亮当前行。
上一篇文章介绍了Scintilla的基本应用,这里继续学习Scintilla更多的控制命令和实现细节,完善我们的编辑器
页边(Margins)和标记(Markers) 代码折叠是现代IDE和代码编辑器的必备功能。
页边(Margins):页边是位于文本显示区左边的一竖条区域,它可以用于显示行号、书签、断点标记等东东。Scintilla最多可以有5个页边(从左 到右的编号为0~4),每个页边可以使用SCI_SETMARGINTYPEN
命令确定是用于显示行号还是符号。我们可以用 SCI_SETMARGINWIDTHN
命令控制一个页边的宽度,如果设置为0,则表示不显示该页边。默认是只显示宽度为16的1号页边。
标记(Markers):标记,不用说也知道是用来标记文本位置(确切地说,是文本行)的。我们可以使用32种标记(编号031),我们可以自由决定这 32种标记的意义,如标记0用来表示断点、标记110表示书签、标记20表示语法错误行等等。不过,如果编辑器要支持代码折叠功能,我们得把标记 25~31留出来,把这7个标记作为代码折叠专用标记(后面还会讲到)。
告诉页边显示哪些标记 当页边不是设定为显示行号时(由SCI_SETMARGINTYPEN
命令设置),那么它就会显示标记。刚才说过Scintilla有32种标记,一般来说不会让一个页边来显示所有的标记,而是只显示部分标记。
在一个页边里可以显示哪几种标记由SCI_SETMARGINMASKN
命令设置,它的参数是一个32位掩码(mask)值,掩码值的第n位为1时表示该页边可显示n号标记。
所有页边相关的命令以SCI_SETMARGIN
或SCI_GETMARGIN
作为前缀,如:
1 2 3 4 5 6 7 8 9 SCI_SETMARGINTYPEN (int margin, int type)SCI_SETMARGINWIDTHN (int margin, int pixelWidth)SCI_SETMARGINMASKN (int margin, int mask)SCI_SETMARGINSENSITIVEN (int margin, bool sensitive)
所有标记相关的命令以SCI_MARKER
作为前缀,如:
1 2 3 4 5 6 7 8 9 10 11 12 SCI_MARKERADD (int line, int markerNumber)SCI_MARKERDEFINE (int markerNumber, int markerSymbols)SCI_MARKERDELETE (int line, int markerNumber)SCI_MARKERDELETEALL (int markerNumber)SCI_MARKERSETFORE (int markerNumber, int colour)SCI_MARKERSETBACK (int markerNumber, int colour)
演示代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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
命令改变标记的样式,可选的有:
1 2 3 4 5 6 7 8 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中,我们可以找到它们的定义
1 2 3 4 5 6 7 #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
,同样头文件里已经定义好了:
1 #define SC_MASK_FOLDERS 0xFE000000
要加入代码折叠功能,还有一个最最关键的事情,就是要得到语法解析器(Lexer)的支持,上面的这些标记都是由语法解析器自动添加删除的。一般来说,只要用下面这条命令就可以了让语法解析器支持代码折叠了:
1 SendEditor (SCI_SETPROPERTY,(sptr_t )"fold" ,(sptr_t )"1" );
是时候上代码了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #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的窗口处理函数。
代码折叠以后我们要通过点击页边上的+和-标记来打开和折叠代码,所以需要页边接收鼠标点击事件:
1 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就可以 xpm比较特殊的地方是它可以作为头文件直接被C语言调用,吃惊吧?用文本编辑器打开它看看,呵呵,其实它就是一个数组定义。 如下面这个数据(代码)就是后面马上就要用到的minus.xpm
和plus.xpm
图片文件的内容(从eclipse里挖出来的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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" };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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" };
演示,使用自定义图形 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #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
,处理这两个事件,我们的编辑器支持自动缩进啦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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的函数提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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<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 = 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的可以考虑用WAVE,Spirit,Regex等库帮助解析;象偶这种不牛的同学,则可以考虑利用CTAGS工具在后台线程中生成tag文件,我们从tag文件里取就可以了(当然,效率嘛~~可以参考C++Builder的函数提示效率-_-)。
代码完成和函数提示的用法类似,前缀是SCI_AUTOC
Show Me the Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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
命令的第一个参数表示已经输入了多少个字符。这对于代码自动完成是很有帮助的,比如我们可以用它帮助用户输入长串的单词,如:
1 2 3 4 5 6 7 8 9 10 11 if (strcmp (word,"Create" ) == 0 ){ SendEditor (SCI_AUTOCSHOW,6 , sptr_t ( "CreateBitmap " "CreateDC " "CreateHandle " "CreateWindow " "CreateWindowEx" )); }
支持中文 Scintilla默认用的是ANSI编码,所以编辑中文之类的多字节编码时,会出错半个字符的问题。我们可以使用SCI_SETCODEPAGE
命令设置使用的编码。
为了支持多语言,建议使用UTF8编码:
1 2 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
的超类就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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) { m_se = new TScEdit (this ); m_se->Parent = this ; m_se->Align = alClient; setCppStyle (); setFold (); }