Skip to content

Commit 6969b51

Browse files
Merge pull request #18695 from jasonmalinowski/fix-codemodel-crash-if-file-is-closed
Fix CodeModel crash if the file is closed
2 parents 8432de2 + 208da19 commit 6969b51

File tree

14 files changed

+132
-157
lines changed

14 files changed

+132
-157
lines changed

src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ class D { }
220220

221221
await VerifyRootTypeNameAsync(workspace, "C");
222222

223-
workspace.OnDocumentClosed(document.Id);
223+
workspace.CloseDocument(document.Id);
224224
}
225225
}
226226

@@ -405,7 +405,7 @@ public void TestRemoveProjectWithClosedDocuments()
405405

406406
workspace.AddTestProject(project1);
407407
workspace.OnDocumentOpened(document.Id, document.GetOpenTextContainer());
408-
workspace.OnDocumentClosed(document.Id);
408+
workspace.CloseDocument(document.Id);
409409
workspace.OnProjectRemoved(project1.Id);
410410
}
411411
}
@@ -425,7 +425,7 @@ public void TestRemoveOpenedDocument()
425425

426426
Assert.Throws<ArgumentException>(() => workspace.OnDocumentRemoved(document.Id));
427427

428-
workspace.OnDocumentClosed(document.Id);
428+
workspace.CloseDocument(document.Id);
429429
workspace.OnProjectRemoved(project1.Id);
430430
}
431431
}
@@ -692,7 +692,7 @@ public async Task TestOpenAndChangeDocument()
692692
var syntaxTree = await doc.GetSyntaxTreeAsync(CancellationToken.None);
693693
Assert.True(syntaxTree.GetRoot().Width() > 0, "syntaxTree.GetRoot().Width should be > 0");
694694

695-
workspace.OnDocumentClosed(document.Id);
695+
workspace.CloseDocument(document.Id);
696696
workspace.OnProjectRemoved(project1.Id);
697697
}
698698
}

src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs

+13-30
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
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

3+
using System;
34
using System.Collections.Generic;
45
using System.Collections.Immutable;
56
using System.Linq;
@@ -20,7 +21,7 @@ public class TestHostDocument
2021
{
2122
private readonly ExportProvider _exportProvider;
2223
private HostLanguageServices _languageServiceProvider;
23-
private readonly string _initialText;
24+
private readonly Lazy<string> _initialText;
2425
private IWpfTextView _textView;
2526

2627
private DocumentId _id;
@@ -32,7 +33,6 @@ public class TestHostDocument
3233
private readonly SourceCodeKind _sourceCodeKind;
3334
private readonly string _filePath;
3435
private readonly IReadOnlyList<string> _folders;
35-
private readonly TextLoader _loader;
3636

3737
public DocumentId Id
3838
{
@@ -82,14 +82,7 @@ public bool IsGenerated
8282
}
8383
}
8484

85-
public TextLoader Loader
86-
{
87-
get
88-
{
89-
return _loader;
90-
}
91-
}
92-
85+
public TextLoader Loader { get; }
9386
public int? CursorPosition { get; }
9487
public IList<TextSpan> SelectedSpans { get; }
9588
public IDictionary<string, IList<TextSpan>> AnnotatedSpans { get; }
@@ -118,6 +111,7 @@ internal TestHostDocument(
118111
_languageServiceProvider = languageServiceProvider;
119112
this.TextBuffer = textBuffer;
120113
this.InitialTextSnapshot = textBuffer.CurrentSnapshot;
114+
_initialText = new Lazy<string>(() => this.InitialTextSnapshot.GetText());
121115
_filePath = filePath;
122116
_folders = folders;
123117
_name = filePath;
@@ -137,7 +131,7 @@ internal TestHostDocument(
137131
this.AnnotatedSpans.Add(namedSpanList);
138132
}
139133

140-
_loader = new TestDocumentLoader(this);
134+
Loader = new TestDocumentLoader(this);
141135
}
142136

