Skip to content

Commit

Permalink
track non roslyn text buffer changes as well as roslyn text buffer ch…
Browse files Browse the repository at this point in the history
…anges to delay solution crawler

There are 2 things incremental processor takes care of

@1 is making sure we delay processing any work until there is enough idle (ex, typing) in host.
@2 is managing cancellation and pending works.

we used to do #1 and #2 only for Roslyn files. and that is usually fine since most of time solution contains only roslyn files.

but for mixed solution (ex, Roslyn files + HTML + JS + CSS), #2 still makes sense but #1 doesn't. We want to pause any work while something is going on in other project types as well.

we need to make sure we play nice with neighbors as well.

now, we don't care where changes are coming from. if there is any change in host, we puase oursevles for a while.
  • Loading branch information
heejaechang committed May 5, 2015
1 parent 2151da1 commit bb715ae
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 27 deletions.
7 changes: 7 additions & 0 deletions src/Features/Core/SolutionCrawler/IDocumentTrackingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,12 @@ internal interface IDocumentTrackingService : IWorkspaceService
ImmutableArray<DocumentId> GetVisibleDocuments();

event EventHandler<DocumentId> ActiveDocumentChanged;

/// <summary>
/// Events for Non Roslyn text buffer changes.
///
/// It raises events for buffers opened in a view in host.
/// </summary>
event EventHandler<EventArgs> NonRoslynBufferTextChanged;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,36 @@ public GlobalOperationAwareIdleProcessor(
base(listener, backOffTimeSpanInMs, shutdownToken)
{
this.Processor = processor;

_globalOperation = null;
_globalOperationTask = SpecializedTasks.EmptyTask;

_globalOperationNotificationService = globalOperationNotificationService;
_globalOperationNotificationService.Started += OnGlobalOperationStarted;
_globalOperationNotificationService.Stopped += OnGlobalOperationStopped;

if (this.Processor._documentTracker != null)
{
this.Processor._documentTracker.NonRoslynBufferTextChanged += OnNonRoslynBufferTextChanged;
}
}

private void OnNonRoslynBufferTextChanged(object sender, EventArgs e)
{
// There are 2 things incremental processor takes care of
//
// #1 is making sure we delay processing any work until there is enough idle (ex, typing) in host.
// #2 is managing cancellation and pending works.
//
// we used to do #1 and #2 only for Roslyn files. and that is usually fine since most of time solution contains only roslyn files.
//
// but for mixed solution (ex, Roslyn files + HTML + JS + CSS), #2 still makes sense but #1 doesn't. We want
// to pause any work while something is going on in other project types as well.
//
// we need to make sure we play nice with neighbors as well.
//
// now, we don't care where changes are coming from. if there is any change in host, we puase oursevles for a while.
this.UpdateLastAccessTime();
}

protected Task GlobalOperationTask
Expand Down Expand Up @@ -114,6 +138,11 @@ public virtual void Shutdown()
{
_globalOperationNotificationService.Started -= OnGlobalOperationStarted;
_globalOperationNotificationService.Stopped -= OnGlobalOperationStopped;

if (this.Processor._documentTracker != null)
{
this.Processor._documentTracker.NonRoslynBufferTextChanged -= OnNonRoslynBufferTextChanged;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,30 @@ private void OnBeforeDocumentWindowShow(IVsWindowFrame frame, uint docCookie, bo
{
OnBeforeDocumentWindowShow(frame, id, firstShow);
}

if (ids.Count == 0)
{
// deal with non roslyn text file opened in the editor
var buffer = TryGetTextBufferFromDocData(RunningDocumentTable.GetDocumentData(docCookie));
if (buffer != null)
{
OnBeforeNonRoslynDocumentWindowShow(buffer, firstShow);
}
}
}

protected virtual void OnBeforeDocumentWindowShow(IVsWindowFrame frame, DocumentId id, bool firstShow)
{
}

protected virtual void OnBeforeNonRoslynDocumentWindowShow(ITextBuffer buffer, bool firstShow)
{
}

protected virtual void OnBeforeNonRoslynDocumentClose(ITextBuffer buffer)
{
}

private IList<DocumentId> GetDocumentIdsFromDocCookie(uint docCookie)
{
List<DocumentKey> documentKeys;
Expand Down Expand Up @@ -300,35 +318,44 @@ private IList<DocumentId> GetDocumentIdsFromDocCookie(uint docCookie)
private void CloseDocuments(uint docCookie, string monikerToKeep)
{
List<DocumentKey> documentKeys;
if (_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out documentKeys))
if (!_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out documentKeys))
{
// We will remove from documentKeys the things we successfully closed,
// so clone the list so we can mutate while enumerating
var documentsToClose = documentKeys.Where(key => !StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, monikerToKeep)).ToList();

// For a given set of open linked or shared files, we may be closing one of the
// documents (e.g. excluding a linked file from one of its owning projects or
// unloading one of the head projects for a shared project) or the entire set of
// documents (e.g. closing the tab of a shared document). If the entire set of
// documents is closing, then we should avoid the process of updating the active
// context document between the closing of individual documents in the set. In the
// case of closing the tab of a shared document, this avoids updating the shared
// item context hierarchy for the entire shared project to head project owning the
// last documentKey in this list.
var updateActiveContext = documentsToClose.Count == 1;

foreach (var documentKey in documentsToClose)
// let others know about non roslyn document close
var buffer = TryGetTextBufferFromDocData(RunningDocumentTable.GetDocumentData(docCookie));
if (buffer != null)
{
var document = _documentMap[documentKey];
document.ProcessClose(updateActiveContext);
Contract.ThrowIfFalse(documentKeys.Remove(documentKey));
OnBeforeNonRoslynDocumentClose(buffer);
}

// If we removed all the keys, then remove the list entirely
if (documentKeys.Count == 0)
{
_docCookiesToOpenDocumentKeys.Remove(docCookie);
}
return;
}

// We will remove from documentKeys the things we successfully closed,
// so clone the list so we can mutate while enumerating
var documentsToClose = documentKeys.Where(key => !StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, monikerToKeep)).ToList();

// For a given set of open linked or shared files, we may be closing one of the
// documents (e.g. excluding a linked file from one of its owning projects or
// unloading one of the head projects for a shared project) or the entire set of
// documents (e.g. closing the tab of a shared document). If the entire set of
// documents is closing, then we should avoid the process of updating the active
// context document between the closing of individual documents in the set. In the
// case of closing the tab of a shared document, this avoids updating the shared
// item context hierarchy for the entire shared project to head project owning the
// last documentKey in this list.
var updateActiveContext = documentsToClose.Count == 1;

foreach (var documentKey in documentsToClose)
{
var document = _documentMap[documentKey];
document.ProcessClose(updateActiveContext);
Contract.ThrowIfFalse(documentKeys.Remove(documentKey));
}

// If we removed all the keys, then remove the list entirely
if (documentKeys.Count == 0)
{
_docCookiesToOpenDocumentKeys.Remove(docCookie);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
// Copyright (c) Microsoft. 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.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
internal class VisualStudioDocumentTrackingService : IDocumentTrackingService, IVsSelectionEvents, IDisposable
{
private readonly NonRoslynTextBufferTracker _tracker;

private IVsMonitorSelection _monitorSelection;
private uint _cookie;
private ImmutableList<FrameListener> _visibleFrames;
private IVsWindowFrame _activeFrame;

public VisualStudioDocumentTrackingService(IServiceProvider serviceProvider)
{
_tracker = new NonRoslynTextBufferTracker(this);

_visibleFrames = ImmutableList<FrameListener>.Empty;

_monitorSelection = (IVsMonitorSelection)serviceProvider.GetService(typeof(SVsShellMonitorSelection));
Expand Down Expand Up @@ -132,6 +140,18 @@ public int OnCmdUIContextChanged([ComAliasName("Microsoft.VisualStudio.Shell.Int
return VSConstants.E_NOTIMPL;
}

public event EventHandler<EventArgs> NonRoslynBufferTextChanged;

public void OnNonRoslynBufferOpened(ITextBuffer buffer)
{
_tracker.OnOpened(buffer);
}

public void OnNonRoslynBufferClosed(ITextBuffer buffer)
{
_tracker.OnClosed(buffer);
}

public void Dispose()
{
if (_cookie != VSConstants.VSCOOKIE_NIL && _monitorSelection != null)
Expand Down Expand Up @@ -262,5 +282,51 @@ internal string GetDebuggerDisplay()
return caption.ToString();
}
}

/// <summary>
/// It tracks non roslyn text buffer text changes.
/// </summary>
private class NonRoslynTextBufferTracker : ForegroundThreadAffinitizedObject
{
private readonly VisualStudioDocumentTrackingService _owner;
private readonly HashSet<ITextBuffer> _buffers;

public NonRoslynTextBufferTracker(VisualStudioDocumentTrackingService owner)
{
_owner = owner;
_buffers = new HashSet<ITextBuffer>();
}

public void OnOpened(ITextBuffer buffer)
{
AssertIsForeground();

if (_buffers.Contains(buffer))
{
return;
}

_buffers.Add(buffer);
buffer.PostChanged += OnTextChanged;
}

public void OnClosed(ITextBuffer buffer)
{
AssertIsForeground();

if (!_buffers.Contains(buffer))
{
return;
}

buffer.PostChanged -= OnTextChanged;
_buffers.Remove(buffer);
}

private void OnTextChanged(object sender, EventArgs e)
{
_owner.NonRoslynBufferTextChanged?.Invoke(sender, e);
}
}
}
}
23 changes: 21 additions & 2 deletions src/VisualStudio/Core/Def/RoslynDocumentProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.VisualStudio.LanguageServices.Implementation;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;

namespace Microsoft.VisualStudio.LanguageServices
{
Expand All @@ -23,10 +24,28 @@ public RoslynDocumentProvider(

protected override void OnBeforeDocumentWindowShow(IVsWindowFrame frame, DocumentId id, bool firstShow)
{
if (_documentTrackingService != null)
base.OnBeforeDocumentWindowShow(frame, id, firstShow);

_documentTrackingService?.DocumentFrameShowing(frame, id, firstShow);
}

protected override void OnBeforeNonRoslynDocumentWindowShow(ITextBuffer buffer, bool firstShow)
{
base.OnBeforeNonRoslynDocumentWindowShow(buffer, firstShow);

if (!firstShow)
{
_documentTrackingService.DocumentFrameShowing(frame, id, firstShow);
return;
}

_documentTrackingService?.OnNonRoslynBufferOpened(buffer);
}

protected override void OnBeforeNonRoslynDocumentClose(ITextBuffer buffer)
{
base.OnBeforeNonRoslynDocumentClose(buffer);

_documentTrackingService?.OnNonRoslynBufferClosed(buffer);
}
}
}

0 comments on commit bb715ae

Please sign in to comment.