//
//
//
//
// $Revision$
//
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
{
///
/// Resolves resource references using NRefactory.
///
public class NRefactoryResourceResolver : AbstractResourceResolver
{
///
/// The AddIn tree path where the NRefactory resource resolvers are registered.
///
public const string NRefactoryResourceResolversAddInTreePath = "/AddIns/ResourceToolkit/NRefactoryResourceResolver/Resolvers";
// ********************************************************************************************************************************
static List resolvers;
///
/// Gets a list of all registered NRefactory resource resolvers.
///
public static IEnumerable Resolvers {
get {
if (resolvers == null) {
resolvers = AddInTree.BuildItems(NRefactoryResourceResolversAddInTreePath, null, false);
}
return resolvers;
}
}
// ********************************************************************************************************************************
///
/// Initializes a new instance of the class.
///
public NRefactoryResourceResolver() : base()
{
}
// ********************************************************************************************************************************
///
/// Determines whether this resolver supports resolving resources in the given file.
///
/// The name of the file to examine.
/// true, if this resolver supports resolving resources in the given file, false otherwise.
public override bool SupportsFile(string fileName)
{
// Any source code file supported by NRefactory is supported
return (GetFileLanguage(fileName) != null);
}
///
/// 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.
///
/// The name of the file to get a list of possible patterns for.
public override IEnumerable GetPossiblePatternsForFile(string fileName)
{
if (this.SupportsFile(fileName)) {
List patterns = new List();
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];
}
// ********************************************************************************************************************************
///
/// Attempts to resolve a reference to a resource.
///
/// The name of the file that contains the expression to be resolved.
/// 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 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, 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;
}
// ********************************************************************************************************************************
///
/// Tries to resolve the resource reference using all available
/// NRefactory resource resolvers.
///
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;
}
// ********************************************************************************************************************************
///
/// Determines the file which contains the resources referenced by the specified manifest resource name.
///
/// The name of the source code file which the reference occurs in.
/// The manifest resource name to find the resource file for.
/// A with the specified resource set name and the name of the file that contains the resources with the specified manifest resource name, or null if the file name cannot be determined.
/// The parameter is null.
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 possibleSourceFiles = new List();
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 ?? "")+"'.");
#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);
}
// ********************************************************************************************************************************
///
/// Gets the NRefactory language for the specified file name.
///
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;
}
///
/// Gets the language properties for the project the specified member
/// belongs to.
/// Returns null if the language cannot be determined.
///
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;
}
///
/// Gets the language properties for the specified file.
///
/// The file to get the language properties for.
/// The language properties of the specified file, or null if the language cannot be determined.
public static LanguageProperties GetLanguagePropertiesForFile(string fileName)
{
ICSharpCode.SharpDevelop.Dom.IParser p = ParserService.GetParser(fileName);
if (p == null) {
return null;
}
return p.Language;
}
}
}