Refactor `ImportDocumentManager` to not be windows specific.
- Added a `FileChangeTracker`, `FileChangeTrackerFactory` and corresponding implementations. These types now enable us to implement Windows and Mac file change tracking instead of replacing the entire import manager. - Changed the import manager to be a Roslyn based service. - Moved import manager implementation to the editor.razor assembly now since it no longer depends on windows. - Updated import manager unit tests. - Added import manager integration test - Added file change tracking tests. #1804
This commit is contained in:
parent
65cdddf5d9
commit
4476a72ecf
|
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
{
|
||||||
|
internal class DefaultImportDocumentManager : ImportDocumentManager
|
||||||
|
{
|
||||||
|
private readonly FileChangeTrackerFactory _fileChangeTrackerFactory;
|
||||||
|
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||||
|
private readonly ErrorReporter _errorReporter;
|
||||||
|
private readonly RazorTemplateEngineFactoryService _templateEngineFactoryService;
|
||||||
|
private readonly Dictionary<string, ImportTracker> _importTrackerCache;
|
||||||
|
|
||||||
|
public override event EventHandler<ImportChangedEventArgs> Changed;
|
||||||
|
|
||||||
|
public DefaultImportDocumentManager(
|
||||||
|
ForegroundDispatcher foregroundDispatcher,
|
||||||
|
ErrorReporter errorReporter,
|
||||||
|
FileChangeTrackerFactory fileChangeTrackerFactory,
|
||||||
|
RazorTemplateEngineFactoryService templateEngineFactoryService)
|
||||||
|
{
|
||||||
|
if (foregroundDispatcher == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReporter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(errorReporter));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileChangeTrackerFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(fileChangeTrackerFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (templateEngineFactoryService == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(templateEngineFactoryService));
|
||||||
|
}
|
||||||
|
|
||||||
|
_foregroundDispatcher = foregroundDispatcher;
|
||||||
|
_errorReporter = errorReporter;
|
||||||
|
_fileChangeTrackerFactory = fileChangeTrackerFactory;
|
||||||
|
_templateEngineFactoryService = templateEngineFactoryService;
|
||||||
|
_importTrackerCache = new Dictionary<string, ImportTracker>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSubscribed(VisualStudioDocumentTracker tracker)
|
||||||
|
{
|
||||||
|
if (tracker == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tracker));
|
||||||
|
}
|
||||||
|
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
var imports = GetImportItems(tracker);
|
||||||
|
foreach (var import in imports)
|
||||||
|
{
|
||||||
|
var importFilePath = import.PhysicalPath;
|
||||||
|
Debug.Assert(importFilePath != null);
|
||||||
|
|
||||||
|
if (!_importTrackerCache.TryGetValue(importFilePath, out var importTracker))
|
||||||
|
{
|
||||||
|
// First time seeing this import. Start tracking it.
|
||||||
|
var fileChangeTracker = _fileChangeTrackerFactory.Create(importFilePath);
|
||||||
|
importTracker = new ImportTracker(fileChangeTracker);
|
||||||
|
_importTrackerCache[importFilePath] = importTracker;
|
||||||
|
|
||||||
|
fileChangeTracker.Changed += FileChangeTracker_Changed;
|
||||||
|
fileChangeTracker.StartListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
importTracker.AssociatedDocuments.Add(tracker.FilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUnsubscribed(VisualStudioDocumentTracker tracker)
|
||||||
|
{
|
||||||
|
if (tracker == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tracker));
|
||||||
|
}
|
||||||
|
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
var imports = GetImportItems(tracker);
|
||||||
|
foreach (var import in imports)
|
||||||
|
{
|
||||||
|
var importFilePath = import.PhysicalPath;
|
||||||
|
Debug.Assert(importFilePath != null);
|
||||||
|
|
||||||
|
if (_importTrackerCache.TryGetValue(importFilePath, out var importTracker))
|
||||||
|
{
|
||||||
|
importTracker.AssociatedDocuments.Remove(tracker.FilePath);
|
||||||
|
|
||||||
|
if (importTracker.AssociatedDocuments.Count == 0)
|
||||||
|
{
|
||||||
|
// There are no open documents that care about this import. We no longer need to track it.
|
||||||
|
importTracker.FileChangeTracker.StopListening();
|
||||||
|
_importTrackerCache.Remove(importFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<RazorProjectItem> GetImportItems(VisualStudioDocumentTracker tracker)
|
||||||
|
{
|
||||||
|
var projectDirectory = Path.GetDirectoryName(tracker.ProjectPath);
|
||||||
|
var templateEngine = _templateEngineFactoryService.Create(projectDirectory, _ => { });
|
||||||
|
var imports = templateEngine.GetImportItems(tracker.FilePath);
|
||||||
|
|
||||||
|
return imports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnChanged(ImportTracker importTracker, FileChangeKind changeKind)
|
||||||
|
{
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
if (Changed == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = new ImportChangedEventArgs(importTracker.FilePath, changeKind, importTracker.AssociatedDocuments);
|
||||||
|
Changed.Invoke(this, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FileChangeTracker_Changed(object sender, FileChangeEventArgs args)
|
||||||
|
{
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
if (_importTrackerCache.TryGetValue(args.FilePath, out var importTracker))
|
||||||
|
{
|
||||||
|
OnChanged(importTracker, args.Kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ImportTracker
|
||||||
|
{
|
||||||
|
public ImportTracker(FileChangeTracker fileChangeTracker)
|
||||||
|
{
|
||||||
|
FileChangeTracker = fileChangeTracker;
|
||||||
|
AssociatedDocuments = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FilePath => FileChangeTracker.FilePath;
|
||||||
|
|
||||||
|
public FileChangeTracker FileChangeTracker { get; }
|
||||||
|
|
||||||
|
public HashSet<string> AssociatedDocuments { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Composition;
|
||||||
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
using Microsoft.CodeAnalysis.Host.Mef;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
{
|
||||||
|
[Shared]
|
||||||
|
[ExportLanguageServiceFactory(typeof(ImportDocumentManager), RazorLanguage.Name, ServiceLayer.Default)]
|
||||||
|
internal class DefaultImportDocumentManagerFactory : ILanguageServiceFactory
|
||||||
|
{
|
||||||
|
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||||
|
{
|
||||||
|
if (languageServices == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(languageServices));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dispatcher = languageServices.WorkspaceServices.GetRequiredService<ForegroundDispatcher>();
|
||||||
|
var errorReporter = languageServices.WorkspaceServices.GetRequiredService<ErrorReporter>();
|
||||||
|
var fileChangeTrackerFactory = languageServices.GetRequiredService<FileChangeTrackerFactory>();
|
||||||
|
var templateEngineFactoryService = languageServices.GetRequiredService<RazorTemplateEngineFactoryService>();
|
||||||
|
|
||||||
|
return new DefaultImportDocumentManager(
|
||||||
|
dispatcher,
|
||||||
|
errorReporter,
|
||||||
|
fileChangeTrackerFactory,
|
||||||
|
templateEngineFactoryService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,13 +18,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
{
|
{
|
||||||
private readonly TextBufferProjectService _projectService;
|
private readonly TextBufferProjectService _projectService;
|
||||||
private readonly ITextDocumentFactoryService _textDocumentFactory;
|
private readonly ITextDocumentFactoryService _textDocumentFactory;
|
||||||
private readonly ImportDocumentManager _importDocumentManager;
|
|
||||||
|
|
||||||
[ImportingConstructor]
|
[ImportingConstructor]
|
||||||
public DefaultVisualStudioDocumentTrackerFactoryFactory(
|
public DefaultVisualStudioDocumentTrackerFactoryFactory(
|
||||||
TextBufferProjectService projectService,
|
TextBufferProjectService projectService,
|
||||||
ITextDocumentFactoryService textDocumentFactory,
|
ITextDocumentFactoryService textDocumentFactory)
|
||||||
ImportDocumentManager importDocumentManager)
|
|
||||||
{
|
{
|
||||||
if (projectService == null)
|
if (projectService == null)
|
||||||
{
|
{
|
||||||
|
|
@ -36,14 +34,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
throw new ArgumentNullException(nameof(textDocumentFactory));
|
throw new ArgumentNullException(nameof(textDocumentFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importDocumentManager == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(importDocumentManager));
|
|
||||||
}
|
|
||||||
|
|
||||||
_projectService = projectService;
|
_projectService = projectService;
|
||||||
_textDocumentFactory = textDocumentFactory;
|
_textDocumentFactory = textDocumentFactory;
|
||||||
_importDocumentManager = importDocumentManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||||
|
|
@ -56,6 +48,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
var dispatcher = languageServices.WorkspaceServices.GetRequiredService<ForegroundDispatcher>();
|
var dispatcher = languageServices.WorkspaceServices.GetRequiredService<ForegroundDispatcher>();
|
||||||
var projectManager = languageServices.GetRequiredService<ProjectSnapshotManager>();
|
var projectManager = languageServices.GetRequiredService<ProjectSnapshotManager>();
|
||||||
var editorSettingsManager = languageServices.GetRequiredService<EditorSettingsManagerInternal>();
|
var editorSettingsManager = languageServices.GetRequiredService<EditorSettingsManagerInternal>();
|
||||||
|
var importDocumentManager = languageServices.GetRequiredService<ImportDocumentManager>();
|
||||||
|
|
||||||
return new DefaultVisualStudioDocumentTrackerFactory(
|
return new DefaultVisualStudioDocumentTrackerFactory(
|
||||||
dispatcher,
|
dispatcher,
|
||||||
|
|
@ -63,7 +56,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
editorSettingsManager,
|
editorSettingsManager,
|
||||||
_projectService,
|
_projectService,
|
||||||
_textDocumentFactory,
|
_textDocumentFactory,
|
||||||
_importDocumentManager,
|
importDocumentManager,
|
||||||
languageServices.WorkspaceServices.Workspace);
|
languageServices.WorkspaceServices.Workspace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
{
|
||||||
|
internal sealed class FileChangeEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public FileChangeEventArgs(string filePath, FileChangeKind kind)
|
||||||
|
{
|
||||||
|
FilePath = filePath;
|
||||||
|
Kind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FilePath { get; }
|
||||||
|
|
||||||
|
public FileChangeKind Kind { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.Editor.Razor
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
{
|
{
|
||||||
internal enum ImportChangeKind
|
internal enum FileChangeKind
|
||||||
{
|
{
|
||||||
Added,
|
Added,
|
||||||
Removed,
|
Removed,
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
{
|
||||||
|
internal abstract class FileChangeTracker
|
||||||
|
{
|
||||||
|
public abstract event EventHandler<FileChangeEventArgs> Changed;
|
||||||
|
|
||||||
|
public abstract string FilePath { get; }
|
||||||
|
|
||||||
|
public abstract void StartListening();
|
||||||
|
|
||||||
|
public abstract void StopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
{
|
||||||
|
internal abstract class FileChangeTrackerFactory : ILanguageService
|
||||||
|
{
|
||||||
|
public abstract FileChangeTracker Create(string filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
{
|
{
|
||||||
internal class ImportChangedEventArgs : EventArgs
|
internal class ImportChangedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public ImportChangedEventArgs(string filePath, ImportChangeKind kind, IEnumerable<string> associatedDocuments)
|
public ImportChangedEventArgs(string filePath, FileChangeKind kind, IEnumerable<string> associatedDocuments)
|
||||||
{
|
{
|
||||||
FilePath = filePath;
|
FilePath = filePath;
|
||||||
Kind = kind;
|
Kind = kind;
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
|
||||||
public string FilePath { get; }
|
public string FilePath { get; }
|
||||||
|
|
||||||
public ImportChangeKind Kind { get; }
|
public FileChangeKind Kind { get; }
|
||||||
|
|
||||||
public IEnumerable<string> AssociatedDocuments { get; }
|
public IEnumerable<string> AssociatedDocuments { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.Editor.Razor
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
{
|
{
|
||||||
internal abstract class ImportDocumentManager
|
internal abstract class ImportDocumentManager : ILanguageService
|
||||||
{
|
{
|
||||||
public abstract event EventHandler<ImportChangedEventArgs> Changed;
|
public abstract event EventHandler<ImportChangedEventArgs> Changed;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
using Microsoft.VisualStudio.Editor.Razor;
|
||||||
|
using Microsoft.VisualStudio.Shell.Interop;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
|
{
|
||||||
|
internal class DefaultFileChangeTracker : FileChangeTracker, IVsFileChangeEvents
|
||||||
|
{
|
||||||
|
private const uint FileChangeFlags = (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Size | _VSFILECHANGEFLAGS.VSFILECHG_Del | _VSFILECHANGEFLAGS.VSFILECHG_Add);
|
||||||
|
|
||||||
|
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||||
|
private readonly ErrorReporter _errorReporter;
|
||||||
|
private readonly IVsFileChangeEx _fileChangeService;
|
||||||
|
private uint _fileChangeCookie;
|
||||||
|
|
||||||
|
public override event EventHandler<FileChangeEventArgs> Changed;
|
||||||
|
|
||||||
|
public DefaultFileChangeTracker(
|
||||||
|
string filePath,
|
||||||
|
ForegroundDispatcher foregroundDispatcher,
|
||||||
|
ErrorReporter errorReporter,
|
||||||
|
IVsFileChangeEx fileChangeService)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foregroundDispatcher == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReporter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(errorReporter));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileChangeService == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(fileChangeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath = filePath;
|
||||||
|
_foregroundDispatcher = foregroundDispatcher;
|
||||||
|
_errorReporter = errorReporter;
|
||||||
|
_fileChangeService = fileChangeService;
|
||||||
|
_fileChangeCookie = VSConstants.VSCOOKIE_NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string FilePath { get; }
|
||||||
|
|
||||||
|
public override void StartListening()
|
||||||
|
{
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_fileChangeCookie == VSConstants.VSCOOKIE_NIL)
|
||||||
|
{
|
||||||
|
var hr = _fileChangeService.AdviseFileChange(
|
||||||
|
FilePath,
|
||||||
|
FileChangeFlags,
|
||||||
|
this,
|
||||||
|
out _fileChangeCookie);
|
||||||
|
|
||||||
|
Marshal.ThrowExceptionForHR(hr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_errorReporter.ReportError(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void StopListening()
|
||||||
|
{
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_fileChangeCookie != VSConstants.VSCOOKIE_NIL)
|
||||||
|
{
|
||||||
|
var hr = _fileChangeService.UnadviseFileChange(_fileChangeCookie);
|
||||||
|
Marshal.ThrowExceptionForHR(hr);
|
||||||
|
_fileChangeCookie = VSConstants.VSCOOKIE_NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_errorReporter.ReportError(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FilesChanged(uint fileCount, string[] filePaths, uint[] fileChangeFlags)
|
||||||
|
{
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
foreach (var fileChangeFlag in fileChangeFlags)
|
||||||
|
{
|
||||||
|
var fileChangeKind = FileChangeKind.Changed;
|
||||||
|
var changeFlag = (_VSFILECHANGEFLAGS)fileChangeFlag;
|
||||||
|
if ((changeFlag & _VSFILECHANGEFLAGS.VSFILECHG_Del) == _VSFILECHANGEFLAGS.VSFILECHG_Del)
|
||||||
|
{
|
||||||
|
fileChangeKind = FileChangeKind.Removed;
|
||||||
|
}
|
||||||
|
else if ((changeFlag & _VSFILECHANGEFLAGS.VSFILECHG_Add) == _VSFILECHANGEFLAGS.VSFILECHG_Add)
|
||||||
|
{
|
||||||
|
fileChangeKind = FileChangeKind.Added;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purposefully not passing through the file paths here because we know this change has to do with this trackers FilePath.
|
||||||
|
// We use that FilePath instead so any path normalization the file service did does not impact callers.
|
||||||
|
OnChanged(fileChangeKind);
|
||||||
|
}
|
||||||
|
|
||||||
|
return VSConstants.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DirectoryChanged(string pszDirectory) => VSConstants.S_OK;
|
||||||
|
|
||||||
|
private void OnChanged(FileChangeKind changeKind)
|
||||||
|
{
|
||||||
|
_foregroundDispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
if (Changed == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = new FileChangeEventArgs(FilePath, changeKind);
|
||||||
|
Changed.Invoke(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
using Microsoft.VisualStudio.Editor.Razor;
|
||||||
|
using Microsoft.VisualStudio.Shell.Interop;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
|
{
|
||||||
|
internal partial class DefaultFileChangeTrackerFactory : FileChangeTrackerFactory
|
||||||
|
{
|
||||||
|
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||||
|
private readonly ErrorReporter _errorReporter;
|
||||||
|
private readonly IVsFileChangeEx _fileChangeService;
|
||||||
|
|
||||||
|
public DefaultFileChangeTrackerFactory(
|
||||||
|
ForegroundDispatcher foregroundDispatcher,
|
||||||
|
ErrorReporter errorReporter,
|
||||||
|
IVsFileChangeEx fileChangeService)
|
||||||
|
{
|
||||||
|
if (foregroundDispatcher == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReporter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(errorReporter));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileChangeService == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(fileChangeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
_foregroundDispatcher = foregroundDispatcher;
|
||||||
|
_errorReporter = errorReporter;
|
||||||
|
_fileChangeService = fileChangeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override FileChangeTracker Create(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileChangeTracker = new DefaultFileChangeTracker(filePath, _foregroundDispatcher, _errorReporter, _fileChangeService);
|
||||||
|
return fileChangeTracker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Composition;
|
||||||
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
using Microsoft.CodeAnalysis.Host.Mef;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
using Microsoft.VisualStudio.Editor.Razor;
|
||||||
|
using Microsoft.VisualStudio.Shell;
|
||||||
|
using Microsoft.VisualStudio.Shell.Interop;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
|
{
|
||||||
|
[Shared]
|
||||||
|
[ExportLanguageServiceFactory(typeof(FileChangeTrackerFactory), RazorLanguage.Name, ServiceLayer.Default)]
|
||||||
|
internal class DefaultFileChangeTrackerFactoryFactory : ILanguageServiceFactory
|
||||||
|
{
|
||||||
|
private readonly IVsFileChangeEx _fileChangeService;
|
||||||
|
|
||||||
|
[ImportingConstructor]
|
||||||
|
public DefaultFileChangeTrackerFactoryFactory(SVsServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
if (serviceProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(serviceProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
_fileChangeService = serviceProvider.GetService(typeof(SVsFileChangeEx)) as IVsFileChangeEx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||||
|
{
|
||||||
|
if (languageServices == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(languageServices));
|
||||||
|
}
|
||||||
|
|
||||||
|
var foregroundDispatcher = languageServices.WorkspaceServices.GetRequiredService<ForegroundDispatcher>();
|
||||||
|
var errorReporter = languageServices.WorkspaceServices.GetRequiredService<ErrorReporter>();
|
||||||
|
return new DefaultFileChangeTrackerFactory(foregroundDispatcher, errorReporter, _fileChangeService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,255 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.Composition;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
|
||||||
using Microsoft.CodeAnalysis.Razor;
|
|
||||||
using Microsoft.VisualStudio.Editor.Razor;
|
|
||||||
using Microsoft.VisualStudio.Shell;
|
|
||||||
using Microsoft.VisualStudio.Shell.Interop;
|
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|
||||||
{
|
|
||||||
[System.Composition.Shared]
|
|
||||||
[Export(typeof(ImportDocumentManager))]
|
|
||||||
internal class DefaultImportDocumentManager : ImportDocumentManager
|
|
||||||
{
|
|
||||||
private const uint FileChangeFlags = (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Size | _VSFILECHANGEFLAGS.VSFILECHG_Del | _VSFILECHANGEFLAGS.VSFILECHG_Add);
|
|
||||||
|
|
||||||
private readonly IVsFileChangeEx _fileChangeService;
|
|
||||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
|
||||||
private readonly ErrorReporter _errorReporter;
|
|
||||||
private readonly RazorTemplateEngineFactoryService _templateEngineFactoryService;
|
|
||||||
private readonly Dictionary<string, ImportTracker> _importTrackerCache;
|
|
||||||
|
|
||||||
public override event EventHandler<ImportChangedEventArgs> Changed;
|
|
||||||
|
|
||||||
[ImportingConstructor]
|
|
||||||
public DefaultImportDocumentManager(
|
|
||||||
[Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
|
|
||||||
VisualStudioWorkspaceAccessor workspaceAccessor)
|
|
||||||
{
|
|
||||||
if (serviceProvider == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(serviceProvider));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workspaceAccessor == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(workspaceAccessor));
|
|
||||||
}
|
|
||||||
|
|
||||||
_fileChangeService = serviceProvider.GetService(typeof(SVsFileChangeEx)) as IVsFileChangeEx;
|
|
||||||
|
|
||||||
var workspace = workspaceAccessor.Workspace;
|
|
||||||
_foregroundDispatcher = workspace.Services.GetRequiredService<ForegroundDispatcher>();
|
|
||||||
_errorReporter = workspace.Services.GetRequiredService<ErrorReporter>();
|
|
||||||
|
|
||||||
var razorLanguageServices = workspace.Services.GetLanguageServices(RazorLanguage.Name);
|
|
||||||
_templateEngineFactoryService = razorLanguageServices.GetRequiredService<RazorTemplateEngineFactoryService>();
|
|
||||||
|
|
||||||
_importTrackerCache = new Dictionary<string, ImportTracker>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is only used for testing.
|
|
||||||
internal DefaultImportDocumentManager(
|
|
||||||
IVsFileChangeEx fileChangeService,
|
|
||||||
RazorTemplateEngineFactoryService templateEngineFactoryService,
|
|
||||||
ForegroundDispatcher foregroundDispatcher,
|
|
||||||
ErrorReporter errorReporter)
|
|
||||||
{
|
|
||||||
_fileChangeService = fileChangeService;
|
|
||||||
_templateEngineFactoryService = templateEngineFactoryService;
|
|
||||||
_foregroundDispatcher = foregroundDispatcher;
|
|
||||||
_errorReporter = errorReporter;
|
|
||||||
|
|
||||||
_importTrackerCache = new Dictionary<string, ImportTracker>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnSubscribed(VisualStudioDocumentTracker tracker)
|
|
||||||
{
|
|
||||||
if (tracker == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(tracker));
|
|
||||||
}
|
|
||||||
|
|
||||||
_foregroundDispatcher.AssertForegroundThread();
|
|
||||||
|
|
||||||
var imports = GetImportItems(tracker);
|
|
||||||
foreach (var import in imports)
|
|
||||||
{
|
|
||||||
var importFilePath = import.PhysicalPath;
|
|
||||||
Debug.Assert(importFilePath != null);
|
|
||||||
|
|
||||||
if (!_importTrackerCache.TryGetValue(importFilePath, out var importTracker))
|
|
||||||
{
|
|
||||||
// First time seeing this import. Start tracking it.
|
|
||||||
importTracker = new ImportTracker(importFilePath);
|
|
||||||
_importTrackerCache[importFilePath] = importTracker;
|
|
||||||
|
|
||||||
StartListeningForChanges(importTracker);
|
|
||||||
}
|
|
||||||
|
|
||||||
importTracker.AssociatedDocuments.Add(tracker.FilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnUnsubscribed(VisualStudioDocumentTracker tracker)
|
|
||||||
{
|
|
||||||
if (tracker == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(tracker));
|
|
||||||
}
|
|
||||||
|
|
||||||
_foregroundDispatcher.AssertForegroundThread();
|
|
||||||
|
|
||||||
var imports = GetImportItems(tracker);
|
|
||||||
foreach (var import in imports)
|
|
||||||
{
|
|
||||||
var importFilePath = import.PhysicalPath;
|
|
||||||
Debug.Assert(importFilePath != null);
|
|
||||||
|
|
||||||
if (_importTrackerCache.TryGetValue(importFilePath, out var importTracker))
|
|
||||||
{
|
|
||||||
importTracker.AssociatedDocuments.Remove(tracker.FilePath);
|
|
||||||
|
|
||||||
if (importTracker.AssociatedDocuments.Count == 0)
|
|
||||||
{
|
|
||||||
// There are no open documents that care about this import. We no longer need to track it.
|
|
||||||
StopListeningForChanges(importTracker);
|
|
||||||
_importTrackerCache.Remove(importFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<RazorProjectItem> GetImportItems(VisualStudioDocumentTracker tracker)
|
|
||||||
{
|
|
||||||
var projectDirectory = Path.GetDirectoryName(tracker.ProjectPath);
|
|
||||||
var templateEngine = _templateEngineFactoryService.Create(projectDirectory, _ => { });
|
|
||||||
var imports = templateEngine.GetImportItems(tracker.FilePath);
|
|
||||||
|
|
||||||
return imports;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FireImportChanged(string importPath, ImportChangeKind kind)
|
|
||||||
{
|
|
||||||
_foregroundDispatcher.AssertForegroundThread();
|
|
||||||
|
|
||||||
var handler = Changed;
|
|
||||||
if (handler != null && _importTrackerCache.TryGetValue(importPath, out var importTracker))
|
|
||||||
{
|
|
||||||
var args = new ImportChangedEventArgs(importPath, kind, importTracker.AssociatedDocuments);
|
|
||||||
handler(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal for testing.
|
|
||||||
internal void OnFilesChanged(uint fileCount, string[] filePaths, uint[] fileChangeFlags)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < fileCount; i++)
|
|
||||||
{
|
|
||||||
var kind = ImportChangeKind.Changed;
|
|
||||||
var flag = (_VSFILECHANGEFLAGS)fileChangeFlags[i];
|
|
||||||
|
|
||||||
if ((flag & _VSFILECHANGEFLAGS.VSFILECHG_Del) == _VSFILECHANGEFLAGS.VSFILECHG_Del)
|
|
||||||
{
|
|
||||||
kind = ImportChangeKind.Removed;
|
|
||||||
}
|
|
||||||
else if ((flag & _VSFILECHANGEFLAGS.VSFILECHG_Add) == _VSFILECHANGEFLAGS.VSFILECHG_Add)
|
|
||||||
{
|
|
||||||
kind = ImportChangeKind.Added;
|
|
||||||
}
|
|
||||||
|
|
||||||
FireImportChanged(filePaths[i], kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartListeningForChanges(ImportTracker importTracker)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (importTracker.FileChangeCookie == VSConstants.VSCOOKIE_NIL)
|
|
||||||
{
|
|
||||||
var hr = _fileChangeService.AdviseFileChange(
|
|
||||||
importTracker.FilePath,
|
|
||||||
FileChangeFlags,
|
|
||||||
new ImportDocumentEventSink(this, _foregroundDispatcher),
|
|
||||||
out var cookie);
|
|
||||||
|
|
||||||
Marshal.ThrowExceptionForHR(hr);
|
|
||||||
|
|
||||||
importTracker.FileChangeCookie = cookie;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_errorReporter.ReportError(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopListeningForChanges(ImportTracker importTracker)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (importTracker.FileChangeCookie != VSConstants.VSCOOKIE_NIL)
|
|
||||||
{
|
|
||||||
var hr = _fileChangeService.UnadviseFileChange(importTracker.FileChangeCookie);
|
|
||||||
Marshal.ThrowExceptionForHR(hr);
|
|
||||||
importTracker.FileChangeCookie = VSConstants.VSCOOKIE_NIL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_errorReporter.ReportError(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ImportTracker
|
|
||||||
{
|
|
||||||
public ImportTracker(string filePath)
|
|
||||||
{
|
|
||||||
FilePath = filePath;
|
|
||||||
AssociatedDocuments = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
FileChangeCookie = VSConstants.VSCOOKIE_NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FilePath { get; }
|
|
||||||
|
|
||||||
public HashSet<string> AssociatedDocuments { get; }
|
|
||||||
|
|
||||||
public uint FileChangeCookie { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ImportDocumentEventSink : IVsFileChangeEvents
|
|
||||||
{
|
|
||||||
private readonly DefaultImportDocumentManager _importDocumentManager;
|
|
||||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
|
||||||
|
|
||||||
public ImportDocumentEventSink(DefaultImportDocumentManager importDocumentManager, ForegroundDispatcher foregroundDispatcher)
|
|
||||||
{
|
|
||||||
_importDocumentManager = importDocumentManager;
|
|
||||||
_foregroundDispatcher = foregroundDispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int FilesChanged(uint cChanges, string[] rgpszFile, uint[] rggrfChange)
|
|
||||||
{
|
|
||||||
_foregroundDispatcher.AssertForegroundThread();
|
|
||||||
|
|
||||||
_importDocumentManager.OnFilesChanged(cChanges, rgpszFile, rggrfChange);
|
|
||||||
|
|
||||||
return VSConstants.S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int DirectoryChanged(string pszDirectory)
|
|
||||||
{
|
|
||||||
return VSConstants.S_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
{
|
||||||
|
public class DefaultImportDocumentManagerIntegrationTest : ForegroundDispatcherTestBase
|
||||||
|
{
|
||||||
|
[ForegroundFact]
|
||||||
|
public void Changed_TrackerChanged_ResultsInChangedHavingCorrectArgs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filePath = "C:\\path\\to\\project\\Views\\Home\\file.cshtml";
|
||||||
|
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
|
||||||
|
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
||||||
|
var testImportsPath = "C:\\path\\to\\project\\_ViewImports.cshtml";
|
||||||
|
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
||||||
|
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
|
||||||
|
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
||||||
|
var fileChangeTracker = new Mock<FileChangeTracker>();
|
||||||
|
fileChangeTracker.Setup(f => f.FilePath).Returns(testImportsPath);
|
||||||
|
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create(testImportsPath))
|
||||||
|
.Returns(fileChangeTracker.Object);
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create("C:\\path\\to\\project\\Views\\_ViewImports.cshtml"))
|
||||||
|
.Returns(Mock.Of<FileChangeTracker>());
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create("C:\\path\\to\\project\\Views\\Home\\_ViewImports.cshtml"))
|
||||||
|
.Returns(Mock.Of<FileChangeTracker>());
|
||||||
|
|
||||||
|
var called = false;
|
||||||
|
var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
|
||||||
|
manager.OnSubscribed(tracker);
|
||||||
|
manager.OnSubscribed(anotherTracker);
|
||||||
|
manager.Changed += (sender, args) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
Assert.Same(sender, manager);
|
||||||
|
Assert.Equal(testImportsPath, args.FilePath);
|
||||||
|
Assert.Equal(FileChangeKind.Changed, args.Kind);
|
||||||
|
Assert.Collection(
|
||||||
|
args.AssociatedDocuments,
|
||||||
|
f => Assert.Equal(filePath, f),
|
||||||
|
f => Assert.Equal(anotherFilePath, f));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
fileChangeTracker.Raise(t => t.Changed += null, new FileChangeEventArgs(testImportsPath, FileChangeKind.Changed));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(called);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RazorTemplateEngineFactoryService GetTemplateEngineFactoryService()
|
||||||
|
{
|
||||||
|
var projectManager = new Mock<ProjectSnapshotManager>();
|
||||||
|
projectManager.Setup(p => p.Projects).Returns(Array.Empty<ProjectSnapshot>());
|
||||||
|
|
||||||
|
var service = new DefaultTemplateEngineFactoryService(projectManager.Object);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
{
|
||||||
|
public class DefaultImportDocumentManagerTest : ForegroundDispatcherTestBase
|
||||||
|
{
|
||||||
|
[ForegroundFact]
|
||||||
|
public void OnSubscribed_StartsFileChangeTrackers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filePath = "C:\\path\\to\\project\\Views\\Home\\file.cshtml";
|
||||||
|
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
||||||
|
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
||||||
|
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
||||||
|
var fileChangeTracker1 = new Mock<FileChangeTracker>();
|
||||||
|
fileChangeTracker1.Setup(f => f.StartListening()).Verifiable();
|
||||||
|
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create("C:\\path\\to\\project\\Views\\Home\\_ViewImports.cshtml"))
|
||||||
|
.Returns(fileChangeTracker1.Object)
|
||||||
|
.Verifiable();
|
||||||
|
var fileChangeTracker2 = new Mock<FileChangeTracker>();
|
||||||
|
fileChangeTracker2.Setup(f => f.StartListening()).Verifiable();
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create("C:\\path\\to\\project\\Views\\_ViewImports.cshtml"))
|
||||||
|
.Returns(fileChangeTracker2.Object)
|
||||||
|
.Verifiable();
|
||||||
|
var fileChangeTracker3 = new Mock<FileChangeTracker>();
|
||||||
|
fileChangeTracker3.Setup(f => f.StartListening()).Verifiable();
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create("C:\\path\\to\\project\\_ViewImports.cshtml"))
|
||||||
|
.Returns(fileChangeTracker3.Object)
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
manager.OnSubscribed(tracker);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
fileChangeTrackerFactory.Verify();
|
||||||
|
fileChangeTracker1.Verify();
|
||||||
|
fileChangeTracker2.Verify();
|
||||||
|
fileChangeTracker3.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void OnSubscribed_AlreadySubscribed_DoesNothing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filePath = "C:\\path\\to\\project\\file.cshtml";
|
||||||
|
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
||||||
|
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
||||||
|
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
||||||
|
|
||||||
|
var callCount = 0;
|
||||||
|
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create(It.IsAny<string>()))
|
||||||
|
.Returns(Mock.Of<FileChangeTracker>())
|
||||||
|
.Callback(() => callCount++);
|
||||||
|
|
||||||
|
var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
|
||||||
|
manager.OnSubscribed(tracker); // Start tracking the import.
|
||||||
|
|
||||||
|
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
|
||||||
|
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
manager.OnSubscribed(anotherTracker);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, callCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void OnUnsubscribed_StopsFileChangeTracker()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filePath = "C:\\path\\to\\project\\file.cshtml";
|
||||||
|
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
||||||
|
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
||||||
|
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
||||||
|
|
||||||
|
var fileChangeTracker = new Mock<FileChangeTracker>();
|
||||||
|
fileChangeTracker.Setup(f => f.StopListening()).Verifiable();
|
||||||
|
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>(MockBehavior.Strict);
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create("C:\\path\\to\\project\\_ViewImports.cshtml"))
|
||||||
|
.Returns(fileChangeTracker.Object)
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
|
||||||
|
manager.OnSubscribed(tracker); // Start tracking the import.
|
||||||
|
|
||||||
|
// Act
|
||||||
|
manager.OnUnsubscribed(tracker);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
fileChangeTrackerFactory.Verify();
|
||||||
|
fileChangeTracker.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void OnUnsubscribed_AnotherDocumentTrackingImport_DoesNotStopFileChangeTracker()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filePath = "C:\\path\\to\\project\\file.cshtml";
|
||||||
|
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
||||||
|
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
||||||
|
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
||||||
|
|
||||||
|
var fileChangeTracker = new Mock<FileChangeTracker>();
|
||||||
|
fileChangeTracker
|
||||||
|
.Setup(f => f.StopListening())
|
||||||
|
.Throws(new InvalidOperationException());
|
||||||
|
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
|
||||||
|
fileChangeTrackerFactory
|
||||||
|
.Setup(f => f.Create(It.IsAny<string>()))
|
||||||
|
.Returns(fileChangeTracker.Object);
|
||||||
|
|
||||||
|
var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
|
||||||
|
manager.OnSubscribed(tracker); // Starts tracking import for the first document.
|
||||||
|
|
||||||
|
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
|
||||||
|
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
|
||||||
|
manager.OnSubscribed(anotherTracker); // Starts tracking import for the second document.
|
||||||
|
|
||||||
|
// Act & Assert (Does not throw)
|
||||||
|
manager.OnUnsubscribed(tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RazorTemplateEngineFactoryService GetTemplateEngineFactoryService()
|
||||||
|
{
|
||||||
|
var projectManager = new Mock<ProjectSnapshotManager>();
|
||||||
|
projectManager.Setup(p => p.Projects).Returns(Array.Empty<ProjectSnapshot>());
|
||||||
|
|
||||||
|
var service = new DefaultTemplateEngineFactoryService(projectManager.Object);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -137,7 +137,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
called = true;
|
called = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
var importChangedArgs = new ImportChangedEventArgs("path/to/import", ImportChangeKind.Changed, new[] { FilePath });
|
var importChangedArgs = new ImportChangedEventArgs("path/to/import", FileChangeKind.Changed, new[] { FilePath });
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
documentTracker.Import_Changed(null, importChangedArgs);
|
documentTracker.Import_Changed(null, importChangedArgs);
|
||||||
|
|
@ -157,7 +157,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
};
|
};
|
||||||
|
|
||||||
var importChangedArgs = new ImportChangedEventArgs("path/to/import", ImportChangeKind.Changed, new[] { "path/to/differentfile" });
|
var importChangedArgs = new ImportChangedEventArgs("path/to/import", FileChangeKind.Changed, new[] { "path/to/differentfile" });
|
||||||
|
|
||||||
// Act & Assert (Does not throw)
|
// Act & Assert (Does not throw)
|
||||||
documentTracker.Import_Changed(null, importChangedArgs);
|
documentTracker.Import_Changed(null, importChangedArgs);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
using Microsoft.VisualStudio.Editor.Razor;
|
||||||
|
using Microsoft.VisualStudio.Shell.Interop;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
|
{
|
||||||
|
public class DefaultFileChangeTrackerTest : ForegroundDispatcherTestBase
|
||||||
|
{
|
||||||
|
private ErrorReporter ErrorReporter { get; } = new DefaultErrorReporter();
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void StartListening_AdvisesForFileChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
uint cookie;
|
||||||
|
var fileChangeService = new Mock<IVsFileChangeEx>();
|
||||||
|
fileChangeService
|
||||||
|
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
||||||
|
.Returns(VSConstants.S_OK)
|
||||||
|
.Verifiable();
|
||||||
|
var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tracker.StartListening();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
fileChangeService.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void StartListening_AlreadyListening_DoesNothing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
uint cookie = 100;
|
||||||
|
var callCount = 0;
|
||||||
|
var fileChangeService = new Mock<IVsFileChangeEx>();
|
||||||
|
fileChangeService
|
||||||
|
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
||||||
|
.Returns(VSConstants.S_OK)
|
||||||
|
.Callback(() => callCount++);
|
||||||
|
var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object);
|
||||||
|
tracker.StartListening();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tracker.StartListening();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, callCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void StopListening_UnadvisesForFileChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
uint cookie = 100;
|
||||||
|
var fileChangeService = new Mock<IVsFileChangeEx>(MockBehavior.Strict);
|
||||||
|
fileChangeService
|
||||||
|
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
||||||
|
.Returns(VSConstants.S_OK)
|
||||||
|
.Verifiable();
|
||||||
|
fileChangeService
|
||||||
|
.Setup(f => f.UnadviseFileChange(cookie))
|
||||||
|
.Returns(VSConstants.S_OK)
|
||||||
|
.Verifiable();
|
||||||
|
var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object);
|
||||||
|
tracker.StartListening(); // Start listening for changes.
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tracker.StopListening();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
fileChangeService.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void StartListening_NotListening_DoesNothing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
uint cookie = VSConstants.VSCOOKIE_NIL;
|
||||||
|
var fileChangeService = new Mock<IVsFileChangeEx>(MockBehavior.Strict);
|
||||||
|
fileChangeService
|
||||||
|
.Setup(f => f.UnadviseFileChange(cookie))
|
||||||
|
.Throws(new InvalidOperationException());
|
||||||
|
var tracker = new DefaultFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
tracker.StopListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundTheory]
|
||||||
|
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Size, (int)FileChangeKind.Changed)]
|
||||||
|
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Time, (int)FileChangeKind.Changed)]
|
||||||
|
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Add, (int)FileChangeKind.Added)]
|
||||||
|
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Del, (int)FileChangeKind.Removed)]
|
||||||
|
public void FilesChanged_WithSpecificFlags_InvokesChangedHandler_WithExpectedArguments(uint fileChangeFlag, int expectedKind)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filePath = "C:\\path\\to\\project\\_ViewImports.cshtml";
|
||||||
|
uint cookie;
|
||||||
|
var fileChangeService = new Mock<IVsFileChangeEx>();
|
||||||
|
fileChangeService
|
||||||
|
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
||||||
|
.Returns(VSConstants.S_OK);
|
||||||
|
var tracker = new DefaultFileChangeTracker(filePath, Dispatcher, ErrorReporter, fileChangeService.Object);
|
||||||
|
|
||||||
|
var called = false;
|
||||||
|
tracker.Changed += (sender, args) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
Assert.Same(sender, tracker);
|
||||||
|
Assert.Equal(filePath, args.FilePath);
|
||||||
|
Assert.Equal((FileChangeKind)expectedKind, args.Kind);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tracker.FilesChanged(fileCount: 1, filePaths: new[] { filePath }, fileChangeFlags: new[] { fileChangeFlag });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(called);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.CodeAnalysis.Razor;
|
|
||||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
|
||||||
using Microsoft.VisualStudio.Editor.Razor;
|
|
||||||
using Microsoft.VisualStudio.Shell.Interop;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|
||||||
{
|
|
||||||
public class DefaultImportDocumentManagerTest : ForegroundDispatcherTestBase
|
|
||||||
{
|
|
||||||
[ForegroundFact]
|
|
||||||
public void OnSubscribed_StartTrackingImport()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filePath = "C:\\path\\to\\project\\Views\\Home\\file.cshtml";
|
|
||||||
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
|
||||||
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
|
||||||
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
|
||||||
|
|
||||||
uint cookie;
|
|
||||||
var fileChangeService = new Mock<IVsFileChangeEx>(MockBehavior.Strict);
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.AdviseFileChange("C:\\path\\to\\project\\Views\\Home\\_ViewImports.cshtml", It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
|
||||||
.Returns(VSConstants.S_OK)
|
|
||||||
.Verifiable();
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.AdviseFileChange("C:\\path\\to\\project\\Views\\_ViewImports.cshtml", It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
|
||||||
.Returns(VSConstants.S_OK)
|
|
||||||
.Verifiable();
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.AdviseFileChange("C:\\path\\to\\project\\_ViewImports.cshtml", It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
|
||||||
.Returns(VSConstants.S_OK)
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var manager = new DefaultImportDocumentManager(fileChangeService.Object, templateEngineFactoryService, Dispatcher, new DefaultErrorReporter());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
manager.OnSubscribed(tracker);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
fileChangeService.Verify();
|
|
||||||
}
|
|
||||||
|
|
||||||
[ForegroundFact]
|
|
||||||
public void OnSubscribed_AlreadyTrackingImport_DoesNothing()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filePath = "C:\\path\\to\\project\\file.cshtml";
|
|
||||||
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
|
||||||
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
|
||||||
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
|
||||||
|
|
||||||
uint cookie;
|
|
||||||
var callCount = 0;
|
|
||||||
var fileChangeService = new Mock<IVsFileChangeEx>();
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
|
||||||
.Returns(VSConstants.S_OK)
|
|
||||||
.Callback(() => callCount++);
|
|
||||||
|
|
||||||
var manager = new DefaultImportDocumentManager(fileChangeService.Object, templateEngineFactoryService, Dispatcher, new DefaultErrorReporter());
|
|
||||||
manager.OnSubscribed(tracker); // Start tracking the import.
|
|
||||||
|
|
||||||
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
|
|
||||||
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
manager.OnSubscribed(anotherTracker);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(1, callCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ForegroundFact]
|
|
||||||
public void OnUnsubscribed_StopsTrackingImport()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filePath = "C:\\path\\to\\project\\file.cshtml";
|
|
||||||
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
|
||||||
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
|
||||||
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
|
||||||
|
|
||||||
uint cookie = 100;
|
|
||||||
var fileChangeService = new Mock<IVsFileChangeEx>(MockBehavior.Strict);
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.AdviseFileChange("C:\\path\\to\\project\\_ViewImports.cshtml", It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
|
||||||
.Returns(VSConstants.S_OK)
|
|
||||||
.Verifiable();
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.UnadviseFileChange(cookie))
|
|
||||||
.Returns(VSConstants.S_OK)
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var manager = new DefaultImportDocumentManager(fileChangeService.Object, templateEngineFactoryService, Dispatcher, new DefaultErrorReporter());
|
|
||||||
manager.OnSubscribed(tracker); // Start tracking the import.
|
|
||||||
|
|
||||||
// Act
|
|
||||||
manager.OnUnsubscribed(tracker);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
fileChangeService.Verify();
|
|
||||||
}
|
|
||||||
|
|
||||||
[ForegroundFact]
|
|
||||||
public void OnUnsubscribed_AnotherDocumentTrackingImport_DoesNotStopTrackingImport()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filePath = "C:\\path\\to\\project\\file.cshtml";
|
|
||||||
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
|
||||||
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
|
||||||
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
|
||||||
|
|
||||||
uint cookie;
|
|
||||||
var fileChangeService = new Mock<IVsFileChangeEx>();
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
|
||||||
.Returns(VSConstants.S_OK);
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.UnadviseFileChange(It.IsAny<uint>()))
|
|
||||||
.Returns(VSConstants.S_OK)
|
|
||||||
.Callback(() => throw new InvalidOperationException());
|
|
||||||
|
|
||||||
var manager = new DefaultImportDocumentManager(fileChangeService.Object, templateEngineFactoryService, Dispatcher, new DefaultErrorReporter());
|
|
||||||
manager.OnSubscribed(tracker); // Starts tracking import for the first document.
|
|
||||||
|
|
||||||
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
|
|
||||||
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
|
|
||||||
manager.OnSubscribed(anotherTracker); // Starts tracking import for the second document.
|
|
||||||
|
|
||||||
// Act & Assert (Does not throw)
|
|
||||||
manager.OnUnsubscribed(tracker);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ForegroundTheory]
|
|
||||||
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Size, (int)ImportChangeKind.Changed)]
|
|
||||||
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Time, (int)ImportChangeKind.Changed)]
|
|
||||||
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Add, (int)ImportChangeKind.Added)]
|
|
||||||
[InlineData((uint)_VSFILECHANGEFLAGS.VSFILECHG_Del, (int)ImportChangeKind.Removed)]
|
|
||||||
public void OnFilesChanged_WithSpecificFlags_InvokesChangedHandler_WithExpectedArguments(uint fileChangeFlag, int expectedKind)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var filePath = "C:\\path\\to\\project\\file.cshtml";
|
|
||||||
var projectPath = "C:\\path\\to\\project\\project.csproj";
|
|
||||||
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
|
|
||||||
var templateEngineFactoryService = GetTemplateEngineFactoryService();
|
|
||||||
|
|
||||||
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
|
|
||||||
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
|
|
||||||
|
|
||||||
uint cookie;
|
|
||||||
var fileChangeService = new Mock<IVsFileChangeEx>();
|
|
||||||
fileChangeService
|
|
||||||
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
|
|
||||||
.Returns(VSConstants.S_OK);
|
|
||||||
var manager = new DefaultImportDocumentManager(fileChangeService.Object, templateEngineFactoryService, Dispatcher, new DefaultErrorReporter());
|
|
||||||
manager.OnSubscribed(tracker);
|
|
||||||
manager.OnSubscribed(anotherTracker);
|
|
||||||
|
|
||||||
var called = false;
|
|
||||||
manager.Changed += (sender, args) =>
|
|
||||||
{
|
|
||||||
called = true;
|
|
||||||
Assert.Same(sender, manager);
|
|
||||||
Assert.Equal("C:\\path\\to\\project\\_ViewImports.cshtml", args.FilePath);
|
|
||||||
Assert.Equal((ImportChangeKind)expectedKind, args.Kind);
|
|
||||||
Assert.Collection(
|
|
||||||
args.AssociatedDocuments,
|
|
||||||
f => Assert.Equal(filePath, f),
|
|
||||||
f => Assert.Equal(anotherFilePath, f));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
manager.OnFilesChanged(fileCount: 1, filePaths: new[] { "C:\\path\\to\\project\\_ViewImports.cshtml" }, fileChangeFlags: new[] { fileChangeFlag });
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(called);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RazorTemplateEngineFactoryService GetTemplateEngineFactoryService()
|
|
||||||
{
|
|
||||||
var projectManager = new Mock<ProjectSnapshotManager>();
|
|
||||||
projectManager.Setup(p => p.Projects).Returns(Array.Empty<ProjectSnapshot>());
|
|
||||||
|
|
||||||
var service = new DefaultTemplateEngineFactoryService(projectManager.Object);
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue