// Internal for testing
        internal void DocumentManager_Changed(object sender, LSPDocumentChangeEventArgs args)
        {
            if (args is null)
            {
                throw new ArgumentNullException(nameof(args));
            }

            if (args.Kind != LSPDocumentChangeKind.VirtualDocumentChanged)
            {
                return;
            }

            var lspDocument = args.New;

            for (var i = 0; i < lspDocument.VirtualDocuments.Count; i++)
            {
                if (!_synchronizingContexts.TryGetValue(lspDocument.VirtualDocuments[i].Uri, out var synchronizingContext))
                {
                    continue;
                }

                if (lspDocument.Version == synchronizingContext.ExpectedHostDocumentVersion)
                {
                    synchronizingContext.SetSynchronized(true);
                }
                else if (lspDocument.Version > synchronizingContext.ExpectedHostDocumentVersion)
                {
                    // The LSP document version has surpassed what the projected document was expecting for a version. No longer able to synchronize.
                    synchronizingContext.SetSynchronized(false);
                }
            }
        }
        // Internal for testing
        internal void DocumentManager_Changed(object sender, LSPDocumentChangeEventArgs args)
        {
            // We need the below check to address a race condition between when a request is sent to the C# server
            // for a generated document and when the C# server receives a document/didOpen notification. This race
            // condition may occur when the Razor server finishes initializing before C# receives and processes the
            // document open request.
            // This workaround adds the Razor client name to the generated document so the C# server will recognize
            // it, despite the document not being formally opened. Note this is meant to only be a temporary
            // workaround until a longer-term solution is implemented in the future.
            if (args.Kind == LSPDocumentChangeKind.Added && _dynamicFileInfoProvider is DefaultRazorDynamicFileInfoProvider defaultProvider)
            {
                defaultProvider.PromoteBackgroundDocument(args.New.Uri, CSharpDocumentPropertiesService.Instance);
            }

            if (args.Kind != LSPDocumentChangeKind.VirtualDocumentChanged)
            {
                return;
            }

            if (args.VirtualNew is CSharpVirtualDocumentSnapshot)
            {
                var csharpContainer = new CSharpVirtualDocumentContainer(_lspDocumentMappingProvider, args.New, args.VirtualNew.Snapshot);
                _dynamicFileInfoProvider.UpdateLSPFileInfo(args.New.Uri, csharpContainer);
            }
        }
        public async Task TrySynchronizeVirtualDocumentAsync_SimultaneousEqualSynchronizationRequests_ReturnsTrue()
        {
            // Arrange
            var synchronizer = new DefaultLSPDocumentSynchronizer(DocumentManager, JoinableTaskContext);

            synchronizer._synchronizationTimeout = TimeSpan.FromMilliseconds(500);
            var originalVirtualDocument = new TestVirtualDocumentSnapshot(VirtualDocumentUri, 123);
            var originalDocument        = new TestLSPDocumentSnapshot(LSPDocumentUri, 124, originalVirtualDocument);

            // Start synchronize
            var synchronizeTask1 = synchronizer.TrySynchronizeVirtualDocumentAsync(originalDocument, originalVirtualDocument, CancellationToken.None);
            var synchronizeTask2 = synchronizer.TrySynchronizeVirtualDocumentAsync(originalDocument, originalVirtualDocument, CancellationToken.None);

            var newVirtualDocument = originalVirtualDocument.Fork(124);
            var newDocument        = originalDocument.Fork(124, newVirtualDocument);
            var args = new LSPDocumentChangeEventArgs(originalDocument, newDocument, LSPDocumentChangeKind.VirtualDocumentChanged);

            // Act
            synchronizer.DocumentManager_Changed(DocumentManager, args);
            var result1 = await synchronizeTask1.ConfigureAwait(false);

            var result2 = await synchronizeTask2.ConfigureAwait(false);

            // Assert
            Assert.True(result1);
            Assert.True(result2);
        }
