这里继续学习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号标记指定背景色

演示代码

  1. // 标记和页边演示
  2. void TForm1::example()
  3. {
  4. // 先写10行文本上去
  5. for(int i=0; i<10; i++)
  6. SendEditor(SCI_APPENDTEXT, 12, (sptr_t)"hello world ");
  7. // 0号页边,宽度为9,显示0号标记(0..0001B)
  8. SendEditor(SCI_SETMARGINTYPEN,0,SC_MARGIN_SYMBOL);
  9. SendEditor(SCI_SETMARGINWIDTHN,0, 9);
  10. SendEditor(SCI_SETMARGINMASKN,0, 0x01);
  11. // 1号页边,宽度为9,显示1,2号标记(0..0110B)
  12. SendEditor(SCI_SETMARGINTYPEN,1, SC_MARGIN_SYMBOL);
  13. SendEditor(SCI_SETMARGINWIDTHN,1, 9);
  14. SendEditor(SCI_SETMARGINMASKN,1, 0x06);
  15. // 2号页边,宽度为20,显示行号
  16. SendEditor(SCI_SETMARGINTYPEN,2, SC_MARGIN_NUMBER);
  17. SendEditor(SCI_SETMARGINWIDTHN,2, 20);
  18. for(int i=0; i<10; i++)
  19. {
  20. // 前10行分别加入0~2号标记
  21. SendEditor(SCI_MARKERADD, i, i%3);
  22. }
  23. // 设置标记的前景色
  24. SendEditor(SCI_MARKERSETFORE,0,0x0000ff);//0-红色
  25. SendEditor(SCI_MARKERSETFORE,1,0x00ff00);//1-绿色
  26. SendEditor(SCI_MARKERSETFORE,2,0xff0000);//2-蓝色
  27. }

显示效果是:

如果你不喜欢这些圆圈,可以用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");

是时候上代码了:

  1. #define MARGIN_FOLD_INDEX 2
  2. void TForm1::setFold()
  3. {
  4. SendEditor(SCI_SETPROPERTY,(sptr_t)"fold",(sptr_t)"1");
  5. SendEditor(SCI_SETMARGINTYPEN, MARGIN_FOLD_INDEX, SC_MARGIN_SYMBOL);//页边类型
  6. SendEditor(SCI_SETMARGINMASKN, MARGIN_FOLD_INDEX, SC_MASK_FOLDERS); //页边掩码
  7. SendEditor(SCI_SETMARGINWIDTHN, MARGIN_FOLD_INDEX, 11); //页边宽度
  8. SendEditor(SCI_SETMARGINSENSITIVEN, MARGIN_FOLD_INDEX, TRUE); //响应鼠标消息
  9. // 折叠标签样式
  10. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_CIRCLEPLUS);
  11. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_CIRCLEMINUS);
  12. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND,  SC_MARK_CIRCLEPLUSCONNECTED);
  13. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_CIRCLEMINUSCONNECTED);
  14. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE);
  15. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
  16. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE);
  17. // 折叠标签颜色
  18. SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERSUB, 0xa0a0a0);
  19. SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERMIDTAIL, 0xa0a0a0);
  20. SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERTAIL, 0xa0a0a0);
  21. SendEditor(SCI_SETFOLDFLAGS, 16|4, 0); //如果折叠就在折叠行的上下各画一条横线
  22. }
  23. __fastcall TForm1::TForm1(TComponent* Owner)
  24. : TForm(Owner)
  25. {
  26. ...
  27. setFold();
  28. }
  29. void __fastcall TForm1::WndProc(Messages::TMessage &Message)
  30. {
  31. TForm::WndProc(Message);
  32. if(Message.Msg == WM_NOTIFY){
  33. SCNotification* notify = (SCNotification*)Message.LParam;
  34. if(notify->nmhdr.code == SCN_MARGINCLICK &&
  35. notify->nmhdr.idFrom == SCINT_ID){
  36. // 确定是页边点击事件
  37. const int line_number = SendEditor(SCI_LINEFROMPOSITION,notify->position);
  38. SendEditor(SCI_TOGGLEFOLD, line_number);
  39. }
  40. }
  41. }

现在的效果

这里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格式的,比如我喜欢的免费看图软件XnView就可以,想必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"
};

