这节的目标就是写出语法高亮,代码折叠这两个效果。
还是首先给大家发个效果图看一下:
主要参考文档:
http://www.cnblogs.com/superanyi/archive/2011/04/07/2008632.html
http://www.cnblogs.com/superanyi/archive/2011/04/07/2008636.html
当然官方文档是必不可少的。
其实有了第一节的工作,如果你玩过vim,那么接下来的工作有点类似于配置.vimrc文件了。
只不过比配置vimrc文件麻烦的是我们要自己确定发送消息的时机。
让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);//文档注释(/**开头)上面这段代码自己在c下面还得改一改。在第一篇里面自己定义的SendEditor函数是有四个参数的。所以我在传递参数的时候也必须满足4个参数。
所以所有关于SendEditor的函数就变成了类似下面这样:
SendEditor(ptr,SCI_SETLEXER, SCLEX_CPP, 0); //C++语法解析
语法解析只负责把代码拆分开,至于哪些是关键字,还得我们来指定。
要支持语法高亮,要做三件事:
一、选定语法解析器设置关键字
语法解析器用于把一大段代码分解成一个个的单词(token),另外还用于代码折叠的控制(后面会说到)。
选定语法解析器的命令是SCI_SETLEXER,如:
SendEditor(SCI_SETLEXER, SCLEX_CPP);
二、设置关键字
语法解析只负责把代码拆分开,至于哪些是关键字,还得我们来指定。
这种方式带来了些许的灵活性,比如我们要高亮一种自定义的语言,这种语言的风格与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)
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) //设置粗体
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) //设置粗体
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一致,最后单独设置其它元素。
加上当前行高亮功能:
SendEditor(SCI_SETCARETLINEVISIBLE, TRUE); SendEditor(SCI_SETCARETLINEBACK, 0xb0ffff);把TAB宽度由默认的8改为4(依个人习惯~~)
SendEditor(SCI_SETTABWIDTH, 4);
代码折叠是现代IDE和代码编辑器的必备功能,如果现在推出一个不支持折叠的编辑器,那是要被BS地~~。为了不被BS,很有必要先“研究”一下Scintilla的页边(Margins)和标记(Markers)功能。
当页边不是设定为显示行号时(由SCI_SETMARGINTYPEN命令设置),那么它就会显示标记。刚才说过Scintilla有32种标记,一般来说不会让一个页边来显示所有的标记,而是只显示部分标记。
在一个页边里可以显示哪几种标记由SCI_SETMARGINMASKN命令设置,它的参数是一个32位掩码(mask)值,掩码值的第n位为1时表示该页边可显示n号标记。
所有页边相关的命令以SCI_SETMARGIN或SCI_GETMARGIN作为前缀,如:
所有标记相关的命令以SCI_MARKER作为前缀,如:
// 先写10行文本上去 for(int i=0; i<10; i++) SendEditor(ptr,SCI_APPENDTEXT, 12, (sptr_t)"hello world\n"); // 1号页边,宽度为20,显示行号 SendEditor(ptr,SCI_SETMARGINTYPEN,1, SC_MARGIN_NUMBER); SendEditor(ptr,SCI_SETMARGINWIDTHN,1, 20); for(int i=0; i<10; i++) { // 前10行分别加入0~2号标记 SendEditor(ptr,SCI_MARKERADD, i, i%3); } // 设置标记的前景色 SendEditor(ptr,SCI_MARKERSETFORE,0,0x0000ff);//0-红色 SendEditor(ptr,SCI_MARKERSETFORE,1,0x00ff00);//1-绿色 SendEditor(ptr,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加入代码折叠功能了...
前面曾说过当编辑器有代码折叠功能时,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");
SendEditor(ptr,SCI_SETPROPERTY,(sptr_t)"fold",(sptr_t)"1"); SendEditor(ptr,SCI_SETMARGINTYPEN, MARGIN_FOLD_INDEX, SC_MARGIN_SYMBOL);//页边类型 SendEditor(ptr,SCI_SETMARGINMASKN, MARGIN_FOLD_INDEX, SC_MASK_FOLDERS); //页边掩码 SendEditor(ptr,SCI_SETMARGINWIDTHN, MARGIN_FOLD_INDEX, 11); //页边宽度 SendEditor(ptr,SCI_SETMARGINSENSITIVEN, MARGIN_FOLD_INDEX, TRUE); //响应鼠标消息 // 折叠标签样式 SendEditor(ptr,SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_CIRCLEPLUS); SendEditor(ptr,SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_CIRCLEMINUS); SendEditor(ptr,SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_CIRCLEPLUSCONNECTED); SendEditor(ptr,SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_CIRCLEMINUSCONNECTED); SendEditor(ptr,SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE); SendEditor(ptr,SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE); SendEditor(ptr,SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE); // 折叠标签颜色 SendEditor(ptr,SCI_MARKERSETBACK, SC_MARKNUM_FOLDERSUB, 0xa0a0a0); SendEditor(ptr,SCI_MARKERSETBACK, SC_MARKNUM_FOLDERMIDTAIL, 0xa0a0a0); SendEditor(ptr,SCI_MARKERSETBACK, SC_MARKNUM_FOLDERTAIL, 0xa0a0a0); SendEditor(ptr,SCI_SETFOLDFLAGS, 16|4, 0); //如果折叠就在折叠行的上下各画一条横线
当然还有最最关键的一步是什么时候要知道什么时候折叠。
我们在点击页边的时候(这里我用了自己的方法,根据margin页码数目来判断的)
#define MARGIN_FOLD_INDEX 2 if(lpnmhdr->hwndFrom==hwndScintilla) { switch(lpnmhdr->code) { case SCN_MARGINCLICK: if (notify->margin == MARGIN_FOLD_INDEX) { line_number = SendEditor(ptr,SCI_LINEFROMPOSITION,notify->position,0); SendEditor(ptr,SCI_TOGGLEFOLD, line_number,0); } break; } }好啦。这样一来我们就实现了代码高亮和代码折叠这两个功能了。
如果想继续深入学习更多scintilla的功能的话,就只能啃读官方文档了。