怎样支持自动缩进
来自http://www.cppprog.com/2009/1111/176_3.html
在VS里编写C++代码时,输入回车换行后会保持和上一行的缩进一致,输入"{'字符后回车还会帮我们多缩进一次,输入'}'后又能自动退回。我们的编辑器也要实现这个功能。
现在再仔细了解一下Scintilla的通知消息(http://scintilla.sourceforge.net/ScintillaDoc.html#Notifications),除了前面用到的页边点击事件外,还有很多事件非常有用。
实现自动缩进功能我们要关心的事件通知是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指定范围内的字符。