|
9 | 9 | using System.Diagnostics.CodeAnalysis; |
10 | 10 | using System.IO; |
11 | 11 | using System.Linq; |
| 12 | +using System.Runtime.InteropServices; |
12 | 13 | using System.Text; |
13 | 14 | using System.Threading; |
14 | 15 | using System.Threading.Tasks; |
@@ -112,6 +113,12 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac |
112 | 113 | private readonly IAsynchronousOperationListener _workspaceListener; |
113 | 114 | private bool _isExternalErrorDiagnosticUpdateSourceSubscribedToSolutionBuildEvents; |
114 | 115 |
|
| 116 | + /// <summary> |
| 117 | + /// Only read/written on hte UI thread. |
| 118 | + /// </summary> |
| 119 | + private bool _isShowingDocumentChangeErrorInfoBar = false; |
| 120 | + private bool _ignoreDocumentTextChangeErrors; |
| 121 | + |
115 | 122 | public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServiceProvider asyncServiceProvider) |
116 | 123 | : base(VisualStudioMefHostServices.Create(exportProvider)) |
117 | 124 | { |
@@ -1192,29 +1199,81 @@ protected override void ApplyAnalyzerConfigDocumentTextChanged(DocumentId docume |
1192 | 1199 |
|
1193 | 1200 | private void ApplyTextDocumentChange(DocumentId documentId, SourceText newText) |
1194 | 1201 | { |
1195 | | - var containedDocument = ContainedDocument.TryGetContainedDocument(documentId); |
| 1202 | + this._threadingContext.ThrowIfNotOnUIThread(); |
1196 | 1203 |
|
1197 | | - if (containedDocument != null) |
1198 | | - { |
1199 | | - containedDocument.UpdateText(newText); |
1200 | | - } |
1201 | | - else |
| 1204 | + CodeAnalysis.TextDocument? document = null; |
| 1205 | + |
| 1206 | + try |
1202 | 1207 | { |
1203 | | - if (IsDocumentOpen(documentId)) |
1204 | | - { |
1205 | | - var textBuffer = this.CurrentSolution.GetTextDocument(documentId)!.GetTextSynchronously(CancellationToken.None).Container.TryGetTextBuffer(); |
| 1208 | + document = this.CurrentSolution.GetRequiredTextDocument(documentId); |
1206 | 1209 |
|
1207 | | - if (textBuffer != null) |
| 1210 | + var containedDocument = ContainedDocument.TryGetContainedDocument(documentId); |
| 1211 | + if (containedDocument != null) |
| 1212 | + { |
| 1213 | + containedDocument.UpdateText(newText); |
| 1214 | + } |
| 1215 | + else |
| 1216 | + { |
| 1217 | + if (IsDocumentOpen(documentId)) |
1208 | 1218 | { |
1209 | | - TextEditApplication.UpdateText(newText, textBuffer, EditOptions.DefaultMinimalChange); |
1210 | | - return; |
| 1219 | + var textBuffer = document.GetTextSynchronously(CancellationToken.None).Container.TryGetTextBuffer(); |
| 1220 | + if (textBuffer != null) |
| 1221 | + { |
| 1222 | + TextEditApplication.UpdateText(newText, textBuffer, EditOptions.DefaultMinimalChange); |
| 1223 | + return; |
| 1224 | + } |
1211 | 1225 | } |
1212 | | - } |
1213 | 1226 |
|
1214 | | - // The document wasn't open in a normal way, so invisible editor time |
1215 | | - using var invisibleEditor = OpenInvisibleEditor(documentId); |
1216 | | - TextEditApplication.UpdateText(newText, invisibleEditor.TextBuffer, EditOptions.None); |
| 1227 | + // The document wasn't open in a normal way, so invisible editor time |
| 1228 | + using var invisibleEditor = OpenInvisibleEditor(documentId); |
| 1229 | + TextEditApplication.UpdateText(newText, invisibleEditor.TextBuffer, EditOptions.None); |
| 1230 | + } |
1217 | 1231 | } |
| 1232 | + catch (Exception ex) when (FatalError.ReportAndCatch(ex)) |
| 1233 | + { |
| 1234 | + ReportErrorChangingDocumentText(ex); |
| 1235 | + return; |
| 1236 | + } |
| 1237 | + |
| 1238 | + void ReportErrorChangingDocumentText(Exception exception) |
| 1239 | + { |
| 1240 | + // Don't spam the info bar. If the user already has a message up, leave it at that. Also, |
| 1241 | + // if they've asked to not be notified about future doc change issue, respect that flag. |
| 1242 | + if (_ignoreDocumentTextChangeErrors || _isShowingDocumentChangeErrorInfoBar) |
| 1243 | + return; |
| 1244 | + |
| 1245 | + var documentName = document?.Name ?? documentId.ToString(); |
| 1246 | + |
| 1247 | + var errorReportingService = this.Services.GetRequiredService<IErrorReportingService>(); |
| 1248 | + errorReportingService.ShowGlobalErrorInfo( |
| 1249 | + message: string.Format(ServicesVSResources.Error_encountered_updating_0, documentName), |
| 1250 | + TelemetryFeatureName.Workspace, |
| 1251 | + exception, |
| 1252 | + // 'Show stack trace' will not dismiss the info bar. |
| 1253 | + new(WorkspacesResources.Show_Stack_Trace, InfoBarUI.UIKind.HyperLink, |
| 1254 | + () => errorReportingService.ShowDetailedErrorInfo(exception), closeAfterAction: false), |
| 1255 | + // 'Ignore' just closes the info bar, but allows future errors to show up. |
| 1256 | + new(ServicesVSResources.Ignore, InfoBarUI.UIKind.Button, GetDefaultDismissAction()), |
| 1257 | + // 'Ignore (including future errors) closes the info bar, but also sets the flag so the user gets no more messages |
| 1258 | + // in the current session. |
| 1259 | + new(ServicesVSResources.Ignore_including_future_errors, InfoBarUI.UIKind.Button, GetDefaultDismissAction( |
| 1260 | + () => _ignoreDocumentTextChangeErrors = true)), |
| 1261 | + // Close button is the same as 'ignore'. It closes the info bar, but allows future errors to show up. |
| 1262 | + new(string.Empty, InfoBarUI.UIKind.Close, GetDefaultDismissAction())); |
| 1263 | + |
| 1264 | + // Mark that we're showing the info bar at this point. |
| 1265 | + _isShowingDocumentChangeErrorInfoBar = true; |
| 1266 | + } |
| 1267 | + |
| 1268 | + Action GetDefaultDismissAction(Action? additionalAction = null) |
| 1269 | + => () => |
| 1270 | + { |
| 1271 | + additionalAction?.Invoke(); |
| 1272 | + |
| 1273 | + // All info bar actions (except for 'show stack trace') dismiss the info bar, putting us back in the |
| 1274 | + // "we're not showing the user anything" state. |
| 1275 | + _isShowingDocumentChangeErrorInfoBar = false; |
| 1276 | + }; |
1218 | 1277 | } |
1219 | 1278 |
|
1220 | 1279 | protected override void ApplyDocumentInfoChanged(DocumentId documentId, DocumentInfo updatedInfo) |
|
0 commit comments