1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
5
using System.Collections.Generic;
9
using ICSharpCode.Core;
10
using ICSharpCode.SharpDevelop.Editor;
11
using ICSharpCode.SharpDevelop.Project;
13
namespace Hornung.ResourceToolkit.Resolver
16
/// Resolves references to resources that are accessed using ICSharpCode.Core
19
public class ICSharpCodeCoreResourceResolver : AbstractResourceResolver
23
/// Initializes a new instance of the <see cref="ICSharpCodeCoreResourceResolver"/> class.
25
public ICSharpCodeCoreResourceResolver() : base()
29
// ********************************************************************************************************************************
32
/// Determines whether this resolver supports resolving resources in the given file.
34
/// <param name="fileName">The name of the file to examine.</param>
35
/// <returns><c>true</c>, if this resolver supports resolving resources in the given file, <c>false</c> otherwise.</returns>
36
public override bool SupportsFile(string fileName)
38
// Any parseable source code file may contain references
39
if (ResourceResolverService.GetParser(fileName) != null) {
43
// Support additional files by extension
44
switch(Path.GetExtension(fileName).ToLowerInvariant()) {
56
static readonly string[] possiblePatterns = new string[] {
61
/// Gets a list of patterns that can be searched for in the specified file
62
/// to find possible resource references that are supported by this
65
/// <param name="fileName">The name of the file to get a list of possible patterns for.</param>
66
public override IEnumerable<string> GetPossiblePatternsForFile(string fileName)
68
if (this.SupportsFile(fileName)) {
69
return possiblePatterns;
74
// ********************************************************************************************************************************
77
/// The token that indicates a reference to an ICSharpCode.Core resource.
79
public const string ResourceReferenceToken = @"${res:";
82
/// Attempts to resolve a reference to a resource.
84
/// <param name="fileName">The name of the file that contains the expression to be resolved.</param>
85
/// <param name="document">The document that contains the expression to be resolved.</param>
86
/// <param name="caretLine">The 0-based line in the file that contains the expression to be resolved.</param>
87
/// <param name="caretColumn">The 0-based column position of the expression to be resolved.</param>
88
/// <param name="caretOffset">The offset of the position of the expression to be resolved.</param>
89
/// <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>
90
/// <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>
91
protected override ResourceResolveResult Resolve(string fileName, IDocument document, int caretLine, int caretColumn, int caretOffset, char? charTyped)
93
// If Resolve is invoked from code completion,
94
// we are only interested in the ':' character of '${res:'.
95
if (charTyped != null && charTyped != ':') {
99
// Find $ character to the left of the caret.
100
char ch = document.GetCharAt(caretOffset);
101
if (ch == '}' && caretOffset > 0) {
102
ch = document.GetCharAt(--caretOffset);
104
while (!Char.IsWhiteSpace(ch) && ch != '$' && ch != '}' && caretOffset > 0) {
105
ch = document.GetCharAt(--caretOffset);
108
if (caretOffset + 6 >= document.TextLength || document.GetText(caretOffset, 6) != ResourceReferenceToken) {
113
// Read resource key.
114
StringBuilder key = new StringBuilder();
115
while (caretOffset < document.TextLength && !Char.IsWhiteSpace(ch = document.GetCharAt(caretOffset++)) && ch != '}') {
122
LoggingService.Debug("ResourceToolkit: ICSharpCodeCoreResourceResolver found resource key: "+key.ToString());
126
ResourceSetReference resource = ResolveICSharpCodeCoreResourceSet(key == null ? null : key.ToString(), fileName);
129
if (resource.FileName == null) {
130
LoggingService.Info("ResourceToolkit: ICSharpCodeCoreResourceResolver: Could not find the ICSharpCode.Core resource file name for the source file '"+fileName+"', key '"+(key == null ? "<null>" : key.ToString())+"'.");
134
// TODO: Add information about callingClass, callingMember, returnType
135
return new ResourceResolveResult(null, null, null, resource, key == null ? null : key.ToString());
138
// ********************************************************************************************************************************
141
/// Tries to find the resource set which serves as source for
142
/// the resources accessed by the ICSharpCode.Core.
144
/// <param name="key">The resource key to look for. May be null.</param>
145
/// <param name="sourceFileName">The name of the source code file that contains the reference to the resource.</param>
146
public static ResourceSetReference ResolveICSharpCodeCoreResourceSet(string key, string sourceFileName)
149
// As there is no easy way to find out the actual location of the resources
150
// based on the source code, we just look in some standard directories.
152
// Local set (SD addin or standalone application with standard directory structure)
153
ResourceSetReference local = GetICSharpCodeCoreLocalResourceSet(sourceFileName);
155
// Prefer local set, especially if the key is there.
156
if (local.ResourceFileContent != null) {
158
if (local.ResourceFileContent.ContainsKey(key)) {
166
// Resource set of the host application
167
ResourceSetReference host = GetICSharpCodeCoreHostResourceSet(sourceFileName);
169
if (host.ResourceFileContent != null) {
170
if (host.ResourceFileContent.ContainsKey(key)) {
176
// Use local file also if the key is not there
177
// (allows adding of a new key)
178
return local.ResourceFileContent == null ? host : local;
182
/// Tries to find an ICSharpCode.Core resource file in the given path
183
/// according to the file names defined in the AddIn tree.
185
static string FindICSharpCodeCoreResourceFile(string path)
188
foreach (string fileName in AddInTree.BuildItems<string>("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/ResourceFileNames", null, false)) {
189
if ((file = FindResourceFileName(Path.Combine(path, fileName))) != null) {
197
/// Gets a dummy resource set name used to represent the ICSharpCode.Core
198
/// resource set of the local application.
200
public const string ICSharpCodeCoreLocalResourceSetName = "[ICSharpCodeCoreLocalResourceSet]";
203
/// Gets a dummy resource set name used to represent the ICSharpCode.Core
204
/// resource set of the host application.
206
public const string ICSharpCodeCoreHostResourceSetName = "[ICSharpCodeCoreHostResourceSet]";
208
static readonly ResourceSetReference EmptyLocalResourceSetReference = new ResourceSetReference(ICSharpCodeCoreLocalResourceSetName, null);
209
static readonly ResourceSetReference EmptyHostResourceSetReference = new ResourceSetReference(ICSharpCodeCoreHostResourceSetName, null);
212
/// Tries to find the local string resource set used for ICSharpCode.Core resource access.
214
/// <param name="sourceFileName">The name of the source code file which to find the ICSharpCode.Core resource set for.</param>
215
/// <returns>A <see cref="ResourceSetReference"/> that describes the referenced resource set. The contained file name may be <c>null</c> if the file cannot be determined.</returns>
216
public static ResourceSetReference GetICSharpCodeCoreLocalResourceSet(string sourceFileName)
218
IProject project = ProjectFileDictionaryService.GetProjectForFile(sourceFileName);
219
if (project == null || String.IsNullOrEmpty(project.Directory)) {
220
return EmptyLocalResourceSetReference;
224
ResourceSetReference local = null;
226
if (!NRefactoryAstCacheService.CacheEnabled || !cachedLocalResourceSets.TryGetValue(project, out local)) {
227
foreach (string relativePath in AddInTree.BuildItems<string>("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/LocalResourcesLocations", null, false)) {
228
if ((localFile = FindICSharpCodeCoreResourceFile(Path.GetFullPath(Path.Combine(project.Directory, relativePath)))) != null) {
229
local = new ResourceSetReference(ICSharpCodeCoreLocalResourceSetName, localFile);
230
if (NRefactoryAstCacheService.CacheEnabled) {
231
cachedLocalResourceSets.Add(project, local);
238
return local ?? EmptyLocalResourceSetReference;
242
/// Tries to find the string resource set of the host application for ICSharpCode.Core resource access.
244
/// <param name="sourceFileName">The name of the source code file which to find the ICSharpCode.Core resource set for.</param>
245
/// <returns>A <see cref="ResourceSetReference"/> that describes the referenced resource set. The contained file name may be <c>null</c> if the file cannot be determined.</returns>
246
public static ResourceSetReference GetICSharpCodeCoreHostResourceSet(string sourceFileName)
248
IProject project = ProjectFileDictionaryService.GetProjectForFile(sourceFileName);
249
ResourceSetReference host = null;
252
if (project == null ||
253
!NRefactoryAstCacheService.CacheEnabled || !cachedHostResourceSets.TryGetValue(project, out host)) {
255
// Get SD directory using the reference to ICSharpCode.Core
256
string coreAssemblyFullPath = GetICSharpCodeCoreFullPath(project);
258
if (coreAssemblyFullPath == null) {
259
// Look for the ICSharpCode.Core project using all available projects.
260
if (ProjectService.OpenSolution != null) {
261
foreach (IProject p in ProjectService.OpenSolution.Projects) {
262
if ((coreAssemblyFullPath = GetICSharpCodeCoreFullPath(p)) != null) {
269
if (coreAssemblyFullPath == null) {
270
return EmptyHostResourceSetReference;
274
LoggingService.Debug("ResourceToolkit: ICSharpCodeCoreResourceResolver coreAssemblyFullPath = "+coreAssemblyFullPath);
277
foreach (string relativePath in AddInTree.BuildItems<string>("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/HostResourcesLocations", null, false)) {
278
if ((hostFile = FindICSharpCodeCoreResourceFile(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(coreAssemblyFullPath), relativePath)))) != null) {
279
host = new ResourceSetReference(ICSharpCodeCoreHostResourceSetName, hostFile);
280
if (NRefactoryAstCacheService.CacheEnabled && project != null) {
281
cachedHostResourceSets.Add(project, host);
289
return host ?? EmptyHostResourceSetReference;
292
static string GetICSharpCodeCoreFullPath(IProject sourceProject)
294
if (sourceProject == null) {
298
string coreAssemblyFullPath = null;
300
if (sourceProject.Name.Equals("ICSharpCode.Core", StringComparison.OrdinalIgnoreCase)) {
302
// This is the ICSharpCode.Core project itself.
303
coreAssemblyFullPath = sourceProject.OutputAssemblyFullPath;
307
// Get the ICSharpCode.Core.dll path by using the project reference.
308
foreach (ProjectItem item in sourceProject.Items) {
309
ProjectReferenceProjectItem prpi = item as ProjectReferenceProjectItem;
311
if (prpi.ReferencedProject != null) {
312
if (prpi.ReferencedProject.Name.Equals("ICSharpCode.Core", StringComparison.OrdinalIgnoreCase) && prpi.ReferencedProject.OutputAssemblyFullPath != null) {
313
coreAssemblyFullPath = prpi.ReferencedProject.OutputAssemblyFullPath;
318
ReferenceProjectItem rpi = item as ReferenceProjectItem;
320
if (rpi.Name.Equals("ICSharpCode.Core", StringComparison.OrdinalIgnoreCase) && rpi.FileName != null) {
321
coreAssemblyFullPath = rpi.FileName;
329
return coreAssemblyFullPath;
332
#region ICSharpCode.Core resource set mapping cache
334
static Dictionary<IProject, ResourceSetReference> cachedLocalResourceSets;
335
static Dictionary<IProject, ResourceSetReference> cachedHostResourceSets;
337
static ICSharpCodeCoreResourceResolver()
339
cachedLocalResourceSets = new Dictionary<IProject, ResourceSetReference>();
340
cachedHostResourceSets = new Dictionary<IProject, ResourceSetReference>();
341
NRefactoryAstCacheService.CacheEnabledChanged += NRefactoryCacheEnabledChanged;
344
static void NRefactoryCacheEnabledChanged(object sender, EventArgs e)
346
if (!NRefactoryAstCacheService.CacheEnabled) {
347
// Clear cache when disabled.
348
cachedLocalResourceSets.Clear();
349
cachedHostResourceSets.Clear();