1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
5
using ICSharpCode.Core;
6
using ICSharpCode.NRefactory;
7
using ICSharpCode.NRefactory.Visitors;
8
using ICSharpCode.SharpDevelop;
9
using ICSharpCode.SharpDevelop.Dom;
10
using ICSharpCode.SharpDevelop.Dom.CSharp;
11
using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver;
12
using ICSharpCode.SharpDevelop.Dom.Refactoring;
13
using ICSharpCode.SharpDevelop.Editor;
14
using ICSharpCode.SharpDevelop.Editor.CodeCompletion;
15
using AST = ICSharpCode.NRefactory.Ast;
17
namespace CSharpBinding
19
public class CSharpCompletionBinding : NRefactoryCodeCompletionBinding
21
public CSharpCompletionBinding() : base(SupportedLanguage.CSharp)
25
static CSharpExpressionFinder CreateExpressionFinder(string fileName)
27
return new CSharpExpressionFinder(ParserService.GetParseInformation(fileName));
30
public override CodeCompletionKeyPressResult HandleKeyPress(ITextEditor editor, char ch)
32
CSharpExpressionFinder ef = CreateExpressionFinder(editor.FileName);
33
int cursor = editor.Caret.Offset;
35
var line = editor.Document.GetLineForOffset(cursor);
36
/* TODO: AVALONEDIT Reimplement this
37
if (TextUtilities.FindPrevWordStart(editor.ActiveTextAreaControl.Document, cursor) <= line.Offset) {
38
// [ is first character on the line
39
// -> Attribute completion
40
editor.ShowCompletionWindow(new AttributesDataProvider(ParserService.CurrentProjectContent), ch);
43
} else if (ch == ',' && CodeCompletionOptions.InsightRefreshOnComma && CodeCompletionOptions.InsightEnabled) {
44
IInsightWindow insightWindow;
45
if (insightHandler.InsightRefreshOnComma(editor, ch, out insightWindow)) {
46
return CodeCompletionKeyPressResult.Completed;
48
} else if(ch == '=') {
49
var curLine = editor.Document.GetLineForOffset(cursor);
50
string documentText = editor.Document.Text;
51
int position = editor.Caret.Offset - 2;
53
if (position > 0 && (documentText[position + 1] == '+')) {
54
ExpressionResult result = ef.FindFullExpression(documentText, position);
56
if(result.Expression != null) {
57
ResolveResult resolveResult = ParserService.Resolve(result, editor.Caret.Line, editor.Caret.Column, editor.FileName, documentText);
58
if (resolveResult != null && resolveResult.ResolvedType != null) {
59
IClass underlyingClass = resolveResult.ResolvedType.GetUnderlyingClass();
60
if (underlyingClass != null && underlyingClass.IsTypeInInheritanceTree(ParserService.CurrentProjectContent.GetClass("System.MulticastDelegate", 0))) {
61
EventHandlerCompletionItemProvider eventHandlerProvider = new EventHandlerCompletionItemProvider(result.Expression, resolveResult);
62
eventHandlerProvider.ShowCompletion(editor);
63
return CodeCompletionKeyPressResult.Completed;
67
} else if (position > 0) {
68
ExpressionResult result = ef.FindFullExpression(documentText, position);
70
if(result.Expression != null) {
71
ResolveResult resolveResult = ParserService.Resolve(result, editor.Caret.Line, editor.Caret.Column, editor.FileName, documentText);
72
if (resolveResult != null && resolveResult.ResolvedType != null) {
73
if (ProvideContextCompletion(editor, resolveResult.ResolvedType, ch)) {
74
return CodeCompletionKeyPressResult.Completed;
79
} else if (ch == '.') {
80
new CSharpCodeCompletionDataProvider().ShowCompletion(editor);
81
return CodeCompletionKeyPressResult.Completed;
82
} else if (ch == '>') {
83
if (IsInComment(editor)) return CodeCompletionKeyPressResult.None;
84
char prevChar = cursor > 1 ? editor.Document.GetCharAt(cursor - 1) : ' ';
85
if (prevChar == '-') {
86
new PointerArrowCompletionDataProvider().ShowCompletion(editor);
88
return CodeCompletionKeyPressResult.Completed;
92
if (char.IsLetter(ch) && CodeCompletionOptions.CompleteWhenTyping) {
93
if (editor.SelectionLength > 0) {
94
// allow code completion when overwriting an identifier
95
int endOffset = editor.SelectionStart + editor.SelectionLength;
96
// but block code completion when overwriting only part of an identifier
97
if (endOffset < editor.Document.TextLength && char.IsLetterOrDigit(editor.Document.GetCharAt(endOffset)))
98
return CodeCompletionKeyPressResult.None;
100
editor.Document.Remove(editor.SelectionStart, editor.SelectionLength);
101
// Read caret position again after removal - this is required because the document might change in other
102
// locations, too (e.g. bound elements in snippets).
103
cursor = editor.Caret.Offset;
105
char prevChar = cursor > 1 ? editor.Document.GetCharAt(cursor - 1) : ' ';
106
bool afterUnderscore = prevChar == '_';
107
if (afterUnderscore) {
109
prevChar = cursor > 1 ? editor.Document.GetCharAt(cursor - 1) : ' ';
111
if (!char.IsLetterOrDigit(prevChar) && prevChar != '.' && !IsInComment(editor)) {
112
ExpressionResult result = ef.FindExpression(editor.Document.Text, cursor);
113
LoggingService.Debug("CC: Beginning to type a word, result=" + result);
114
if (result.Context != ExpressionContext.IdentifierExpected) {
115
var ctrlSpaceProvider = new NRefactoryCtrlSpaceCompletionItemProvider(LanguageProperties.CSharp, result.Context);
116
ctrlSpaceProvider.ShowTemplates = true;
117
ctrlSpaceProvider.AllowCompleteExistingExpression = afterUnderscore;
118
ctrlSpaceProvider.ShowCompletion(editor);
119
return CodeCompletionKeyPressResult.CompletedIncludeKeyInCompletion;
124
return base.HandleKeyPress(editor, ch);
127
class CSharpCodeCompletionDataProvider : DotCodeCompletionItemProvider
129
public override ResolveResult Resolve(ITextEditor editor, ExpressionResult expressionResult)
131
// bypass ParserService.Resolve and set resolver.LimitMethodExtractionUntilCaretLine
132
ParseInformation parseInfo = ParserService.GetParseInformation(editor.FileName);
133
NRefactoryResolver resolver = new NRefactoryResolver(LanguageProperties.CSharp);
134
resolver.LimitMethodExtractionUntilLine = editor.Caret.Line;
135
return resolver.Resolve(expressionResult, parseInfo, editor.Document.Text);
139
class PointerArrowCompletionDataProvider : DotCodeCompletionItemProvider
141
public override ResolveResult Resolve(ITextEditor editor, ExpressionResult expressionResult)
143
ResolveResult rr = base.Resolve(editor, expressionResult);
144
if (rr != null && rr.ResolvedType != null) {
145
PointerReturnType prt = rr.ResolvedType.CastToDecoratingReturnType<PointerReturnType>();
147
return new ResolveResult(rr.CallingClass, rr.CallingMember, prt.BaseType);
152
public override ExpressionResult GetExpression(ITextEditor editor)
154
// - 1 because the "-" is already inserted (the ">" is about to be inserted)
155
return GetExpressionFromOffset(editor, editor.Caret.Offset - 1);
159
bool IsInComment(ITextEditor editor)
161
CSharpExpressionFinder ef = CreateExpressionFinder(editor.FileName);
162
int cursor = editor.Caret.Offset - 1;
163
return ef.FilterComments(editor.Document.GetText(0, cursor + 1), ref cursor) == null;
166
public override bool HandleKeyword(ITextEditor editor, string word)
170
if (IsInComment(editor)) return false;
172
ParseInformation parseInfo = ParserService.GetParseInformation(editor.FileName);
173
if (parseInfo != null) {
174
IClass innerMostClass = parseInfo.CompilationUnit.GetInnermostClass(editor.Caret.Line, editor.Caret.Column);
175
if (innerMostClass == null) {
176
new NRefactoryCtrlSpaceCompletionItemProvider(LanguageProperties.CSharp, ExpressionContext.Namespace).ShowCompletion(editor);
183
if (IsInComment(editor)) return false;
184
new NRefactoryCtrlSpaceCompletionItemProvider(LanguageProperties.CSharp, ExpressionContext.Type).ShowCompletion(editor);
187
if (IsInComment(editor)) return false;
188
new OverrideCompletionItemProvider().ShowCompletion(editor);
191
return ShowNewCompletion(editor);
193
if (IsInComment(editor)) return false;
194
return DoCaseCompletion(editor);
196
if (IsInComment(editor)) return false;
197
IMember m = GetCurrentMember(editor);
199
return ProvideContextCompletion(editor, m.ReturnType, ' ');
203
return base.HandleKeyword(editor, word);
206
bool ShowNewCompletion(ITextEditor editor)
208
CSharpExpressionFinder ef = CreateExpressionFinder(editor.FileName);
209
int cursor = editor.Caret.Offset;
210
string documentToCursor = editor.Document.GetText(0, cursor);
211
ExpressionResult expressionResult = ef.FindExpression(documentToCursor, cursor);
213
LoggingService.Debug("ShowNewCompletion: expression is " + expressionResult);
214
if (expressionResult.Context.IsObjectCreation) {
215
var currentLine = editor.Document.GetLineForOffset(cursor);
216
string lineText = editor.Document.GetText(currentLine.Offset, cursor - currentLine.Offset);
217
// when the new follows an assignment, improve code-completion by detecting the
218
// type of the variable that is assigned to
219
if (lineText.Replace(" ", "").EndsWith("=new")) {
220
int pos = lineText.LastIndexOf('=');
221
ExpressionContext context = FindExactContextForNewCompletion(editor, documentToCursor,
224
expressionResult.Context = context;
226
new NRefactoryCtrlSpaceCompletionItemProvider(LanguageProperties.CSharp, expressionResult.Context).ShowCompletion(editor);
232
ExpressionContext FindExactContextForNewCompletion(ITextEditor editor, string documentToCursor,
233
IDocumentLine currentLine, int pos)
235
CSharpExpressionFinder ef = CreateExpressionFinder(editor.FileName);
236
// find expression on left hand side of the assignment
237
ExpressionResult lhsExpr = ef.FindExpression(documentToCursor, currentLine.Offset + pos);
238
if (lhsExpr.Expression != null) {
239
ResolveResult rr = ParserService.Resolve(lhsExpr, currentLine.LineNumber, pos, editor.FileName, editor.Document.Text);
240
if (rr != null && rr.ResolvedType != null) {
241
ExpressionContext context;
243
if (rr.ResolvedType.IsArrayReturnType) {
244
// when creating an array, all classes deriving from the array's element type are allowed
245
IReturnType elementType = rr.ResolvedType.CastToArrayReturnType().ArrayElementType;
246
c = elementType != null ? elementType.GetUnderlyingClass() : null;
247
context = ExpressionContext.TypeDerivingFrom(elementType, false);
249
// when creating a normal instance, all non-abstract classes deriving from the type
251
c = rr.ResolvedType.GetUnderlyingClass();
252
context = ExpressionContext.TypeDerivingFrom(rr.ResolvedType, true);
254
if (c != null && context.ShowEntry(c)) {
255
// Try to suggest an entry (List<int> a = new => suggest List<int>).
257
string suggestedClassName = LanguageProperties.CSharp.CodeGenerator.GenerateCode(
258
CodeGenerator.ConvertType(
260
new ClassFinder(ParserService.GetParseInformation(editor.FileName), editor.Caret.Line, editor.Caret.Column)
262
if (suggestedClassName != c.Name) {
263
// create a special code completion item that completes also the type arguments
264
context.SuggestedItem = new SuggestedCodeCompletionItem(c, suggestedClassName);
266
context.SuggestedItem = new CodeCompletionItem(c);
275
#region "case"-keyword completion
276
bool DoCaseCompletion(ITextEditor editor)
278
ITextEditorCaret caret = editor.Caret;
279
NRefactoryResolver r = new NRefactoryResolver(LanguageProperties.CSharp);
280
if (r.Initialize(ParserService.GetParseInformation(editor.FileName), caret.Line, caret.Column)) {
281
AST.INode currentMember = r.ParseCurrentMember(editor.Document.Text);
282
if (currentMember != null) {
283
CaseCompletionSwitchFinder ccsf = new CaseCompletionSwitchFinder(caret.Line, caret.Column);
284
currentMember.AcceptVisitor(ccsf, null);
285
if (ccsf.bestStatement != null) {
286
r.RunLookupTableVisitor(currentMember);
287
ResolveResult rr = r.ResolveInternal(ccsf.bestStatement.SwitchExpression, ExpressionContext.Default);
288
if (rr != null && rr.ResolvedType != null) {
289
return ProvideContextCompletion(editor, rr.ResolvedType, ' ');
297
private class CaseCompletionSwitchFinder : AbstractAstVisitor
299
Location caretLocation;
300
internal AST.SwitchStatement bestStatement;
302
public CaseCompletionSwitchFinder(int caretLine, int caretColumn)
304
caretLocation = new Location(caretColumn, caretLine);
307
public override object VisitSwitchStatement(AST.SwitchStatement switchStatement, object data)
309
if (switchStatement.StartLocation < caretLocation && caretLocation < switchStatement.EndLocation) {
310
bestStatement = switchStatement;
312
return base.VisitSwitchStatement(switchStatement, data);