// // IMemberProvider.cs // // Author: // Mike Krüger // // Copyright (c) 2012 Xamarin Inc. // // 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 ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.CSharp.TypeSystem; using System.Linq; using ICSharpCode.NRefactory.CSharp.Resolver; namespace ICSharpCode.NRefactory.CSharp.Completion { public interface ICompletionContextProvider { IList ConditionalSymbols { get; } void GetCurrentMembers (int offset, out IUnresolvedTypeDefinition currentType, out IUnresolvedMember currentMember); Tuple GetMemberTextToCaret(int caretOffset, IUnresolvedTypeDefinition currentType, IUnresolvedMember currentMember); CSharpAstResolver GetResolver (CSharpResolver resolver, AstNode rootNode); } public class DefaultCompletionContextProvider : ICompletionContextProvider { readonly IDocument document; readonly CSharpUnresolvedFile unresolvedFile; readonly List symbols = new List (); public IList ConditionalSymbols { get { return symbols; } } public DefaultCompletionContextProvider (IDocument document, CSharpUnresolvedFile unresolvedFile) { if (document == null) throw new ArgumentNullException("document"); if (unresolvedFile == null) throw new ArgumentNullException("unresolvedFile"); this.document = document; this.unresolvedFile = unresolvedFile; } public void AddSymbol (string sym) { symbols.Add (sym); } public void GetCurrentMembers(int offset, out IUnresolvedTypeDefinition currentType, out IUnresolvedMember currentMember) { //var document = engine.document; var location = document.GetLocation(offset); currentType = null; foreach (var type in unresolvedFile.TopLevelTypeDefinitions) { if (type.Region.Begin < location) currentType = type; } currentType = FindInnerType (currentType, location); // location is beyond last reported end region, now we need to check, if the end region changed if (currentType != null && currentType.Region.End < location) { if (!IsInsideType (currentType, location)) currentType = null; } currentMember = null; if (currentType != null) { foreach (var member in currentType.Members) { if (member.Region.Begin < location && (currentMember == null || currentMember.Region.Begin < member.Region.Begin)) currentMember = member; } } // location is beyond last reported end region, now we need to check, if the end region changed // NOTE: Enums are a special case, there the "last" field needs to be treated as current member if (currentMember != null && currentMember.Region.End < location && currentType.Kind != TypeKind.Enum) { if (!IsInsideType (currentMember, location)) currentMember = null; }/* var stack = GetBracketStack (engine.GetMemberTextToCaret ().Item1); if (stack.Count == 0) currentMember = null;*/ } IUnresolvedTypeDefinition FindInnerType (IUnresolvedTypeDefinition parent, TextLocation location) { if (parent == null) return null; var currentType = parent; foreach (var type in parent.NestedTypes) { if (type.Region.Begin < location && location < type.Region.End) currentType = FindInnerType (type, location); } return currentType; } bool IsInsideType (IUnresolvedEntity currentType, TextLocation location) { if (currentType.Region.IsEmpty) return false; int startOffset = document.GetOffset (currentType.Region.Begin); int endOffset = document.GetOffset (location); //bool foundEndBracket = false; var bracketStack = new Stack (); bool isInString = false, isInChar = false; bool isInLineComment = false, isInBlockComment = false; for (int i = startOffset; i < endOffset; i++) { char ch = document.GetCharAt (i); switch (ch) { case '(': case '[': case '{': if (!isInString && !isInChar && !isInLineComment && !isInBlockComment) bracketStack.Push (ch); break; case ')': case ']': case '}': if (!isInString && !isInChar && !isInLineComment && !isInBlockComment) if (bracketStack.Count > 0) bracketStack.Pop (); break; case '/': if (isInBlockComment) { if (i > 0 && document.GetCharAt (i - 1) == '*') isInBlockComment = false; } else if (!isInString && !isInChar && i + 1 < document.TextLength) { char nextChar = document.GetCharAt (i + 1); if (nextChar == '/') isInLineComment = true; if (!isInLineComment && nextChar == '*') isInBlockComment = true; } break; case '"': if (!(isInChar || isInLineComment || isInBlockComment)) isInString = !isInString; break; case '\'': if (!(isInString || isInLineComment || isInBlockComment)) isInChar = !isInChar; break; default : if (NewLine.IsNewLine(ch)) { isInLineComment = false; } break; } } return bracketStack.Any (t => t == '{'); } public Tuple GetMemberTextToCaret(int caretOffset, IUnresolvedTypeDefinition currentType, IUnresolvedMember currentMember) { int startOffset; if (currentMember != null && currentType != null && currentType.Kind != TypeKind.Enum) { startOffset = document.GetOffset(currentMember.Region.Begin); } else if (currentType != null) { startOffset = document.GetOffset(currentType.Region.Begin); } else { startOffset = 0; } while (startOffset > 0) { char ch = document.GetCharAt(startOffset - 1); if (ch != ' ' && ch != '\t') { break; } --startOffset; } return Tuple.Create (document.GetText (startOffset, caretOffset - startOffset), document.GetLocation (startOffset)); } public CSharpAstResolver GetResolver (CSharpResolver resolver, AstNode rootNode) { return new CSharpAstResolver (resolver, rootNode, unresolvedFile); } } }