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;
}
}