Skip to content

Commit bb715ae

Browse files
committed
track non roslyn text buffer changes as well as roslyn text buffer changes 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.
1 parent 2151da1 commit bb715ae

File tree

5 files changed

+175
-27
lines changed

5 files changed

+175
-27
lines changed

src/Features/Core/SolutionCrawler/IDocumentTrackingService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,12 @@ internal interface IDocumentTrackingService : IWorkspaceService
2020
ImmutableArray<DocumentId> GetVisibleDocuments();
2121

2222
event EventHandler<DocumentId> ActiveDocumentChanged;
23+
24+
/// <summary>
25+
/// Events for Non Roslyn text buffer changes.
26+
///
27+
/// It raises events for buffers opened in a view in host.
28+
/// </summary>
29+
event EventHandler<EventArgs> NonRoslynBufferTextChanged;
2330
}
2431
}

src/Features/Core/SolutionCrawler/WorkCoordinator.GlobalOperationAwareIdleProcessor.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,36 @@ public GlobalOperationAwareIdleProcessor(
3737
base(listener, backOffTimeSpanInMs, shutdownToken)
3838
{
3939
this.Processor = processor;
40+
4041
_globalOperation = null;
4142
_globalOperationTask = SpecializedTasks.EmptyTask;
4243

4344
_globalOperationNotificationService = globalOperationNotificationService;
4445
_globalOperationNotificationService.Started += OnGlobalOperationStarted;
4546
_globalOperationNotificationService.Stopped += OnGlobalOperationStopped;
47+
48+
if (this.Processor._documentTracker != null)
49+
{
50+
this.Processor._documentTracker.NonRoslynBufferTextChanged += OnNonRoslynBufferTextChanged;
51+
}
52+
}
53+
54+
private void OnNonRoslynBufferTextChanged(object sender, EventArgs e)
55+
{
56+
// There are 2 things incremental processor takes care of
57+
//
58+
// #1 is making sure we delay processing any work until there is enough idle (ex, typing) in host.
59+
// #2 is managing cancellation and pending works.
60+
//
61+
// 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.
62+
//
63+
// but for mixed solution (ex, Roslyn files + HTML + JS + CSS), #2 still makes sense but #1 doesn't. We want
64+
// to pause any work while something is going on in other project types as well.
65+
//
66+
// we need to make sure we play nice with neighbors as well.
67+
//
68+
// now, we don't care where changes are coming from. if there is any change in host, we puase oursevles for a while.
69+
this.UpdateLastAccessTime();
4670
}
4771

4872
protected Task GlobalOperationTask
@@ -114,6 +138,11 @@ public virtual void Shutdown()
114138
{
115139
_globalOperationNotificationService.Started -= OnGlobalOperationStarted;
116140
_globalOperationNotificationService.Stopped -= OnGlobalOperationStopped;
141+
142+
if (this.Processor._documentTracker != null)
143+
{
144+
this.Processor._documentTracker.NonRoslynBufferTextChanged -= OnNonRoslynBufferTextChanged;
145+
}
117146
}
118147
}
119148
}

src/VisualStudio/Core/Def/Implementation/ProjectSystem/DocumentProvider.cs

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,30 @@ private void OnBeforeDocumentWindowShow(IVsWindowFrame frame, uint docCookie, bo
266266
{
267267
OnBeforeDocumentWindowShow(frame, id, firstShow);
268268
}
269+
270+
if (ids.Count == 0)
271+
{
272+
// deal with non roslyn text file opened in the editor
273+
var buffer = TryGetTextBufferFromDocData(RunningDocumentTable.GetDocumentData(docCookie));
274+
if (buffer != null)
275+
{
276+
OnBeforeNonRoslynDocumentWindowShow(buffer, firstShow);
277+
}
278+
}
269279
}
270280

271281
protected virtual void OnBeforeDocumentWindowShow(IVsWindowFrame frame, DocumentId id, bool firstShow)
272282
{
273283
}
274284

