private void Clear()
                {
                    _threadingContext.ThrowIfNotOnUIThread();

                    _span = default;
                    _classifications.Clear();
                }
 public void Start()
 {
     _threadingContext.ThrowIfNotOnUIThread();
     // Brace completion is not cancellable.
     if (!this.TryStart(CancellationToken.None))
     {
         EndSession();
     }
 }
        private async Task ExecuteAsync(
            ITextView view,
            ITextBuffer textBuffer,
            Document document,
            SnapshotSpan span)
        {
            _threadingContext.ThrowIfNotOnUIThread();
            var indicatorFactory = document.Project.Solution.Workspace.Services.GetRequiredService <IBackgroundWorkIndicatorFactory>();

            using var indicatorContext = indicatorFactory.Create(
                      view, span, EditorFeaturesResources.Applying_Extract_Method_refactoring, cancelOnEdit: true, cancelOnFocusLost: true);

            using var asyncToken = _asyncListener.BeginAsyncOperation(nameof(ExecuteCommand));
            await ExecuteWorkerAsync(view, textBuffer, span.Span.ToTextSpan(), indicatorContext).ConfigureAwait(false);
        }
            public StatusBarUpdater(IVsStatusbar statusBar, IThreadingContext threadingContext, string?projectOrSolutionName, uint totalProjectCount)
            {
                threadingContext.ThrowIfNotOnUIThread();
                _statusBar         = statusBar;
                _threadingContext  = threadingContext;
                _totalProjectCount = totalProjectCount;

                _statusMessageWhileRunning = projectOrSolutionName != null
                    ? string.Format(ServicesVSResources.Running_code_analysis_for_0, projectOrSolutionName)
                    : ServicesVSResources.Running_code_analysis_for_Solution;

                _statusMesageOnCompleted = projectOrSolutionName != null
                    ? string.Format(ServicesVSResources.Code_analysis_completed_for_0, projectOrSolutionName)
                    : ServicesVSResources.Code_analysis_completed_for_Solution;

                _statusMesageOnTerminated = projectOrSolutionName != null
                    ? string.Format(ServicesVSResources.Code_analysis_terminated_before_completion_for_0, projectOrSolutionName)
                    : ServicesVSResources.Code_analysis_terminated_before_completion_for_Solution;

                // Set the initial status bar progress and text.
                _statusBar.Progress(ref _statusBarCookie, fInProgress: 1, _statusMessageWhileRunning, nComplete: 0, nTotal: totalProjectCount);
                _statusBar.SetText(_statusMessageWhileRunning);

                // Create a timer to periodically update the status message while running analysis.
                _timer = new Timer(new TimerCallback(UpdateStatusOnTimer), new AutoResetEvent(false),
                                   dueTime: TimeSpan.FromSeconds(5), period: TimeSpan.FromSeconds(5));
            }
Beispiel #5
0
            private IVsOutputWindowPane CreateOutputPane(IVsOutputWindow outputWindow)
            {
                _threadingContext.ThrowIfNotOnUIThread();

                // Try to get the workspace pane if it has already been registered
                var workspacePaneGuid = s_outputPaneGuid;

                // If the pane has already been created, CreatePane returns it
                if (
                    ErrorHandler.Succeeded(
                        outputWindow.CreatePane(
                            ref workspacePaneGuid,
                            "Roslyn Logger Output",
                            fInitVisible: 1,
                            fClearWithSolution: 1
                            )
                        ) &&
                    ErrorHandler.Succeeded(
                        outputWindow.GetPane(ref workspacePaneGuid, out var pane)
                        )
                    )
                {
                    return(pane);
                }

                return(null);
            }
Beispiel #6
0
        public TModel WaitForController()
        {
            ThreadingContext.ThrowIfNotOnUIThread();

            var model = ModelTask.WaitAndGetResult(CancellationToken.None);

            if (!_notifyControllerTask.IsCompleted)
            {
                OnModelUpdated(model, updateController: true);

                // Reset lastTask so controller.OnModelUpdated is only called once
                _lastTask = Task.FromResult(model);
            }

            return(model);
        }
            internal void CheckNewIdentifier(StateMachine stateMachine, ITextSnapshot snapshot)
            {
                _threadingContext.ThrowIfNotOnUIThread();

                _newIdentifierBindsTask = _isRenamableIdentifierTask.SafeContinueWithFromAsync(
                    async t => t.Result != TriggerIdentifierKind.NotRenamable &&
                    TriggerIdentifierKind.RenamableReference ==
                    await DetermineIfRenamableIdentifierAsync(
                        TrackingSpan.GetSpan(snapshot),
                        initialCheck: false).ConfigureAwait(false),
                    _cancellationToken,
                    TaskContinuationOptions.OnlyOnRanToCompletion,
                    TaskScheduler.Default);

                QueueUpdateToStateMachine(stateMachine, _newIdentifierBindsTask);
            }
Beispiel #8
0
        public bool TryCreateSession(ITextView textView, SnapshotPoint openingPoint, char openingBrace, char closingBrace, out IBraceCompletionSession session)
        {
            _threadingContext.ThrowIfNotOnUIThread();
            var textSnapshot = openingPoint.Snapshot;
            var document     = textSnapshot.GetOpenDocumentInCurrentContextWithChanges();

            if (document != null)
            {
                var editorSessionFactory = document.GetLanguageService <IBraceCompletionServiceFactory>();
                if (editorSessionFactory != null)
                {
                    // Brace completion is (currently) not cancellable.
                    var cancellationToken = CancellationToken.None;

                    var editorSession = editorSessionFactory.TryGetServiceAsync(document, openingPoint, openingBrace, cancellationToken).WaitAndGetResult(cancellationToken);
                    if (editorSession != null)
                    {
                        var undoHistory = _undoManager.GetTextBufferUndoManager(textView.TextBuffer).TextBufferUndoHistory;
                        session = new BraceCompletionSession(
                            textView, openingPoint.Snapshot.TextBuffer, openingPoint, openingBrace, closingBrace,
                            undoHistory, _editorOperationsFactoryService,
                            editorSession, _globalOptions, _threadingContext);
                        return(true);
                    }
                }
            }

            session = null;
            return(false);
        }
        /// <summary>
        /// Get <see cref="Document"/> from <see cref="Text.Extensions.GetOpenDocumentInCurrentContextWithChanges(ITextSnapshot)"/>
        /// once <see cref="IWorkspaceStatusService.WaitUntilFullyLoadedAsync(CancellationToken)"/> returns
        /// </summary>
        public static Document?GetFullyLoadedOpenDocumentInCurrentContextWithChanges(
            this ITextSnapshot snapshot, IUIThreadOperationContext operationContext, IThreadingContext threadingContext)
        {
            // make sure this is only called from UI thread
            threadingContext.ThrowIfNotOnUIThread();

            return(threadingContext.JoinableTaskFactory.Run(() =>
                                                            snapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChangesAsync(operationContext)));
        }
 public void ClearSearch()
 {
     _threadingContext.ThrowIfNotOnUIThread();
     // remove filter on tablar data controls
     foreach (var tableControl in _wpfTableControls)
     {
         _ = tableControl.SetFilter(string.Empty, null);
     }
 }
        /// <summary>
        /// This function is called when the user clicks the menu item that shows the
        /// tool window. See the Initialize method to see how the menu item is associated to
        /// this function using the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void ShowToolWindow(object sender, EventArgs e)
        {
            _threadingContext.ThrowIfNotOnUIThread();

            JoinableTaskFactory.RunAsync(async() =>
            {
                await ShowToolWindowAsync(typeof(DiagnosticsWindow), id: 0, create: true, this.DisposalToken).ConfigureAwait(true);
            });
        }
Beispiel #12
0
        public async Task <IReadOnlyList <object>?> GetPreviewsAsync(DocumentId?preferredDocumentId = null, ProjectId?preferredProjectId = null, CancellationToken cancellationToken = default)
        {
            _threadingContext.ThrowIfNotOnUIThread();
            cancellationToken.ThrowIfCancellationRequested();

            var orderedPreviews = _previews.OrderBy((i1, i2) =>
            {
                return(i1.DocumentId == preferredDocumentId && i2.DocumentId != preferredDocumentId ? -1 :
                       i1.DocumentId != preferredDocumentId && i2.DocumentId == preferredDocumentId ? 1 :
                       _previews.IndexOf(i1) - _previews.IndexOf(i2));
            }).ThenBy((i1, i2) =>
            {
                return(i1.ProjectId == preferredProjectId && i2.ProjectId != preferredProjectId ? -1 :
                       i1.ProjectId != preferredProjectId && i2.ProjectId == preferredProjectId ? 1 :
                       _previews.IndexOf(i1) - _previews.IndexOf(i2));
            }).ThenBy((i1, i2) =>
            {
                return(i1.Text == null && i2.Text != null ? -1 :
                       i1.Text != null && i2.Text == null ? 1 :
                       _previews.IndexOf(i1) - _previews.IndexOf(i2));
            });

            var result         = new List <object>();
            var gotRichPreview = false;

            try
            {
                foreach (var previewItem in _previews)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    if (previewItem.Text != null)
                    {
                        result.Add(previewItem.Text);
                    }
                    else if (!gotRichPreview)
                    {
                        var preview = await previewItem.LazyPreview(cancellationToken).ConfigureAwait(true);

                        if (preview != null)
                        {
                            result.Add(preview);
                            gotRichPreview = true;
                        }
                    }
                }

                return(result.Count == 0 ? null : result);
            }
            catch (OperationCanceledException)
            {
                // make sure we dispose all disposable preview objects before
                // we let control to exit this method
                result.OfType <IDisposable>().Do(d => d.Dispose());
                throw;
            }
        }
        internal void EventHookupFoundInSession(EventHookupSession analyzedSession)
        {
            ThreadingContext.ThrowIfNotOnUIThread();

            var caretPoint = analyzedSession.TextView.GetCaretPoint(analyzedSession.SubjectBuffer);

            // only generate tooltip if it is not already shown (_toolTipPresenter == null)
            // Ensure the analyzed session matches the current session and that the caret is still
            // in the session's tracking span.
            if (_toolTipPresenter == null &&
                CurrentSession == analyzedSession &&
                caretPoint.HasValue &&
                IsCaretWithinSpanOrAtEnd(analyzedSession.TrackingSpan, analyzedSession.TextView.TextSnapshot, caretPoint.Value))
            {
                // Create a tooltip presenter that stays alive, even when the user types, without tracking the mouse.
                _toolTipPresenter = _toolTipService.CreatePresenter(analyzedSession.TextView,
                                                                    new ToolTipParameters(trackMouse: false, ignoreBufferChange: true));

                // tooltips text is: Program_MyEvents;      (Press TAB to insert)
                // GetEventNameTask() gets back the event name, only needs to add a semicolon after it.
                var textRuns = new[]
                {
                    new ClassifiedTextRun(ClassificationTypeNames.MethodName, analyzedSession.GetEventNameTask.Result, ClassifiedTextRunStyle.UseClassificationFont),
                    new ClassifiedTextRun(ClassificationTypeNames.Punctuation, ";", ClassifiedTextRunStyle.UseClassificationFont),
                    new ClassifiedTextRun(ClassificationTypeNames.Text, CSharpEditorResources.Press_TAB_to_insert),
                };
                var content = new[] { new ClassifiedTextElement(textRuns) };

                _toolTipPresenter.StartOrUpdate(analyzedSession.TrackingSpan, content);

                // For test purposes only!
                TEST_MostRecentToolTipContent = content;

                // Watch all text buffer changes & caret moves while this event hookup session is active
                analyzedSession.TextView.TextSnapshot.TextBuffer.Changed += TextBuffer_Changed;
                CurrentSession.Dismissed += () => { analyzedSession.TextView.TextSnapshot.TextBuffer.Changed -= TextBuffer_Changed; };

                analyzedSession.TextView.Caret.PositionChanged += Caret_PositionChanged;
                CurrentSession.Dismissed += () => { analyzedSession.TextView.Caret.PositionChanged -= Caret_PositionChanged; };
            }
        }
        IBackgroundWorkIndicatorContext IBackgroundWorkIndicatorFactory.Create(
            ITextView textView,
            SnapshotSpan applicableToSpan,
            string description,
            bool cancelOnEdit,
            bool cancelOnFocusLost)
        {
            _threadingContext.ThrowIfNotOnUIThread();

            // If we have an outstanding context in flight, cancel it and create a new one to show the user.
            _currentContext?.CancelAndDispose();

            // Create the indicator in its default/empty state.
            _currentContext = new BackgroundWorkIndicatorContext(
                this, textView, applicableToSpan, description,
                cancelOnEdit, cancelOnFocusLost);

            // Then add a single scope representing the how the UI should look initially.
            _currentContext.AddScope(allowCancellation: true, description);
            return(_currentContext);
        }
            /// <summary>
            /// Determines if the ColorableItemInfo's Foreground has been customized to a color that doesn't match the
            /// selected scheme.
            /// </summary>
            private bool IsClassificationCustomized(
                ImmutableDictionary <string, uint> coreThemeColors,
                ImmutableDictionary <string, uint> schemeThemeColors,
                ColorableItemInfo colorItem,
                string classification)
            {
                _threadingContext.ThrowIfNotOnUIThread();
                Contract.ThrowIfNull(_fontAndColorUtilities);

                var foregroundColorRef = colorItem.crForeground;

                if (_fontAndColorUtilities.GetColorType(foregroundColorRef, out var foregroundColorType) != VSConstants.S_OK)
                {
                    // Without being able to check color type, we cannot make a determination.
                    return(false);
                }

                // If the color is defaulted then it isn't customized.
                if (foregroundColorType == (int)__VSCOLORTYPE.CT_AUTOMATIC)
                {
                    return(false);
                }

                // Since the color type isn't default then it has been customized, we will
                // perform an additional check for RGB colors to see if the customized color
                // matches the color scheme color.
                if (foregroundColorType != (int)__VSCOLORTYPE.CT_RAW)
                {
                    return(true);
                }

                if (coreThemeColors.TryGetValue(classification, out var coreColor))
                {
                    return(foregroundColorRef != coreColor);
                }

                if (schemeThemeColors.TryGetValue(classification, out var schemeColor))
                {
                    return(foregroundColorRef != schemeColor);
                }

                // Since Classification inheritance isn't represented in the scheme files,
                // this switch case will handle the 3 cases we expect.
                var fallbackColor = classification switch
                {
                    ClassificationTypeNames.OperatorOverloaded => coreThemeColors[ClassificationTypeNames.Operator],
                    ClassificationTypeNames.ControlKeyword => coreThemeColors[ClassificationTypeNames.Keyword],
                    _ => coreThemeColors[ClassificationTypeNames.Identifier]
                };

                return(foregroundColorRef != fallbackColor);
            }
        }
