public FloatingQuickFixIconWidget( CodeActionEditorExtension codeActionEditorExtension, LanguageItemWindow window, SourceEditorView sourceEditorView, CodeActionContainer fixes, Cairo.Point point) : base(Gtk.WindowType.Popup) { this.ext = codeActionEditorExtension; this.window = window; this.sourceEditorView = sourceEditorView; this.fixes = fixes; this.point = point; this.Decorated = false; this.Events |= EventMask.ButtonPressMask | EventMask.LeaveNotifyMask | EventMask.EnterNotifyMask; TypeHint = Gdk.WindowTypeHint.Utility; var fr = new Gtk.HBox(); fr.BorderWidth = 2; var view = new ImageView(SmartTagMarginMarker.GetIconId(fixes.GetSmartTagSeverity()), Gtk.IconSize.Menu); fr.PackStart(view, false, false, 0); fr.PackEnd(new RectangleMarker(), false, false, 0); Add(fr); ext.FixesMenuClosed += Ext_FixesMenuClosed; ShowAll(); }
static void AssertCodeFixes(CodeActionContainer fixes, ExpectedCodeFixes expected) { var fixActions = fixes.CodeFixActions.SelectMany(x => x.Fixes).ToArray(); Assert.AreEqual(expected.CodeFixData.Length, fixActions.Length); for (int j = 0; j < expected.CodeFixData.Length; ++j) { Assert.AreEqual(expected.CodeFixData [j].Message, fixActions [j].Action.Message); } var fixRefactorings = fixes.CodeRefactoringActions.SelectMany(x => x.Actions).ToArray(); Assert.AreEqual(expected.CodeRefactoringData.Length, fixRefactorings.Length); for (int j = 0; j < expected.CodeRefactoringData.Length; ++j) { Assert.AreEqual(expected.CodeRefactoringData [j].Message, fixRefactorings [j].Message); } }
void CreateSmartTag (CodeActionContainer fixes, int offset) { if (!AnalysisOptions.EnableFancyFeatures || fixes.IsEmpty) { RemoveWidget (); return; } var editor = Editor; if (editor == null) { RemoveWidget (); return; } if (DocumentContext.ParsedDocument == null || DocumentContext.ParsedDocument.IsInvalid) { RemoveWidget (); return; } // var container = editor.Parent; // if (container == null) { // RemoveWidget (); // return; // } bool first = true; var smartTagLocBegin = offset; foreach (var fix in fixes.CodeFixActions.Concat (fixes.CodeRefactoringActions)) { var textSpan = fix.ValidSegment; if (textSpan.IsEmpty) continue; if (first || offset < textSpan.Start) { smartTagLocBegin = textSpan.Start; } first = false; } // if (smartTagLocBegin.Line != loc.Line) // smartTagLocBegin = new DocumentLocation (loc.Line, 1); // got no fix location -> try to search word start // if (first) { // int offset = document.Editor.LocationToOffset (smartTagLocBegin); // while (offset > 0) { // char ch = document.Editor.GetCharAt (offset - 1); // if (!char.IsLetterOrDigit (ch) && ch != '_') // break; // offset--; // } // smartTagLocBegin = document.Editor.OffsetToLocation (offset); // } if (currentSmartTag != null && currentSmartTagBegin == smartTagLocBegin) { return; } RemoveWidget (); currentSmartTagBegin = smartTagLocBegin; var realLoc = Editor.OffsetToLocation (smartTagLocBegin); currentSmartTag = TextMarkerFactory.CreateSmartTagMarker (Editor, smartTagLocBegin, realLoc); currentSmartTag.CancelPopup += CurrentSmartTag_CancelPopup; currentSmartTag.ShowPopup += CurrentSmartTag_ShowPopup; currentSmartTag.Tag = fixes; currentSmartTag.IsVisible = fixes.CodeFixActions.Count > 0; editor.AddMarker (currentSmartTag); }
void HandleCaretPositionChanged (object sender, EventArgs e) { if (Editor.IsInAtomicUndo) return; CancelQuickFixTimer (); if (AnalysisOptions.EnableFancyFeatures && DocumentContext.ParsedDocument != null && !Debugger.DebuggingService.IsDebugging) { var token = quickFixCancellationTokenSource.Token; var curOffset = Editor.CaretOffset; if (HasCurrentFixes) { foreach (var fix in GetCurrentFixes ().AllValidCodeActions) { if (!fix.ValidSegment.Contains (curOffset)) { RemoveWidget (); break; } } } var loc = Editor.CaretOffset; var ad = DocumentContext.AnalysisDocument; if (ad == null) { return; } TextSpan span; if (Editor.IsSomethingSelected) { var selectionRange = Editor.SelectionRange; span = selectionRange.Offset >= 0 ? TextSpan.FromBounds (selectionRange.Offset, selectionRange.EndOffset) : TextSpan.FromBounds (loc, loc); } else { span = TextSpan.FromBounds (loc, loc); } var diagnosticsAtCaret = Editor.GetTextSegmentMarkersAt (Editor.CaretOffset) .OfType<IGenericTextSegmentMarker> () .Select (rm => rm.Tag) .OfType<DiagnosticResult> () .Select (dr => dr.Diagnostic) .ToList (); var errorList = Editor .GetTextSegmentMarkersAt (Editor.CaretOffset) .OfType<IErrorMarker> () .Where (rm => !string.IsNullOrEmpty (rm.Error.Id)).ToList (); int editorLength = Editor.Length; smartTagTask = Task.Run (async delegate { try { var codeIssueFixes = new List<ValidCodeDiagnosticAction> (); var diagnosticIds = diagnosticsAtCaret.Select (diagnostic => diagnostic.Id).Concat (errorList.Select (rm => rm.Error.Id)).ToList (); if (codeFixes == null) { codeFixes = (await CodeRefactoringService.GetCodeFixesAsync (DocumentContext, CodeRefactoringService.MimeTypeToLanguage (Editor.MimeType), token).ConfigureAwait (false)).ToList (); } foreach (var cfp in codeFixes) { if (token.IsCancellationRequested) return CodeActionContainer.Empty; var provider = cfp.GetCodeFixProvider (); if (!provider.FixableDiagnosticIds.Any (diagnosticIds.Contains)) continue; try { var groupedDiagnostics = diagnosticsAtCaret .Concat (errorList.Select (em => em.Error.Tag) .OfType<Diagnostic> ()) .GroupBy (d => d.Location.SourceSpan); foreach (var g in groupedDiagnostics) { if (token.IsCancellationRequested) return CodeActionContainer.Empty; var diagnosticSpan = g.Key; var validDiagnostics = g.Where (d => provider.FixableDiagnosticIds.Contains (d.Id)).ToImmutableArray (); if (validDiagnostics.Length == 0) continue; await provider.RegisterCodeFixesAsync (new CodeFixContext (ad, diagnosticSpan, validDiagnostics, (ca, d) => codeIssueFixes.Add (new ValidCodeDiagnosticAction (cfp, ca, validDiagnostics, diagnosticSpan)), token)); // TODO: Is that right ? Currently it doesn't really make sense to run one code fix provider on several overlapping diagnostics at the same location // However the generate constructor one has that case and if I run it twice the same code action is generated twice. So there is a dupe check problem there. // Work around for now is to only take the first diagnostic batch. break; } } catch (OperationCanceledException) { return CodeActionContainer.Empty; } catch (AggregateException ae) { ae.Flatten ().Handle (aex => aex is OperationCanceledException); return CodeActionContainer.Empty; } catch (Exception ex) { LoggingService.LogError ("Error while getting refactorings from code fix provider " + cfp.Name, ex); continue; } } var codeActions = new List<ValidCodeAction> (); foreach (var action in await CodeRefactoringService.GetValidActionsAsync (Editor, DocumentContext, span, token).ConfigureAwait (false)) { codeActions.Add (action); } var codeActionContainer = new CodeActionContainer (codeIssueFixes, codeActions, diagnosticsAtCaret); Application.Invoke (delegate { if (token.IsCancellationRequested) return; if (codeActionContainer.IsEmpty) { RemoveWidget (); return; } CreateSmartTag (codeActionContainer, loc); }); return codeActionContainer; } catch (AggregateException ae) { ae.Flatten ().Handle (aex => aex is OperationCanceledException); return CodeActionContainer.Empty; } catch (OperationCanceledException) { return CodeActionContainer.Empty; } catch (TargetInvocationException ex) { if (ex.InnerException is OperationCanceledException) return CodeActionContainer.Empty; throw; } }, token); } else { RemoveWidget (); } }
static CommandInfoSet CreateFixMenu(TextEditor editor, DocumentContext ctx, SemanticModel semanticModel, CodeActionContainer container) { if (editor == null) { throw new ArgumentNullException(nameof(editor)); } if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } if (container == null) { throw new ArgumentNullException(nameof(container)); } var result = new CommandInfoSet(); result.Text = GettextCatalog.GetString("Fix"); foreach (var diagnostic in container.CodeFixActions) { var info = new CommandInfo(diagnostic.CodeAction.Title); result.CommandInfos.Add(info, new Action(async() => await new CodeActionEditorExtension.ContextActionRunner(diagnostic.CodeAction, editor, ctx).Run())); } bool firstDiagnosticOption = result.CommandInfos.Count != 0; var warningsAtCaret = semanticModel .GetDiagnostics(new TextSpan(editor.CaretOffset, 0)) .Where(diag => diag.Severity == DiagnosticSeverity.Warning).ToList(); foreach (var warning in warningsAtCaret) { if (firstDiagnosticOption) { result.CommandInfos.AddSeparator(); firstDiagnosticOption = false; } var label = GettextCatalog.GetString("_Options for \"{0}\"", warning.Descriptor.Title); var subMenu = new CommandInfoSet(); subMenu.Text = label; var info = new CommandInfo(GettextCatalog.GetString("_Suppress with #pragma")); subMenu.CommandInfos.Add(info, new Action(async delegate { var fixes = await CSharpSuppressionFixProvider.Instance.GetSuppressionsAsync(ctx.AnalysisDocument, new TextSpan(editor.CaretOffset, 0), new [] { warning }, default(CancellationToken)).ConfigureAwait(false); foreach (var f in fixes) { CodeDiagnosticDescriptor.RunAction(ctx, f.Action, default(CancellationToken)); } })); result.CommandInfos.Add(subMenu); } foreach (var fix in container.DiagnosticsAtCaret) { var inspector = BuiltInCodeDiagnosticProvider.GetCodeDiagnosticDescriptor(fix.Id); if (inspector == null) { continue; } if (firstDiagnosticOption) { result.CommandInfos.AddSeparator(); firstDiagnosticOption = false; } var label = GettextCatalog.GetString("_Options for \"{0}\"", fix.GetMessage()); var subMenu = new CommandInfoSet(); subMenu.Text = label; // if (inspector.CanSuppressWithAttribute) { // var menuItem = new FixMenuEntry (GettextCatalog.GetString ("_Suppress with attribute"), // delegate { // // inspector.SuppressWithAttribute (Editor, DocumentContext, GetTextSpan (fix.Item2)); // }); // subMenu.Add (menuItem); // } if (inspector.CanDisableWithPragma) { var info = new CommandInfo(GettextCatalog.GetString("_Suppress with #pragma")); subMenu.CommandInfos.Add(info, new Action(() => inspector.DisableWithPragma(editor, ctx, fix))); info = new CommandInfo(GettextCatalog.GetString("_Suppress with file")); subMenu.CommandInfos.Add(info, new Action(() => inspector.DisableWithFile(editor, ctx, fix))); } var configInfo = new CommandInfo(GettextCatalog.GetString("_Configure Rule")); subMenu.CommandInfos.Add(configInfo, new Action(() => { IdeApp.Workbench.ShowGlobalPreferencesDialog(null, "C#", dialog => { var panel = dialog.GetPanel <CodeIssuePanel> ("C#"); if (panel == null) { return; } panel.Widget.SelectCodeIssue(inspector.IdString); }); })); foreach (var fix2 in container.CodeFixActions) { var provider = fix2.Diagnostic.GetCodeFixProvider().GetFixAllProvider(); if (provider == null) { continue; } if (!provider.GetSupportedFixAllScopes().Contains(FixAllScope.Document)) { continue; } var subMenu2 = new CommandInfoSet(); subMenu2.Text = GettextCatalog.GetString("Fix all"); var diagnosticAnalyzer = fix2.Diagnostic.GetCodeDiagnosticDescriptor(LanguageNames.CSharp).GetProvider(); if (!diagnosticAnalyzer.SupportedDiagnostics.Contains(fix.Descriptor)) { continue; } var info = new CommandInfo(GettextCatalog.GetString("In _Document")); subMenu2.CommandInfos.Add(info, new Action(async delegate { var fixAllDiagnosticProvider = new CodeActionEditorExtension.FixAllDiagnosticProvider(diagnosticAnalyzer.SupportedDiagnostics.Select(d => d.Id).ToImmutableHashSet(), async(Microsoft.CodeAnalysis.Document doc, ImmutableHashSet <string> diagnostics, CancellationToken token) => { var model = await doc.GetSemanticModelAsync(token); var compilationWithAnalyzer = model.Compilation.WithAnalyzers(new [] { diagnosticAnalyzer }.ToImmutableArray(), null, token); return(await compilationWithAnalyzer.GetAnalyzerSemanticDiagnosticsAsync(model, null, token)); }, (arg1, arg2, arg3, arg4) => { return(Task.FromResult((IEnumerable <Diagnostic>) new Diagnostic [] { })); }); var ctx2 = new FixAllContext( ctx.AnalysisDocument, fix2.Diagnostic.GetCodeFixProvider(), FixAllScope.Document, fix2.CodeAction.EquivalenceKey, diagnosticAnalyzer.SupportedDiagnostics.Select(d => d.Id), fixAllDiagnosticProvider, default(CancellationToken) ); var fixAll = await provider.GetFixAsync(ctx2); using (var undo = editor.OpenUndoGroup()) { CodeDiagnosticDescriptor.RunAction(ctx, fixAll, default(CancellationToken)); } })); subMenu.CommandInfos.Add(subMenu2); } result.CommandInfos.Add(subMenu); } return(result); }
static CommandInfoSet CreateFixMenu (TextEditor editor, DocumentContext ctx, CodeActionContainer container) { if (editor == null) throw new ArgumentNullException ("editor"); if (ctx == null) throw new ArgumentNullException ("ctx"); if (container == null) throw new ArgumentNullException ("container"); var result = new CommandInfoSet (); result.Text = GettextCatalog.GetString ("Fix"); foreach (var diagnostic in container.CodeFixActions) { var info = new CommandInfo (diagnostic.CodeAction.Title); result.CommandInfos.Add (info, new Action (new CodeActionEditorExtension.ContextActionRunner (diagnostic.CodeAction, editor, ctx).Run)); } if (result.CommandInfos.Count == 0) return result; bool firstDiagnosticOption = true; foreach (var fix in container.DiagnosticsAtCaret) { var inspector = BuiltInCodeDiagnosticProvider.GetCodeDiagnosticDescriptor (fix.Id); if (inspector == null) continue; if (firstDiagnosticOption) { result.CommandInfos.AddSeparator (); firstDiagnosticOption = false; } var label = GettextCatalog.GetString ("_Options for \"{0}\"", fix.GetMessage ()); var subMenu = new CommandInfoSet (); subMenu.Text = label; // if (inspector.CanSuppressWithAttribute) { // var menuItem = new FixMenuEntry (GettextCatalog.GetString ("_Suppress with attribute"), // delegate { // // inspector.SuppressWithAttribute (Editor, DocumentContext, GetTextSpan (fix.Item2)); // }); // subMenu.Add (menuItem); // } if (inspector.CanDisableWithPragma) { var info = new CommandInfo (GettextCatalog.GetString ("_Suppress with #pragma")); subMenu.CommandInfos.Add (info, new Action (() => inspector.DisableWithPragma (editor, ctx, fix))); info = new CommandInfo (GettextCatalog.GetString ("_Suppress with file")); subMenu.CommandInfos.Add (info, new Action (() => inspector.DisableWithFile (editor, ctx, fix))); } var configInfo = new CommandInfo (GettextCatalog.GetString ("_Configure Rule")); subMenu.CommandInfos.Add (configInfo, new Action (() => { IdeApp.Workbench.ShowGlobalPreferencesDialog (null, "C#", dialog => { var panel = dialog.GetPanel<CodeIssuePanel> ("C#"); if (panel == null) return; panel.Widget.SelectCodeIssue (inspector.IdString); }); })); foreach (var fix2 in container.CodeFixActions) { var provider = fix2.Diagnostic.GetCodeFixProvider ().GetFixAllProvider (); if (provider == null) continue; if (!provider.GetSupportedFixAllScopes ().Contains (FixAllScope.Document)) continue; var subMenu2 = new CommandInfoSet (); subMenu2.Text = GettextCatalog.GetString ("Fix all"); var diagnosticAnalyzer = fix2.Diagnostic.GetCodeDiagnosticDescriptor (LanguageNames.CSharp).GetProvider (); if (!diagnosticAnalyzer.SupportedDiagnostics.Contains (fix.Descriptor)) continue; var info = new CommandInfo (GettextCatalog.GetString ("In _Document")); subMenu2.CommandInfos.Add (info, new Action (async delegate { var fixAllDiagnosticProvider = new CodeActionEditorExtension.FixAllDiagnosticProvider (diagnosticAnalyzer.SupportedDiagnostics.Select (d => d.Id).ToImmutableHashSet (), async (Microsoft.CodeAnalysis.Document doc, ImmutableHashSet<string> diagnostics, CancellationToken token) => { var model = await doc.GetSemanticModelAsync (token); var compilationWithAnalyzer = model.Compilation.WithAnalyzers (new [] { diagnosticAnalyzer }.ToImmutableArray (), null, token); return await compilationWithAnalyzer.GetAnalyzerSemanticDiagnosticsAsync (model, null, token); }, (arg1, arg2, arg3, arg4) => { return Task.FromResult ((IEnumerable<Diagnostic>)new Diagnostic[] { }); }); var ctx2 = new FixAllContext ( ctx.AnalysisDocument, fix2.Diagnostic.GetCodeFixProvider (), FixAllScope.Document, fix2.CodeAction.EquivalenceKey, diagnosticAnalyzer.SupportedDiagnostics.Select (d => d.Id), fixAllDiagnosticProvider, default (CancellationToken) ); var fixAll = await provider.GetFixAsync (ctx2); using (var undo = editor.OpenUndoGroup ()) { CodeDiagnosticDescriptor.RunAction (ctx, fixAll, default (CancellationToken)); } })); subMenu.CommandInfos.Add (subMenu2); } result.CommandInfos.Add (subMenu); } return result; }