285+
protected virtual void OnBeforeNonRoslynDocumentWindowShow(ITextBuffer buffer, bool firstShow)
286+
{
287+
}
288+
289+
protected virtual void OnBeforeNonRoslynDocumentClose(ITextBuffer buffer)
290+
{
291+
}
292+
275293
private IList<DocumentId> GetDocumentIdsFromDocCookie(uint docCookie)
276294
{
277295
List<DocumentKey> documentKeys;
@@ -300,35 +318,44 @@ private IList<DocumentId> GetDocumentIdsFromDocCookie(uint docCookie)
300318
private void CloseDocuments(uint docCookie, string monikerToKeep)
301319
{
302320
List<DocumentKey> documentKeys;
303-
if (_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out documentKeys))
321+
if (!_docCookiesToOpenDocumentKeys.TryGetValue(docCookie, out documentKeys))
304322
{
305-
// We will remove from documentKeys the things we successfully closed,
306-
// so clone the list so we can mutate while enumerating
307-
var documentsToClose = documentKeys.Where(key => !StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, monikerToKeep)).ToList();
308-
309-
// For a given set of open linked or shared files, we may be closing one of the
310-
// documents (e.g. excluding a linked file from one of its owning projects or
311-
// unloading one of the head projects for a shared project) or the entire set of
312-
// documents (e.g. closing the tab of a shared document). If the entire set of
313-
// documents is closing, then we should avoid the process of updating the active
314-
// context document between the closing of individual documents in the set. In the
315-
// case of closing the tab of a shared document, this avoids updating the shared
316-
// item context hierarchy for the entire shared project to head project owning the
317-
// last documentKey in this list.
318-
var updateActiveContext = documentsToClose.Count == 1;
319-
320-
foreach (var documentKey in documentsToClose)
323+
// let others know about non roslyn document close
324+
var buffer = TryGetTextBufferFromDocData(RunningDocumentTable.GetDocumentData(docCookie));
325+
if (buffer != null)
321326
{
322-
var document = _documentMap[documentKey];
323-
document.ProcessClose(updateActiveContext);
324-
Contract.ThrowIfFalse(documentKeys.Remove(documentKey));
327+
OnBeforeNonRoslynDocumentClose(buffer);
325328
}
326329

327-
// If we removed all the keys, then remove the list entirely
328-
if (documentKeys.Count == 0)
329-
{
330-
_docCookiesToOpenDocumentKeys.Remove(docCookie);
331-
}
330+
return;
331+
}
332+
333+
// We will remove from documentKeys the things we successfully closed,
334+
// so clone the list so we can mutate while enumerating
335+
var documentsToClose = documentKeys.Where(key => !StringComparer.OrdinalIgnoreCase.Equals(key.Moniker, monikerToKeep)).ToList();
336+
337+
// For a given set of open linked or shared files, we may be closing one of the
338+
// documents (e.g. excluding a linked file from one of its owning projects or
339+
// unloading one of the head projects for a shared project) or the entire set of
340+
// documents (e.g. closing the tab of a shared document). If the entire set of
341+
// documents is closing, then we should avoid the process of updating the active
342+
// context document between the closing of individual documents in the set. In the
343+
// case of closing the tab of a shared document, this avoids updating the shared
344+
// item context hierarchy for the entire shared project to head project owning the
345+
// last documentKey in this list.
346+
var updateActiveContext = documentsToClose.Count == 1;
347+
348+
foreach (var documentKey in documentsToClose)
349+
{
350+
var document = _documentMap[documentKey];
351+
document.ProcessClose(updateActiveContext);
352+
Contract.ThrowIfFalse(documentKeys.Remove(documentKey));
353+
}
354+
355+
// If we removed all the keys, then remove the list entirely
356+
if (documentKeys.Count == 0)
357+
{
358+
_docCookiesToOpenDocumentKeys.Remove(docCookie);
332359
}
333360
}
334361

src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioDocumentTrackingService.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

33
using System;
4+
using System.Collections.Generic;
45
using System.Collections.Immutable;
56
using System.Diagnostics;
67
using System.Runtime.InteropServices;
78
using System.Text;
89
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
11+
using Microsoft.VisualStudio.ComponentModelHost;
912
using Microsoft.VisualStudio.Shell.Interop;
13+
using Microsoft.VisualStudio.Text;
1014
using Roslyn.Utilities;
1115