Beispiel #16
0
 public void ClearSearch()
 {
     _threadingContext.ThrowIfNotOnUIThread();
     if (_control is not null)
     {
         var tables = _control.GetTableControls();
         // remove filter on tablar data controls
         foreach (var tableControl in tables)
         {
             _ = tableControl.SetFilter(string.Empty, null);
         }
     }
 }
Beispiel #17
0
            private void Buffer_Changed(object sender, TextContentChangedEventArgs e)
            {
                ThreadingContext.ThrowIfNotOnUIThread();

                if (!GlobalOptions.GetOption(InternalFeatureOnOffOptions.RenameTracking))
                {
                    // When disabled, ignore all text buffer changes and do not trigger retagging
                    return;
                }

                using (Logger.LogBlock(FunctionId.Rename_Tracking_BufferChanged, CancellationToken.None))
                {
                    // When the buffer changes, several things might be happening:
                    // 1. If a non-identifier character has been added or deleted, we stop tracking
                    //    completely.
                    // 2. Otherwise, if the changes are completely contained an existing session, then
                    //    continue that session.
                    // 3. Otherwise, we're starting a new tracking session. Find and track the span of
                    //    the relevant word in the foreground, and use a task to figure out whether the
                    //    original word was a renameable identifier or not.

                    if (e.Changes.Count != 1 || ShouldClearTrackingSession(e.Changes.Single()))
                    {
                        ClearTrackingSession();
                        return;
                    }

                    // The change is trackable. Figure out whether we should continue an existing
                    // session

                    var change = e.Changes.Single();

                    if (this.TrackingSession == null)
                    {
                        StartTrackingSession(e);
                        return;
                    }

                    // There's an existing session. Continue that session if the current change is
                    // contained inside the tracking span.

                    var trackingSpanInNewSnapshot = this.TrackingSession.TrackingSpan.GetSpan(e.After);
                    if (trackingSpanInNewSnapshot.Contains(change.NewSpan))
                    {
                        // Continuing an existing tracking session. If there may have been a tag
                        // showing, then update the tags.
                        UpdateTrackingSessionIfRenamable();
                    }
                    else
                    {
                        StartTrackingSession(e);
                    }
                }
            }
        public IAccurateTagger <T>?CreateTagger <T>(ITextBuffer buffer) where T : ITag
        {
            _threadingContext.ThrowIfNotOnUIThread();

            // The LSP client will handle producing tags when running under the LSP editor.
            // Our tagger implementation should return nothing to prevent conflicts.
            if (buffer.IsInLspEditorContext())
            {
                return(null);
            }

            return(new Tagger(this, buffer, _asyncListener, _globalOptions) as IAccurateTagger <T>);
        }
        public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable <DocumentId> changedDocumentIDs, ISymbol symbol, string newName, bool throwOnFailure)
        {
            _threadingContext.ThrowIfNotOnUIThread();
            if (TryGetRenameAPIRequiredArguments(workspace, changedDocumentIDs, symbol, out var hierarchyToItemIDsMap, out var rqnames))
            {
                foreach (var hierarchy in hierarchyToItemIDsMap.Keys)
                {
                    var itemIDs = hierarchyToItemIDsMap[hierarchy];

                    if (hierarchy is IVsHierarchyRefactorNotify refactorNotify)
                    {
                        var hresult = refactorNotify.OnBeforeGlobalSymbolRenamed(
                            (uint)itemIDs.Count,
                            itemIDs.ToArray(),
                            (uint)rqnames.Length,
                            rqnames,
                            newName,
                            promptContinueOnFail: 1);

                        if (hresult < 0)
                        {
                            if (throwOnFailure)
                            {
                                Marshal.ThrowExceptionForHR(hresult);
                            }
                            else
                            {
                                return(false);
                            }
                        }
                    }
                }
            }

            return(true);
        }
