using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using GammaJul.ForTea.Core.Psi.Cache; using GammaJul.ForTea.Core.Tree; using JetBrains.Annotations; using JetBrains.Application.Parts; using JetBrains.Application.Threading; using JetBrains.DataFlow; using JetBrains.Diagnostics; using JetBrains.Interop.WinApi; using JetBrains.Lifetimes; using JetBrains.ProjectModel; using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Caches; using JetBrains.Util; using JetBrains.VsIntegration.ProjectModel; using Microsoft.VisualStudio.Shell.Interop; namespace JetBrains.ForTea.ReSharperPlugin.Psi.Resolve.Macros { /// /// In R#, it is only possible to resolve macros on the main thread, /// so we have to cache them to be able to access them from the daemon /// [PsiComponent(InstantiationEx.LegacyDefault)] public sealed class T4MacroResolutionCache : T4PsiAwareCacheBase { [NotNull] public Signal OnFileMarkedForInvalidation { get; } public T4MacroResolutionCache( Lifetime lifetime, [NotNull] IShellLocks locks, [NotNull] IPersistentIndexManager persistentIndexManager, [NotNull] ISolution solution ) : base(lifetime, locks, persistentIndexManager, T4MacroResolutionDataMarshaller.Instance) => OnFileMarkedForInvalidation = new(lifetime, "Update from T4MacroResolutionCache"); [NotNull] protected override T4MacroResolutionRequest Build(IT4File file) { var macros = file .GetThisAndChildrenOfType() .Where(macro => macro.RawAttributeValue != null) .Distinct(macro => macro.RawAttributeValue.GetText()); return new T4MacroResolutionRequest(macros.ToList()); } public override void Merge(IPsiSourceFile sourceFile, object builtPart) { var request = (T4MacroResolutionRequest)builtPart.NotNull(); var projectFile = sourceFile.ToProjectFile().NotNull(); var macroNames = request.MacrosToResolve.Select(macro => macro.RawAttributeValue.GetText()); var oldKeys = Map.TryGetValue(sourceFile)?.ResolvedMacros.Keys ?? EmptyList.Instance; var data = new T4MacroResolutionData(ResolveHeavyMacros(macroNames, projectFile)); var newKeys = data.ResolvedMacros.Keys; base.Merge(sourceFile, data); if (!newKeys.All(it => oldKeys.Contains(it))) { OnFileMarkedForInvalidation.Fire(sourceFile); } } [CanBeNull] public T4MacroResolutionData TryGetValue(IPsiSourceFile sourceFile) { base.TryGetValue(sourceFile, out var value); return value; } [NotNull] private IReadOnlyDictionary ResolveHeavyMacros( [NotNull] IEnumerable macros, [NotNull] IProjectFile file) { var result = new Dictionary(); Lazy vsBuildMacroInfo = Lazy.Of(() => TryGetVsBuildMacroInfo(file), false); foreach (string macro in macros) { bool succeeded = false; string value = null; if (vsBuildMacroInfo.Value != null) { succeeded = HResultHelpers.SUCCEEDED(vsBuildMacroInfo.Value.GetBuildMacroValue(macro, out value)) && !string.IsNullOrEmpty(value); } if (!succeeded) { value = MSBuildExtensions.GetStringValue(T4ResolutionUtils.TryGetVsHierarchy(file), macro, null); succeeded = !string.IsNullOrEmpty(value); } if (succeeded) { result[macro] = value; } } return result; } /// /// The representing the project file /// normally implements . /// /// An instance of if found. [CanBeNull, SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")] private static IVsBuildMacroInfo TryGetVsBuildMacroInfo([NotNull] IProjectFile file) => T4ResolutionUtils.TryGetVsHierarchy(file) as IVsBuildMacroInfo; } }