介绍
SharpDevelop的源代码里自带一个CSharp代码自动完成功能(Code Completion)的例子。如下图所示:
图1. 代码完成
看上去似乎好像挺不好做的,理论上要做词法分析、语法分析,还要解析一些如mscorlib之类的DLL。但是事实上SharpDevelop已经为我们做了这些,上面的例子只要写几个类就可以完成。整个Solution如下图所示:
![](https://www.cnblogs.com/images/cnblogs_com/nankezhishi/CodeCompletion/solution.PNG)
图2. 代码完成例子的Solution
![](https://www.cnblogs.com/images/cnblogs_com/nankezhishi/CodeCompletion/CodeMetrics.PNG)
图3. Code Metrics Results
直接使用SharpDevelop里的分析器,就可以很简单地完成Code Completion的功能。平均每个文件只有100行的代码。也许有人要说这样没有意思,但是我们不应该“重新做个轮子”对吗?而且就算要深入地学习,也要先来把这个例子弄明白的。
这个例子里的Code Completion并不象SharpDevelop里的那么完成,没有提供下面的功能。
![](https://www.cnblogs.com/images/cnblogs_com/nankezhishi/CodeCompletion/TODO.PNG)
图4. 缺失的ToolTip提示
在输入左括号时,应该在ToolTip中给出这个函数的所有重载,但是这个例子里并没有给出这个功能的实现。下面在介绍代码完成的同时,会给出这个ToolTip的实现。
博客园的Michael Zhang在其SharpDevelop浅析系列文章中也介绍了代码完成功能。如果对整个SharpDevelop有兴趣可以参考一下。下面进入正题……
第一部分 Code Completion的实现
下图是这个例子里的类体系
![](https://www.cnblogs.com/images/cnblogs_com/nankezhishi/CodeCompletion/classes.PNG)
图5. 整个例子的类体系
从类的结构来看,其实与SharpDevelop本身的实现不是一个层次的,这个类结构的耦合性很强,还好要看的是如何实现Code Completion,而不是它的设计。要看设计,还是看SharpDevelop好了。
下面逐个介绍一下里面的类。
l CodeCompletionData类:是用于表示代码完成列表中每个项的Visual Object。看看它的父类会更明显一些。
![](https://www.cnblogs.com/images/cnblogs_com/nankezhishi/CodeCompletion/DefaultCompletionData.PNG)
图6. DefaultCompletionData类图
l MainForm类:就是看到的整个窗体。里面有三个控件,一个是SharpDevelop的TextEditorControl,一个状态栏,一个ImageList。主窗体加载之后会开启一个Parser线程,每2秒对整个文档做一次Parse。(汗,用Stopwatch测试了一下,对一个很小的文件的一次Parse要50ms左右。)Parse之后,用于做Code Completion的数据就有了。在哪儿?没去认真找过。应该是在ProjectContent里。下面是Parse的步骤:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
1
void ParseStep()
2![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
3
string code = null;
4![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Invoke(new MethodInvoker(delegate
{
5
code = textEditorControl1.Text;
6
}));
7
TextReader textReader = new StringReader(code);
8
Dom.ICompilationUnit newCompilationUnit;
9
NRefactory.SupportedLanguage supportedLanguage;
10
if (IsVisualBasic)
11
supportedLanguage = NRefactory.SupportedLanguage.VBNet;
12
else
13
supportedLanguage = NRefactory.SupportedLanguage.CSharp;
14![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
using (NRefactory.IParser p = NRefactory.ParserFactory.CreateParser(supportedLanguage, textReader))
{
15
// we only need to parse types and method definitions, no method bodies
16
// so speed up the parser and make it more resistent to syntax
17
// errors in methods
18
p.ParseMethodBodies = false;
19
20
p.Parse();
21
newCompilationUnit = ConvertCompilationUnit(p.CompilationUnit);
22
}
23
// Remove information from lastCompilationUnit and add information from newCompilationUnit.
24
myProjectContent.UpdateCompilationUnit(lastCompilationUnit, newCompilationUnit, DummyFileName);
25
lastCompilationUnit = newCompilationUnit;
26
parseInformation.SetCompilationUnit(newCompilationUnit);
27
}
l CodeCompletionKeyHandler类:看名字就知道,就是针对CodeCompletion处理窗体的键盘事件的,如果点了”.”,就实例化一个CodeCompletionProvider并显示CodeCompletion窗口。按VS的分析,这个只有22行代码的文件就不细介绍了。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
ShowCodeCompletionList
1![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
if (key == '.')
{
2
ICompletionDataProvider completionDataProvider = new CodeCompletionProvider(mainForm);
3
4
codeCompletionWindow = CodeCompletionWindow.ShowCompletionWindow(
5
mainForm, // The parent window for the completion window
6
editor, // The text editor to show the window for
7
MainForm.DummyFileName, // Filename - will be passed back to the provider
8
completionDataProvider, // Provider to get the list of possible completions
9
key // Key pressed - will be passed to the provider
10
);
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (codeCompletionWindow != null)
{
12
// ShowCompletionWindow can return null when the provider returns an empty list
13
codeCompletionWindow.Closed += new EventHandler(CloseCodeCompletionWindow);
14
}
15
}
l HostCallbackImplementation类:这个更少,就13行代码。做的事件就是后台分析出错的时候,把消息显示出来。
l CodeCompletionProvider类:虽然最重要的部分,却也只有51行代码。主要完成了两件事情:1. 生成CodeCompletion列表。2. 用户触发一个Item的时候把相应的内容插入(就2行)。
生成CodeCompletion列表的功能也十分简单,大致分成三个步骤:
a. FindExpression:就是找到当前用户的点”.”之前的那个东西到底是个什么东西。
b. Resolve:得到找到的那个东西的Memeber列表。
c. GenerateData:从得到的列表生成用于在UI上显示的CodeCompletionData。
l ToolTipProvider类:这个可不是那个缺失的功能哦。这个只是在MouseOver一个Member时,显示出一些信息。在图1的上面的那个就是了。(好像ToolTip还错了……)这个过程和CodeCompletionProvider大致相同。就不再赘述了。
这样,整个CodeCompletion的功能就完成了。整个例子359行代码。(仔细看看代码,你会有信心把它到缩减到200行。)
第二部分 添加Code Insight功能
什么是Code Insight?就是之前提到的缺失的ToolTip啊。在SharpDevelop里给了它一个更专业的名字叫“Code Insight”。
一向奉行“Don’t recreate the wheel”,这次也不例外。Sample没有,SharpDevelop有啊。Ctrl+V过来不就行了?(当然商业项目不行哦,连Sample也不行。人家是GPL)
然后就在SharpDevelop的源代码中找到了一个名为MethodInsightDataProvider的文件(在ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor名字空间下)加了进来,然后在KeyHandler里加入对InsightWindow的支持。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
ShowInsightWindow
1
else if (key == '(')
2![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
IInsightDataProvider insightDataProvider = new MethodInsightDataProvider(mainForm);
4![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
5
InsightWindow insightWindow = new InsightWindow(mainForm, editor);
6
insightWindow.AddInsightDataProvider(insightDataProvider, MainForm.DummyFileName);
7
insightWindow.ShowInsightWindow();
8
}
再把SharpDevelop里的CodeCompletionData里的GetDocumentation函数也Copy到这个Sample的CodeCompletionData类中。
这样所有的类的准备完了。
然而在MethodInsightDataProvider中使用到的ParserService、AmbienceService、LoggingService和MessageService都没有找到引用。又去看了一下SharpDevelop的运行流程,找到了开启这几个Service的地方,发现差不多也已经同时把整个IDE开启了。这个可不是我想要的。而且很多类都是protected sealed类。从外界是Call不到的。看来必须要”Recreate the wheel”,把那个DataProvider重新写一个了。还好那个Provider只有100多行代码,自己写一个并不复杂。把LoggingService和MessageService的代码都删除。而ParserService和AmbienceService的作用不过是Parser和Ambience的工厂类,自己实例化一个不就得了?这样,DataProvider中所引用的所有Service都被剥离了。
整个MethodInsightDataProvider的代码如下所示:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
MethodInsightDataProvider
1
// <file>
2
// <copyright see="prj:///doc/copyright.txt"/>
3
// <license see="prj:///doc/license.txt"/>
4
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
5
// <modifier name="Hugo Gu" email="nankezhishi@gmail.com"
6
// <version>$Revision: 3105 $</version>
7
// </file>
8![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
9
using System;
10
using System.Collections;
11
using System.Collections.Generic;
12
using System.Linq;
13
using System.Text;
14
using ICSharpCode.SharpDevelop.Dom;
15
using ICSharpCode.SharpDevelop.Dom.CSharp;
16
using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver;
17
using ICSharpCode.SharpDevelop.Dom.VBNet;
18
using ICSharpCode.TextEditor;
19
using ICSharpCode.TextEditor.Document;
20
using ICSharpCode.TextEditor.Gui.InsightWindow;
21![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
22
namespace CSharpEditor
23![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
24
class MethodInsightDataProvider : IInsightDataProvider
25![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
26
MainForm mainForm;
27
string fileName = null;
28
IDocument document = null;
29
TextArea textArea = null;
30
protected List<IMethodOrProperty> methods = new List<IMethodOrProperty>();
31![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
32
public List<IMethodOrProperty> Methods
33![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
34
get
35![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
36
return methods;
37
}
38
}
39![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
40
public int InsightDataCount
41![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
42
get
43![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
44
return methods.Count;
45
}
46
}
47![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
48
int defaultIndex = -1;
49![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
50
public int DefaultIndex
51![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
52
get
53![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
54
return defaultIndex;
55
}
56
set
57![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
58
defaultIndex = value;
59
}
60
}
61![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
62
public string GetInsightData(int number)
63![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
64
IMember method = methods[number];
65
IAmbience conv = MainForm.IsVisualBasic ? (IAmbience)new VBNetAmbience() : new CSharpAmbience();
66
conv.ConversionFlags = ConversionFlags.StandardConversionFlags | ConversionFlags.UseFullyQualifiedMemberNames;
67
string documentation = method.Documentation;
68
string text = conv.Convert(method);
69
return text + "\n" + CodeCompletionData.GetDocumentation(documentation);
70
}
71![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
72
int lookupOffset = -1;
73
bool setupOnlyOnce;
74![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
75![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
76
///
77
/// </summary>
78
/// <param name="mainForm"></param>
79
public MethodInsightDataProvider(MainForm mainForm)
80![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
81
this.mainForm = mainForm;
82
}
83![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
84![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
85
/// Creates a MethodInsightDataProvider looking at the specified position.
86
/// </summary>
87
public MethodInsightDataProvider(int lookupOffset, bool setupOnlyOnce)
88![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
89
this.lookupOffset = lookupOffset;
90
this.setupOnlyOnce = setupOnlyOnce;
91
}
92![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
93
int initialOffset;
94![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
95
public void SetupDataProvider(string fileName, TextArea textArea)
96![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
97
if (setupOnlyOnce && this.textArea != null) return;
98
IDocument document = textArea.Document;
99
this.fileName = fileName;
100
this.document = document;
101
this.textArea = textArea;
102
int useOffset = (lookupOffset < 0) ? textArea.Caret.Offset : lookupOffset;
103
initialOffset = useOffset;
104![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
105
IExpressionFinder expressionFinder;
106
if (MainForm.IsVisualBasic)
107![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
108
expressionFinder = new VBExpressionFinder();
109
}
110
else
111![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
112
expressionFinder = new CSharpExpressionFinder(mainForm.parseInformation);
113
}
114![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
115
ExpressionResult expressionResult;
116
if (expressionFinder == null)
117
expressionResult = new ExpressionResult(TextUtilities.GetExpressionBeforeOffset(textArea, useOffset));
118
else
119
expressionResult = expressionFinder.FindExpression(textArea.Document.TextContent, useOffset);
120![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
121
if (expressionResult.Expression == null) // expression is null when cursor is in string/comment
122
return;
123
expressionResult.Expression = expressionResult.Expression.Trim();
124![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
125
int caretLineNumber = document.GetLineNumberForOffset(useOffset);
126
int caretColumn = useOffset - document.GetLineSegment(caretLineNumber).Offset;
127
// the parser works with 1 based coordinates
128
SetupDataProvider(fileName, document, expressionResult, caretLineNumber + 1, caretColumn + 1);
129
}
130![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
131
protected virtual void SetupDataProvider(string fileName, IDocument document, ExpressionResult expressionResult, int caretLineNumber, int caretColumn)
132![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
133
bool constructorInsight = false;
134
if (expressionResult.Context == ExpressionContext.Attribute)
135![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
136
constructorInsight = true;
137
}
138
else if (expressionResult.Context.IsObjectCreation)
139![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
140
constructorInsight = true;
141
expressionResult.Context = ExpressionContext.Type;
142
}
143
else if (expressionResult.Context == ExpressionContext.BaseConstructorCall)
144![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
145
constructorInsight = true;
146
}
147![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
148
NRefactoryResolver resolver = new NRefactoryResolver(mainForm.myProjectContent.Language);
149![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
150
ResolveResult results = resolver.Resolve(expressionResult, mainForm.parseInformation, document.TextContent);
151
LanguageProperties language = mainForm.myProjectContent.Language;
152
TypeResolveResult trr = results as TypeResolveResult;
153
if (trr == null && language.AllowObjectConstructionOutsideContext)
154![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
155
if (results is MixedResolveResult)
156
trr = (results as MixedResolveResult).TypeResult;
157
}
158
if (trr != null && !constructorInsight)
159![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
160
if (language.AllowObjectConstructionOutsideContext)
161
constructorInsight = true;
162
}
163
if (constructorInsight)
164![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
165
if (trr == null)
166![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
167
if ((expressionResult.Expression == "this") && (expressionResult.Context == ExpressionContext.BaseConstructorCall))
168![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
169
methods.AddRange(GetConstructorMethods(results.ResolvedType.GetMethods()));
170
}
171![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
172
if ((expressionResult.Expression == "base") && (expressionResult.Context == ExpressionContext.BaseConstructorCall))
173![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
174
if (results.CallingClass.BaseType.DotNetName == "System.Object")
175
return;
176
methods.AddRange(GetConstructorMethods(results.CallingClass.BaseType.GetMethods()));
177
}
178
}
179
else
180![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
181
methods.AddRange(GetConstructorMethods(trr.ResolvedType.GetMethods()));
182![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
183
if (methods.Count == 0 && trr.ResolvedClass != null && !trr.ResolvedClass.IsAbstract && !trr.ResolvedClass.IsStatic)
184![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
185
// add default constructor
186
methods.Add(Constructor.CreateDefault(trr.ResolvedClass));
187
}
188
}
189
}
190
else
191![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
192
MethodGroupResolveResult result = results as MethodGroupResolveResult;
193
if (result == null)
194
return;
195
bool classIsInInheritanceTree = false;
196
if (result.CallingClass != null)
197
classIsInInheritanceTree = result.CallingClass.IsTypeInInheritanceTree(result.ContainingType.GetUnderlyingClass());
198![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
199
foreach (IMethod method in result.ContainingType.GetMethods())
200![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
201
if (language.NameComparer.Equals(method.Name, result.Name))
202![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
203
if (method.IsAccessible(result.CallingClass, classIsInInheritanceTree))
204![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
205
methods.Add(method);
206
}
207
}
208
}
209
if (methods.Count == 0 && result.CallingClass != null && language.SupportsExtensionMethods)
210![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
211
ArrayList list = new ArrayList();
212
ResolveResult.AddExtensions(language, list, result.CallingClass, result.ContainingType);
213
foreach (IMethodOrProperty mp in list)
214![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
215
if (language.NameComparer.Equals(mp.Name, result.Name) && mp is IMethod)
216![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
217
DefaultMethod m = (DefaultMethod)mp.CreateSpecializedMember();
218
// for the insight window, remove first parameter and mark the
219
// method as normal - this is required to show the list of
220
// parameters the method expects.
221
m.IsExtensionMethod = false;
222
m.Parameters.RemoveAt(0);
223
methods.Add(m);
224
}
225
}
226
}
227
}
228
}
229![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
230
List<IMethodOrProperty> GetConstructorMethods(List<IMethod> methods)
231![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
232
List<IMethodOrProperty> constructorMethods = new List<IMethodOrProperty>();
233
foreach (IMethod method in methods)
234![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
235
if (method.IsConstructor && !method.IsStatic)
236![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
237
constructorMethods.Add(method);
238
}
239
}
240
return constructorMethods;
241
}
242![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
243
public bool CaretOffsetChanged()
244![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
245
bool closeDataProvider = textArea.Caret.Offset <= initialOffset;
246
int brackets = 0;
247
int curlyBrackets = 0;
248
if (!closeDataProvider)
249![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
250
bool insideChar = false;
251
bool insideString = false;
252
for (int offset = initialOffset; offset < Math.Min(textArea.Caret.Offset, document.TextLength); ++offset)
253![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
254
char ch = document.GetCharAt(offset);
255
switch (ch)
256![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
257
case '\'':
258
insideChar = !insideChar;
259
break;
260
case '(':
261
if (!(insideChar || insideString))
262![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
263
++brackets;
264
}
265
break;
266
case ')':
267
if (!(insideChar || insideString))
268![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
269
--brackets;
270
}
271
if (brackets <= 0)
272![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
273
return true;
274
}
275
break;
276
case '"':
277
insideString = !insideString;
278
break;
279
case '}':
280
if (!(insideChar || insideString))
281![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
282
--curlyBrackets;
283
}
284
if (curlyBrackets < 0)
285![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
286
return true;
287
}
288
break;
289
case '{':
290
if (!(insideChar || insideString))
291![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
292
++curlyBrackets;
293
}
294
break;
295
case ';':
296
if (!(insideChar || insideString))
297![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
298
return true;
299
}
300
break;
301
}
302
}
303
}
304![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
305
return closeDataProvider;
306
}
307![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
308
public bool CharTyped()
309![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
310
return false;
311
}
312
}
313
}
314![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
运行一下。
![](https://www.cnblogs.com/images/cnblogs_com/nankezhishi/CodeCompletion/MethodInsight.PNG)
图7. Method Insight
大功告成。
其实还有一种Insight——IndexerInsight。就是在输入‘[’的时候给出ToolTip提示。这个就留给有兴趣的读者吧。
/Files/nankezhishi/source/CSharpCodeCompletion.zip这里是改版的代码完成的所有代码。
在下一篇中,将为这个示例添加Boo语言的支持。