Beispiel #20
0
        private async Task ExecuteAsync(
            EncapsulateFieldCommandArgs args,
            Document initialDocument,
            SnapshotSpan span)
        {
            _threadingContext.ThrowIfNotOnUIThread();

            var subjectBuffer = args.SubjectBuffer;
            var workspace     = initialDocument.Project.Solution.Workspace;

            var indicatorFactory = workspace.Services.GetRequiredService <IBackgroundWorkIndicatorFactory>();

            using var context = indicatorFactory.Create(
                      args.TextView, span, EditorFeaturesResources.Computing_Encapsulate_Field_information,
                      cancelOnEdit: true, cancelOnFocusLost: true);

            var cancellationToken = context.UserCancellationToken;
            var document          = await subjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChangesAsync(context).ConfigureAwait(false);

            Contract.ThrowIfNull(document);

            var service = document.GetRequiredLanguageService <AbstractEncapsulateFieldService>();

            var result = await service.EncapsulateFieldsInSpanAsync(
                document, span.Span.ToTextSpan(), _globalOptions.CreateProvider(), useDefaultBehavior : true, cancellationToken).ConfigureAwait(false);

            if (result == null)
            {
                await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

                // We are about to show a modal UI dialog so we should take over the command execution
                // wait context. That means the command system won't attempt to show its own wait dialog
                // and also will take it into consideration when measuring command handling duration.
                context.TakeOwnership();

                var notificationService = workspace.Services.GetRequiredService <INotificationService>();
                notificationService.SendNotification(EditorFeaturesResources.Please_select_the_definition_of_the_field_to_encapsulate, severity: NotificationSeverity.Error);
                return;
            }

            await ApplyChangeAsync(subjectBuffer, document, result, cancellationToken).ConfigureAwait(false);
        }
        private async Task ExecuteAsync(Document document, SnapshotSpan snapshotSpan, ITextView textView)
        {
            _threadingContext.ThrowIfNotOnUIThread();

            var indicatorFactory = document.Project.Solution.Workspace.Services.GetRequiredService <IBackgroundWorkIndicatorFactory>();

            using var backgroundWorkContext = indicatorFactory.Create(
                      textView,
                      snapshotSpan,
                      DialogText,
                      cancelOnEdit: true,
                      cancelOnFocusLost: true);

            var cancellationToken = backgroundWorkContext.UserCancellationToken;

            // We're going to log the same thing on success or failure since this blocks the UI thread. This measurement is
            // intended to tell us how long we're blocking the user from typing with this action.
            using var blockLogger = Logger.LogBlock(FunctionId.CommandHandler_Paste_ImportsOnPaste, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken);

            var addMissingImportsService = document.GetRequiredLanguageService <IAddMissingImportsFeatureService>();

            var cleanupOptions = await document.GetCodeCleanupOptionsAsync(_globalOptions, cancellationToken).ConfigureAwait(false);

            var options = new AddMissingImportsOptions(
                CleanupOptions: cleanupOptions,
                HideAdvancedMembers: _globalOptions.GetOption(CompletionOptionsStorage.HideAdvancedMembers, document.Project.Language));

            var textSpan        = snapshotSpan.Span.ToTextSpan();
            var updatedDocument = await addMissingImportsService.AddMissingImportsAsync(document, textSpan, options, cancellationToken).ConfigureAwait(false);

            if (updatedDocument is null)
            {
                return;
            }

            // Required to switch back to the UI thread to call TryApplyChanges
            await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            document.Project.Solution.Workspace.TryApplyChanges(updatedDocument.Project.Solution);
        }