演示,使用自定义图形

  1. #include "minus.xpm"
  2. #include "plus.xpm"
  3. void TForm1::setFold()
  4. {
  5. ...
  6. // 折叠标签样式
  7. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_PIXMAP);
  8. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_PIXMAP);
  9. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND,  SC_MARK_PIXMAP);
  10. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_PIXMAP);
  11. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE);
  12. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
  13. SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE);
  14. //
  15. SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDER, (sptr_t)plus_xpm);
  16. SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEROPEN, (sptr_t)minus_xpm);
  17. SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEREND, (sptr_t)plus_xpm);
  18. SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEROPENMID, (sptr_t)minus_xpm);
  19. ...
  20. }

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

怎样支持自动缩进

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

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

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

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

演示代码

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

  1. void __fastcall TForm1::WndProc(Messages::TMessage &Message)
  2. {
  3. TForm::WndProc(Message);
  4. if(Message.Msg == WM_NOTIFY)
  5. {
  6. ...
  7. // 处理自动缩进
  8. static int LastProcessedChar = 0;
  9. //在CharAdded事件中记录最后输入的字符
  10. if(notify->nmhdr.code == SCN_CHARADDED)
  11. {
  12. LastProcessedChar = notify->ch;
  13. }
  14. // 在UpdateUI事件中处理缩进
  15. if(notify->nmhdr.code == SCN_UPDATEUI && LastProcessedChar!=0)
  16. {
  17. int pos = SendEditor(SCI_GETCURRENTPOS); //取得当前位置
  18. int line = SendEditor(SCI_LINEFROMPOSITION,pos); //取得当前行
  19. //如果最后输入的字符是右括号的话就自动让当前行缩进和它匹配的左括号所在行一致
  20. if( strchr("})>]",LastProcessedChar) &&
  21. isspace(SendEditor(SCI_GETCHARAT,pos-2)) && //要求右括号左边是空白字符
  22. LastProcessedChar!=0)
  23. {
  24. //找前一个单词起始位置,这里用它来确定右括号左边是否全是空白字符
  25. int startpos = SendEditor(SCI_WORDSTARTPOSITION,pos-1,false);
  26. int linepos = SendEditor(SCI_POSITIONFROMLINE,line); //当前行起始位置
  27. if(startpos == linepos) //这样相当于判断右括号左边是否全是空白字符
  28. {
  29. int othpos = SendEditor(SCI_BRACEMATCH,pos-1); //得到对应的左括号所在的位置
  30. int othline = SendEditor(SCI_LINEFROMPOSITION,othpos);  //左括号所在行
  31. int nIndent = SendEditor(SCI_GETLINEINDENTATION,othline);//左括号所在行的缩进值
  32. // 替换右括号前面的空白字符,使之与左括号缩进一致
  33. char space[1024];
  34. memset(space,' ',1024);
  35. SendEditor(SCI_SETTARGETSTART, startpos);
  36. SendEditor(SCI_SETTARGETEND, pos-1);
  37. SendEditor(SCI_REPLACETARGET,nIndent,(sptr_t)space);
  38. }
  39. }
  40. // 如果输入的是回车,则保持与上一行缩进一致
  41. // 如果上一行最后有效字符为左括号,就多缩进四个空格
  42. if(LastProcessedChar == ' ')
  43. {
  44. if(line > 0)
  45. {
  46. // 得到上一行缩进设置
  47. int nIndent = SendEditor(SCI_GETLINEINDENTATION,line-1);
  48. // 查找上一行最后一个有效字符(非空白字符)
  49. int nPrevLinePos = SendEditor(SCI_POSITIONFROMLINE,line-1);
  50. int c = ' ';
  51. for(int p = pos-2;
  52. p>=nPrevLinePos && isspace(c);
  53. p--, c=SendEditor(SCI_GETCHARAT,p));
  54. // 如果是左括号,就多缩进四格
  55. if(c && strchr("{([<",c)) nIndent+=4;
  56. // 缩进...
  57. char space[1024];
  58. memset(space,' ',1024);
  59. space[nIndent] = 0;
  60. SendEditor(SCI_REPLACESEL, 0, (sptr_t)space);
  61. }
  62. }
  63. LastProcessedChar = 0;
  64. }
  65. }
  66. }