143137
public TestHostDocument(
@@ -148,10 +142,10 @@ public TestHostDocument(
148142
{
149143
_exportProvider = TestExportProvider.ExportProviderWithCSharpAndVisualBasic;
150144
_id = id;
151-
_initialText = text;
145+
_initialText = new Lazy<string>(() => text);
152146
_name = displayName;
153147
_sourceCodeKind = sourceCodeKind;
154-
_loader = new TestDocumentLoader(this);
148+
Loader = new TestDocumentLoader(this);
155149
_filePath = filePath;
156150
_folders = folders;
157151
}
@@ -178,7 +172,7 @@ internal void SetProject(TestHostProject project)
178172
{
179173
var contentTypeService = _languageServiceProvider.GetService<IContentTypeLanguageService>();
180174
var contentType = contentTypeService.GetDefaultContentType();
181-
this.TextBuffer = _exportProvider.GetExportedValue<ITextBufferFactoryService>().CreateTextBuffer(_initialText, contentType);
175+
this.TextBuffer = _exportProvider.GetExportedValue<ITextBufferFactoryService>().CreateTextBuffer(_initialText.Value, contentType);
182176
this.InitialTextSnapshot = this.TextBuffer.CurrentSnapshot;
183177
}
184178
}
@@ -194,18 +188,13 @@ internal TestDocumentLoader(TestHostDocument hostDocument)
194188

195189
public override Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
196190
{
197-
return Task.FromResult(TextAndVersion.Create(_hostDocument.LoadText(cancellationToken), VersionStamp.Create(), "test"));
198-
}
199-
}
200-
201-
public IContentType ContentType
202-
{
203-
get
204-
{
205-
return this.TextBuffer.ContentType;
191+
// Create a simple SourceText so that way we're not backing "closed" files by editors to best reflect
192+
// what closed files look like in reality.
193+
var text = SourceText.From(_hostDocument.GetTextBuffer().CurrentSnapshot.GetText());
194+
return Task.FromResult(TextAndVersion.Create(text, VersionStamp.Create(), _hostDocument.FilePath));
206195
}
207196
}
208-
197+
209198
public IWpfTextView GetTextView()
210199
{
211200
if (_textView == null)
@@ -234,12 +223,6 @@ public ITextBuffer GetTextBuffer()
234223
return this.TextBuffer;
235224
}
236225