Beispiel #22
0
        private bool TryPopulateOpenTextBufferManagerForBuffer(ITextBuffer buffer)
        {
            _threadingContext.ThrowIfNotOnUIThread();
            VerifyNotDismissed();

            if (_workspace.Kind == WorkspaceKind.Interactive)
            {
                Debug.Assert(buffer.GetRelatedDocuments().Count() == 1);
                Debug.Assert(buffer.IsReadOnly(0) == buffer.IsReadOnly(VisualStudio.Text.Span.FromBounds(0, buffer.CurrentSnapshot.Length))); // All or nothing.
                if (buffer.IsReadOnly(0))
                {
                    return(false);
                }
            }

            if (!_openTextBuffers.ContainsKey(buffer) && buffer.SupportsRename())
            {
                _openTextBuffers[buffer] = new OpenTextBufferManager(this, buffer, _workspace, _textBufferFactoryService);
                return(true);
            }

            return(_openTextBuffers.ContainsKey(buffer));
        }
        public bool ExecuteCommand(TabKeyCommandArgs args, CommandExecutionContext context)
        {
            ThreadingContext.ThrowIfNotOnUIThread();
            if (!AreSnippetsEnabled(args))
            {
                return(false);
            }

            if (args.TextView.Properties.TryGetProperty(typeof(AbstractSnippetExpansionClient), out AbstractSnippetExpansionClient snippetExpansionClient) &&
                snippetExpansionClient.TryHandleTab())
            {
                return(true);
            }

            // Insert snippet/show picker only if we don't have a selection: the user probably wants to indent instead
            if (args.TextView.Selection.IsEmpty)
            {
                if (TryHandleTypedSnippet(args.TextView, args.SubjectBuffer))
                {
                    return(true);
                }

                if (TryInvokeSnippetPickerOnQuestionMark(args.TextView, args.SubjectBuffer))
                {
                    return(true);
                }
            }

            return(false);
        }
 public override void Connect()
 {
     _threadingContext.ThrowIfNotOnUIThread();
     _textView.LayoutChanged += OnLayoutChanged;
 }
 public void Cancel()
 {
     _threadingContext.ThrowIfNotOnUIThread();
     _cancellationTokenSource.Cancel();
 }
 public void ShowFormattingOptionPage()
 {
     _threadingContext.ThrowIfNotOnUIThread();
     _package.ShowOptionPage(typeof(FormattingOptionPage));
 }