下面是代码中用到的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的函数提示

  1. //我们要高亮的两个函数
  2. const size_t FUNCSIZE=2;
  3. char* g_szFuncList[FUNCSIZE]={ //函数名
  4. "CreateWindow(",
  5. "MoveWindow("
  6. };
  7. char* g_szFuncDesc[FUNCSIZE]={ //函数信息
  8. "HWND CreateWindow("
  9. "LPCTSTR lpClassName,"
  10. " LPCTSTR lpWindowName,"
  11. " DWORD dwStyle, "
  12. " int x,"
  13. " int y,"
  14. " int nWidth,"
  15. " int nHeight, "
  16. " HWND hWndParent,"
  17. " HMENU hMenu,"
  18. " HANDLE hInstance,"
  19. " PVOID lpParam"
  20. ")",
  21. "BOOL MoveWindow("
  22. "HWND hWnd,"
  23. " int X,"
  24. " int Y,"
  25. " int nWidth,"
  26. " int nHeight,"
  27. " BOOL bRepaint"
  28. ")"
  29. };
  30. void __fastcall TForm1::WndProc(Messages::TMessage &Message)
  31. {
  32. TForm::WndProc(Message);
  33. if(Message.Msg == WM_NOTIFY)
  34. {
  35. SCNotification* notify = (SCNotification*)Message.LParam;
  36. ...
  37. if(notify->nmhdr.code == SCN_CHARADDED)
  38. {
  39. ...
  40. // 函数提示功能
  41. static const char* pCallTipNextWord = NULL;//下一个高亮位置
  42. static const char* pCallTipCurDesc = NULL;//当前提示的函数信息
  43. if(notify->ch == '(') //如果输入了左括号,显示函数提示
  44. {
  45. char word[1000]; //保存当前光标下的单词(函数名)
  46. TextRange tr;    //用于SCI_GETTEXTRANGE命令
  47. int pos = SendEditor(SCI_GETCURRENTPOS); //取得当前位置(括号的位置)
  48. int startpos = SendEditor(SCI_WORDSTARTPOSITION,pos-1);//当前单词起始位置
  49. int endpos = SendEditor(SCI_WORDENDPOSITION,pos-1);//当前单词终止位置
  50. tr.chrg.cpMin = startpos;  //设定单词区间,取出单词
  51. tr.chrg.cpMax = endpos;
  52. tr.lpstrText = word;
  53. SendEditor(SCI_GETTEXTRANGE,0, sptr_t(&tr));
  54. for(size_t i=0; i<FUNCSIZE; i++) //找找有没有我们认识的函数?
  55. {
  56. if(memcmp(g_szFuncList[i],word,sizeof(g_szFuncList[i])) == 0)
  57. {     //找到啦,那么显示提示吧
  58. pCallTipCurDesc = g_szFuncDesc[i]; //当前提示的函数信息
  59. SendEditor(SCI_CALLTIPSHOW,pos,sptr_t(pCallTipCurDesc));//显示这个提示
  60. const char *pStart = strchr(pCallTipCurDesc,'(')+1; //高亮第一个参数
  61. const char *pEnd = strchr(pStart,',');//参数列表以逗号分隔
  62. if(pEnd == NULL) pEnd = strchr(pStart,')');//若是最后一个参数,后面是右括号
  63. SendEditor(SCI_CALLTIPSETHLT,
  64. pStart-pCallTipCurDesc, pEnd-pCallTipCurDesc);
  65. pCallTipNextWord = pEnd+1;//指向下一参数位置
  66. break;
  67. }
  68. }
  69. }
  70. else if(notify->ch == ')') //如果输入右括号,就关闭函数提示
  71. {
  72. SendEditor(SCI_CALLTIPCANCEL);
  73. pCallTipCurDesc = NULL;
  74. pCallTipNextWord = NULL;
  75. }
  76. else if(notify->ch == ',' && SendEditor(SCI_CALLTIPACTIVE) && pCallTipCurDesc)
  77. {
  78. //输入的是逗号,高亮下一个参数
  79. const char *pStart = pCallTipNextWord;
  80. const char *pEnd = strchr(pStart,',');
  81. if(pEnd == NULL) pEnd = strchr(pStart,')');
  82. if(pEnd == NULL) //没有下一个参数啦,关闭提示
  83. SendEditor(SCI_CALLTIPCANCEL);
  84. else
  85. {
  86. SendEditor(SCI_CALLTIPSETHLT,pStart-pCallTipCurDesc, pEnd-pCallTipCurDesc);
  87. pCallTipNextWord = pEnd+1;
  88. }
  89. }
  90. }//if(notify->nmhdr.code == SCN_CHARADDED)
  91. ...
  92. }//if(Message.Msg == WM_NOTIFY)
  93. }

效果如下:

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

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

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

直接上代码:

  1. void __fastcall TForm1::WndProc(Messages::TMessage &Message)
  2. {
  3. TForm::WndProc(Message);
  4. if(Message.Msg == WM_NOTIFY)
  5. {
  6. ...
  7. if(notify->nmhdr.code == SCN_CHARADDED)
  8. {
  9. ...
  10. if(notify->ch == '.')
  11. {
  12. char word[1000]; //保存当前光标下的单词
  13. TextRange tr;    //用于SCI_GETTEXTRANGE命令
  14. int pos = SendEditor(SCI_GETCURRENTPOS); //取得当前位置
  15. int startpos = SendEditor(SCI_WORDSTARTPOSITION,pos-1);//当前单词起始位置
  16. int endpos = SendEditor(SCI_WORDENDPOSITION,pos-1);//当前单词终止位置
  17. tr.chrg.cpMin = startpos;  //设定单词区间,取出单词
  18. tr.chrg.cpMax = endpos;
  19. tr.lpstrText = word;
  20. SendEditor(SCI_GETTEXTRANGE,0, sptr_t(&tr));
  21. if(strcmp(word,"file.") == 0) //输入file.后提示file对象的几个方法
  22. {
  23. SendEditor(SCI_AUTOCSHOW,0,
  24. sptr_t(
  25. "close "
  26. "eof "
  27. "good "
  28. "open "
  29. "rdbuf "
  30. "size"
  31. ));
  32. }
  33. }
  34. ...

效果如下:

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

  1. if(strcmp(word,"Create") == 0)
  2. {
  3. SendEditor(SCI_AUTOCSHOW,6,//已经输入了6位字符
  4. sptr_t(
  5. "CreateBitmap "
  6. "CreateDC "
  7. "CreateHandle "
  8. "CreateWindow "
  9. "CreateWindowEx"
  10. ));
  11. }

支持中文

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

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

  1. // UTF-8编码
  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. class TScEdit : public TWinControl{
  2. protected:
  3. virtual void __fastcall CreateParams(Controls::TCreateParams &Params)
  4. {
  5. TWinControl::CreateParams(Params);
  6. CreateSubClass(Params, "Scintilla");
  7. }
  8. virtual void __fastcall WndProc(Messages::TMessage &Message)
  9. {
  10. TWinControl::WndProc(Message);
  11. if(Message.Msg == WM_GETDLGCODE) //让窗体接受方向键和TAB键
  12. Message.Result = DLGC_WANTALLKEYS|DLGC_WANTARROWS|DLGC_WANTTAB;
  13. }
  14. public:
  15. __fastcall TScEdit(Classes::TComponent* AOwner)
  16. :TWinControl(AOwner){;}
  17. sptr_t SendEditor(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0)
  18. {
  19. return SendMessage(Handle, iMessage, wParam, lParam);
  20. }
  21. };

现在,我们可以用这个TScEdit代替之前的用CreateWindow建立的Scintilla了:

  1. class TForm1 : public TForm
  2. {
  3. ...
  4. TScEdit *m_se;
  5. sptr_t SendEditor(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0)
  6. {
  7. return m_se->SendEditor(iMessage, wParam, lParam);
  8. }
  9. };
  10. __fastcall TForm1::TForm1(TComponent* Owner)
  11. : TForm(Owner)
  12. {
  13. /* 在C++Builder世界里,抛弃CreateWindow吧-_-
  14. HWND hwndEditor = ::CreateWindow(_T("Scintilla"),
  15. NULL, WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_VISIBLE,
  16. 0,0,ClientWidth,ClientHeight,
  17. Handle,
  18. (HMENU)SCINT_ID, HInstance, NULL);
  19. m_fnDirect = (SciFnDirect)SendMessage(hwndEditor,SCI_GETDIRECTFUNCTION,0,0);
  20. m_ptrDirect = (sptr_t)SendMessage(hwndEditor,SCI_GETDIRECTPOINTER,0,0);
  21. setCppStyle();
  22. setFold();
  23. */
  24. m_se = new TScEdit(this);
  25. m_se->Parent = this;
  26. m_se->Align = alClient;//自动占满整个父窗体
  27. setCppStyle();
  28. setFold();
  29. }

<全文完>

Source URL: http://www.cppprog.com/2009/1111/176.html

Scintilla的高级技法相关推荐

  1. c4d完全学习手册_动态视觉设计就业班,全商业项目实训,一线制作团队10人小班授课,持续提升学习...

    CUBE专注动态视觉设计培训.CUBE依托本身设计公司制作资源优势,将培训制作完美结合,开设有北京实体培训课程以及网络案例实战课程. CUBE课程与工作要求完美对接,16周高强度集训,零基础学员毕业后 ...

  2. 计算机动画专业要学什么课程,计算机动画制作专业主要课程有哪些?

    对于想要报考计算机动画制作专业的同学来说,现在也有很多同学对于该专业还是比较感兴趣的,而且计算机动画制作专业现在的发展形势也是比较好的,那么计算机动画制作专业主要课程有哪些? 培养目标 重点培养学生各 ...

  3. 《OpenGL编程指南》一第3章 OpenGL绘制方式

    本节书摘来自华章出版社<OpenGL编程指南>一书中的第3章,作者 Bill Licea-Kane ,更多章节内容可以访问云栖社区"华章计算机"公众号查看 第3章 Op ...

  4. 分享一些Photoshop的教程电子档(pdf格式),初学者与设计师适用

    http://115.com/file/cljpf1fq#   中文版Photoshop_CS_2商业设计与印前技术详解_11852286.pdf  http://115.com/file/bhy2u ...

  5. 一种云化busybox demolets的设想和一种根本降低编程实践难度的设想:免部署无语法编程

    本文关键字:shell language,debuginbuilt+google oriented programming practise+drive.programming:dgv program ...

  6. lisp获取qleader端点_中文版AutoCAD2013高手之道

    中文版AutoCAD2013高手之道 1 AutoCAD快速入门 1.1 初步了解AutoCAD 2013 1.1.1 什么是 AutoCAD 1.1.2 AutoCAD 和 AutoCAD LT 的 ...

  7. 转: 大年三十整理的asp.net资料!(经典)

    使用SqlBulkCopy类加载其他源数据到SQL表 在数据回发时,维护ASP.NET Tree控件的位置 vagerent的vs2005网站开发技巧 ASP.NET2.0小技巧--内部控件权限的实现 ...

  8. (轉貼) 大年三十整理的asp.net资料! (.NET) (ASP.NET)

     大年三十整理的asp.net资料! 使用SqlBulkCopy类加载其他源数据到SQL表 在数据回发时,维护ASP.NET Tree控件的位置 vagerent的vs2005网站开发技巧 ASP.N ...

  9. 利用人性弱点的互联网产品(三)虚荣

    [核心提示] 互联网时代,自我虚荣心,自我满足感的强大可能超乎你的想象.虚荣心有多大,市场就有多大. 三年前,极客公园有一个利用人性弱点的互联网系列观察文章,依次从"贪婪".&qu ...

  10. ASP.NET 实用资料[转]

    使用SqlBulkCopy类加载其他源数据到SQL表  在数据回发时,维护ASP.NET Tree控件的位置  vagerent的vs2005网站开发技巧  ASP.NET2.0小技巧--内部控件权限 ...

最新文章

  1. [HNOI2019]JOJO
  2. 浅析网站备案的三大好处——你的网站备案了吗?
  3. php清空html_php怎么清除html代码
  4. Docker入门简明教程
  5. CuteEditor6.0使用配置心得体会(转)
  6. 2022届互联网秋招备战
  7. Oracle 安装报错 [INS-06101] IP address of localhost could not be determined 解决方法[转]
  8. LeetCode:旋转链表【61】
  9. Nginx 的多站点配置
  10. 液压机行业研究及十四五规划分析报告
  11. git gui :Updating the Git index failed. A rescan will be automatically started to res
  12. C 通过四个点计算两条直线的交点
  13. android开发如何获取电话号码的归属地信息
  14. 什么是子域名?如何设置子域名解析?
  15. 反射-获取信息详细(转)
  16. 人类数据总量_人类已产生五十亿GB数据 X一代贡献的数据总量最大
  17. 土地利用转移矩阵分析与制图(以沮漳河流域为例)
  18. 屏幕适配Autoresizing / Autolayout / Mansory / 自定义Frame实现
  19. 【FFMPEG】vs2019调用FFmpeg动态库教程
  20. 怎样合理地规划使用电脑硬盘

热门文章

  1. 看懂Oracle执行计划
  2. 1075_MISRA_C规范学习_2004_Rule_5.1
  3. 利用公网Msf+MS17010跨网段攻击内网(不详细立马关站)
  4. 分享typecho博客的Next主题包
  5. 《东周列国志》第五十九回 宠胥童晋国大乱 诛岸贾赵氏复兴
  6. 超市微信小程序怎么做_小程序怎么做的 超市微信小程序怎么做
  7. STM32机器人控制开发教程No.2 霍尔编码器电机测速以及增量式PID控制(基于HAL库)
  8. 圣思园JavaWeb随手笔记
  9. 麒麟V10打印机ppd文件导入导出
  10. 通过InstallShield官网申请注册码