237-
public SourceText LoadText(CancellationToken cancellationToken = default(CancellationToken))
238-
{
239-
var loadedBuffer = _exportProvider.GetExportedValue<ITextBufferFactoryService>().CreateTextBuffer(this.InitialTextSnapshot.GetText(), this.InitialTextSnapshot.ContentType);
240-
return loadedBuffer.CurrentSnapshot.AsText();
241-
}
242-
243226
public SourceTextContainer GetOpenTextContainer()
244227
{
245228
return this.GetTextBuffer().AsTextContainer();

src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs

+5-12
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,7 @@ public void AddTestProject(TestHostProject project)
210210
{
211211
base.OnDocumentOpened(documentId, textContainer, isCurrentContext);
212212
}
213-
214-
public void OnDocumentClosed(DocumentId documentId)
215-
{
216-
var testDocument = this.GetTestDocument(documentId);
217-
this.OnDocumentClosed(documentId, testDocument.Loader);
218-
}
219-
213+
220214
public new void OnParseOptionsChanged(ProjectId projectId, ParseOptions parseOptions)
221215
{
222216
base.OnParseOptionsChanged(projectId, parseOptions);
@@ -594,15 +588,14 @@ private void MapMarkupSpans(IDictionary<string, IList<TextSpan>> markupSpans, ou
594588

595589
public override void OpenDocument(DocumentId documentId, bool activate = true)
596590
{
597-
OnDocumentOpened(documentId, this.CurrentSolution.GetDocument(documentId).GetTextAsync().Result.Container);
591+
var testDocument = this.GetTestDocument(documentId);
592+
OnDocumentOpened(documentId, testDocument.GetOpenTextContainer());
598593
}
599594

600595
public override void CloseDocument(DocumentId documentId)
601596
{
602-
var currentDoc = this.CurrentSolution.GetDocument(documentId);
603-
604-
OnDocumentClosed(documentId,
605-
TextLoader.From(TextAndVersion.Create(currentDoc.GetTextAsync().Result, currentDoc.GetTextVersionAsync().Result)));
597+
var testDocument = this.GetTestDocument(documentId);
598+
this.OnDocumentClosed(documentId, testDocument.Loader);
606599
}
607600

608601
public void ChangeDocument(DocumentId documentId, SourceText text)

src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs

+23-28
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.CodeAnalysis.CSharp.Extensions;
99
using Microsoft.CodeAnalysis.CSharp.Syntax;
1010
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
11+
using Microsoft.CodeAnalysis.Options;
1112
using Microsoft.CodeAnalysis.Shared.Extensions;
1213
using Microsoft.CodeAnalysis.Text;
1314
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
@@ -18,22 +19,16 @@ internal partial class CSharpCodeModelService
1819
{
1920
protected override AbstractNodeLocator CreateNodeLocator()
2021
{
21-
return new NodeLocator(this);
22+
return new NodeLocator();
2223
}
2324

2425
private class NodeLocator : AbstractNodeLocator
2526
{
26-
public NodeLocator(CSharpCodeModelService codeModelService)
27-
: base(codeModelService)
28-
{
29-
}
27+
protected override string LanguageName => LanguageNames.CSharp;
3028

31-
protected override EnvDTE.vsCMPart DefaultPart
32-
{
33-
get { return EnvDTE.vsCMPart.vsCMPartWholeWithAttributes; }
34-
}
29+
protected override EnvDTE.vsCMPart DefaultPart => EnvDTE.vsCMPart.vsCMPartWholeWithAttributes;
3530

36-
protected override VirtualTreePoint? GetStartPoint(SourceText text, SyntaxNode node, EnvDTE.vsCMPart part)
31+
protected override VirtualTreePoint? GetStartPoint(SourceText text, OptionSet options, SyntaxNode node, EnvDTE.vsCMPart part)
3732
{
3833
switch (node.Kind())
3934
{
@@ -53,16 +48,16 @@ protected override EnvDTE.vsCMPart DefaultPart
5348
case SyntaxKind.DestructorDeclaration:
5449
case SyntaxKind.OperatorDeclaration:
5550
case SyntaxKind.ConversionOperatorDeclaration:
56-
return GetStartPoint(text, (BaseMethodDeclarationSyntax)node, part);
51+
return GetStartPoint(text, options, (BaseMethodDeclarationSyntax)node, part);
5752
case SyntaxKind.PropertyDeclaration:
5853
case SyntaxKind.IndexerDeclaration:
5954
case SyntaxKind.EventDeclaration:
60-
return GetStartPoint(text, (BasePropertyDeclarationSyntax)node, part);
55+
return GetStartPoint(text, options, (BasePropertyDeclarationSyntax)node, part);
6156
case SyntaxKind.GetAccessorDeclaration:
6257
case SyntaxKind.SetAccessorDeclaration:
6358
case SyntaxKind.AddAccessorDeclaration:
6459
case SyntaxKind.RemoveAccessorDeclaration:
65-
return GetStartPoint(text, (AccessorDeclarationSyntax)node, part);
60+
return GetStartPoint(text, options, (AccessorDeclarationSyntax)node, part);
6661
case SyntaxKind.DelegateDeclaration:
6762
return GetStartPoint(text, (DelegateDeclarationSyntax)node, part);
6863
case SyntaxKind.NamespaceDeclaration:
@@ -81,7 +76,7 @@ protected override EnvDTE.vsCMPart DefaultPart
8176
}
8277
}
8378

84-
protected override VirtualTreePoint? GetEndPoint(SourceText text, SyntaxNode node, EnvDTE.vsCMPart part)
79+
protected override VirtualTreePoint? GetEndPoint(SourceText text, OptionSet options, SyntaxNode node, EnvDTE.vsCMPart part)
8580
{
8681
switch (node.Kind())
8782
{
@@ -141,7 +136,7 @@ private VirtualTreePoint GetBodyStartPoint(SourceText text, SyntaxToken openBrac
141136
: new VirtualTreePoint(openBrace.SyntaxTree, text, openBrace.Span.End);
142137
}
143138

144-
private VirtualTreePoint GetBodyStartPoint(SourceText text, SyntaxToken openBrace, SyntaxToken closeBrace, int memberStartColumn)
139+
private VirtualTreePoint GetBodyStartPoint(SourceText text, OptionSet options, SyntaxToken openBrace, SyntaxToken closeBrace, int memberStartColumn)
145140
{
146141
Debug.Assert(!openBrace.IsMissing);
147142
Debug.Assert(!closeBrace.IsMissing);
@@ -181,7 +176,7 @@ private VirtualTreePoint GetBodyStartPoint(SourceText text, SyntaxToken openBrac
181176

182177
// If the line is all whitespace then place the caret at the first indent after the start
183178
// of the member.
184-
var indentSize = GetTabSize(text);
179+
var indentSize = GetTabSize(options);
185180
var lineText = lineAfterOpenBrace.ToString();
186181

187182
var lineEndColumn = lineText.GetColumnFromLineOffset(lineText.Length, indentSize);
@@ -347,7 +342,7 @@ private VirtualTreePoint GetStartPoint(SourceText text, BaseTypeDeclarationSynta
347342
return new VirtualTreePoint(node.SyntaxTree, text, startPosition);
348343
}
349344

350-
private VirtualTreePoint GetStartPoint(SourceText text, BaseMethodDeclarationSyntax node, EnvDTE.vsCMPart part)
345+
private VirtualTreePoint GetStartPoint(SourceText text, OptionSet options, BaseMethodDeclarationSyntax node, EnvDTE.vsCMPart part)
351346
{
352347
int startPosition;
353348

@@ -380,9 +375,9 @@ private VirtualTreePoint GetStartPoint(SourceText text, BaseMethodDeclarationSyn
380375
if (node.Body != null && !node.Body.OpenBraceToken.IsMissing)
381376
{
382377
var line = text.Lines.GetLineFromPosition(node.SpanStart);
383-
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(text));
378+
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(options));
384379

385-
return GetBodyStartPoint(text, node.Body.OpenBraceToken, node.Body.CloseBraceToken, indentation);
380+
return GetBodyStartPoint(text, options, node.Body.OpenBraceToken, node.Body.CloseBraceToken, indentation);
386381
}
387382
else
388383
{
@@ -436,7 +431,7 @@ private AccessorDeclarationSyntax FindFirstAccessorNode(BasePropertyDeclarationS
436431
return node.AccessorList.Accessors.FirstOrDefault();
437432
}
438433

439-
private VirtualTreePoint GetStartPoint(SourceText text, BasePropertyDeclarationSyntax node, EnvDTE.vsCMPart part)
434+
private VirtualTreePoint GetStartPoint(SourceText text, OptionSet options, BasePropertyDeclarationSyntax node, EnvDTE.vsCMPart part)
440435
{
441436
int startPosition;
442437

@@ -467,17 +462,17 @@ private VirtualTreePoint GetStartPoint(SourceText text, BasePropertyDeclarationS
467462
if (firstAccessorNode != null)
468463
{
469464
var line = text.Lines.GetLineFromPosition(firstAccessorNode.SpanStart);
470-
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(text));
465+
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(options));
471466

472467
if (firstAccessorNode.Body != null)
473468
{
474-
return GetBodyStartPoint(text, firstAccessorNode.Body.OpenBraceToken, firstAccessorNode.Body.CloseBraceToken, indentation);
469+
return GetBodyStartPoint(text, options, firstAccessorNode.Body.OpenBraceToken, firstAccessorNode.Body.CloseBraceToken, indentation);
475470
}
476471
else if (!firstAccessorNode.SemicolonToken.IsMissing)
477472
{
478473
// This is total weirdness from the old C# code model with auto props.
479474
// If there isn't a body, the semi-colon is used
480-
return GetBodyStartPoint(text, firstAccessorNode.SemicolonToken, firstAccessorNode.SemicolonToken, indentation);
475+
return GetBodyStartPoint(text, options, firstAccessorNode.SemicolonToken, firstAccessorNode.SemicolonToken, indentation);
481476
}
482477
}
483478

@@ -487,9 +482,9 @@ private VirtualTreePoint GetStartPoint(SourceText text, BasePropertyDeclarationS
487482
if (node.AccessorList != null && !node.AccessorList.OpenBraceToken.IsMissing)
488483
{
489484
var line = text.Lines.GetLineFromPosition(node.SpanStart);
490-
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(text));
485+
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(options));
491486

492-
return GetBodyStartPoint(text, node.AccessorList.OpenBraceToken, node.AccessorList.CloseBraceToken, indentation);
487+
return GetBodyStartPoint(text, options, node.AccessorList.OpenBraceToken, node.AccessorList.CloseBraceToken, indentation);
493488
}
494489

495490
throw Exceptions.ThrowEFail();
@@ -501,7 +496,7 @@ private VirtualTreePoint GetStartPoint(SourceText text, BasePropertyDeclarationS
501496
return new VirtualTreePoint(node.SyntaxTree, text, startPosition);
502497
}
503498

504-
private VirtualTreePoint GetStartPoint(SourceText text, AccessorDeclarationSyntax node, EnvDTE.vsCMPart part)
499+
private VirtualTreePoint GetStartPoint(SourceText text, OptionSet options, AccessorDeclarationSyntax node, EnvDTE.vsCMPart part)
505500
{
506501
int startPosition;
507502

@@ -526,9 +521,9 @@ private VirtualTreePoint GetStartPoint(SourceText text, AccessorDeclarationSynta
526521
if (node.Body != null && !node.Body.OpenBraceToken.IsMissing)
527522
{
528523
var line = text.Lines.GetLineFromPosition(node.SpanStart);
529-
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(text));
524+
var indentation = line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(GetTabSize(options));
530525

531-
return GetBodyStartPoint(text, node.Body.OpenBraceToken, node.Body.CloseBraceToken, indentation);
526+
return GetBodyStartPoint(text, options, node.Body.OpenBraceToken, node.Body.CloseBraceToken, indentation);
532527
}
533528

534529
throw Exceptions.ThrowEFail();

src/VisualStudio/Core/Def/Implementation/ICodeModelNavigationPointService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.CodeAnalysis;
44
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
55
using Microsoft.CodeAnalysis.Host;
6+
using Microsoft.CodeAnalysis.Options;
67

78
namespace Microsoft.VisualStudio.LanguageServices.Implementation
89
{
@@ -11,11 +12,11 @@ internal interface ICodeModelNavigationPointService : ILanguageService
1112
/// <summary>
1213
/// Retrieves the start point of a given node for the specified EnvDTE.vsCMPart.
1314
/// </summary>
14-
VirtualTreePoint? GetStartPoint(SyntaxNode node, EnvDTE.vsCMPart? part = null);
15+
VirtualTreePoint? GetStartPoint(SyntaxNode node, OptionSet options, EnvDTE.vsCMPart? part = null);
1516

1617
/// <summary>
1718
/// Retrieves the end point of a given node for the specified EnvDTE.vsCMPart.
1819
/// </summary>
19-
VirtualTreePoint? GetEndPoint(SyntaxNode node, EnvDTE.vsCMPart? part = null);
20+
VirtualTreePoint? GetEndPoint(SyntaxNode node, OptionSet options, EnvDTE.vsCMPart? part = null);
2021
}
2122
}

0 commit comments

Comments
 (0)