From 42e127b823ed033d592e9d02cb39218aa52381ee Mon Sep 17 00:00:00 2001 From: Christian Hornung Date: Sat, 2 Dec 2006 13:08:30 +0000 Subject: [PATCH] ResourceToolkit: The resource resolvers now examine expressions more precisely and also take the code completion trigger character into account instead of simply trying to resolve every possible sub-expression. This saves a few unneeded attempts to resolve expressions and fixes some problems where the resource code completion window opened when it should not, e.g. after typing something like 'ResourceService.GetString("...").Replace('. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@2118 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- ...NRefactoryResourceCodeCompletionBinding.cs | 2 +- .../Commands/TextEditorContextMenuBuilder.cs | 2 +- .../Src/ProjectFileDictionaryService.cs | 2 + .../Refactoring/ResourceRefactoringService.cs | 2 +- .../Src/Resolver/AbstractResourceResolver.cs | 13 +- .../Resolver/BclNRefactoryResourceResolver.cs | 176 ++++++++++++++++-- ...SharpCodeCoreNRefactoryResourceResolver.cs | 26 ++- .../ICSharpCodeCoreResourceResolver.cs | 9 +- .../Resolver/INRefactoryResourceResolver.cs | 4 +- .../Project/Src/Resolver/IResourceResolver.cs | 6 +- .../Src/Resolver/NRefactoryAstCacheService.cs | 52 +++++- .../Resolver/NRefactoryResourceResolver.cs | 38 +--- .../Project/Src/ResourceResolverService.cs | 10 +- .../Src/ToolTips/ResourceToolTipProvider.cs | 2 +- 14 files changed, 267 insertions(+), 77 deletions(-) diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/CodeCompletion/AbstractNRefactoryResourceCodeCompletionBinding.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/CodeCompletion/AbstractNRefactoryResourceCodeCompletionBinding.cs index 382c7c0e59..6f3dc56c83 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/CodeCompletion/AbstractNRefactoryResourceCodeCompletionBinding.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/CodeCompletion/AbstractNRefactoryResourceCodeCompletionBinding.cs @@ -23,7 +23,7 @@ namespace Hornung.ResourceToolkit.CodeCompletion if (this.CompletionPossible(editor, ch)) { - ResourceResolveResult result = ResourceResolverService.Resolve(editor); + ResourceResolveResult result = ResourceResolverService.Resolve(editor, ch); if (result != null) { if (result.ResourceFileContent != null) { editor.ShowCompletionWindow(new ResourceCodeCompletionDataProvider(result.ResourceFileContent, this.OutputVisitor, result.CallingClass != null ? result.CallingClass.Name+"." : null), ch); diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Commands/TextEditorContextMenuBuilder.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Commands/TextEditorContextMenuBuilder.cs index 2bab5c0586..ed4fdaac8f 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Commands/TextEditorContextMenuBuilder.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Commands/TextEditorContextMenuBuilder.cs @@ -34,7 +34,7 @@ namespace Hornung.ResourceToolkit.Commands return new ToolStripItem[0]; } - ResourceResolveResult result = ResourceResolverService.Resolve(editor); + ResourceResolveResult result = ResourceResolverService.Resolve(editor, null); if (result != null && result.ResourceFileContent != null && result.Key != null) { List items = new List(); diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/ProjectFileDictionaryService.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/ProjectFileDictionaryService.cs index 50dd8bf32f..89c2947168 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/ProjectFileDictionaryService.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/ProjectFileDictionaryService.cs @@ -55,7 +55,9 @@ namespace Hornung.ResourceToolkit } + #if DEBUG LoggingService.Debug("ResourceToolkit: ProjectFileDictionary: Could not determine project for file '"+(fileName ?? "")+"'."); + #endif return ProjectService.CurrentProject; } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs index 1577d79ee4..f4c8d9616a 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs @@ -104,7 +104,7 @@ namespace Hornung.ResourceToolkit.Refactoring while ((pos = finder.GetNextPossibleOffset(fileName, fileContent, pos)) >= 0) { Point docPos = doc.OffsetToPosition(pos); - ResourceResolveResult rrr = ResourceResolverService.Resolve(fileName, doc, docPos.Y, docPos.X); + ResourceResolveResult rrr = ResourceResolverService.Resolve(fileName, doc, docPos.Y, docPos.X, null); if (rrr != null && rrr.ResourceFileContent != null && rrr.Key != null) { if (finder.IsReferenceToResource(rrr)) { diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/AbstractResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/AbstractResourceResolver.cs index 864e8d977b..ea62be1994 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/AbstractResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/AbstractResourceResolver.cs @@ -29,8 +29,9 @@ namespace Hornung.ResourceToolkit.Resolver /// The document that contains the expression to be resolved. /// The 0-based line in the file that contains the expression to be resolved. /// The 0-based column position of the expression to be resolved. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the specified position in the specified file, or null if that expression does not reference a (known) resource or if the specified position is invalid. - public ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn) + public ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, char? charTyped) { if (fileName == null || document == null) { LoggingService.Debug("ResourceToolkit: "+this.GetType().ToString()+".Resolve called with null fileName or document argument"); @@ -40,17 +41,18 @@ namespace Hornung.ResourceToolkit.Resolver LoggingService.Debug("ResourceToolkit: "+this.GetType().ToString()+".Resolve called with invalid position arguments"); return null; } - return this.Resolve(fileName, document, caretLine, caretColumn, document.PositionToOffset(new Point(caretColumn, caretLine))); + return this.Resolve(fileName, document, caretLine, caretColumn, document.PositionToOffset(new Point(caretColumn, caretLine)), charTyped); } /// /// Attempts to resolve a reference to a resource. /// /// The text editor for which a resource resolution attempt should be performed. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the caret in the specified editor, or null if that expression does not reference a (known) resource. - public ResourceResolveResult Resolve(TextEditorControl editor) + public ResourceResolveResult Resolve(TextEditorControl editor, char? charTyped) { - return this.Resolve(editor.FileName, editor.Document, editor.ActiveTextAreaControl.Caret.Line, editor.ActiveTextAreaControl.Caret.Column); + return this.Resolve(editor.FileName, editor.Document, editor.ActiveTextAreaControl.Caret.Line, editor.ActiveTextAreaControl.Caret.Column, charTyped); } // ******************************************************************************************************************************** @@ -63,8 +65,9 @@ namespace Hornung.ResourceToolkit.Resolver /// The 0-based line in the file that contains the expression to be resolved. /// The 0-based column position of the expression to be resolved. /// The offset of the position of the expression to be resolved. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the specified position in the specified file, or null if that expression does not reference a (known) resource. - protected abstract ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset); + protected abstract ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset, char? charTyped); /// /// Determines whether this resolver supports resolving resources in the given file. diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs index fb29c40d57..180e3a9a7c 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs @@ -35,31 +35,131 @@ namespace Hornung.ResourceToolkit.Resolver /// The column where the expression is located. /// The name of the source file where the expression is located. /// The content of the source file where the expression is located. + /// The ExpressionFinder for the file. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A ResourceResolveResult describing the referenced resource, or null, if this expression does not reference a resource using the standard .NET framework classes. - public ResourceResolveResult Resolve(ExpressionResult expressionResult, Expression expr, ResolveResult resolveResult, int caretLine, int caretColumn, string fileName, string fileContent) + public ResourceResolveResult Resolve(ExpressionResult expressionResult, Expression expr, ResolveResult resolveResult, int caretLine, int caretColumn, string fileName, string fileContent, IExpressionFinder expressionFinder, char? charTyped) { - IResourceFileContent rfc = null; + /* + * We need to catch the following cases here: + * + * Something.GetString( + * Something.GetString("...") + * Something[ + * Something["..."] + * + */ - MemberResolveResult mrr = resolveResult as MemberResolveResult; - if (mrr != null) { - rfc = ResolveResourceFileContent(mrr.ResolvedMember); - } else { + if (charTyped == '(') { - LocalResolveResult lrr = resolveResult as LocalResolveResult; - if (lrr != null) { - if (!lrr.IsParameter) { - rfc = ResolveResourceFileContent(lrr.Field); + // Something.GetString + // This is a MethodResolveResult and we need the reference to "Something", + // which is the next outer expression. + // This is only valid when invoked from code completion + // and the method invocation character ('(' in C# and VB) + // has been typed. + + // This code is also reused when reducing a complete InvocationExpression + // (MemberResolveResult) to the method reference by passing '(' as + // charTyped explicitly. + + MethodResolveResult methrr = resolveResult as MethodResolveResult; + if (methrr != null) { + if ((methrr.Name == "GetString" || methrr.Name == "GetObject" || methrr.Name == "GetStream") && + (resolveResult = NRefactoryAstCacheService.ResolveNextOuterExpression(ref expressionResult, caretLine, caretColumn, fileName, expressionFinder)) != null) { + + return ResolveResource(resolveResult, expr); + + } else { + + return null; + } } } + // Do not use "else if" here. + // '(' is also the IndexerExpressionStartToken for VB, + // so the "else" block further down might still apply. - if (rfc != null) { - string key = GetKeyFromExpression(expr); + if (charTyped == null) { + + // A MemberResolveResult with a complete expression + // must only be considered a valid resource reference + // when Resolve is not invoked from code completion + // (i.e. charTyped == null) because this indicates + // that the resource reference is already before the typed character + // and we are not interested in the following expression. + // This may happen when typing something like: + // Something.GetString("...")[ + + MemberResolveResult mrr = resolveResult as MemberResolveResult; + if (mrr != null) { + + if (mrr.ResolvedMember is IMethod && + (mrr.ResolvedMember.Name == "GetString" || mrr.ResolvedMember.Name == "GetObject" || mrr.ResolvedMember.Name == "GetStream")) { + + // Something.GetString("...") + // This is a MemberResolveResult and we need the reference to "Something". + // The expression finder may only remove the string literal, so + // we have to call Resolve again in this case to resolve + // the method reference. + + if ((resolveResult = NRefactoryAstCacheService.ResolveNextOuterExpression(ref expressionResult, caretLine, caretColumn, fileName, expressionFinder)) != null) { + + if (resolveResult is MethodResolveResult) { + return this.Resolve(expressionResult, expr, resolveResult, caretLine, caretColumn, fileName, fileContent, expressionFinder, '('); + } else { + return ResolveResource(resolveResult, expr); + } + + } else { + + return null; + + } + + } else if (expr is IndexerExpression && + IsResourceManager(mrr.ResolvedMember.DeclaringType.DefaultReturnType, fileName)) { + + // Something["..."] is an IndexerExpression. + // We need the reference to Something and this is + // the next outer expression. + + if ((resolveResult = NRefactoryAstCacheService.ResolveNextOuterExpression(ref expressionResult, caretLine, caretColumn, fileName, expressionFinder)) != null) { + return ResolveResource(resolveResult, expr); + } else { + return null; + } + + } + + } + + } else { + + // This request is triggered from code completion. + // The only case that has not been caught above is: + // Something[ + // The reference to "Something" is already in this expression. + // So we have to test the trigger character against the + // indexer expression start token of the file's language. + + LanguageProperties lp = NRefactoryResourceResolver.GetLanguagePropertiesForFile(fileName); + if (lp != null && + !String.IsNullOrEmpty(lp.IndexerExpressionStartToken) && + lp.IndexerExpressionStartToken[0] == charTyped) { + + #if DEBUG + LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver: Indexer expression start typed, ResolveResult: "+resolveResult.ToString()); + LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver: -> Expression: "+expr.ToString()); + #endif + + return ResolveResource(resolveResult, expr); + + } - // TODO: Add information about return type (of the resource, if present). - return new ResourceResolveResult(resolveResult.CallingClass, resolveResult.CallingMember, null, rfc, key); } return null; @@ -87,6 +187,44 @@ namespace Hornung.ResourceToolkit.Resolver #endregion + /// + /// Tries to find a resource reference in the specified expression. + /// + /// The ResolveResult that describes the referenced member. + /// The AST representation of the full expression. + /// + /// The ResourceResolveResult describing the referenced resource, if successful, + /// or a null reference, if the referenced member is not a resource manager + /// or if the resource file cannot be determined. + /// + static ResourceResolveResult ResolveResource(ResolveResult resolveResult, Expression expr) + { + IResourceFileContent rfc = null; + + MemberResolveResult mrr = resolveResult as MemberResolveResult; + if (mrr != null) { + rfc = ResolveResourceFileContent(mrr.ResolvedMember); + } else { + + LocalResolveResult lrr = resolveResult as LocalResolveResult; + if (lrr != null) { + if (!lrr.IsParameter) { + rfc = ResolveResourceFileContent(lrr.Field); + } + } + + } + + if (rfc != null) { + string key = GetKeyFromExpression(expr); + + // TODO: Add information about return type (of the resource, if present). + return new ResourceResolveResult(resolveResult.CallingClass, resolveResult.CallingMember, null, rfc, key); + } + + return null; + } + /// /// Tries to determine the resource file content which is referenced by the /// resource manager which is assigned to the specified member. @@ -149,6 +287,8 @@ namespace Hornung.ResourceToolkit.Resolver /// Determines if the specified type is a ResourceManager type that can /// be handled by this resolver. /// + /// The type that will be checked if it is a ResourceManager. + /// The name of the source code file where the reference to this type occurs. static bool IsResourceManager(IReturnType type, string sourceFileName) { IProject p = ProjectFileDictionaryService.GetProjectForFile(sourceFileName); @@ -163,13 +303,13 @@ namespace Hornung.ResourceToolkit.Resolver return false; } - IClass resourceManager = pc.GetClass("System.Resources.ResourceManager"); - if (resourceManager == null) { + IClass c = type.GetUnderlyingClass(); + if (c == null) { return false; } - IClass c = type.GetUnderlyingClass(); - if (c == null) { + IClass resourceManager = pc.GetClass("System.Resources.ResourceManager"); + if (resourceManager == null) { return false; } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs index eb2fbe9042..d449cf4fb6 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs @@ -31,8 +31,10 @@ namespace Hornung.ResourceToolkit.Resolver /// The column where the expression is located. /// The name of the source file where the expression is located. /// The content of the source file where the expression is located. + /// The ExpressionFinder for the file. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A ResourceResolveResult describing the referenced resource, or null, if this expression does not reference a resource using the ICSharpCode.Core.ResourceService class. - public ResourceResolveResult Resolve(ExpressionResult expressionResult, Expression expr, ResolveResult resolveResult, int caretLine, int caretColumn, string fileName, string fileContent) + public ResourceResolveResult Resolve(ExpressionResult expressionResult, Expression expr, ResolveResult resolveResult, int caretLine, int caretColumn, string fileName, string fileContent, IExpressionFinder expressionFinder, char? charTyped) { IMember member = null; @@ -41,12 +43,30 @@ namespace Hornung.ResourceToolkit.Resolver // has already been typed. MemberResolveResult mrr = resolveResult as MemberResolveResult; if (mrr != null) { - member = mrr.ResolvedMember; + + // If it is a MemberResolveResult, this indicates that + // the complete expression is already in the buffer. + // So we only assign the member if Resolve is not invoked + // from code completion to prevent the code completion window + // from opening when typing something like: + // ResourceService.GetString(...)[ + if (charTyped == null) { + member = mrr.ResolvedMember; + } + } else { + MethodResolveResult methrr = resolveResult as MethodResolveResult; if (methrr != null) { - member = methrr.GetMethodIfSingleOverload(); + + // If it is a MethodResolveResult, the expression is incomplete. + // Accept only if '(' has been typed. + if (charTyped == '(') { + member = methrr.GetMethodIfSingleOverload(); + } + } + } if (member is IMethod && diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs index f55809cacb..8bda00a009 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs @@ -91,9 +91,16 @@ namespace Hornung.ResourceToolkit.Resolver /// The 0-based line in the file that contains the expression to be resolved. /// The 0-based column position of the expression to be resolved. /// The offset of the position of the expression to be resolved. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the specified position in the specified file, or null if that expression does not reference a (known) resource. - protected override ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset) + protected override ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset, char? charTyped) { + // If Resolve is invoked from code completion, + // we are only interested in the ':' character of '${res:'. + if (charTyped != null && charTyped != ':') { + return null; + } + // Find $ character to the left of the caret. caretOffset += 1; char ch; diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs index ef512a8f51..d0dc9e9b9c 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs @@ -30,8 +30,10 @@ namespace Hornung.ResourceToolkit.Resolver /// The column where the expression is located. /// The name of the source file where the expression is located. /// The content of the source file where the expression is located. + /// The ExpressionFinder for the file. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A ResourceResolveResult describing the referenced resource, or null, if this expression does not reference a resource in a known way. - ResourceResolveResult Resolve(ExpressionResult expressionResult, Expression expr, ResolveResult resolveResult, int caretLine, int caretColumn, string fileName, string fileContent); + ResourceResolveResult Resolve(ExpressionResult expressionResult, Expression expr, ResolveResult resolveResult, int caretLine, int caretColumn, string fileName, string fileContent, IExpressionFinder expressionFinder, char? charTyped); /// /// Gets a list of patterns that can be searched for in the specified file diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/IResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/IResourceResolver.cs index d55924b193..df0bbda64c 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/IResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/IResourceResolver.cs @@ -27,15 +27,17 @@ namespace Hornung.ResourceToolkit.Resolver /// The document that contains the expression to be resolved. /// The 1-based line in the file that contains the expression to be resolved. /// The 1-based column position of the expression to be resolved. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the specified position in the specified file, or null if that expression does not reference a (known) resource. - ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn); + ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, char? charTyped); /// /// Attempts to resolve a reference to a resource. /// /// The text editor for which a resource resolution attempt should be performed. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the caret in the specified editor, or null if that expression does not reference a (known) resource. - ResourceResolveResult Resolve(TextEditorControl editor); + ResourceResolveResult Resolve(TextEditorControl editor, char? charTyped); /// /// Determines whether this resolver supports resolving resources in the given file. diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs index 68cd6ee9b8..367b16f96d 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs @@ -118,6 +118,24 @@ namespace Hornung.ResourceToolkit.Resolver // ******************************************************************************************************************************** + /// + /// Parses an expression with NRefactory. + /// + /// The file name of the source code file that contains the expression. + /// The expression to parse. + /// The parsed expression or null if the expression cannot be parsed or the language of the source code file is not supported. + public static Expression ParseExpression(string fileName, string expression) + { + SupportedLanguage? l = NRefactoryResourceResolver.GetFileLanguage(fileName); + if (l == null) { + return null; + } + + using (ICSharpCode.NRefactory.IParser p = ICSharpCode.NRefactory.ParserFactory.CreateParser(l.Value, new System.IO.StringReader(expression))) { + return p.ParseExpression(); + } + } + /// /// Resolves an expression using low-level NRefactoryResolver methods and making /// use of the cache if possible. @@ -131,13 +149,9 @@ namespace Hornung.ResourceToolkit.Resolver /// A ResolveResult or null if the expression cannot be resolved. public static ResolveResult ResolveLowLevel(string fileName, int caretLine, int caretColumn, CompilationUnit compilationUnit, string expression, ExpressionContext context) { - using (ICSharpCode.NRefactory.IParser p = ICSharpCode.NRefactory.ParserFactory.CreateParser(NRefactoryResourceResolver.GetFileLanguage(fileName).Value, new System.IO.StringReader(expression))) { - Expression expr = p.ParseExpression(); - if (expr == null) { - return null; - } - return ResolveLowLevel(fileName, caretLine, caretColumn, compilationUnit, expression, expr, context); - } + Expression expr = ParseExpression(fileName, expression); + if (expr == null) return null; + return ResolveLowLevel(fileName, caretLine, caretColumn, compilationUnit, expression, expr, context); } /// @@ -216,7 +230,7 @@ namespace Hornung.ResourceToolkit.Resolver if (resolver.CallingMember != null) { - // Cache member->node mappings to improves performance + // Cache member->node mappings to improve performance // (if cache is enabled) INode memberNode; if (!CacheEnabled || !cachedMemberMappings.TryGetValue(resolver.CallingMember, out memberNode)) { @@ -238,5 +252,27 @@ namespace Hornung.ResourceToolkit.Resolver return resolver.ResolveInternal(expression, context); } + + /// + /// Resolves the next outer expression of the specified expression + /// using low-level NRefactoryResolver methods and making + /// use of the cache if possible. + /// + /// The ExpressionResult that contains the expression to be resolved. The contained expression will be set to the next outer expression or null if there is no such expression. + /// The 0-based line number of the expression. + /// The 0-based column number of the expression. + /// The file name of the source code file that contains the expression to be resolved. + /// The ExpressionFinder for this source code file. + /// A ResolveResult or null if the outer expression cannot be resolved or if the specified expression is the outermost expression. + public static ResolveResult ResolveNextOuterExpression(ref ExpressionResult expressionResult, int caretLine, int caretColumn, string fileName, IExpressionFinder expressionFinder) + { + if (!String.IsNullOrEmpty(expressionResult.Expression = expressionFinder.RemoveLastPart(expressionResult.Expression))) { + Expression nextExpression; + if ((nextExpression = ParseExpression(fileName, expressionResult.Expression)) != null) { + return ResolveLowLevel(fileName, caretLine + 1, caretColumn + 1, null, expressionResult.Expression, nextExpression, expressionResult.Context); + } + } + return null; + } } } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs index 35e1b48069..e2dbdda768 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs @@ -102,8 +102,9 @@ namespace Hornung.ResourceToolkit.Resolver /// The 0-based line in the file that contains the expression to be resolved. /// The 0-based column position of the expression to be resolved. /// The offset of the position of the expression to be resolved. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the specified position in the specified file, or null if that expression does not reference a (known) resource. - protected override ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset) + protected override ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset, char? charTyped) { IExpressionFinder ef = ParserService.GetExpressionFinder(fileName); if (ef == null) { @@ -124,38 +125,13 @@ namespace Hornung.ResourceToolkit.Resolver if (result.Expression != null) { - ResourceResolveResult rrr = null; + Expression expr = NRefactoryAstCacheService.ParseExpression(fileName, result.Expression); - SupportedLanguage? language = GetFileLanguage(fileName); - if (language == null) { + if (expr == null) { return null; } - // The resolve routine needs the member which contains the actual member being referenced. - // If a complete expression is given, the expression needs to be reduced to - // the member reference. - Expression fullExpr = null; - while (result.Expression != null && result.Expression.Length > 0) { - Expression expr = null; - using(ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser(language.Value, new StringReader(result.Expression))) { - if (parser != null) { - expr = parser.ParseExpression(); - } - } - - if (expr == null) { - break; - } - if (fullExpr == null) { - fullExpr = expr; - } - if ((rrr = TryResolve(result, expr, fullExpr, caretLine, caretColumn, fileName, document.TextContent)) != null) { - break; - } - result.Expression = ef.RemoveLastPart(result.Expression); - } - - return rrr; + return TryResolve(result, expr, caretLine, caretColumn, fileName, document.TextContent, ef, charTyped); } @@ -168,14 +144,14 @@ namespace Hornung.ResourceToolkit.Resolver /// Tries to resolve the resource reference using all available /// NRefactory resource resolvers. /// - static ResourceResolveResult TryResolve(ExpressionResult result, Expression expr, Expression fullExpr, int caretLine, int caretColumn, string fileName, string fileContent) + static ResourceResolveResult TryResolve(ExpressionResult result, Expression expr, int caretLine, int caretColumn, string fileName, string fileContent, IExpressionFinder expressionFinder, char? charTyped) { ResolveResult rr = NRefactoryAstCacheService.ResolveLowLevel(fileName, caretLine+1, caretColumn+1, null, result.Expression, expr, result.Context); if (rr != null) { ResourceResolveResult rrr; foreach (INRefactoryResourceResolver resolver in Resolvers) { - if ((rrr = resolver.Resolve(result, fullExpr, rr, caretLine, caretColumn, fileName, fileContent)) != null) { + if ((rrr = resolver.Resolve(result, expr, rr, caretLine, caretColumn, fileName, fileContent, expressionFinder, charTyped)) != null) { return rrr; } } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/ResourceResolverService.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/ResourceResolverService.cs index 3002d5da51..dca4f77a89 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/ResourceResolverService.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/ResourceResolverService.cs @@ -50,12 +50,13 @@ namespace Hornung.ResourceToolkit /// Attempts to resolve a reference to a resource using all registered resolvers. /// /// The text editor for which a resource resolution attempt should be performed. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the caret in the specified editor, or null if all registered resolvers return null. - public static ResourceResolveResult Resolve(TextEditorControl editor) + public static ResourceResolveResult Resolve(TextEditorControl editor, char? charTyped) { ResourceResolveResult result; foreach (IResourceResolver resolver in Resolvers) { - if ((result = resolver.Resolve(editor)) != null) { + if ((result = resolver.Resolve(editor, charTyped)) != null) { return result; } } @@ -69,12 +70,13 @@ namespace Hornung.ResourceToolkit /// The document that contains the expression to be resolved. /// The 0-based line in the file that contains the expression to be resolved. /// The 0-based column position of the expression to be resolved. + /// The character that has been typed at the caret position but is not yet in the buffer (this is used when invoked from code completion), or null. /// A that describes which resource is referenced by the expression at the caret in the specified editor, or null if all registered resolvers return null. - public static ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn) + public static ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, char? charTyped) { ResourceResolveResult result; foreach (IResourceResolver resolver in Resolvers) { - if ((result = resolver.Resolve(fileName, document, caretLine, caretColumn)) != null) { + if ((result = resolver.Resolve(fileName, document, caretLine, caretColumn, charTyped)) != null) { return result; } } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs index 33b837c6c0..fa60693fa1 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs @@ -28,7 +28,7 @@ namespace Hornung.ResourceToolkit.ToolTips return null; } - ResourceResolveResult result = ResourceResolverService.Resolve(textArea.MotherTextEditorControl.FileName, doc, logicPos.Y, logicPos.X); + ResourceResolveResult result = ResourceResolverService.Resolve(textArea.MotherTextEditorControl.FileName, doc, logicPos.Y, logicPos.X, null); if (result != null && result.ResourceFileContent != null) { return new ToolTipInfo(ResourceResolverService.FormatResourceDescription(result.ResourceFileContent, result.Key));