Exemple #4
0
        public override void UpdateVirtualDocument <TVirtualDocument>(
            Uri hostDocumentUri,
            IReadOnlyList <TextChange> changes,
            long hostDocumentVersion)
        {
            if (hostDocumentUri is null)
            {
                throw new ArgumentNullException(nameof(hostDocumentUri));
            }

            if (changes is null)
            {
                throw new ArgumentNullException(nameof(changes));
            }

            Debug.Assert(_joinableTaskContext.IsOnMainThread);

            if (!_documents.TryGetValue(hostDocumentUri, out var lspDocument))
            {
                // Don't know about document, noop.
                return;
            }

            var virtualDocumentAcquired = lspDocument.TryGetVirtualDocument <TVirtualDocument>(out var virtualDocument);

            if (changes.Count == 0 &&
                virtualDocumentAcquired &&
                virtualDocument.HostDocumentSyncVersion == hostDocumentVersion)
            {
                // The current virtual document already knows about this update. Ignore it so we don't prematurely invoke a change event.
                return;
            }

            var old        = lspDocument.CurrentSnapshot;
            var oldVirtual = virtualDocument.CurrentSnapshot;
            var @new       = lspDocument.UpdateVirtualDocument <TVirtualDocument>(changes, hostDocumentVersion);

            if (old == @new)
            {
                return;
            }

            if (!lspDocument.TryGetVirtualDocument <TVirtualDocument>(out var newVirtualDocument))
            {
                throw new InvalidOperationException("This should never ever happen.");
            }

            var newVirtual = newVirtualDocument.CurrentSnapshot;
            var args       = new LSPDocumentChangeEventArgs(
                old,
                @new,
                oldVirtual,
                newVirtual,
                LSPDocumentChangeKind.VirtualDocumentChanged);

            Changed?.Invoke(this, args);
        }
        public void DocumentManager_Changed_Removed_Noops()
        {
            // Arrange
            var fileInfoProvider = new Mock <RazorDynamicFileInfoProvider>(MockBehavior.Strict);
            var publisher        = new CSharpVirtualDocumentPublisher(fileInfoProvider.Object);
            var args             = new LSPDocumentChangeEventArgs(old: Mock.Of <LSPDocumentSnapshot>(), @new: null, LSPDocumentChangeKind.Removed);

            // Act & Assert
            publisher.DocumentManager_Changed(sender: null, args);
        }
        // Internal for testing
        internal void DocumentManager_Changed(object sender, LSPDocumentChangeEventArgs args)
        {
            if (args.Kind != LSPDocumentChangeKind.VirtualDocumentChanged)
            {
                return;
            }

            if (args.VirtualNew is CSharpVirtualDocumentSnapshot)
            {
                var csharpContainer = new CSharpVirtualDocumentContainer(args.VirtualNew.Snapshot);
                _dynamicFileInfoProvider.UpdateLSPFileInfo(args.New.Uri, csharpContainer);
            }
        }
        public void DocumentManager_Changed_VirtualDocumentChanged_UpdatesFileInfo()
        {
            // Arrange
            var csharpSnapshot   = new CSharpVirtualDocumentSnapshot(new Uri("C:/path/to/something.razor.g.cs"), Mock.Of <ITextSnapshot>(), hostDocumentSyncVersion: 1337);
            var lspDocument      = new TestLSPDocumentSnapshot(new Uri("C:/path/to/something.razor"), 1337, csharpSnapshot);
            var fileInfoProvider = new Mock <RazorDynamicFileInfoProvider>(MockBehavior.Strict);

            fileInfoProvider.Setup(provider => provider.UpdateLSPFileInfo(lspDocument.Uri, It.IsAny <DynamicDocumentContainer>()))
            .Verifiable();
            var publisher = new CSharpVirtualDocumentPublisher(fileInfoProvider.Object);
            var args      = new LSPDocumentChangeEventArgs(
                old: Mock.Of <LSPDocumentSnapshot>(), @new: lspDocument,
                virtualOld: Mock.Of <VirtualDocumentSnapshot>(), virtualNew: csharpSnapshot,
                LSPDocumentChangeKind.VirtualDocumentChanged);

            // Act
            publisher.DocumentManager_Changed(sender: null, args);

            // Assert
            fileInfoProvider.VerifyAll();
        }
        // Internal for testing
        internal void DocumentManager_Changed(object sender, LSPDocumentChangeEventArgs args)
        {
            if (args is null)
            {
                throw new ArgumentNullException(nameof(args));
            }

            lock (DocumentContextLock)
            {
                if (args.Kind == LSPDocumentChangeKind.Added)
                {
                    var lspDocument = args.New;
                    for (var i = 0; i < lspDocument.VirtualDocuments.Count; i++)
                    {
                        var virtualDocument = lspDocument.VirtualDocuments[i];

                        Debug.Assert(!_virtualDocumentContexts.ContainsKey(virtualDocument.Uri));

                        var virtualDocumentTextBuffer = virtualDocument.Snapshot.TextBuffer;
                        virtualDocumentTextBuffer.PostChanged        += VirtualDocumentBuffer_PostChanged;
                        _virtualDocumentContexts[virtualDocument.Uri] = new DocumentContext(_synchronizationTimeout);
                    }
                }
                else if (args.Kind == LSPDocumentChangeKind.Removed)
                {
                    var lspDocument = args.Old;
                    for (var i = 0; i < lspDocument.VirtualDocuments.Count; i++)
                    {
                        var virtualDocument = lspDocument.VirtualDocuments[i];

                        Debug.Assert(_virtualDocumentContexts.ContainsKey(virtualDocument.Uri));

                        var virtualDocumentTextBuffer = virtualDocument.Snapshot.TextBuffer;
                        virtualDocumentTextBuffer.PostChanged -= VirtualDocumentBuffer_PostChanged;
                        _virtualDocumentContexts.Remove(virtualDocument.Uri);
                    }
                }
            }
        }
