// // CSharpCompletionEngine.cs // // Author: // Mike Krüger // // Copyright (c) 2011 Xamarin Inc. (http://xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Text; using ICSharpCode.NRefactory.Completion; using ICSharpCode.NRefactory.CSharp.Refactoring; using ICSharpCode.NRefactory.CSharp.Resolver; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; namespace ICSharpCode.NRefactory.CSharp.Completion { public class CSharpCompletionEngine : CSharpCompletionEngineBase { internal ICompletionDataFactory factory; #region Additional input properties public CSharpFormattingOptions FormattingPolicy { get; set; } public string EolMarker { get; set; } public string IndentString { get; set; } #endregion #region Result properties public bool AutoCompleteEmptyMatch; public bool AutoSelect; public string DefaultCompletionString; #endregion public CSharpCompletionEngine (IDocument document, ICompletionDataFactory factory) { this.document = document; this.factory = factory; } public IEnumerable GetCompletionData (int offset, bool controlSpace) { this.AutoCompleteEmptyMatch = true; this.AutoSelect = true; this.DefaultCompletionString = null; SetOffset (offset); if (offset > 0) { char lastChar = document.GetCharAt (offset - 1); var result = MagicKeyCompletion (lastChar, controlSpace) ?? Enumerable.Empty (); if (controlSpace && char.IsWhiteSpace (lastChar)) { offset -= 2; while (offset >= 0 && char.IsWhiteSpace (document.GetCharAt (offset))) offset--; if (offset > 0) { var nonWsResult = MagicKeyCompletion (document.GetCharAt (offset), controlSpace); if (nonWsResult != null) result = result.Concat (nonWsResult); } } return result; } return Enumerable.Empty (); } IEnumerable MagicKeyCompletion (char completionChar, bool controlSpace) { switch (completionChar) { // Magic key completion case ':': case '.': if (IsInsideComment () || IsInsideString ()) return Enumerable.Empty (); var expr = GetExpressionBeforeCursor (); if (expr == null) return null; // do not complete . (but ..) if (expr.Item2 is PrimitiveExpression) { var pexpr = (PrimitiveExpression)expr.Item2; if (!(pexpr.Value is string || pexpr.Value is char) && !pexpr.LiteralValue.Contains ('.')) return null; } var resolveResult = ResolveExpression (expr.Item1, expr.Item2, expr.Item3); if (resolveResult == null) return null; if (expr.Item2 is AstType) return CreateTypeAndNamespaceCompletionData (location, resolveResult.Item1, expr.Item2, resolveResult.Item2); return CreateCompletionData (location, resolveResult.Item1, expr.Item2, resolveResult.Item2); case '#': if (IsInsideComment () || IsInsideString ()) return null; return GetDirectiveCompletionData (); // XML doc completion case '<': if (IsInsideDocComment ()) return GetXmlDocumentationCompletionData (); if (controlSpace) return DefaultControlSpaceItems (); return null; case '>': if (!IsInsideDocComment ()) return null; string lineText = document.GetText (document.GetLineByNumber (location.Line)); int startIndex = Math.Min (location.Column - 1, lineText.Length - 1); while (startIndex >= 0 && lineText [startIndex] != '<') { --startIndex; if (lineText [startIndex] == '/') { // already closed. startIndex = -1; break; } } if (startIndex >= 0) { int endIndex = startIndex; while (endIndex <= location.Column && endIndex < lineText.Length && !Char.IsWhiteSpace (lineText [endIndex])) { endIndex++; } string tag = endIndex - startIndex - 1 > 0 ? lineText.Substring (startIndex + 1, endIndex - startIndex - 2) : null; if (!string.IsNullOrEmpty (tag) && commentTags.IndexOf (tag) >= 0) document.Insert (offset, ""); } return null; // Parameter completion case '(': if (IsInsideComment () || IsInsideString ()) return null; var invoke = GetInvocationBeforeCursor (true); if (invoke == null) return null; if (invoke.Item2 is TypeOfExpression) return CreateTypeList (); var invocationResult = ResolveExpression (invoke.Item1, invoke.Item2, invoke.Item3); if (invocationResult == null) return null; var methodGroup = invocationResult.Item1 as MethodGroupResolveResult; if (methodGroup != null) return CreateParameterCompletion (methodGroup, invocationResult.Item2, invoke.Item2, 0, controlSpace); return null; case ',': int cpos2; if (!GetParameterCompletionCommandOffset (out cpos2)) return null; // completionContext = CompletionWidget.CreateCodeCompletionContext (cpos2); // int currentParameter2 = MethodParameterDataProvider.GetCurrentParameterIndex (CompletionWidget, completionContext) - 1; // return CreateParameterCompletion (CreateResolver (), location, ExpressionContext.MethodBody, provider.Methods, currentParameter); break; // Completion on space: case ' ': if (IsInsideComment () || IsInsideString ()) return null; int tokenIndex = offset; string token = GetPreviousToken (ref tokenIndex, false); // check propose name, for context (but only in control space context) IType isAsType = null; var isAsExpression = GetExpressionAt (offset); if (controlSpace && isAsExpression != null && isAsExpression.Item2 is VariableDeclarationStatement && token != "new") { var parent = isAsExpression.Item2 as VariableDeclarationStatement; int offset1 = document.GetOffset (parent.Type.StartLocation); int offset2 = document.GetOffset (parent.Type.EndLocation); string name = document.GetText (offset1, offset2 - offset1); var names = new List (); int lastNameStart = 0; for (int i = 1; i < name.Length; i++) { if (Char.IsUpper (name [i])) { names.Add (name.Substring (lastNameStart, i - lastNameStart)); lastNameStart = i; } } names.Add (name.Substring (lastNameStart, name.Length - lastNameStart)); var proposeNameList = new CompletionDataWrapper (this); var possibleName = new StringBuilder (); for (int i = 0; i < names.Count; i++) { possibleName.Length = 0; for (int j = i; j < names.Count; j++) { if (string.IsNullOrEmpty (names [j])) continue; if (j == i) names [j] = Char.ToLower (names [j] [0]) + names [j].Substring (1); possibleName.Append (names [j]); } if (possibleName.Length > 0) proposeNameList.Result.Add (factory.CreateLiteralCompletionData (possibleName.ToString ())); } AutoSelect = false; AutoCompleteEmptyMatch = false; return proposeNameList.Result; } // int tokenIndex = offset; // string token = GetPreviousToken (ref tokenIndex, false); // if (result.ExpressionContext == ExpressionContext.ObjectInitializer) { // resolver = CreateResolver (); // ExpressionContext exactContext = new NewCSharpExpressionFinder (dom).FindExactContextForObjectInitializer (document, resolver.Unit, Document.FileName, resolver.CallingType); // IReturnType objectInitializer = ((ExpressionContext.TypeExpressionContext)exactContext).UnresolvedType; // if (objectInitializer != null && objectInitializer.ArrayDimensions == 0 && objectInitializer.PointerNestingLevel == 0 && (token == "{" || token == ",")) // return CreateCtrlSpaceCompletionData (completionContext, result); // } if (token == "=") { int j = tokenIndex; string prevToken = GetPreviousToken (ref j, false); if (prevToken == "=" || prevToken == "+" || prevToken == "-") { token = prevToken + token; tokenIndex = j; } } switch (token) { case "(": case ",": int cpos; if (!GetParameterCompletionCommandOffset (out cpos)) break; int currentParameter = GetCurrentParameterIndex (cpos, 0) - 1; if (currentParameter < 0) return null; invoke = GetInvocationBeforeCursor (token == "("); if (invoke == null) return null; invocationResult = ResolveExpression (invoke.Item1, invoke.Item2, invoke.Item3); if (invocationResult == null) return null; methodGroup = invocationResult.Item1 as MethodGroupResolveResult; if (methodGroup != null) return CreateParameterCompletion (methodGroup, invocationResult.Item2, invoke.Item2, currentParameter, controlSpace); return null; case "=": case "==": GetPreviousToken (ref tokenIndex, false); var expressionOrVariableDeclaration = GetExpressionAt (tokenIndex); if (expressionOrVariableDeclaration == null) return null; resolveResult = ResolveExpression (expressionOrVariableDeclaration.Item1, expressionOrVariableDeclaration.Item2, expressionOrVariableDeclaration.Item3); if (resolveResult == null) return null; if (resolveResult.Item1.Type.Kind == TypeKind.Enum) { var wrapper = new CompletionDataWrapper (this); AddContextCompletion (wrapper, resolveResult.Item2, expressionOrVariableDeclaration.Item2); AddEnumMembers (wrapper, resolveResult.Item1.Type, resolveResult.Item2); AutoCompleteEmptyMatch = false; return wrapper.Result; } // // if (resolvedType.FullName == DomReturnType.Bool.FullName) { // CompletionDataList completionList = new ProjectDomCompletionDataList (); // CompletionDataCollector cdc = new CompletionDataCollector (this, dom, completionList, Document.CompilationUnit, resolver.CallingType, location); // completionList.AutoCompleteEmptyMatch = false; // cdc.Add ("true", "md-keyword"); // cdc.Add ("false", "md-keyword"); // resolver.AddAccessibleCodeCompletionData (result.ExpressionContext, cdc); // return completionList; // } // if (resolvedType.ClassType == ClassType.Delegate && token == "=") { // CompletionDataList completionList = new ProjectDomCompletionDataList (); // string parameterDefinition = AddDelegateHandlers (completionList, resolvedType); // string varName = GetPreviousMemberReferenceExpression (tokenIndex); // completionList.Add (new EventCreationCompletionData (document, varName, resolvedType, null, parameterDefinition, resolver.CallingMember, resolvedType)); // // CompletionDataCollector cdc = new CompletionDataCollector (this, dom, completionList, Document.CompilationUnit, resolver.CallingType, location); // resolver.AddAccessibleCodeCompletionData (result.ExpressionContext, cdc); // foreach (var data in completionList) { // if (data is MemberCompletionData) // ((MemberCompletionData)data).IsDelegateExpected = true; // } // return completionList; // } return null; case "+=": case "-=": GetPreviousToken (ref tokenIndex, false); expressionOrVariableDeclaration = GetExpressionAt (tokenIndex); if (expressionOrVariableDeclaration == null) return null; resolveResult = ResolveExpression (expressionOrVariableDeclaration.Item1, expressionOrVariableDeclaration.Item2, expressionOrVariableDeclaration.Item3); if (resolveResult == null) return null; var mrr = resolveResult.Item1 as MemberResolveResult; if (mrr != null) { var evt = mrr.Member as IEvent; if (evt == null) return null; var delegateType = evt.ReturnType.Resolve (ctx); if (!delegateType.IsDelegate ()) return null; var wrapper = new CompletionDataWrapper (this); if (currentType != null) { // bool includeProtected = DomType.IncludeProtected (dom, typeFromDatabase, resolver.CallingType); foreach (var method in currentType.GetMethods (ctx)) { if (MatchDelegate (delegateType, method) /*&& method.IsAccessibleFrom (dom, resolver.CallingType, resolver.CallingMember, includeProtected) &&*/) { wrapper.AddMember (method); // data.SetText (data.CompletionText + ";"); } } } if (token == "+=") { string parameterDefinition = AddDelegateHandlers (wrapper, delegateType); string varName = GetPreviousMemberReferenceExpression (tokenIndex); wrapper.Result.Add (factory.CreateEventCreationCompletionData (varName, delegateType, evt, parameterDefinition, currentMember, currentType)); } return wrapper.Result; } return null; case ":": if (currentMember == null) { var wrapper = new CompletionDataWrapper (this); AddTypesAndNamespaces (wrapper, GetState (), null, t => currentType != null ? !currentType.Equals (t) : true); return wrapper.Result; } return null; } var keywordCompletion = HandleKeywordCompletion (tokenIndex, token); if (keywordCompletion == null && controlSpace) goto default; return keywordCompletion; // Automatic completion default: if (IsInsideComment () || IsInsideString ()) return null; var identifierStart = GetExpressionAtCursor (); if (IsInLinqContext (offset)) { tokenIndex = offset; token = GetPreviousToken (ref tokenIndex, false); // token last typed if (linqKeywords.Contains (token)) { if (token == "from") // after from no auto code completion. return null; return DefaultControlSpaceItems (); } var dataList = new CompletionDataWrapper (this); AddKeywords (dataList, linqKeywords); return dataList.Result; } if (!(char.IsLetter (completionChar) || completionChar == '_') && (identifierStart == null || !(identifierStart.Item2 is ArrayInitializerExpression))) return controlSpace ? DefaultControlSpaceItems () : null; char prevCh = offset > 2 ? document.GetCharAt (offset - 2) : '\0'; char nextCh = offset < document.TextLength ? document.GetCharAt (offset) : ' '; const string allowedChars = ";,[(){}+-*/%^?:&|~!<>="; if (!Char.IsWhiteSpace (nextCh) && allowedChars.IndexOf (nextCh) < 0) return null; if (!(Char.IsWhiteSpace (prevCh) || allowedChars.IndexOf (prevCh) >= 0)) return null; // Do not pop up completion on identifier identifier (should be handled by keyword completion). tokenIndex = offset - 1; token = GetPreviousToken (ref tokenIndex, false); if (string.IsNullOrEmpty (token) && !(IsInsideComment (tokenIndex) || IsInsideString (tokenIndex))) { char last = token [token.Length - 1]; if (!char.IsLetterOrDigit (last) && last != '_') return null; } if (identifierStart == null) return null; CSharpResolver csResolver; AstNode n = identifierStart.Item2; var contextList = new CompletionDataWrapper (this); if (n is ArrayInitializerExpression) { var initalizerResult = ResolveExpression (identifierStart.Item1, n.Parent, identifierStart.Item3); if (initalizerResult != null) { foreach (var property in initalizerResult.Item1.Type.GetProperties (ctx)) { if (!property.IsPublic) continue; contextList.AddMember (property); } foreach (var field in initalizerResult.Item1.Type.GetProperties (ctx)){ if (!field.IsPublic) continue; contextList.AddMember (field); } return contextList.Result; } return null; } if (n != null/* && !(identifierStart.Item2 is TypeDeclaration)*/) { csResolver = new CSharpResolver (ctx, System.Threading.CancellationToken.None); var nodes = new List (); nodes.Add (n); if (n.Parent is ICSharpCode.NRefactory.CSharp.Attribute) nodes.Add (n.Parent); var navigator = new NodeListResolveVisitorNavigator (nodes); var visitor = new ResolveVisitor (csResolver, identifierStart.Item1, navigator); visitor.Scan (identifierStart.Item3); try { csResolver = visitor.GetResolverStateBefore (n); } catch (Exception) { csResolver = GetState (); } // add attribute properties. if (n.Parent is ICSharpCode.NRefactory.CSharp.Attribute) { var resolved = visitor.GetResolveResult (n.Parent); if (resolved != null && resolved.Type != null) { foreach (var property in resolved.Type.GetProperties (ctx).Where (p => p.Accessibility == Accessibility.Public)) { contextList.AddMember (property); } foreach (var field in resolved.Type.GetFields (ctx).Where (p => p.Accessibility == Accessibility.Public)) { contextList.AddMember (field); } } } } else { csResolver = GetState (); } // identifier has already started with the first letter offset--; AddContextCompletion (contextList, csResolver, identifierStart.Item2); return contextList.Result; // if (stub.Parent is BlockStatement) // result = FindExpression (dom, completionContext, -1); // if (result == null) // return null; // else if (result.ExpressionContext != ExpressionContext.IdentifierExpected) { // triggerWordLength = 1; // bool autoSelect = true; // IType returnType = null; // if ((prevCh == ',' || prevCh == '(') && GetParameterCompletionCommandOffset (out cpos)) { // ctx = CompletionWidget.CreateCodeCompletionContext (cpos); // NRefactoryParameterDataProvider dataProvider = ParameterCompletionCommand (ctx) as NRefactoryParameterDataProvider; // if (dataProvider != null) { // int i = dataProvider.GetCurrentParameterIndex (CompletionWidget, ctx) - 1; // foreach (var method in dataProvider.Methods) { // if (i < method.Parameters.Count) { // returnType = dom.GetType (method.Parameters [i].ReturnType); // autoSelect = returnType == null || returnType.ClassType != ClassType.Delegate; // break; // } // } // } // } // // Bug 677531 - Auto-complete doesn't always highlight generic parameter in method signature // //if (result.ExpressionContext == ExpressionContext.TypeName) // // autoSelect = false; // CompletionDataList dataList = CreateCtrlSpaceCompletionData (completionContext, result); // AddEnumMembers (dataList, returnType); // dataList.AutoSelect = autoSelect; // return dataList; // } else { // result = FindExpression (dom, completionContext, 0); // tokenIndex = offset; // // // check foreach case, unfortunately the expression finder is too dumb to handle full type names // // should be overworked if the expression finder is replaced with a mcs ast based analyzer. // var possibleForeachToken = GetPreviousToken (ref tokenIndex, false); // starting letter // possibleForeachToken = GetPreviousToken (ref tokenIndex, false); // varname // // // read return types to '(' token // possibleForeachToken = GetPreviousToken (ref tokenIndex, false); // varType // if (possibleForeachToken == ">") { // while (possibleForeachToken != null && possibleForeachToken != "(") { // possibleForeachToken = GetPreviousToken (ref tokenIndex, false); // } // } else { // possibleForeachToken = GetPreviousToken (ref tokenIndex, false); // ( // if (possibleForeachToken == ".") // while (possibleForeachToken != null && possibleForeachToken != "(") // possibleForeachToken = GetPreviousToken (ref tokenIndex, false); // } // possibleForeachToken = GetPreviousToken (ref tokenIndex, false); // foreach // // if (possibleForeachToken == "foreach") { // result.ExpressionContext = ExpressionContext.ForeachInToken; // } else { // return null; // // result.ExpressionContext = ExpressionContext.IdentifierExpected; // } // result.Expression = ""; // result.Region = DomRegion.Empty; // // return CreateCtrlSpaceCompletionData (completionContext, result); // } // break; } return null; } bool IsInLinqContext (int offset) { string token; while (null != (token = GetPreviousToken (ref offset, true))) { if (token == "from") return true; if (token == ";") return false; } return false; } IEnumerable DefaultControlSpaceItems () { var wrapper = new CompletionDataWrapper (this); var node = Unit.GetNodeAt (location); var rr = ResolveExpression (CSharpParsedFile, node, Unit); AddContextCompletion (wrapper, rr != null ? rr.Item2 : GetState (), node); return wrapper.Result; } void AddContextCompletion (CompletionDataWrapper wrapper, CSharpResolver state, AstNode node) { if (state == null) return; foreach (var variable in state.LocalVariables) { wrapper.AddVariable (variable); } if (state.CurrentMember is IParameterizedMember) { var param = (IParameterizedMember)state.CurrentMember; foreach (var p in param.Parameters) { wrapper.AddVariable (p); } } if (state.CurrentMember is IMethod) { var method = (IMethod)state.CurrentMember; foreach (var p in method.TypeParameters) { wrapper.AddTypeParameter (p); } } Predicate typePred = null; if (node is Attribute) { var attribute = ctx.GetTypeDefinition ("System", "Attribute", 0, StringComparer.Ordinal); typePred = t => t.GetAllBaseTypeDefinitions (ctx).Any (bt => bt.Equals (attribute)); } AddTypesAndNamespaces (wrapper, state, node, typePred); wrapper.Result.Add (factory.CreateLiteralCompletionData ("global")); if (state.CurrentMember != null) { AddKeywords (wrapper, statementStartKeywords); AddKeywords (wrapper, expressionLevelKeywords); } else if (state.CurrentTypeDefinition != null) { AddKeywords (wrapper, typeLevelKeywords); } else { AddKeywords (wrapper, globalLevelKeywords); } if (IsInSwitchContext(node)) { wrapper.AddCustom ("case"); wrapper.AddCustom ("default"); } AddKeywords (wrapper, primitiveTypesKeywords); wrapper.Result.AddRange (factory.CreateCodeTemplateCompletionData ()); } static bool IsInSwitchContext(AstNode node) { var n = node; while (n != null && !(n is MemberDeclaration)) { Console.WriteLine (n.GetType ()); if (n is SwitchStatement) return true; if (n is BlockStatement) return false; n = n.Parent; } return false; } void AddTypesAndNamespaces (CompletionDataWrapper wrapper, CSharpResolver state, AstNode node, Predicate typePred = null, Predicate memberPred = null) { var currentType = state.CurrentTypeDefinition ?? this.currentType; var currentMember = state.CurrentMember ?? this.currentMember; if (currentType != null) { for (var ct = currentType; ct != null; ct = ct.DeclaringTypeDefinition) { foreach (var nestedType in ct.NestedTypes) { if (typePred == null || typePred (nestedType)) { string name = nestedType.Name; if (node is Attribute && name.EndsWith ("Attribute") && name.Length > "Attribute".Length) name = name.Substring (0, name.Length - "Attribute".Length); wrapper.AddType (nestedType, name); } } } if (currentMember != null) { foreach (var member in currentType.Resolve (ctx).GetMembers (ctx)) { if (memberPred == null || memberPred (member)) wrapper.AddMember (member); } } foreach (var p in currentType.TypeParameters) { wrapper.AddTypeParameter (p); } } for (var n = state.CurrentUsingScope; n != null; n = n.Parent) { foreach (var pair in n.UsingAliases) { wrapper.AddNamespace ("", pair.Key); } foreach (var u in n.Usings) { var ns = u.ResolveNamespace (ctx); if (ns == null) continue; foreach (var type in ctx.GetTypes (ns.NamespaceName, StringComparer.Ordinal)) { if (typePred == null || typePred (type)) { string name = type.Name; if (node is Attribute && name.EndsWith ("Attribute") && name.Length > "Attribute".Length) name = name.Substring (0, name.Length - "Attribute".Length); wrapper.AddType (type, name); } } } foreach (var type in ctx.GetTypes (n.NamespaceName, StringComparer.Ordinal)) { if (typePred == null || typePred (type)) { wrapper.AddType (type, type.Name); } } foreach (var curNs in ctx.GetNamespaces ().Where (sn => sn.StartsWith (n.NamespaceName) && sn != n.NamespaceName)) { wrapper.AddNamespace (n.NamespaceName, curNs); } } } IEnumerable HandleKeywordCompletion (int wordStart, string word) { if (IsInsideComment () || IsInsideString ()) return null; switch (word) { case "using": case "namespace": if (currentType != null) return null; var wrapper = new CompletionDataWrapper (this); AddTypesAndNamespaces (wrapper, GetState (), null, t => false); return wrapper.Result; case "case": return CreateCaseCompletionData (location); // case ",": // case ":": // if (result.ExpressionContext == ExpressionContext.InheritableType) { // IType cls = NRefactoryResolver.GetTypeAtCursor (Document.CompilationUnit, Document.FileName, new TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset)); // CompletionDataList completionList = new ProjectDomCompletionDataList (); // List namespaceList = GetUsedNamespaces (); // var col = new CSharpTextEditorCompletion.CompletionDataCollector (this, dom, completionList, Document.CompilationUnit, null, location); // bool isInterface = false; // HashSet baseTypeNames = new HashSet (); // if (cls != null) { // baseTypeNames.Add (cls.Name); // if (cls.ClassType == ClassType.Struct) // isInterface = true; // } // int tokenIndex = offset; // // // Search base types " : [Type1, ... ,TypeN,] " // string token = null; // do { // token = GetPreviousToken (ref tokenIndex, false); // if (string.IsNullOrEmpty (token)) // break; // token = token.Trim (); // if (Char.IsLetterOrDigit (token [0]) || token [0] == '_') { // IType baseType = dom.SearchType (Document.CompilationUnit, cls, result.Region.Start, token); // if (baseType != null) { // if (baseType.ClassType != ClassType.Interface) // isInterface = true; // baseTypeNames.Add (baseType.Name); // } // } // } while (token != ":"); // foreach (object o in dom.GetNamespaceContents (namespaceList, true, true)) { // IType type = o as IType; // if (type != null && (type.IsStatic || type.IsSealed || baseTypeNames.Contains (type.Name) || isInterface && type.ClassType != ClassType.Interface)) { // continue; // } // if (o is Namespace && !namespaceList.Any (ns => ns.StartsWith (((Namespace)o).FullName))) // continue; // col.Add (o); // } // // Add inner classes // Stack innerStack = new Stack (); // innerStack.Push (cls); // while (innerStack.Count > 0) { // IType curType = innerStack.Pop (); // if (curType == null) // continue; // foreach (IType innerType in curType.InnerTypes) { // if (innerType != cls) // // don't add the calling class as possible base type // col.Add (innerType); // } // if (curType.DeclaringType != null) // innerStack.Push (curType.DeclaringType); // } // return completionList; // } // break; case "is": case "as": if (currentType == null) return null; IType isAsType = null; var isAsExpression = GetExpressionAt (wordStart); if (isAsExpression != null) { var parent = isAsExpression.Item2.Parent; if (parent is VariableInitializer) parent = parent.Parent; if (parent is VariableDeclarationStatement) { var resolved = ResolveExpression (isAsExpression.Item1, parent, isAsExpression.Item3); if (resolved != null) isAsType = resolved.Item1.Type; } } var isAsWrapper = new CompletionDataWrapper (this); AddTypesAndNamespaces (isAsWrapper, GetState (), null, t => isAsType == null || t.IsDerivedFrom (isAsType.GetDefinition (), ctx)); return isAsWrapper.Result; // { // CompletionDataList completionList = new ProjectDomCompletionDataList (); // ExpressionResult expressionResult = FindExpression (dom, completionContext, wordStart - document.Caret.Offset); // NRefactoryResolver resolver = CreateResolver (); // ResolveResult resolveResult = resolver.Resolve (expressionResult, new TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset)); // if (resolveResult != null && resolveResult.ResolvedType != null) { // CompletionDataCollector col = new CompletionDataCollector (this, dom, completionList, Document.CompilationUnit, resolver.CallingType, location); // IType foundType = null; // if (word == "as") { // ExpressionContext exactContext = new NewCSharpExpressionFinder (dom).FindExactContextForAsCompletion (document, Document.CompilationUnit, Document.FileName, resolver.CallingType); // if (exactContext is ExpressionContext.TypeExpressionContext) { // foundType = resolver.SearchType (((ExpressionContext.TypeExpressionContext)exactContext).Type); // AddAsCompletionData (col, foundType); // } // } // // if (foundType == null) // foundType = resolver.SearchType (resolveResult.ResolvedType); // // if (foundType != null) { // if (foundType.ClassType == ClassType.Interface) // foundType = resolver.SearchType (DomReturnType.Object); // // foreach (IType type in dom.GetSubclasses (foundType)) { // if (type.IsSpecialName || type.Name.StartsWith ("<")) // continue; // AddAsCompletionData (col, type); // } // } // List namespaceList = GetUsedNamespaces (); // foreach (object o in dom.GetNamespaceContents (namespaceList, true, true)) { // if (o is IType) { // IType type = (IType)o; // if (type.ClassType != ClassType.Interface || type.IsSpecialName || type.Name.StartsWith ("<")) // continue; // // if (foundType != null && !dom.GetInheritanceTree (foundType).Any (x => x.FullName == type.FullName)) // // continue; // AddAsCompletionData (col, type); // continue; // } // if (o is Namespace) // continue; // col.Add (o); // } // return completionList; // } // result.ExpressionContext = ExpressionContext.TypeName; // return CreateCtrlSpaceCompletionData (completionContext, result); // } case "override": // Look for modifiers, in order to find the beginning of the declaration int firstMod = wordStart; int i = wordStart; for (int n = 0; n < 3; n++) { string mod = GetPreviousToken (ref i, true); if (mod == "public" || mod == "protected" || mod == "private" || mod == "internal" || mod == "sealed") { firstMod = i; } else if (mod == "static") { // static methods are not overridable return null; } else break; } if (!IsLineEmptyUpToEol ()) return null; var overrideCls = CSharpParsedFile.GetInnermostTypeDefinition (location); if (overrideCls != null && (overrideCls.Kind == TypeKind.Class || overrideCls.Kind == TypeKind.Struct)) { string modifiers = document.GetText (firstMod, wordStart - firstMod); return GetOverrideCompletionData (overrideCls, modifiers); } return null; // case "partial": // // Look for modifiers, in order to find the beginning of the declaration // firstMod = wordStart; // i = wordStart; // for (int n = 0; n < 3; n++) { // string mod = GetPreviousToken (ref i, true); // if (mod == "public" || mod == "protected" || mod == "private" || mod == "internal" || mod == "sealed") { // firstMod = i; // } else if (mod == "static") { // // static methods are not overridable // return null; // } else // break; // } // if (!IsLineEmptyUpToEol ()) // return null; // // overrideCls = NRefactoryResolver.GetTypeAtCursor (Document.CompilationUnit, Document.FileName, new TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset)); // if (overrideCls != null && (overrideCls.ClassType == ClassType.Class || overrideCls.ClassType == ClassType.Struct)) { // string modifiers = document.GetTextBetween (firstMod, wordStart); // return GetPartialCompletionData (completionContext, overrideCls, modifiers); // } // return null; // case "public": case "protected": case "private": case "internal": case "sealed": case "static": wrapper = new CompletionDataWrapper (this); var state = GetState (); AddTypesAndNamespaces (wrapper, state, null, m => false); AddKeywords (wrapper, typeLevelKeywords); AddKeywords (wrapper, primitiveTypesKeywords); return wrapper.Result; case "new": int j = offset - 4; // string token = GetPreviousToken (ref j, true); IType hintType = null; var expressionOrVariableDeclaration = GetExpressionAt (j); if (expressionOrVariableDeclaration != null && expressionOrVariableDeclaration.Item2 is VariableDeclarationStatement) { var varDecl = (VariableDeclarationStatement)expressionOrVariableDeclaration.Item2; var resolved = ResolveExpression (expressionOrVariableDeclaration.Item1, varDecl.Type, expressionOrVariableDeclaration.Item3); if (resolved != null) { hintType = resolved.Item1.Type; } } return CreateTypeCompletionData (hintType); // IType callingType = NRefactoryResolver.GetTypeAtCursor (Document.CompilationUnit, Document.FileName, new TextLocation (document.Caret.Line, document.Caret.Column)); // ExpressionContext newExactContext = new NewCSharpExpressionFinder (dom).FindExactContextForNewCompletion (document, Document.CompilationUnit, Document.FileName, callingType); // if (newExactContext is ExpressionContext.TypeExpressionContext) // return CreateTypeCompletionData (location, callingType, newExactContext, ((ExpressionContext.TypeExpressionContext)newExactContext).Type, ((ExpressionContext.TypeExpressionContext)newExactContext).UnresolvedType); // if (newExactContext == null) { // int j = offset - 4; // // string yieldToken = GetPreviousToken (ref j, true); // if (token == "return") { // NRefactoryResolver resolver = CreateResolver (); // resolver.SetupResolver (new TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset)); // IReturnType returnType = resolver.CallingMember.ReturnType; // if (yieldToken == "yield" && returnType.GenericArguments.Count > 0) // returnType = returnType.GenericArguments [0]; // if (resolver.CallingMember != null) // return CreateTypeCompletionData (location, callingType, newExactContext, null, returnType); // } // } // return CreateCtrlSpaceCompletionData (completionContext, null); // case "if": // case "elif": // if (stateTracker.Engine.IsInsidePreprocessorDirective) // return GetDefineCompletionData (); // return null; case "yield": var yieldDataList = new CompletionDataWrapper (this); DefaultCompletionString = "return"; yieldDataList.AddCustom ("break"); yieldDataList.AddCustom ("return"); return yieldDataList.Result; case "in": var inList = new CompletionDataWrapper (this); var node = Unit.GetNodeAt (location); var rr = ResolveExpression (CSharpParsedFile, node, Unit); AddContextCompletion (inList, rr != null ? rr.Item2 : GetState (), node); return inList.Result; // case "where": // CompletionDataList whereDataList = new CompletionDataList (); // NRefactoryResolver constraintResolver = CreateResolver (); // constraintResolver.SetupResolver (new TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset)); // if (constraintResolver.CallingMember is IMethod) { // foreach (ITypeParameter tp in ((IMethod)constraintResolver.CallingMember).TypeParameters) { // whereDataList.Add (tp.Name, "md-keyword"); // } // } else { // if (constraintResolver.CallingType != null) { // foreach (ITypeParameter tp in constraintResolver.CallingType.TypeParameters) { // whereDataList.Add (tp.Name, "md-keyword"); // } // } // } // // return whereDataList; } // if (IsInLinqContext (result)) { // if (linqKeywords.Contains (word)) { // if (word == "from") // after from no auto code completion. // return null; // result.Expression = ""; // return CreateCtrlSpaceCompletionData (completionContext, result); // } // CompletionDataList dataList = new ProjectDomCompletionDataList (); // CompletionDataCollector col = new CompletionDataCollector (this, dom, dataList, Document.CompilationUnit, null, new TextLocation (completionContext.TriggerLine, completionContext.TriggerLineOffset)); // foreach (string kw in linqKeywords) { // col.Add (kw, "md-keyword"); // } // return dataList; // } return null; } bool IsLineEmptyUpToEol () { var line = document.GetLineByNumber (location.Line); for (int j = offset; j < line.EndOffset; j++) { char ch = document.GetCharAt (j); if (!char.IsWhiteSpace (ch)) return false; } return true; } string GetLineIndent (int lineNr) { var line = document.GetLineByNumber (lineNr); for (int j = offset; j < line.EndOffset; j++) { char ch = document.GetCharAt (j); if (!char.IsWhiteSpace (ch)) return document.GetText (line.Offset, j - line.Offset - 1); } return ""; } IEnumerable CreateTypeCompletionData (IType hintType) { var wrapper = new CompletionDataWrapper (this); var state = GetState (); Predicate pred = null; if (hintType != null && !hintType.Equals (SharedTypes.UnknownType)) { var lookup = new MemberLookup (ctx, currentType, ProjectContent); pred = t => { // check if type is in inheritance tree. if (hintType.GetDefinition () != null && !t.IsDerivedFrom (hintType.GetDefinition (), ctx)) return false; // check for valid constructors if (t.Methods.Count (m => m.IsConstructor) == 0) return true; bool isProtectedAllowed = currentType != null ? currentType.IsDerivedFrom (t, ctx) : false; return t.Methods.Any (m => m.IsConstructor && lookup.IsAccessible (m, isProtectedAllowed)); }; DefaultCompletionString = GetShortType (hintType, GetState ()); wrapper.AddType (hintType, DefaultCompletionString); } AddTypesAndNamespaces (wrapper, state, null, pred, m => false); AddKeywords (wrapper, primitiveTypesKeywords.Where (k => k != "void")); AutoCompleteEmptyMatch = true; return wrapper.Result; } IEnumerable GetOverrideCompletionData (ITypeDefinition type, string modifiers) { var wrapper = new CompletionDataWrapper (this); var alreadyInserted = new Dictionary (); bool addedVirtuals = false; int declarationBegin = offset; int j = declarationBegin; for (int i = 0; i < 3; i++) { switch (GetPreviousToken (ref j, true)) { case "public": case "protected": case "private": case "internal": case "sealed": case "override": declarationBegin = j; break; case "static": return null; // don't add override completion for static members } } foreach (var baseType in type.GetAllBaseTypeDefinitions (ctx)) { AddVirtuals (alreadyInserted, wrapper, type, modifiers, baseType, declarationBegin); addedVirtuals = true; } if (!addedVirtuals) AddVirtuals (alreadyInserted, wrapper, type, modifiers, ctx.GetTypeDefinition (typeof(object)), declarationBegin); return wrapper.Result; } static string GetNameWithParamCount (IMember member) { var e = member as IMethod; if (e == null || e.TypeParameters.Count == 0) return member.Name; return e.Name + "`" + e.TypeParameters.Count; } void AddVirtuals (Dictionary alreadyInserted, CompletionDataWrapper col, ITypeDefinition type, string modifiers, ITypeDefinition curType, int declarationBegin) { if (curType == null) return; foreach (var m in curType.Methods.Where (m => !m.IsConstructor && !m.IsDestructor).Cast ().Concat (curType.Properties.Cast ())) { if (m.IsSynthetic || curType.Kind != TypeKind.Interface && !(m.IsVirtual || m.IsOverride || m.IsAbstract)) continue; // filter out the "Finalize" methods, because finalizers should be done with destructors. if (m is IMethod && m.Name == "Finalize") continue; var data = factory.CreateNewOverrideCompletionData (declarationBegin, type, m); string text = GetNameWithParamCount (m); // check if the member is already implemented bool foundMember = type.Members.Any (cm => GetNameWithParamCount (cm) == text); if (!foundMember && !alreadyInserted.ContainsKey (text)) { alreadyInserted [text] = true; data.CompletionCategory = col.GetCompletionCategory (curType); col.Add (data); } } } static void AddKeywords (CompletionDataWrapper wrapper, IEnumerable keywords) { foreach (string keyword in keywords) { wrapper.AddCustom (keyword); } } public string GetPreviousMemberReferenceExpression (int tokenIndex) { string result = GetPreviousToken (ref tokenIndex, false); result = GetPreviousToken (ref tokenIndex, false); if (result != ".") { result = null; } else { var names = new List (); while (result == ".") { result = GetPreviousToken (ref tokenIndex, false); if (result == "this") { names.Add ("handle"); } else if (result != null) { string trimmedName = result.Trim (); if (trimmedName.Length == 0) break; names.Insert (0, trimmedName); } result = GetPreviousToken (ref tokenIndex, false); } result = String.Join ("", names.ToArray ()); foreach (char ch in result) { if (!char.IsLetterOrDigit (ch) && ch != '_') { result = ""; break; } } } return result; } bool MatchDelegate (IType delegateType, IMethod method) { var delegateMethod = delegateType.GetDelegateInvokeMethod (); if (delegateMethod == null || delegateMethod.Parameters.Count != method.Parameters.Count) return false; for (int i = 0; i < delegateMethod.Parameters.Count; i++) { if (!delegateMethod.Parameters [i].Type.Resolve (ctx).Equals (method.Parameters [i].Type.Resolve (ctx))) return false; } return true; } string AddDelegateHandlers (CompletionDataWrapper completionList, IType delegateType, bool addSemicolon = true, bool addDefault = true) { IMethod delegateMethod = delegateType.GetDelegateInvokeMethod (); var thisLineIndent = GetLineIndent (location.Line); string delegateEndString = EolMarker + thisLineIndent + "}" + (addSemicolon ? ";" : ""); bool containsDelegateData = completionList.Result.Any (d => d.DisplayText.StartsWith ("delegate(")); if (addDefault) completionList.AddCustom ("delegate", "Creates anonymous delegate.", "delegate {" + EolMarker + thisLineIndent + IndentString + "|" + delegateEndString); var sb = new StringBuilder ("("); var sbWithoutTypes = new StringBuilder ("("); for (int k = 0; k < delegateMethod.Parameters.Count; k++) { if (k > 0) { sb.Append (", "); sbWithoutTypes.Append (", "); } var parameterType = delegateMethod.Parameters [k].Type.Resolve (ctx); sb.Append (GetShortType (parameterType, GetState ())); sb.Append (" "); sb.Append (delegateMethod.Parameters [k].Name); sbWithoutTypes.Append (delegateMethod.Parameters [k].Name); } sb.Append (")"); sbWithoutTypes.Append (")"); completionList.AddCustom ("delegate" + sb, "Creates anonymous delegate.", "delegate" + sb + " {" + EolMarker + thisLineIndent + IndentString + "|" + delegateEndString); if (!completionList.Result.Any (data => data.DisplayText == sbWithoutTypes.ToString ())) completionList.AddCustom (sbWithoutTypes.ToString (), "Creates lambda expression.", sbWithoutTypes + " => |" + (addSemicolon ? ";" : "")); /* TODO:Make factory method out of it. // It's needed to temporarly disable inserting auto matching bracket because the anonymous delegates are selectable with '(' // otherwise we would end up with () => ) if (!containsDelegateData) { var savedValue = MonoDevelop.SourceEditor.DefaultSourceEditorOptions.Instance.AutoInsertMatchingBracket; MonoDevelop.SourceEditor.DefaultSourceEditorOptions.Instance.AutoInsertMatchingBracket = false; completionList.Result.CompletionListClosed += delegate { MonoDevelop.SourceEditor.DefaultSourceEditorOptions.Instance.AutoInsertMatchingBracket = savedValue; }; }*/ return sb.ToString (); } bool IsAccessibleFrom (IEntity member, ITypeDefinition calledType, IMember currentMember, bool includeProtected) { if (currentMember == null) return member.IsStatic || member.IsPublic; // if (currentMember is MonoDevelop.Projects.Dom.BaseResolveResult.BaseMemberDecorator) // return member.IsPublic | member.IsProtected; // if (member.IsStatic && !IsStatic) // return false; if (member.IsPublic || calledType != null && calledType.Kind == TypeKind.Interface && !member.IsProtected) return true; if (member.DeclaringTypeDefinition != null) { if (member.DeclaringTypeDefinition.Kind == TypeKind.Interface) return IsAccessibleFrom (member.DeclaringTypeDefinition, calledType, currentMember, includeProtected); if (member.IsProtected && !(member.DeclaringTypeDefinition.IsProtectedOrInternal && !includeProtected)) return includeProtected; } if (member.IsInternal || member.IsProtectedAndInternal || member.IsProtectedOrInternal) { var type1 = member is ITypeDefinition ? (ITypeDefinition)member : member.DeclaringTypeDefinition; var type2 = currentMember is ITypeDefinition ? (ITypeDefinition)currentMember : currentMember.DeclaringTypeDefinition; bool result; // easy case, projects are the same if (type1.ProjectContent == type2.ProjectContent) { result = true; } else if (type1.ProjectContent != null) { // maybe type2 hasn't project dom set (may occur in some cases), check if the file is in the project //TODO !! // result = type1.ProjectContent.Annotation ().GetProjectFile (type2.Region.FileName) != null; result = false; } else if (type2.ProjectContent != null) { //TODO!! // result = type2.ProjectContent.Annotation ().GetProjectFile (type1.Region.FileName) != null; result = false; } else { // should never happen ! result = true; } return member.IsProtectedAndInternal ? includeProtected && result : result; } if (!(currentMember is IType) && (currentMember.DeclaringTypeDefinition == null || member.DeclaringTypeDefinition == null)) return false; // inner class var declaringType = currentMember.DeclaringTypeDefinition; while (declaringType != null) { if (declaringType.ReflectionName == currentMember.DeclaringType.ReflectionName) return true; declaringType = declaringType.DeclaringTypeDefinition; } return currentMember.DeclaringTypeDefinition != null && member.DeclaringTypeDefinition.FullName == currentMember.DeclaringTypeDefinition.FullName; } IEnumerable CreateTypeAndNamespaceCompletionData (TextLocation location, ResolveResult resolveResult, AstNode resolvedNode, CSharpResolver state) { if (resolveResult == null || resolveResult.IsError) return null; var result = new CompletionDataWrapper (this); if (resolveResult is NamespaceResolveResult) { var nr = (NamespaceResolveResult)resolveResult; foreach (var cl in ctx.GetTypes (nr.NamespaceName, StringComparer.Ordinal)) { result.AddType (cl, cl.Name); } foreach (var ns in ctx.GetNamespaces ().Where (n => n.Length > nr.NamespaceName.Length && n.StartsWith (nr.NamespaceName))) { result.AddNamespace (nr.NamespaceName, ns); } } else if (resolveResult is TypeResolveResult) { var type = resolveResult.Type.Resolve (ctx); foreach (var nested in type.GetNestedTypes (ctx)) { result.AddType (nested, nested.Name); } } return result.Result; } IEnumerable CreateTypeList () { foreach (var cl in ctx.GetTypes ("", StringComparer.Ordinal)) { yield return factory.CreateTypeCompletionData (cl, cl.Name); } foreach (var ns in ctx.GetNamespaces ()) { string name = ns; int idx = name.IndexOf ("."); if (idx >= 0) name = name.Substring (0, idx); yield return factory.CreateNamespaceCompletionData (name); } } public ITypeDefinition GetTypeFromContext (ITypeDefinition type) { if (type == null || type.DeclaringType != null) return type; var result = ctx.GetTypeDefinition (type.Namespace, type.Name, type.TypeParameterCount, StringComparer.Ordinal) ?? type; if (result.GetParts ().Count == 1) return type; // Console.WriteLine ("result:"+ result); return result; } IEnumerable CreateParameterCompletion (MethodGroupResolveResult resolveResult, CSharpResolver state, AstNode invocation, int parameter, bool controlSpace) { var result = new CompletionDataWrapper (this); var addedEnums = new HashSet (); var addedDelegates = new HashSet (); foreach (var method in resolveResult.Methods) { if (method.Parameters.Count <= parameter) continue; var resolvedType = method.Parameters [parameter].Type.Resolve (ctx); if (resolvedType.Kind == TypeKind.Enum) { if (addedEnums.Contains (resolvedType.ReflectionName)) continue; addedEnums.Add (resolvedType.ReflectionName); AddEnumMembers (result, resolvedType, state); } else if (resolvedType.Kind == TypeKind.Delegate) { // if (addedDelegates.Contains (resolvedType.DecoratedFullName)) // continue; // addedDelegates.Add (resolvedType.DecoratedFullName); // string parameterDefinition = AddDelegateHandlers (completionList, resolvedType, false, addedDelegates.Count == 1); // string varName = "Handle" + method.Parameters [parameter].ReturnType.Name + method.Parameters [parameter].Name; // result.Add (new EventCreationCompletionData (document, varName, resolvedType, null, parameterDefinition, resolver.Unit.GetMemberAt (location), resolvedType) { AddSemicolon = false }); } } if (!controlSpace) { if (addedEnums.Count + addedDelegates.Count == 0) return Enumerable.Empty (); AutoCompleteEmptyMatch = false; AutoSelect = false; } AddContextCompletion (result, state, invocation); // resolver.AddAccessibleCodeCompletionData (ExpressionContext.MethodBody, cdc); // if (addedDelegates.Count > 0) { // foreach (var data in result.Result) { // if (data is MemberCompletionData) // ((MemberCompletionData)data).IsDelegateExpected = true; // } // } return result.Result; } string GetShortType (IType type, CSharpResolver state) { var builder = new TypeSystemAstBuilder (state); var shortType = builder.ConvertType (type); using (var w = new System.IO.StringWriter ()) { var visitor = new CSharpOutputVisitor (w, FormattingPolicy); shortType.AcceptVisitor (visitor, null); return w.ToString (); } } void AddEnumMembers (CompletionDataWrapper completionList, IType resolvedType, CSharpResolver state) { if (resolvedType.Kind != TypeKind.Enum) return; string typeString = GetShortType (resolvedType, state); if (typeString.Contains (".")) completionList.AddType (resolvedType, typeString); foreach (var field in resolvedType.GetFields (ctx)) { if (field.IsConst || field.IsStatic) completionList.Result.Add (factory.CreateEntityCompletionData (field, typeString + "." + field.Name)); } DefaultCompletionString = typeString; } IEnumerable CreateCompletionData (TextLocation location, ResolveResult resolveResult, AstNode resolvedNode, CSharpResolver state) { if (resolveResult == null /*|| resolveResult.IsError*/) return null; if (resolveResult is NamespaceResolveResult) { var nr = (NamespaceResolveResult)resolveResult; var namespaceContents = new CompletionDataWrapper (this); foreach (var cl in ctx.GetTypes (nr.NamespaceName, StringComparer.Ordinal)) { namespaceContents.AddType (cl, cl.Name); } foreach (var ns in ctx.GetNamespaces ().Where (n => n.Length > nr.NamespaceName.Length && n.StartsWith (nr.NamespaceName))) { namespaceContents.AddNamespace (nr.NamespaceName, ns); } return namespaceContents.Result; } IType type = GetTypeFromContext (resolveResult.Type.GetDefinition ()) ?? resolveResult.Type; var typeDef = resolveResult.Type.GetDefinition (); var lookup = new MemberLookup (ctx, currentType, ProjectContent); var result = new CompletionDataWrapper (this); bool isProtectedAllowed = false; bool includeStaticMembers = false; if (resolveResult is LocalResolveResult) { isProtectedAllowed = currentType != null && typeDef != null ? typeDef.GetAllBaseTypeDefinitions (ctx).Any (bt => bt.Equals (currentType)) : false; if (resolvedNode is IdentifierExpression) { var mrr = (LocalResolveResult)resolveResult; includeStaticMembers = mrr.Variable.Name == mrr.Type.Name; } } else { isProtectedAllowed = currentType != null && typeDef != null ? currentType.GetAllBaseTypeDefinitions (ctx).Any (bt => bt.Equals (typeDef)) : false; } if (resolveResult is TypeResolveResult && type.Kind == TypeKind.Enum) { foreach (var field in type.GetFields (ctx)) { result.AddMember (field); } foreach (var m in type.GetMethods (ctx)) { if (m.Name == "TryParse") result.AddMember (m); } return result.Result; } if (resolveResult is MemberResolveResult && resolvedNode is IdentifierExpression) { var mrr = (MemberResolveResult)resolveResult; includeStaticMembers = mrr.Member.Name == mrr.Type.Name; } // Console.WriteLine ("type:" + type +"/"+type.GetType ()); // Console.WriteLine ("IS PROT ALLOWED:" + isProtectedAllowed); // Console.WriteLine (resolveResult); // Console.WriteLine (currentMember != null ? currentMember.IsStatic : "currentMember == null"); if (resolvedNode.Annotation () == null) { //tags the created expression as part of an object create expression. foreach (var member in type.GetMembers (ctx)) { if (!lookup.IsAccessible (member, isProtectedAllowed)) { // Console.WriteLine ("skip access: " + member.FullName); continue; } if (resolvedNode is BaseReferenceExpression && member.IsAbstract) continue; if (!includeStaticMembers && member.IsStatic && !(resolveResult is TypeResolveResult)) { // Console.WriteLine ("skip static member: " + member.FullName); continue; } if (!member.IsStatic && (resolveResult is TypeResolveResult)) { // Console.WriteLine ("skip non static member: " + member.FullName); continue; } // Console.WriteLine ("add : "+ member.FullName + " --- " + member.IsStatic); result.AddMember (member); } } if (resolveResult is TypeResolveResult || includeStaticMembers) { foreach (var nested in type.GetNestedTypes (ctx)) { result.AddType (nested, nested.Name); } } else { var baseTypes = new List (type.GetAllBaseTypes (ctx)); var conv = new Conversions (ctx); for (var n = state.CurrentUsingScope; n != null; n = n.Parent) { AddExtensionMethods (result, conv, baseTypes, n.NamespaceName); foreach (var u in n.Usings) { var ns = u.ResolveNamespace (ctx); if (ns == null) continue; AddExtensionMethods (result, conv, baseTypes, ns.NamespaceName); } } } // IEnumerable objects = resolveResult.CreateResolveResult (dom, resolver != null ? resolver.CallingMember : null); // CompletionDataCollector col = new CompletionDataCollector (this, dom, result, Document.CompilationUnit, resolver != null ? resolver.CallingType : null, location); // col.HideExtensionParameter = !resolveResult.StaticResolve; // col.NamePrefix = expressionResult.Expression; // bool showOnlyTypes = expressionResult.Contexts.Any (ctx => ctx == ExpressionContext.InheritableType || ctx == ExpressionContext.Constraints); // if (objects != null) { // foreach (object obj in objects) { // if (expressionResult.ExpressionContext != null && expressionResult.ExpressionContext.FilterEntry (obj)) // continue; // if (expressionResult.ExpressionContext == ExpressionContext.NamespaceNameExcepted && !(obj is Namespace)) // continue; // if (showOnlyTypes && !(obj is IType)) // continue; // CompletionData data = col.Add (obj); // if (data != null && expressionResult.ExpressionContext == ExpressionContext.Attribute && data.CompletionText != null && data.CompletionText.EndsWith ("Attribute")) { // string newText = data.CompletionText.Substring (0, data.CompletionText.Length - "Attribute".Length); // data.SetText (newText); // } // } // } return result.Result; } void AddExtensionMethods (CompletionDataWrapper result, Conversions conv, List baseTypes, string namespaceName) { foreach (var typeDefinition in ctx.GetTypes (namespaceName, StringComparer.Ordinal).Where (t => t.IsStatic && t.HasExtensionMethods)) { foreach (var m in typeDefinition.Methods.Where (m => m.IsExtensionMethod )) { var pt = m.Parameters.First ().Type.Resolve (ctx); string reflectionName = pt is ParameterizedType ? ((ParameterizedType)pt).GetDefinition ().ReflectionName : pt.ReflectionName; if (baseTypes.Any (bt => (bt is ParameterizedType ? ((ParameterizedType)bt).GetDefinition ().ReflectionName : bt.ReflectionName) == reflectionName)) { result.AddMember (m); } } } } IEnumerable CreateCaseCompletionData (TextLocation location) { var unit = ParseStub ("a: break;"); if (unit == null) return null; var s = unit.GetNodeAt (location); if (s == null) return null; var offset = document.GetOffset (s.Expression.StartLocation); var expr = GetExpressionAt (offset); if (expr == null) return null; var resolveResult = ResolveExpression (expr.Item1, expr.Item2, expr.Item3); if (resolveResult == null || resolveResult.Item1.Type.Kind != TypeKind.Enum) return null; var wrapper = new CompletionDataWrapper (this); AddEnumMembers (wrapper, resolveResult.Item1.Type, resolveResult.Item2); AutoCompleteEmptyMatch = false; return wrapper.Result; } #region Parsing methods Tuple GetExpressionBeforeCursor () { CompilationUnit baseUnit; if (currentMember == null) { baseUnit = ParseStub ("st {}", false); var type = baseUnit.GetNodeAt (location); if (type == null) { baseUnit = ParseStub ("a;", false); type = baseUnit.GetNodeAt (location); } if (type != null) { // insert target type into compilation unit, to respect the var target = type.Target; target.Remove (); var node = Unit.GetNodeAt (location) ?? Unit; node.AddChild (target, AstNode.Roles.Type); return Tuple.Create (CSharpParsedFile, (AstNode)target, Unit); } } if (currentMember == null && currentType == null) { return null; } baseUnit = ParseStub ("a()"); Print (baseUnit); var memberLocation = currentMember != null ? currentMember.Region.Begin : currentType.Region.Begin; var mref = baseUnit.GetNodeAt (location); if (mref == null){ var invoke = baseUnit.GetNodeAt (location); if (invoke != null) mref = invoke.Target as MemberReferenceExpression; } Expression expr = null; if (mref != null) { expr = mref.Target.Clone (); mref.Parent.ReplaceWith (expr); } else { Expression tref = baseUnit.GetNodeAt (location); var memberType = tref != null ? ((TypeReferenceExpression)tref).Type as MemberType : null; if (memberType == null) { memberType = baseUnit.GetNodeAt (location); if (memberType != null) { tref = baseUnit.GetNodeAt (location); if (tref == null) return null; } if (tref is ObjectCreateExpression) { expr = new TypeReferenceExpression (memberType.Target.Clone ()); expr.AddAnnotation (new ObjectCreateExpression ()); } } if (memberType == null) return null; if (expr == null) expr = new TypeReferenceExpression (memberType.Target.Clone ()); tref.ReplaceWith (expr); } var member = Unit.GetNodeAt (memberLocation); var member2 = baseUnit.GetNodeAt (memberLocation); member2.Remove (); member.ReplaceWith (member2); var tsvisitor = new TypeSystemConvertVisitor (ProjectContent, this.CSharpParsedFile.FileName); Unit.AcceptVisitor (tsvisitor, null); return Tuple.Create (tsvisitor.ParsedFile, (AstNode)expr, Unit); } Tuple GetExpressionAtCursor () { // if (currentMember == null && currentType == null) // return null; TextLocation memberLocation; if (currentMember != null) { memberLocation = currentMember.Region.Begin; } else if (currentType != null) { memberLocation = currentType.Region.Begin; } else { memberLocation = location; } var baseUnit = ParseStub (""); var tmpUnit = baseUnit; AstNode expr = baseUnit.GetNodeAt (location.Line, location.Column - 1); if (expr == null) expr = baseUnit.GetNodeAt (location.Line, location.Column - 1); if (expr == null) { baseUnit = ParseStub ("()"); expr = baseUnit.GetNodeAt (location.Line, location.Column - 1); } // try initializer expression if (expr == null) { baseUnit = ParseStub ("a = b};", false); expr = baseUnit.GetNodeAt (location.Line, location.Column - 1); } // try statement if (expr == null) { expr = tmpUnit.GetNodeAt (location.Line, location.Column - 1); } if (expr == null) return null; var member = Unit.GetNodeAt (memberLocation); var member2 = baseUnit.GetNodeAt (memberLocation); if (member != null && member2 != null) { member2.Remove (); if (member is TypeDeclaration) { member.AddChild (member2, TypeDeclaration.MemberRole); } else { member.ReplaceWith (member2); } } else { var tsvisitor2 = new TypeSystemConvertVisitor (ProjectContent, CSharpParsedFile.FileName); Unit.AcceptVisitor (tsvisitor2, null); return Tuple.Create (tsvisitor2.ParsedFile, expr, baseUnit); } var tsvisitor = new TypeSystemConvertVisitor (ProjectContent, CSharpParsedFile.FileName); Unit.AcceptVisitor (tsvisitor, null); return Tuple.Create (tsvisitor.ParsedFile, expr, Unit); } Tuple GetExpressionAt (int offset) { var parser = new CSharpParser (); string text = this.document.GetText (0, this.offset); var sb = new StringBuilder (text); sb.Append ("a;"); AppendMissingClosingBrackets (sb, text, false); var stream = new System.IO.StringReader (sb.ToString ()); var completionUnit = parser.Parse (stream, 0); stream.Close (); var loc = document.GetLocation (offset); var expr = completionUnit.GetNodeAt (loc, n => n is Expression || n is VariableDeclarationStatement); if (expr == null) return null; var tsvisitor = new TypeSystemConvertVisitor (ProjectContent, CSharpParsedFile.FileName); completionUnit.AcceptVisitor (tsvisitor, null); return Tuple.Create (tsvisitor.ParsedFile, expr, completionUnit); } #endregion #region Helper methods string GetPreviousToken (ref int i, bool allowLineChange) { char c; if (i <= 0) return null; do { c = document.GetCharAt (--i); } while (i > 0 && char.IsWhiteSpace (c) && (allowLineChange ? true : c != '\n')); if (i == 0) return null; if (!char.IsLetterOrDigit (c)) return new string (c, 1); int endOffset = i + 1; do { c = document.GetCharAt (i - 1); if (!(char.IsLetterOrDigit (c) || c == '_')) break; i--; } while (i > 0); return document.GetText (i, endOffset - i); } bool GetParameterCompletionCommandOffset (out int cpos) { // Start calculating the parameter offset from the beginning of the // current member, instead of the beginning of the file. cpos = offset - 1; IMember mem = currentMember; if (mem == null || (mem is IType)) return false; int startPos = document.GetOffset (mem.Region.BeginLine, mem.Region.BeginColumn); int parenDepth = 0; int chevronDepth = 0; while (cpos > startPos) { char c = document.GetCharAt (cpos); if (c == ')') parenDepth++; if (c == '>') chevronDepth++; if (parenDepth == 0 && c == '(' || chevronDepth == 0 && c == '<') { int p = GetCurrentParameterIndex (cpos + 1, startPos); if (p != -1) { cpos++; return true; } else { return false; } } if (c == '(') parenDepth--; if (c == '<') chevronDepth--; cpos--; } return false; } int GetCurrentParameterIndex (int offset, int memberStart) { int cursor = this.offset; int i = offset; if (i > cursor) return -1; if (i == cursor) return 1; // parameters are 1 based int index = memberStart + 1; int parentheses = 0; int bracket = 0; bool insideQuote = false, insideString = false, insideSingleLineComment = false, insideMultiLineComment = false; do { char c = document.GetCharAt (i - 1); switch (c) { case '\\': if (insideString || insideQuote) i++; break; case '\'': if (!insideString && !insideSingleLineComment && !insideMultiLineComment) insideQuote = !insideQuote; break; case '"': if (!insideQuote && !insideSingleLineComment && !insideMultiLineComment) insideString = !insideString; break; case '/': if (!insideQuote && !insideString && !insideMultiLineComment) { if (document.GetCharAt (i) == '/') insideSingleLineComment = true; if (document.GetCharAt (i) == '*') insideMultiLineComment = true; } break; case '*': if (insideMultiLineComment && document.GetCharAt (i) == '/') insideMultiLineComment = false; break; case '\n': case '\r': insideSingleLineComment = false; break; case '{': if (!insideQuote && !insideString && !insideSingleLineComment && !insideMultiLineComment) bracket++; break; case '}': if (!insideQuote && !insideString && !insideSingleLineComment && !insideMultiLineComment) bracket--; break; case '(': if (!insideQuote && !insideString && !insideSingleLineComment && !insideMultiLineComment) parentheses++; break; case ')': if (!insideQuote && !insideString && !insideSingleLineComment && !insideMultiLineComment) parentheses--; break; case ',': if (!insideQuote && !insideString && !insideSingleLineComment && !insideMultiLineComment && parentheses == 1 && bracket == 0) index++; break; } i++; } while (i <= cursor && parentheses >= 0); return parentheses != 1 || bracket > 0 ? -1 : index; } CSharpResolver GetState () { var state = new CSharpResolver (ctx, System.Threading.CancellationToken.None); state.CurrentMember = CSharpParsedFile.GetMember (location); state.CurrentTypeDefinition = CSharpParsedFile.GetInnermostTypeDefinition (location); state.CurrentUsingScope = CSharpParsedFile.GetUsingScope (location); if (state.CurrentMember != null) { var node = Unit.GetNodeAt (location); if (node == null) return state; var navigator = new NodeListResolveVisitorNavigator (new[] { node }); var visitor = new ResolveVisitor (state, CSharpParsedFile, navigator); Unit.AcceptVisitor (visitor, null); try { var newState = visitor.GetResolverStateBefore (node); if (newState != null) state = newState; } catch (Exception) { } } return state; } #endregion #region Preprocessor IEnumerable GetDirectiveCompletionData () { yield return factory.CreateLiteralCompletionData ("if"); yield return factory.CreateLiteralCompletionData ("else"); yield return factory.CreateLiteralCompletionData ("elif"); yield return factory.CreateLiteralCompletionData ("endif"); yield return factory.CreateLiteralCompletionData ("define"); yield return factory.CreateLiteralCompletionData ("undef"); yield return factory.CreateLiteralCompletionData ("warning"); yield return factory.CreateLiteralCompletionData ("error"); yield return factory.CreateLiteralCompletionData ("pragma"); yield return factory.CreateLiteralCompletionData ("line"); yield return factory.CreateLiteralCompletionData ("line hidden"); yield return factory.CreateLiteralCompletionData ("line default"); yield return factory.CreateLiteralCompletionData ("region"); yield return factory.CreateLiteralCompletionData ("endregion"); } #endregion #region Xml Comments static readonly List commentTags = new List (new string[] { "c", "code", "example", "exception", "include", "list", "listheader", "item", "term", "description", "para", "param", "paramref", "permission", "remarks", "returns", "see", "seealso", "summary", "value" }); IEnumerable GetXmlDocumentationCompletionData () { yield return factory.CreateLiteralCompletionData ("c", "Set text in a code-like font"); yield return factory.CreateLiteralCompletionData ("code", "Set one or more lines of source code or program output"); yield return factory.CreateLiteralCompletionData ("example", "Indicate an example"); yield return factory.CreateLiteralCompletionData ("exception", "Identifies the exceptions a method can throw", "exception cref=\"|\">"); yield return factory.CreateLiteralCompletionData ("include", "Includes comments from a external file", "include file=\"|\" path=\"\">"); yield return factory.CreateLiteralCompletionData ("list", "Create a list or table", "list type=\"|\">"); yield return factory.CreateLiteralCompletionData ("listheader", "Define the heading row"); yield return factory.CreateLiteralCompletionData ("item", "Defines list or table item"); yield return factory.CreateLiteralCompletionData ("term", "A term to define"); yield return factory.CreateLiteralCompletionData ("description", "Describes a list item"); yield return factory.CreateLiteralCompletionData ("para", "Permit structure to be added to text"); yield return factory.CreateLiteralCompletionData ("param", "Describe a parameter for a method or constructor", "param name=\"|\">"); yield return factory.CreateLiteralCompletionData ("paramref", "Identify that a word is a parameter name", "paramref name=\"|\"/>"); yield return factory.CreateLiteralCompletionData ("permission", "Document the security accessibility of a member", "permission cref=\"|\""); yield return factory.CreateLiteralCompletionData ("remarks", "Describe a type"); yield return factory.CreateLiteralCompletionData ("returns", "Describe the return value of a method"); yield return factory.CreateLiteralCompletionData ("see", "Specify a link", "see cref=\"|\"/>"); yield return factory.CreateLiteralCompletionData ("seealso", "Generate a See Also entry", "seealso cref=\"|\"/>"); yield return factory.CreateLiteralCompletionData ("summary", "Describe a member of a type"); yield return factory.CreateLiteralCompletionData ("typeparam", "Describe a type parameter for a generic type or method"); yield return factory.CreateLiteralCompletionData ("typeparamref", "Identify that a word is a type parameter name"); yield return factory.CreateLiteralCompletionData ("value", "Describe a property"); } #endregion #region Keywords static string[] expressionLevelKeywords = new string [] { "as", "is", "else", "out", "ref", "null", "delegate", "default"}; static string[] primitiveTypesKeywords = new string [] { "void", "object", "bool", "byte", "sbyte", "char", "short", "int", "long", "ushort", "uint", "ulong", "float", "double", "decimal", "string"}; static string[] statementStartKeywords = new string [] { "base", "new", "sizeof", "this", "true", "false", "typeof", "checked", "unchecked", "from", "break", "checked", "unchecked", "const", "continue", "do", "finally", "fixed", "for", "foreach", "goto", "if", "lock", "return", "stackalloc", "switch", "throw", "try", "unsafe", "using", "while", "yield", "dynamic", "var" }; static string[] globalLevelKeywords = new string [] { "namespace", "using", "extern", "public", "internal", "class", "interface", "struct", "enum", "delegate", "abstract", "sealed", "static", "unsafe", "partial" }; static string[] typeLevelKeywords = new string [] { "public", "internal", "protected", "private", "class", "interface", "struct", "enum", "delegate", "abstract", "sealed", "static", "unsafe", "partial", "const", "event", "extern", "fixed","new", "operator", "explicit", "implicit", "override", "readonly", "virtual", "volatile" }; static string[] linqKeywords = new string[] { "from", "where", "select", "group", "into", "orderby", "join", "let", "in", "on", "equals", "by", "ascending", "descending" }; #endregion } }