diff options
Diffstat (limited to 'main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs')
-rw-r--r-- | main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs | 731 |
1 files changed, 403 insertions, 328 deletions
diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs index 8cbdfbf6dc..b2077a0d16 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs @@ -24,26 +24,32 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; -using MonoDevelop.Ide.Gui.Content; using Gtk; -using Mono.TextEditor; using System.Collections.Generic; using MonoDevelop.Components.Commands; -using MonoDevelop.SourceEditor.QuickTasks; using System.Linq; using MonoDevelop.Refactoring; -using ICSharpCode.NRefactory; using System.Threading; using MonoDevelop.Core; -using ICSharpCode.NRefactory.CSharp; -using ICSharpCode.NRefactory.Semantics; -using MonoDevelop.AnalysisCore.Fixes; -using ICSharpCode.NRefactory.Refactoring; -using MonoDevelop.Ide.Gui; +using Microsoft.CodeAnalysis.CodeFixes; +using MonoDevelop.CodeIssues; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; +using MonoDevelop.Ide.TypeSystem; +using MonoDevelop.Ide; +using Microsoft.CodeAnalysis.CodeActions; +using ICSharpCode.NRefactory6.CSharp.Refactoring; +using MonoDevelop.AnalysisCore; +using MonoDevelop.Ide.Editor; +using MonoDevelop.Components; +using MonoDevelop.Ide.Editor.Extension; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis; namespace MonoDevelop.CodeActions { - class CodeActionEditorExtension : TextEditorExtension + class CodeActionEditorExtension : TextEditorExtension { uint quickFixTimeout; @@ -52,11 +58,6 @@ namespace MonoDevelop.CodeActions uint menuCloseTimeoutId; FixMenuDescriptor codeActionMenu; - public IEnumerable<CodeAction> Fixes { - get; - private set; - } - static CodeActionEditorExtension () { var usages = PropertyService.Get<Properties> ("CodeActionUsages", new Properties ()); @@ -67,6 +68,7 @@ namespace MonoDevelop.CodeActions void CancelSmartTagPopupTimeout () { + if (smartTagPopupTimeoutId != 0) { GLib.Source.Remove (smartTagPopupTimeoutId); smartTagPopupTimeoutId = 0; @@ -80,27 +82,28 @@ namespace MonoDevelop.CodeActions menuCloseTimeoutId = 0; } } - + void RemoveWidget () { if (currentSmartTag != null) { - document.Editor.Document.RemoveMarker (currentSmartTag); + Editor.RemoveMarker (currentSmartTag); currentSmartTag = null; - currentSmartTagBegin = DocumentLocation.Empty; + currentSmartTagBegin = -1; } CancelSmartTagPopupTimeout (); - } - + public override void Dispose () { CancelMenuCloseTimer (); CancelQuickFixTimer (); - document.Editor.SelectionChanged -= HandleSelectionChanged; - document.DocumentParsed -= HandleDocumentDocumentParsed; - document.Editor.Parent.BeginHover -= HandleBeginHover; + Editor.CaretPositionChanged -= HandleCaretPositionChanged; + Editor.SelectionChanged -= HandleSelectionChanged; + DocumentContext.DocumentParsed -= HandleDocumentDocumentParsed; + Editor.BeginMouseHover -= HandleBeginHover; + Editor.TextChanged -= Editor_TextChanged; + Editor.EndAtomicUndoOperation -= Editor_EndAtomicUndoOperation; RemoveWidget (); - Fixes = null; base.Dispose (); } @@ -108,6 +111,8 @@ namespace MonoDevelop.CodeActions static void ConfirmUsage (string id) { + if (id == null) + return; if (!CodeActionUsages.ContainsKey (id)) { CodeActionUsages [id] = 1; } else { @@ -120,70 +125,137 @@ namespace MonoDevelop.CodeActions internal static int GetUsage (string id) { int result; - if (!CodeActionUsages.TryGetValue (id, out result)) + if (id == null || !CodeActionUsages.TryGetValue (id, out result)) return 0; return result; } public void CancelQuickFixTimer () { - if (quickFixCancellationTokenSource != null) - quickFixCancellationTokenSource.Cancel (); - if (quickFixTimeout != 0) { - GLib.Source.Remove (quickFixTimeout); - quickFixTimeout = 0; - } + quickFixCancellationTokenSource.Cancel (); + quickFixCancellationTokenSource = new CancellationTokenSource (); + smartTagTask = null; } - CancellationTokenSource quickFixCancellationTokenSource; + Task<CodeActionContainer> smartTagTask; + CancellationTokenSource quickFixCancellationTokenSource = new CancellationTokenSource (); - public override void CursorPositionChanged () + void HandleCaretPositionChanged (object sender, EventArgs e) { + if (Editor.IsInAtomicUndo) + return; CancelQuickFixTimer (); - if (QuickTaskStrip.EnableFancyFeatures && Document.ParsedDocument != null && !Debugger.DebuggingService.IsDebugging) { - quickFixCancellationTokenSource = new CancellationTokenSource (); + if (AnalysisOptions.EnableFancyFeatures && DocumentContext.ParsedDocument != null && !Debugger.DebuggingService.IsDebugging) { var token = quickFixCancellationTokenSource.Token; - quickFixTimeout = GLib.Timeout.Add (100, delegate { - var loc = Document.Editor.Caret.Location; - RefactoringService.QueueQuickFixAnalysis (Document, loc, token, delegate(List<CodeAction> fixes) { - if (!fixes.Any ()) { - ICSharpCode.NRefactory.Semantics.ResolveResult resolveResult; - AstNode node; - if (ResolveCommandHandler.ResolveAt (document, out resolveResult, out node, token)) { - var possibleNamespaces = ResolveCommandHandler.GetPossibleNamespaces (document, node, ref resolveResult); - if (!possibleNamespaces.Any ()) { - if (currentSmartTag != null) - Application.Invoke (delegate { RemoveWidget (); }); - return; + var curOffset = Editor.CaretOffset; + 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)).ToImmutableArray<string> (); + foreach (var cfp in CodeRefactoringService.GetCodeFixesAsync (DocumentContext, CodeRefactoringService.MimeTypeToLanguage (Editor.MimeType)).Result) { + 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) { + 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, 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; } - } else { - if (currentSmartTag != null) - Application.Invoke (delegate { RemoveWidget (); }); - return; + } 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 CodeRefactoringService.GetValidActionsAsync (Editor, DocumentContext, span, token).Result) { + codeActions.Add (action); + } + var codeActionContainer = new CodeActionContainer (codeIssueFixes, codeActions, diagnosticsAtCaret); Application.Invoke (delegate { if (token.IsCancellationRequested) return; - CreateSmartTag (fixes, loc); + if (codeActionContainer.IsEmpty) { + RemoveWidget (); + return; + } + CreateSmartTag (codeActionContainer, loc); }); - }); - quickFixTimeout = 0; - return false; - }); + return codeActionContainer; + + } catch (AggregateException ae) { + ae.Flatten ().Handle (aex => aex is OperationCanceledException); + return CodeActionContainer.Empty; + } catch (OperationCanceledException) { + return CodeActionContainer.Empty; + } + }, token); } else { RemoveWidget (); } - base.CursorPositionChanged (); } - internal static bool IsAnalysisOrErrorFix (CodeAction act) + internal static bool IsAnalysisOrErrorFix (Microsoft.CodeAnalysis.CodeActions.CodeAction act) { - return act is AnalysisContextActionProvider.AnalysisCodeAction || act.Severity == Severity.Error; - } - + return false; + } - class FixMenuEntry + internal class FixMenuEntry { public static readonly FixMenuEntry Separator = new FixMenuEntry ("-", null); public readonly string Label; @@ -197,12 +269,14 @@ namespace MonoDevelop.CodeActions } } - class FixMenuDescriptor : FixMenuEntry + internal class FixMenuDescriptor : FixMenuEntry { readonly List<FixMenuEntry> items = new List<FixMenuEntry> (); - public IReadOnlyList<FixMenuEntry> Items { - get { + public IReadOnlyList<FixMenuEntry> Items + { + get + { return items; } } @@ -217,93 +291,56 @@ namespace MonoDevelop.CodeActions public void Add (FixMenuEntry entry) { - items.Add (entry); + items.Add (entry); } - public object MotionNotifyEvent { + public object MotionNotifyEvent + { get; set; } } + internal static Action<TextEditor, DocumentContext, FixMenuDescriptor> AddPossibleNamespace; + void PopupQuickFixMenu (Gdk.EventButton evt, Action<FixMenuDescriptor> menuAction) { FixMenuDescriptor menu = new FixMenuDescriptor (); var fixMenu = menu; - ResolveResult resolveResult; - ICSharpCode.NRefactory.CSharp.AstNode node; + //ResolveResult resolveResult; + //ICSharpCode.NRefactory.CSharp.AstNode node; int items = 0; - if (ResolveCommandHandler.ResolveAt (document, out resolveResult, out node)) { - var possibleNamespaces = MonoDevelop.Refactoring.ResolveCommandHandler.GetPossibleNamespaces ( - document, - node, - ref resolveResult - ); - - foreach (var t in possibleNamespaces.Where (tp => tp.OnlyAddReference)) { - menu.Add (new FixMenuEntry (t.GetImportText (), delegate { - new ResolveCommandHandler.AddImport (document, resolveResult, null, t.Reference, true, node).Run (); - })); - items++; - } - bool addUsing = !(resolveResult is AmbiguousTypeResolveResult); - if (addUsing) { - foreach (var t in possibleNamespaces.Where (tp => tp.IsAccessibleWithGlobalUsing)) { - string ns = t.Namespace; - var reference = t.Reference; - menu.Add (new FixMenuEntry (t.GetImportText (), - delegate { - new ResolveCommandHandler.AddImport (document, resolveResult, ns, reference, true, node).Run (); - }) - ); - items++; - } - } +// if (AddPossibleNamespace != null) { +// AddPossibleNamespace (Editor, DocumentContext, menu); +// items = menu.Items.Count; +// } - bool resolveDirect = !(resolveResult is UnknownMemberResolveResult); - if (resolveDirect) { - foreach (var t in possibleNamespaces) { - string ns = t.Namespace; - var reference = t.Reference; - menu.Add (new FixMenuEntry (t.GetInsertNamespaceText (document.Editor.GetTextBetween (node.StartLocation, node.EndLocation)), - delegate { - new ResolveCommandHandler.AddImport (document, resolveResult, ns, reference, false, node).Run (); - })); - items++; - } - } - - if (menu.Items.Any () && Fixes.Any ()) { - fixMenu = new FixMenuDescriptor (GettextCatalog.GetString ("Quick Fixes")); - menu.Add (fixMenu); - items++; - } - } PopulateFixes (fixMenu, ref items); + if (items == 0) { return; } - document.Editor.SuppressTooltips = true; - document.Editor.Parent.HideTooltip (); +// document.Editor.SuppressTooltips = true; +// document.Editor.Parent.HideTooltip (); if (menuAction != null) menuAction (menu); - var container = document.Editor.Parent; - var p = container.LocationToPoint (currentSmartTagBegin); + var p = Editor.LocationToPoint (Editor.OffsetToLocation (currentSmartTagBegin)); + Gtk.Widget widget = Editor; var rect = new Gdk.Rectangle ( - p.X + container.Allocation.X , - p.Y + (int)document.Editor.LineHeight + container.Allocation.Y, 0, 0); + (int)p.X + widget.Allocation.X, + (int)p.Y + (int)Editor.LineHeight + widget.Allocation.Y, 0, 0); - ShowFixesMenu (document.Editor.Parent, rect, menu); + ShowFixesMenu (widget, rect, menu); } - #if MAC +#if MAC class ClosingMenuDelegate : AppKit.NSMenuDelegate { - readonly TextEditorData data; + readonly TextEditor data; - public ClosingMenuDelegate (TextEditorData editor_data) + public ClosingMenuDelegate (TextEditor editor_data) { data = editor_data; } @@ -314,10 +351,11 @@ namespace MonoDevelop.CodeActions public override void MenuDidClose (AppKit.NSMenu menu) { - data.SuppressTooltips = false; + // TODO: roslyn port ? (seems to be unused anyways btw.) + //data.SuppressTooltips = false; } } - #endif +#endif bool ShowFixesMenu (Gtk.Widget parent, Gdk.Rectangle evt, FixMenuDescriptor entrySet) { @@ -325,6 +363,7 @@ namespace MonoDevelop.CodeActions return true; try { #if MAC + Gtk.Application.Invoke (delegate { parent.GrabFocus (); int x, y; x = (int)evt.X; @@ -332,7 +371,7 @@ namespace MonoDevelop.CodeActions // Explicitly release the grab because the menu is shown on the mouse position, and the widget doesn't get the mouse release event Gdk.Pointer.Ungrab (Gtk.Global.CurrentEventTime); var menu = CreateNSMenu (entrySet); - menu.Delegate = new ClosingMenuDelegate (document.Editor); + menu.Delegate = new ClosingMenuDelegate (Editor); var nsview = MonoDevelop.Components.Mac.GtkMacInterop.GetNSView (parent); var toplevel = parent.Toplevel as Gtk.Window; int trans_x, trans_y; @@ -351,18 +390,20 @@ namespace MonoDevelop.CodeActions null, 0, 0, 0); AppKit.NSMenu.PopUpContextMenu (menu, tmp_event, nsview); + }); #else var menu = CreateGtkMenu (entrySet); menu.Events |= Gdk.EventMask.AllEventsMask; menu.SelectFirst (true); menu.Hidden += delegate { - document.Editor.SuppressTooltips = false; + // document.Editor.SuppressTooltips = false; }; menu.ShowAll (); menu.SelectFirst (true); menu.MotionNotifyEvent += (o, args) => { - if (args.Event.Window == Editor.Parent.TextArea.GdkWindow) { + Gtk.Widget widget = Editor; + if (args.Event.Window == widget.GdkWindow) { StartMenuCloseTimer (); } else { CancelMenuCloseTimer (); @@ -376,7 +417,8 @@ namespace MonoDevelop.CodeActions } return true; } - #if MAC + +#if MAC AppKit.NSMenu CreateNSMenu (FixMenuDescriptor entrySet) { @@ -401,28 +443,28 @@ namespace MonoDevelop.CodeActions } return menu; } - #endif +#endif static Menu CreateGtkMenu (FixMenuDescriptor entrySet) { var menu = new Menu (); foreach (var item in entrySet.Items) { if (item == FixMenuEntry.Separator) { - menu.Add (new SeparatorMenuItem ()); + menu.Add (new SeparatorMenuItem ()); continue; } var subMenu = item as FixMenuDescriptor; if (subMenu != null) { var gtkSubMenu = new Gtk.MenuItem (item.Label); gtkSubMenu.Submenu = CreateGtkMenu (subMenu); - menu.Add (gtkSubMenu); + menu.Add (gtkSubMenu); continue; } var menuItem = new Gtk.MenuItem (item.Label); menuItem.Activated += delegate { item.Action (); }; - menu.Add (menuItem); + menu.Add (menuItem); } return menu; } @@ -431,275 +473,297 @@ namespace MonoDevelop.CodeActions { int mnemonic = 1; bool gotImportantFix = false, addedSeparator = false; - var fixesAdded = new List<string> (); - foreach (var fix_ in Fixes.OrderByDescending (i => Tuple.Create (IsAnalysisOrErrorFix(i), (int)i.Severity, GetUsage (i.IdString)))) { + foreach (var fix_ in GetCurrentFixes ().CodeFixActions.OrderByDescending (i => Tuple.Create (IsAnalysisOrErrorFix (i.CodeAction), (int)0, GetUsage (i.CodeAction.EquivalenceKey)))) { // filter out code actions that are already resolutions of a code issue - if (fixesAdded.Any (f => fix_.IdString.IndexOf (f, StringComparison.Ordinal) >= 0)) - continue; - fixesAdded.Add (fix_.IdString); - if (IsAnalysisOrErrorFix (fix_)) + if (IsAnalysisOrErrorFix (fix_.CodeAction)) gotImportantFix = true; - if (!addedSeparator && gotImportantFix && !IsAnalysisOrErrorFix(fix_)) { + if (!addedSeparator && gotImportantFix && !IsAnalysisOrErrorFix (fix_.CodeAction)) { menu.Add (FixMenuEntry.Separator); addedSeparator = true; } var fix = fix_; - var escapedLabel = fix.Title.Replace ("_", "__"); + var escapedLabel = fix.CodeAction.Title.Replace ("_", "__"); var label = (mnemonic <= 10) ? "_" + (mnemonic++ % 10).ToString () + " " + escapedLabel : " " + escapedLabel; var thisInstanceMenuItem = new FixMenuEntry (label, delegate { - new ContextActionRunner (fix, document, currentSmartTagBegin).Run (null, EventArgs.Empty); - ConfirmUsage (fix.IdString); + new ContextActionRunner (fix.CodeAction, Editor, DocumentContext).Run (null, EventArgs.Empty); + ConfirmUsage (fix.CodeAction.EquivalenceKey); }); menu.Add (thisInstanceMenuItem); items++; } bool first = true; - var settingsMenuFixes = Fixes - .OfType<AnalysisContextActionProvider.AnalysisCodeAction> () - .Where (f => f.Result is InspectorResults) - .GroupBy (f => ((InspectorResults)f.Result).Inspector); - foreach (var analysisFixGroup_ in settingsMenuFixes) { - var analysisFixGroup = analysisFixGroup_; - var arbitraryFixInGroup = analysisFixGroup.First (); - var ir = (InspectorResults)arbitraryFixInGroup.Result; - + foreach (var fix in GetCurrentFixes ().CodeRefactoringActions) { if (first) { - menu.Add (FixMenuEntry.Separator); + if (items > 0) + menu.Add (FixMenuEntry.Separator); first = false; } - var subMenu = new FixMenuDescriptor (); - foreach (var analysisFix_ in analysisFixGroup) { - var analysisFix = analysisFix_; - if (analysisFix.SupportsBatchRunning) { - var batchRunMenuItem = new FixMenuEntry ( - string.Format (GettextCatalog.GetString ("Apply in file: {0}"), analysisFix.Title), - delegate { - ConfirmUsage (analysisFix.IdString); - new ContextActionRunner (analysisFix, document, this.currentSmartTagBegin).BatchRun (null, EventArgs.Empty); - } - ); - subMenu.Add (batchRunMenuItem); - subMenu.Add (FixMenuEntry.Separator); - } - } - - var inspector = ir.Inspector; - if (inspector.CanSuppressWithAttribute) { - var menuItem = new FixMenuEntry (GettextCatalog.GetString ("_Suppress with attribute"), - delegate { - inspector.SuppressWithAttribute (document, arbitraryFixInGroup.DocumentRegion); - }); - subMenu.Add (menuItem); - } + var escapedLabel = fix.CodeAction.Title.Replace ("_", "__"); + var label = (mnemonic <= 10) + ? "_" + (mnemonic++ % 10).ToString () + " " + escapedLabel + : " " + escapedLabel; + var thisInstanceMenuItem = new FixMenuEntry (label, delegate { + new ContextActionRunner (fix.CodeAction, Editor, DocumentContext).Run (null, EventArgs.Empty); + ConfirmUsage (fix.CodeAction.EquivalenceKey); + }); + menu.Add (thisInstanceMenuItem); + items++; + } - if (inspector.CanDisableWithPragma) { - var menuItem = new FixMenuEntry (GettextCatalog.GetString ("_Suppress with #pragma"), - delegate { - inspector.DisableWithPragma (document, arbitraryFixInGroup.DocumentRegion); - }); - subMenu.Add (menuItem); - } + first = false; + foreach (var fix_ in GetCurrentFixes ().DiagnosticsAtCaret) { + var fix = fix_; + var label = GettextCatalog.GetString ("_Options for \"{0}\"", fix.GetMessage ()); + var subMenu = new FixMenuDescriptor (label); - if (inspector.CanDisableOnce) { - var menuItem = new FixMenuEntry (GettextCatalog.GetString ("_Disable Once"), - delegate { - inspector.DisableOnce (document, arbitraryFixInGroup.DocumentRegion); - }); - subMenu.Add (menuItem); + CodeDiagnosticDescriptor descriptor = BuiltInCodeDiagnosticProvider.GetCodeDiagnosticDescriptor (fix.Id); + if (descriptor == null) + continue; + if (first) { + menu.Add (FixMenuEntry.Separator); + first = false; } - - if (inspector.CanDisableAndRestore) { - var menuItem = new FixMenuEntry (GettextCatalog.GetString ("Disable _and Restore"), + // if (inspector.CanSuppressWithAttribute) { + // var menuItem = new FixMenuEntry (GettextCatalog.GetString ("_Suppress with attribute"), + // delegate { + // + // inspector.SuppressWithAttribute (Editor, DocumentContext, GetTextSpan (fix.Item2)); + // }); + // subMenu.Add (menuItem); + // } + + if (descriptor.CanDisableWithPragma) { + var menuItem = new FixMenuEntry (GettextCatalog.GetString ("_Suppress with #pragma"), delegate { - inspector.DisableAndRestore (document, arbitraryFixInGroup.DocumentRegion); + descriptor.DisableWithPragma (Editor, DocumentContext, fix.Location.SourceSpan); }); subMenu.Add (menuItem); } - var label = GettextCatalog.GetString ("_Options for \"{0}\"", InspectorResults.GetTitle (ir.Inspector)); - var subMenuItem = new FixMenuDescriptor (label); var optionsMenuItem = new FixMenuEntry (GettextCatalog.GetString ("_Configure Rule"), delegate { - arbitraryFixInGroup.ShowOptions (null, EventArgs.Empty); + IdeApp.Workbench.ShowGlobalPreferencesDialog (null, "C#", dialog => { + var panel = dialog.GetPanel<CodeIssuePanel> ("C#"); + if (panel == null) + return; + panel.Widget.SelectCodeIssue (descriptor.IdString); + }); }); - subMenuItem.Add (optionsMenuItem); + subMenu.Add (optionsMenuItem); - menu.Add (subMenuItem); + menu.Add (subMenu); items++; } } - - class ContextActionRunner + internal class ContextActionRunner { - CodeAction act; - Document document; - TextLocation loc; + readonly CodeAction act; + TextEditor editor; + DocumentContext documentContext; - public ContextActionRunner (MonoDevelop.CodeActions.CodeAction act, MonoDevelop.Ide.Gui.Document document, ICSharpCode.NRefactory.TextLocation loc) + public ContextActionRunner (CodeAction act, TextEditor editor, DocumentContext documentContext) { + this.editor = editor; this.act = act; - this.document = document; - this.loc = loc; + this.documentContext = documentContext; } public void Run (object sender, EventArgs e) { - var context = document.ParsedDocument.CreateRefactoringContext (document, CancellationToken.None); - RefactoringService.ApplyFix (act, context); + Run (); } - public void BatchRun (object sender, EventArgs e) + internal async void Run () { - act.BatchRun (document, loc); - } - } + var token = default(CancellationToken); + var insertionAction = act as InsertionAction; + if (insertionAction != null) { + var insertion = await insertionAction.CreateInsertion (token).ConfigureAwait (false); + + var document = IdeApp.Workbench.OpenDocument (insertion.Location.SourceTree.FilePath); + var parsedDocument = document.UpdateParseDocument (); + if (parsedDocument != null) { + var insertionPoints = InsertionPointService.GetInsertionPoints ( + document.Editor, + parsedDocument, + insertion.Type, + insertion.Location + ); + + var options = new InsertionModeOptions ( + insertionAction.Title, + insertionPoints, + async point => { + if (!point.Success) + return; - class SmartTagMarker : TextSegmentMarker, IActionTextLineMarker - { - CodeActionEditorExtension codeActionEditorExtension; - internal List<CodeAction> fixes; - DocumentLocation loc; + var node = Formatter.Format (insertion.Node, TypeSystemService.Workspace, document.GetOptionSet (), token); - public SmartTagMarker (int offset, CodeActionEditorExtension codeActionEditorExtension, List<CodeAction> fixes, DocumentLocation loc) : base (offset, 0) - { - this.codeActionEditorExtension = codeActionEditorExtension; - this.fixes = fixes; - this.loc = loc; - } - - public SmartTagMarker (int offset) : base (offset, 0) - { - } - const double tagMarkerWidth = 8; - const double tagMarkerHeight = 2; - public override void Draw (TextEditor editor, Cairo.Context cr, Pango.Layout layout, bool selected, int startOffset, int endOffset, double y, double startXPos, double endXPos) - { - var line = editor.GetLine (loc.Line); - var x = editor.ColumnToX (line, loc.Column) - editor.HAdjustment.Value + editor.TextViewMargin.XOffset + editor.TextViewMargin.TextStartPosition; + point.InsertionPoint.Insert (document.Editor, document, node.ToString ()); + // document = await Simplifier.ReduceAsync(document.AnalysisDocument, Simplifier.Annotation, cancellationToken: token).ConfigureAwait(false); - cr.Rectangle (Math.Floor (x) + 0.5, Math.Floor (y) + 0.5 + (line == editor.GetLineByOffset (startOffset) ? editor.LineHeight - tagMarkerHeight - 1 : 0), tagMarkerWidth * cr.LineWidth, tagMarkerHeight * cr.LineWidth); + } + ); - if (HslColor.Brightness (editor.ColorStyle.PlainText.Background) < 0.5) { - cr.SetSourceRGBA (0.8, 0.8, 1, 0.9); - } else { - cr.SetSourceRGBA (0.2, 0.2, 1, 0.9); + document.Editor.StartInsertionMode (options); + return; + } } - cr.Stroke (); - } - - #region IActionTextLineMarker implementation - bool IActionTextLineMarker.MousePressed (TextEditor editor, MarginMouseEventArgs args) - { - return false; + var oldSolution = documentContext.AnalysisDocument.Project.Solution; + var updatedSolution = oldSolution; + foreach (var operation in act.GetOperationsAsync (token).Result) { + var applyChanges = operation as ApplyChangesOperation; + if (applyChanges == null) { + operation.Apply (documentContext.RoslynWorkspace, token); + continue; + } + if (updatedSolution == oldSolution) { + updatedSolution = applyChanges.ChangedSolution; + } + operation.Apply (documentContext.RoslynWorkspace, token); + } + TryStartRenameSession (documentContext.RoslynWorkspace, oldSolution, updatedSolution, token); } - void IActionTextLineMarker.MouseHover (TextEditor editor, MarginMouseEventArgs args, TextLineMarkerHoverResult result) + static IEnumerable<DocumentId> GetChangedDocuments (Solution newSolution, Solution oldSolution) { - if (args.Button != 0) - return; - var line = editor.GetLine (loc.Line); - if (line == null) - return; - var x = editor.ColumnToX (line, loc.Column) - editor.HAdjustment.Value + editor.TextViewMargin.TextStartPosition; - var y = editor.LineToY (line.LineNumber + 1) - editor.VAdjustment.Value; - const double xAdditionalSpace = tagMarkerWidth; - if (args.X - x >= -xAdditionalSpace * editor.Options.Zoom && - args.X - x < (tagMarkerWidth + xAdditionalSpace) * editor.Options.Zoom /*&& - args.Y - y < (editor.LineHeight / 2) * editor.Options.Zoom*/) { - result.Cursor = null; - Popup (); - } else { - codeActionEditorExtension.CancelSmartTagPopupTimeout (); + if (newSolution != null) { + var solutionChanges = newSolution.GetChanges (oldSolution); + foreach (var projectChanges in solutionChanges.GetProjectChanges ()) { + foreach (var documentId in projectChanges.GetChangedDocuments ()) { + yield return documentId; + } + } } } - public void Popup () + async void TryStartRenameSession (Workspace workspace, Solution oldSolution, Solution newSolution, CancellationToken cancellationToken) { - codeActionEditorExtension.smartTagPopupTimeoutId = GLib.Timeout.Add (menuTimeout, delegate { - codeActionEditorExtension.PopupQuickFixMenu (null, menu => { - codeActionEditorExtension.codeActionMenu = menu; - }); - codeActionEditorExtension.smartTagPopupTimeoutId = 0; - return false; - }); + var changedDocuments = GetChangedDocuments (newSolution, oldSolution); + foreach (var documentId in changedDocuments) { + var document = newSolution.GetDocument (documentId); + var root = await document.GetSyntaxRootAsync (cancellationToken).ConfigureAwait (false); + + SyntaxToken? renameTokenOpt = root.GetAnnotatedNodesAndTokens (RenameAnnotation.Kind) + .Where (s => s.IsToken) + .Select (s => s.AsToken ()) + .Cast<SyntaxToken?> () + .FirstOrDefault (); + + if (renameTokenOpt.HasValue) { + var latestDocument = workspace.CurrentSolution.GetDocument (documentId); + var latestModel = await latestDocument.GetSemanticModelAsync (cancellationToken).ConfigureAwait (false); + var latestRoot = await latestDocument.GetSyntaxRootAsync (cancellationToken).ConfigureAwait (false); + Application.Invoke (delegate { + try { + var node = latestRoot.FindNode (renameTokenOpt.Value.Parent.Span, false, false); + if (node == null) + return; + var info = latestModel.GetSymbolInfo (node); + var sym = info.Symbol ?? latestModel.GetDeclaredSymbol (node); + if (sym != null) + new MonoDevelop.Refactoring.Rename.RenameRefactoring ().Rename (sym); + } catch (Exception ex) { + LoggingService.LogError ("Error while renaming " + renameTokenOpt.Value.Parent, ex); + } + }); + return; + } + } } - - - #endregion } - SmartTagMarker currentSmartTag; - DocumentLocation currentSmartTagBegin; - void CreateSmartTag (List<CodeAction> fixes, DocumentLocation loc) + ISmartTagMarker currentSmartTag; + int currentSmartTagBegin; + + void CreateSmartTag (CodeActionContainer fixes, int offset) { - Fixes = fixes; - if (!QuickTaskStrip.EnableFancyFeatures) { + if (!AnalysisOptions.EnableFancyFeatures || fixes.IsEmpty) { RemoveWidget (); return; } - var editor = document.Editor; - if (editor == null || editor.Parent == null || !editor.Parent.IsRealized) { + var editor = Editor; + if (editor == null) { RemoveWidget (); return; } - if (document.ParsedDocument == null || document.ParsedDocument.IsInvalid) { + if (DocumentContext.ParsedDocument == null || DocumentContext.ParsedDocument.IsInvalid) { RemoveWidget (); return; } - var container = editor.Parent; - if (container == null) { - RemoveWidget (); - return; - } +// var container = editor.Parent; +// if (container == null) { +// RemoveWidget (); +// return; +// } bool first = true; - DocumentLocation smartTagLocBegin = loc; - foreach (var fix in fixes) { - if (fix.DocumentRegion.IsEmpty) + var smartTagLocBegin = offset; + foreach (var fix in fixes.CodeFixActions.Concat (fixes.CodeRefactoringActions)) { + var textSpan = fix.ValidSegment; + if (textSpan.IsEmpty) continue; - if (first || loc < fix.DocumentRegion.Begin) { - smartTagLocBegin = fix.DocumentRegion.Begin; + if (first || offset < textSpan.Start) { + smartTagLocBegin = textSpan.Start; } first = false; } - if (smartTagLocBegin.Line != loc.Line) - smartTagLocBegin = new DocumentLocation (loc.Line, 1); +// 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 (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) { - currentSmartTag.fixes = fixes; return; } RemoveWidget (); currentSmartTagBegin = smartTagLocBegin; - var line = document.Editor.GetLine (smartTagLocBegin.Line); - currentSmartTag = new SmartTagMarker ((line.NextLine ?? line).Offset, this, fixes, smartTagLocBegin); - document.Editor.Document.AddMarker (currentSmartTag); + var realLoc = Editor.OffsetToLocation (smartTagLocBegin); + + currentSmartTag = TextMarkerFactory.CreateSmartTagMarker (Editor, smartTagLocBegin, realLoc); + currentSmartTag.Tag = fixes; + editor.AddMarker (currentSmartTag); } - - public override void Initialize () + + protected override void Initialize () { base.Initialize (); - document.DocumentParsed += HandleDocumentDocumentParsed; - document.Editor.SelectionChanged += HandleSelectionChanged; - document.Editor.Parent.BeginHover += HandleBeginHover; + DocumentContext.DocumentParsed += HandleDocumentDocumentParsed; + Editor.SelectionChanged += HandleSelectionChanged; + Editor.BeginMouseHover += HandleBeginHover; + Editor.CaretPositionChanged += HandleCaretPositionChanged; + Editor.TextChanged += Editor_TextChanged; + Editor.EndAtomicUndoOperation += Editor_EndAtomicUndoOperation; + } + + void Editor_EndAtomicUndoOperation (object sender, EventArgs e) + { + RemoveWidget (); + HandleCaretPositionChanged (null, EventArgs.Empty); + } + + void Editor_TextChanged (object sender, MonoDevelop.Core.Text.TextChangeEventArgs e) + { + if (Editor.IsInAtomicUndo) + return; + RemoveWidget (); + HandleCaretPositionChanged (null, EventArgs.Empty); } void HandleBeginHover (object sender, EventArgs e) @@ -723,43 +787,54 @@ namespace MonoDevelop.CodeActions void HandleSelectionChanged (object sender, EventArgs e) { - CursorPositionChanged (); + HandleCaretPositionChanged (null, EventArgs.Empty); } - + void HandleDocumentDocumentParsed (object sender, EventArgs e) { - CursorPositionChanged (); + HandleCaretPositionChanged (null, EventArgs.Empty); } - + [CommandUpdateHandler(RefactoryCommands.QuickFix)] public void UpdateQuickFixCommand (CommandInfo ci) { - if (QuickTaskStrip.EnableFancyFeatures) { + if (AnalysisOptions.EnableFancyFeatures) { ci.Enabled = currentSmartTag != null; } else { ci.Enabled = true; } } - + + void CurrentSmartTagPopup () + { + CancelSmartTagPopupTimeout (); + smartTagPopupTimeoutId = GLib.Timeout.Add (menuTimeout, delegate { + PopupQuickFixMenu (null, menu => { + codeActionMenu = menu; + }); + smartTagPopupTimeoutId = 0; + return false; + }); + } + [CommandHandler(RefactoryCommands.QuickFix)] void OnQuickFixCommand () { - if (!QuickTaskStrip.EnableFancyFeatures) { - Fixes = RefactoringService.GetValidActions (Document, Document.Editor.Caret.Location); - currentSmartTagBegin = Document.Editor.Caret.Location; - PopupQuickFixMenu (null, null); + if (!AnalysisOptions.EnableFancyFeatures) { + //Fixes = RefactoringService.GetValidActions (Editor, DocumentContext, Editor.CaretLocation).Result; + currentSmartTagBegin = Editor.CaretOffset; + PopupQuickFixMenu (null, null); return; } if (currentSmartTag == null) return; - currentSmartTag.Popup (); + CurrentSmartTagPopup (); } - static readonly List<CodeAction> emptyList = new List<CodeAction> (); - internal List<CodeAction> GetCurrentFixes () + internal CodeActionContainer GetCurrentFixes () { - return currentSmartTag == null ? emptyList : currentSmartTag.fixes; + return smartTagTask == null ? CodeActionContainer.Empty : smartTagTask.Result; } } -}
\ No newline at end of file +} |