Exemple #9
0
        public override void UntrackDocument(ITextBuffer buffer)
        {
            if (buffer is null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            Debug.Assert(_joinableTaskContext.IsOnMainThread);

            var uri = _fileUriProvider.GetOrCreate(buffer);

            if (!_documents.TryGetValue(uri, out var lspDocument))
            {
                // We don't know about this document, noop.
                return;
            }

            _documents.Remove(uri);

            var args = new LSPDocumentChangeEventArgs(lspDocument.CurrentSnapshot, @new: null, LSPDocumentChangeKind.Removed);

            Changed?.Invoke(this, args);
        }
Exemple #10
0
        public override void TrackDocument(ITextBuffer buffer)
        {
            if (buffer is null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            Debug.Assert(_joinableTaskContext.IsOnMainThread);

            var uri = _fileUriProvider.GetOrCreate(buffer);

            if (_documents.TryGetValue(uri, out _))
            {
                throw new InvalidOperationException($"Can not track document that's already being tracked {uri}");
            }

            var lspDocument = _documentFactory.Create(buffer);

            _documents[uri] = lspDocument;
            var args = new LSPDocumentChangeEventArgs(old: null, lspDocument.CurrentSnapshot, LSPDocumentChangeKind.Added);

            Changed?.Invoke(this, args);
        }
        public async Task TrySynchronizeVirtualDocumentAsync_SynchronizesAfterUpdate_ReturnsTrue()
        {
            // Arrange
            var synchronizer = new DefaultLSPDocumentSynchronizer(DocumentManager, JoinableTaskContext);

            synchronizer._synchronizationTimeout = TimeSpan.FromMilliseconds(500);
            var originalVirtualDocument = new TestVirtualDocumentSnapshot(VirtualDocumentUri, 123);
            var originalDocument        = new TestLSPDocumentSnapshot(LSPDocumentUri, 124, originalVirtualDocument);

            // Start synchronization, this will hang until we invoke a DocumentManager_Changed event because the above virtual document expects host doc version 123 but the host doc is 124
            var synchronizeTask = synchronizer.TrySynchronizeVirtualDocumentAsync(originalDocument, originalVirtualDocument, CancellationToken.None);

            // Create a virtual and host doc that are synchronized (both at version 124).
            var newVirtualDocument = originalVirtualDocument.Fork(124);
            var newDocument        = originalDocument.Fork(124, newVirtualDocument);
            var args = new LSPDocumentChangeEventArgs(originalDocument, newDocument, LSPDocumentChangeKind.VirtualDocumentChanged);

            // Act
            synchronizer.DocumentManager_Changed(DocumentManager, args);
            var result = await synchronizeTask.ConfigureAwait(false);

            // Assert
            Assert.True(result);
        }
        public async Task TrySynchronizeVirtualDocumentAsync_SimultaneousDifferentSynchronizationRequests_CancelsFirst_ReturnsFalseThenTrue()
        {
            // Arrange
            var synchronizer = new DefaultLSPDocumentSynchronizer(DocumentManager, JoinableTaskContext);

            synchronizer._synchronizationTimeout = TimeSpan.FromMilliseconds(500);
            var originalVirtualDocument = new TestVirtualDocumentSnapshot(VirtualDocumentUri, 123);
            var originalDocument        = new TestLSPDocumentSnapshot(LSPDocumentUri, 124, originalVirtualDocument);

            // Start synchronization that will hang because 123 != 124
            var synchronizeTask1 = synchronizer.TrySynchronizeVirtualDocumentAsync(originalDocument, originalVirtualDocument, CancellationToken.None);

            var newVirtualDocument = originalVirtualDocument.Fork(124);
            var newDocument        = originalDocument.Fork(125, newVirtualDocument);

            // Start another synchronization that will also hang because 124 != 125. However, this synchronization request is for the same addressable virtual document (same URI)
            // therefore requesting a second synchronization with a different host doc version expectation will cancel the original synchronization request resulting it returning
            // false.
            var synchronizeTask2 = synchronizer.TrySynchronizeVirtualDocumentAsync(newDocument, newVirtualDocument, CancellationToken.None);

            var finalVirtualDocument = newVirtualDocument.Fork(125);
            var finalDocument        = newDocument.Fork(125, finalVirtualDocument);

            var args = new LSPDocumentChangeEventArgs(newDocument, finalDocument, LSPDocumentChangeKind.VirtualDocumentChanged);


            // Act
            synchronizer.DocumentManager_Changed(DocumentManager, args);
            var result1 = await synchronizeTask1.ConfigureAwait(false);

            var result2 = await synchronizeTask2.ConfigureAwait(false);

            // Assert
            Assert.False(result1);
            Assert.True(result2);
        }
Exemple #13
0
        private static void NotifyLSPDocumentAdded(LSPDocumentSnapshot lspDocument, DefaultLSPDocumentSynchronizer synchronizer)
        {
            var args = new LSPDocumentChangeEventArgs(old: null, @new: lspDocument, LSPDocumentChangeKind.Added);

            synchronizer.DocumentManager_Changed(sender: null, args);
        }