Beispiel #27
0
        public async Task <bool> ApplyAsync(
            Workspace workspace,
            Solution originalSolution,
            Document?fromDocument,
            ImmutableArray <CodeActionOperation> operations,
            string title,
            IProgressTracker progressTracker,
            CancellationToken cancellationToken)
        {
            // Much of the work we're going to do will be on the UI thread, so switch there preemptively.
            // When we get to the expensive parts we can do in the BG then we'll switch over to relinquish
            // the UI thread.
            await this._threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            if (operations.IsDefaultOrEmpty)
            {
                return(_renameService.ActiveSession is null);
            }

            if (_renameService.ActiveSession != null)
            {
                workspace.Services.GetService <INotificationService>()?.SendNotification(
                    EditorFeaturesResources.Cannot_apply_operation_while_a_rename_session_is_active,
                    severity: NotificationSeverity.Error);
                return(false);
            }

            var oldSolution = workspace.CurrentSolution;

            var applied = false;

            // Determine if we're making a simple text edit to a single file or not.
            // If we're not, then we need to make a linked global undo to wrap the
            // application of these operations.  This way we should be able to undo
            // them all with one user action.
            //
            // The reason we don't always create a global undo is that a global undo
            // forces all files to save.  And that's rather a heavyweight and
            // unexpected experience for users (for the common case where a single
            // file got edited).
            var singleChangedDocument = TryGetSingleChangedText(oldSolution, operations);

            if (singleChangedDocument != null)
            {
                var text = await singleChangedDocument.GetTextAsync(cancellationToken).ConfigureAwait(true);

                using (workspace.Services.GetRequiredService <ISourceTextUndoService>().RegisterUndoTransaction(text, title))
                {
                    try
                    {
                        _threadingContext.ThrowIfNotOnUIThread();

                        applied = await operations.Single().TryApplyAsync(
                            workspace, originalSolution, progressTracker, cancellationToken).ConfigureAwait(true);
                    }
                    catch (Exception ex) when(FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken))
                    {
                        throw ExceptionUtilities.Unreachable;
                    }
                }
            }
            else
            {
                // More than just a single document changed.  Make a global undo to run
                // all the changes under.
                using var transaction = workspace.OpenGlobalUndoTransaction(title);

                // link current file in the global undo transaction
                // Do this before processing operations, since that can change
                // documentIds.
                if (fromDocument != null)
                {
                    transaction.AddDocument(fromDocument.Id);
                }

                try
                {
                    // Come back to the UI thread after processing the operations so we can commit the transaction
                    applied = await ProcessOperationsAsync(
                        workspace, originalSolution, operations, progressTracker, cancellationToken).ConfigureAwait(true);
                }
                catch (Exception ex) when(FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken))
                {
                    throw ExceptionUtilities.Unreachable;
                }

                transaction.Commit();
            }

            var updatedSolution = operations.OfType <ApplyChangesOperation>().FirstOrDefault()?.ChangedSolution ?? oldSolution;

            await TryNavigateToLocationOrStartRenameSessionAsync(
                workspace, operations, oldSolution, updatedSolution, cancellationToken).ConfigureAwait(false);

            return(applied);
        }