1216
namespace Microsoft.VisualStudio.LanguageServices.Implementation
1317
{
1418
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
1519
internal class VisualStudioDocumentTrackingService : IDocumentTrackingService, IVsSelectionEvents, IDisposable
1620
{
21+
private readonly NonRoslynTextBufferTracker _tracker;
22+
1723
private IVsMonitorSelection _monitorSelection;
1824
private uint _cookie;
1925
private ImmutableList<FrameListener> _visibleFrames;
2026
private IVsWindowFrame _activeFrame;
2127

2228
public VisualStudioDocumentTrackingService(IServiceProvider serviceProvider)
2329
{
30+
_tracker = new NonRoslynTextBufferTracker(this);
31+
2432
_visibleFrames = ImmutableList<FrameListener>.Empty;
2533

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

143+
public event EventHandler<EventArgs> NonRoslynBufferTextChanged;
144+
145+
public void OnNonRoslynBufferOpened(ITextBuffer buffer)
146+
{
147+
_tracker.OnOpened(buffer);
148+
}
149+
150+
public void OnNonRoslynBufferClosed(ITextBuffer buffer)
151+
{
152+
_tracker.OnClosed(buffer);
153+
}
154+
135155
public void Dispose()
136156
{
137157
if (_cookie != VSConstants.VSCOOKIE_NIL && _monitorSelection != null)
@@ -262,5 +282,51 @@ internal string GetDebuggerDisplay()
262282
return caption.ToString();
263283
}
264284
}
285+
286+
/// <summary>
287+
/// It tracks non roslyn text buffer text changes.
288+
/// </summary>
289+
private class NonRoslynTextBufferTracker : ForegroundThreadAffinitizedObject
290+
{
291+
private readonly VisualStudioDocumentTrackingService _owner;
292+
private readonly HashSet<ITextBuffer> _buffers;
293+
294+
public NonRoslynTextBufferTracker(VisualStudioDocumentTrackingService owner)
295+
{
296+
_owner = owner;
297+
_buffers = new HashSet<ITextBuffer>();
298+
}
299+
300+
public void OnOpened(ITextBuffer buffer)
301+
{
302+
AssertIsForeground();
303+
304+
if (_buffers.Contains(buffer))
305+
{
306+
return;
307+
}
308+
309+
_buffers.Add(buffer);
310+
buffer.PostChanged += OnTextChanged;
311+
}
312+
313+
public void OnClosed(ITextBuffer buffer)
314+
{
315+
AssertIsForeground();
316+
317+
if (!_buffers.Contains(buffer))
318+
{
319+
return;
320+
}
321+
322+
buffer.PostChanged -= OnTextChanged;
323+
_buffers.Remove(buffer);
324+
}
325+
326+
private void OnTextChanged(object sender, EventArgs e)
327+
{
328+
_owner.NonRoslynBufferTextChanged?.Invoke(sender, e);
329+
}
330+
}
265331
}
266332
}

src/VisualStudio/Core/Def/RoslynDocumentProvider.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Microsoft.VisualStudio.LanguageServices.Implementation;
66
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
77
using Microsoft.VisualStudio.Shell.Interop;
8+
using Microsoft.VisualStudio.Text;
89

910
namespace Microsoft.VisualStudio.LanguageServices
1011
{
@@ -23,10 +24,28 @@ public RoslynDocumentProvider(
2324

2425
protected override void OnBeforeDocumentWindowShow(IVsWindowFrame frame, DocumentId id, bool firstShow)
2526
{
26-
if (_documentTrackingService != null)
27+
base.OnBeforeDocumentWindowShow(frame, id, firstShow);
28+
29+
_documentTrackingService?.DocumentFrameShowing(frame, id, firstShow);
30+
}
31+
32+
protected override void OnBeforeNonRoslynDocumentWindowShow(ITextBuffer buffer, bool firstShow)
33+
{
34+
base.OnBeforeNonRoslynDocumentWindowShow(buffer, firstShow);
35+
36+
if (!firstShow)
2737
{
28-
_documentTrackingService.DocumentFrameShowing(frame, id, firstShow);
38+
return;
2939
}
40+
41+
_documentTrackingService?.OnNonRoslynBufferOpened(buffer);
42+
}
43+
44+
protected override void OnBeforeNonRoslynDocumentClose(ITextBuffer buffer)
45+
{
46+
base.OnBeforeNonRoslynDocumentClose(buffer);
47+
48+
_documentTrackingService?.OnNonRoslynBufferClosed(buffer);
3049
}
3150
}
3251
}

0 commit comments

Comments
 (0)