You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
14 KiB
372 lines
14 KiB
// <file> |
|
// <copyright see="prj:///doc/copyright.txt"/> |
|
// <license see="prj:///doc/license.txt"/> |
|
// <owner name="Christian Hornung" email=""/> |
|
// <version>$Revision$</version> |
|
// </file> |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Globalization; |
|
using System.IO; |
|
|
|
using Hornung.ResourceToolkit.ResourceFileContent; |
|
using ICSharpCode.Core; |
|
using ICSharpCode.NRefactory; |
|
using ICSharpCode.NRefactory.Ast; |
|
using ICSharpCode.SharpDevelop; |
|
using ICSharpCode.SharpDevelop.Dom; |
|
using ICSharpCode.SharpDevelop.Project; |
|
using ICSharpCode.TextEditor.Document; |
|
|
|
namespace Hornung.ResourceToolkit.Resolver |
|
{ |
|
/// <summary> |
|
/// Resolves resource references using NRefactory. |
|
/// </summary> |
|
public class NRefactoryResourceResolver : AbstractResourceResolver |
|
{ |
|
|
|
/// <summary> |
|
/// The AddIn tree path where the NRefactory resource resolvers are registered. |
|
/// </summary> |
|
public const string NRefactoryResourceResolversAddInTreePath = "/AddIns/ResourceToolkit/NRefactoryResourceResolver/Resolvers"; |
|
|
|
// ******************************************************************************************************************************** |
|
|
|
static List<INRefactoryResourceResolver> resolvers; |
|
|
|
/// <summary> |
|
/// Gets a list of all registered NRefactory resource resolvers. |
|
/// </summary> |
|
public static IEnumerable<INRefactoryResourceResolver> Resolvers { |
|
get { |
|
if (resolvers == null) { |
|
resolvers = AddInTree.BuildItems<INRefactoryResourceResolver>(NRefactoryResourceResolversAddInTreePath, null, false); |
|
} |
|
return resolvers; |
|
} |
|
} |
|
|
|
// ******************************************************************************************************************************** |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="NRefactoryResourceResolver"/> class. |
|
/// </summary> |
|
public NRefactoryResourceResolver() : base() |
|
{ |
|
} |
|
|
|
// ******************************************************************************************************************************** |
|
|
|
/// <summary> |
|
/// Determines whether this resolver supports resolving resources in the given file. |
|
/// </summary> |
|
/// <param name="fileName">The name of the file to examine.</param> |
|
/// <returns><c>true</c>, if this resolver supports resolving resources in the given file, <c>false</c> otherwise.</returns> |
|
public override bool SupportsFile(string fileName) |
|
{ |
|
// Any source code file supported by NRefactory is supported |
|
return (GetFileLanguage(fileName) != null); |
|
} |
|
|
|
/// <summary> |
|
/// Gets a list of patterns that can be searched for in the specified file |
|
/// to find possible resource references that are supported by this |
|
/// resolver. |
|
/// </summary> |
|
/// <param name="fileName">The name of the file to get a list of possible patterns for.</param> |
|
public override IEnumerable<string> GetPossiblePatternsForFile(string fileName) |
|
{ |
|
if (this.SupportsFile(fileName)) { |
|
List<string> patterns = new List<string>(); |
|
foreach (INRefactoryResourceResolver resolver in Resolvers) { |
|
foreach (string pattern in resolver.GetPossiblePatternsForFile(fileName)) { |
|
if (!patterns.Contains(pattern)) { |
|
patterns.Add(pattern); |
|
} |
|
} |
|
} |
|
return patterns; |
|
} |
|
return new string[0]; |
|
} |
|
|
|
// ******************************************************************************************************************************** |
|
|
|
/// <summary> |
|
/// Attempts to resolve a reference to a resource. |
|
/// </summary> |
|
/// <param name="fileName">The name of the file that contains the expression to be resolved.</param> |
|
/// <param name="document">The document that contains the expression to be resolved.</param> |
|
/// <param name="caretLine">The 0-based line in the file that contains the expression to be resolved.</param> |
|
/// <param name="caretColumn">The 0-based column position of the expression to be resolved.</param> |
|
/// <param name="caretOffset">The offset of the position of the expression to be resolved.</param> |
|
/// <param name="charTyped">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 <c>null</c>.</param> |
|
/// <returns>A <see cref="ResourceResolveResult"/> that describes which resource is referenced by the expression at the specified position in the specified file, or <c>null</c> if that expression does not reference a (known) resource.</returns> |
|
protected override ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset, char? charTyped) |
|
{ |
|
IExpressionFinder ef = ParserService.GetExpressionFinder(fileName); |
|
if (ef == null) { |
|
return null; |
|
} |
|
|
|
ExpressionResult result = ef.FindFullExpression(document.TextContent, caretOffset); |
|
|
|
if (result.Expression == null) { |
|
// may happen if in string |
|
while (--caretOffset > 0 && (result = ef.FindFullExpression(document.TextContent, caretOffset)).Expression == null) { |
|
if (document.GetLineNumberForOffset(caretOffset) != caretLine) { |
|
// only look in same line |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (result.Expression != null) { |
|
|
|
Expression expr = NRefactoryAstCacheService.ParseExpression(fileName, result.Expression); |
|
|
|
if (expr == null) { |
|
return null; |
|
} |
|
|
|
return TryResolve(result, expr, caretLine, caretColumn, fileName, document.TextContent, ef, charTyped); |
|
|
|
} |
|
|
|
return null; |
|
} |
|
|
|
// ******************************************************************************************************************************** |
|
|
|
/// <summary> |
|
/// Tries to resolve the resource reference using all available |
|
/// NRefactory resource resolvers. |
|
/// </summary> |
|
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, expr, rr, caretLine, caretColumn, fileName, fileContent, expressionFinder, charTyped)) != null) { |
|
return rrr; |
|
} |
|
} |
|
|
|
} |
|
|
|
return null; |
|
} |
|
|
|
// ******************************************************************************************************************************** |
|
|
|
/// <summary> |
|
/// Determines the file which contains the resources referenced by the specified manifest resource name. |
|
/// </summary> |
|
/// <param name="sourceFileName">The name of the source code file which the reference occurs in.</param> |
|
/// <param name="resourceName">The manifest resource name to find the resource file for.</param> |
|
/// <returns>A <see cref="ResourceSetReference"/> with the specified resource set name and the name of the file that contains the resources with the specified manifest resource name, or <c>null</c> if the file name cannot be determined.</returns> |
|
/// <exception cref="ArgumentNullException">The <paramref name="resourceName"/> parameter is <c>null</c>.</exception> |
|
public static ResourceSetReference GetResourceSetReference(string sourceFileName, string resourceName) |
|
{ |
|
if (resourceName == null) { |
|
throw new ArgumentNullException("resourceName"); |
|
} |
|
|
|
IProject p = ProjectFileDictionaryService.GetProjectForFile(sourceFileName); |
|
|
|
if (p != null) { |
|
|
|
string fileName = null; |
|
|
|
if (resourceName.StartsWith(p.RootNamespace, StringComparison.InvariantCultureIgnoreCase)) { |
|
|
|
// Look for a resource file in the project with the exact name. |
|
if ((fileName = FindResourceFileName(Path.Combine(p.Directory, resourceName.Substring(p.RootNamespace.Length+1).Replace('.', Path.DirectorySeparatorChar)))) != null) { |
|
return new ResourceSetReference(resourceName, fileName); |
|
} |
|
|
|
} |
|
|
|
// SharpDevelop silently strips the (hard-coded) folder names |
|
// "src" and "source" when generating the default namespace name |
|
// for new files. |
|
// When MSBuild generates the manifest resource names for the |
|
// forms designer resources, it uses the type name of the |
|
// first class in the file. So we should find all files |
|
// that contain a type with the name in resourceName |
|
// and then look for dependent resource files or resource files |
|
// with the same name in the same directory as the source files. |
|
|
|
// Find all source files that contain a type with the same |
|
// name as the resource we are looking for. |
|
List<string> possibleSourceFiles = new List<string>(); |
|
IProjectContent pc = ParserService.GetProjectContent(p); |
|
if (pc != null) { |
|
|
|
IClass resourceClass = pc.GetClass(resourceName); |
|
|
|
if (resourceClass != null) { |
|
CompoundClass cc = resourceClass.GetCompoundClass() as CompoundClass; |
|
|
|
foreach (IClass c in (cc == null ? new IClass[] { resourceClass } : cc.GetParts())) { |
|
if (c.CompilationUnit != null && c.CompilationUnit.FileName != null) { |
|
|
|
#if DEBUG |
|
LoggingService.Debug("ResourceToolkit: NRefactoryResourceResolver found file '"+c.CompilationUnit.FileName+"' to contain the type '"+resourceName+"'"); |
|
#endif |
|
|
|
possibleSourceFiles.Add(c.CompilationUnit.FileName); |
|
|
|
} |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
foreach (string possibleSourceFile in possibleSourceFiles) { |
|
string possibleSourceFileName = Path.GetFileName(possibleSourceFile); |
|
|
|
// Find resource files dependent on these source files. |
|
foreach (ProjectItem pi in p.Items) { |
|
FileProjectItem fpi = pi as FileProjectItem; |
|
if (fpi != null) { |
|
if (fpi.DependentUpon != null && |
|
(fpi.ItemType == ItemType.EmbeddedResource || fpi.ItemType == ItemType.Resource || fpi.ItemType == ItemType.None) && |
|
FileUtility.IsEqualFileName(fpi.DependentUpon, possibleSourceFileName)) { |
|
|
|
#if DEBUG |
|
LoggingService.Debug("ResourceToolkit: NRefactoryResourceResolver trying to use dependent file '"+fpi.FileName+"' as resource file"); |
|
#endif |
|
|
|
if ((fileName = FindResourceFileName(fpi.FileName)) != null) { |
|
// Prefer culture-invariant resource file |
|
// over localized resource file |
|
IResourceFileContent rfc = ResourceFileContentRegistry.GetResourceFileContent(fileName); |
|
if (rfc.Culture.Equals(CultureInfo.InvariantCulture)) { |
|
return new ResourceSetReference(resourceName, fileName); |
|
} |
|
} |
|
|
|
} |
|
} |
|
} |
|
|
|
// Fall back to any found resource file |
|
// if no culture-invariant resource file was found |
|
if (fileName != null) { |
|
return new ResourceSetReference(resourceName, fileName); |
|
} |
|
|
|
// Find resource files with the same name as the source file |
|
// and in the same directory. |
|
if ((fileName = FindResourceFileName(possibleSourceFile)) != null) { |
|
return new ResourceSetReference(resourceName, fileName); |
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
#if DEBUG |
|
LoggingService.Info("ResourceToolkit: NRefactoryResourceResolver.GetResourceSetReference could not determine the project for the source file '"+(sourceFileName ?? "<null>")+"'."); |
|
#endif |
|
|
|
if (sourceFileName != null) { |
|
|
|
// The project could not be determined. |
|
// Try a simple file search. |
|
|
|
string directory = Path.GetDirectoryName(sourceFileName); |
|
string resourcePart = resourceName; |
|
string fileName; |
|
|
|
while (true) { |
|
|
|
#if DEBUG |
|
LoggingService.Debug("ResourceToolkit: NRefactoryResourceResolver.GetResourceSetReference: looking for a resource file like '"+Path.Combine(directory, resourcePart)+"'"); |
|
#endif |
|
|
|
if ((fileName = FindResourceFileName(Path.Combine(directory, resourcePart.Replace('.', Path.DirectorySeparatorChar)))) != null) { |
|
return new ResourceSetReference(resourceName, fileName); |
|
} |
|
if ((fileName = FindResourceFileName(Path.Combine(directory, resourcePart))) != null) { |
|
return new ResourceSetReference(resourceName, fileName); |
|
} |
|
|
|
if (resourcePart.Contains(".")) { |
|
resourcePart = resourcePart.Substring(resourcePart.IndexOf('.')+1); |
|
} else { |
|
break; |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
#if DEBUG |
|
LoggingService.Info("ResourceToolkit: NRefactoryResourceResolver.GetResourceSetReference is unable to find a suitable resource file for '"+resourceName+"'"); |
|
#endif |
|
|
|
return new ResourceSetReference(resourceName, null); |
|
} |
|
|
|
// ******************************************************************************************************************************** |
|
|
|
/// <summary> |
|
/// Gets the NRefactory language for the specified file name. |
|
/// </summary> |
|
public static SupportedLanguage? GetFileLanguage(string fileName) |
|
{ |
|
string ext = Path.GetExtension(fileName); |
|
if (ext.Equals(".cs", StringComparison.InvariantCultureIgnoreCase)) |
|
return SupportedLanguage.CSharp; |
|
if (ext.Equals(".vb", StringComparison.InvariantCultureIgnoreCase)) |
|
return SupportedLanguage.VBNet; |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the language properties for the project the specified member |
|
/// belongs to. |
|
/// Returns <c>null</c> if the language cannot be determined. |
|
/// </summary> |
|
public static LanguageProperties GetLanguagePropertiesForMember(IMember member) |
|
{ |
|
if (member == null) { |
|
return null; |
|
} |
|
if (member.DeclaringType == null) { |
|
return null; |
|
} |
|
if (member.DeclaringType.CompilationUnit == null) { |
|
return null; |
|
} |
|
if (member.DeclaringType.CompilationUnit.ProjectContent == null) { |
|
return null; |
|
} |
|
return member.DeclaringType.CompilationUnit.ProjectContent.Language; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the language properties for the specified file. |
|
/// </summary> |
|
/// <param name="fileName">The file to get the language properties for.</param> |
|
/// <returns>The language properties of the specified file, or <c>null</c> if the language cannot be determined.</returns> |
|
public static LanguageProperties GetLanguagePropertiesForFile(string fileName) |
|
{ |
|
ICSharpCode.SharpDevelop.Dom.IParser p = ParserService.GetParser(fileName); |
|
if (p == null) { |
|
return null; |
|
} |
|
return p.Language; |
|
} |
|
|
|
} |
|
}
|
|
|