From 90608407a4d9a27b237f1b6f1e9d40cdc3b3087e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Tue, 2 Oct 2012 10:52:23 +0200 Subject: [PATCH] Add support for automatically fixing issues. --- .../Project/CSharpBinding.csproj | 2 +- .../{SDScript.cs => EditorScript.cs} | 5 +- .../Src/Refactoring/SDRefactoringContext.cs | 10 +- .../Src/Refactoring/SearchForIssuesCommand.cs | 109 +++++++++++++++++- .../Refactoring/SearchForIssuesDialog.xaml.cs | 1 + .../AvalonEdit.AddIn/Src/CodeEditor.cs | 2 +- .../Project/Engine/SearchManager.cs | 4 +- .../SharpDevelop/Workbench/FileService.cs | 12 +- 8 files changed, 125 insertions(+), 20 deletions(-) rename src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/{SDScript.cs => EditorScript.cs} (94%) diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj index 2f06ac1902..b1f8af8d2f 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj @@ -80,7 +80,7 @@ - + SearchForIssuesDialog.xaml diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/EditorScript.cs similarity index 94% rename from src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs rename to src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/EditorScript.cs index 3948cb1581..9d572e4df6 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDScript.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/EditorScript.cs @@ -21,13 +21,14 @@ namespace CSharpBinding.Refactoring /// /// Refactoring change script. /// - sealed class SDScript : DocumentScript + sealed class EditorScript : DocumentScript { readonly ITextEditor editor; readonly TextSegmentCollection textSegmentCollection; readonly SDRefactoringContext context; - public SDScript(ITextEditor editor, SDRefactoringContext context) : base(editor.Document, FormattingOptionsFactory.CreateSharpDevelop(), context.TextEditorOptions) + public EditorScript(ITextEditor editor, SDRefactoringContext context, CSharpFormattingOptions formattingOptions) + : base(editor.Document, formattingOptions, context.TextEditorOptions) { this.editor = editor; this.context = context; diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs index 4c72eb693d..ed1543ec90 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SDRefactoringContext.cs @@ -60,9 +60,13 @@ namespace CSharpBinding.Refactoring public Script StartScript() { - if (editor == null) - throw new InvalidOperationException("Cannot start a script in IsAvailable()."); - return new SDScript(editor, this); + var formattingOptions = FormattingOptionsFactory.CreateSharpDevelop(); + if (editor != null) + return new EditorScript(editor, this, formattingOptions); + else if (document == null || document is ReadOnlyDocument) + throw new InvalidOperationException("Cannot start a script in a read-only context"); + else + return new DocumentScript(document, formattingOptions, this.TextEditorOptions); } public override TextLocation Location { diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesCommand.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesCommand.cs index 8d57401ee5..b85d41243c 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesCommand.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesCommand.cs @@ -4,17 +4,21 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; - using CSharpBinding.Parser; +using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Core; using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.CSharp.Refactoring; using ICSharpCode.NRefactory.Editor; using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Editor.Search; +using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Project; namespace CSharpBinding.Refactoring @@ -26,13 +30,30 @@ namespace CSharpBinding.Refactoring SearchForIssuesDialog dlg = new SearchForIssuesDialog(); dlg.Owner = SD.Workbench.MainWindow; if (dlg.ShowDialog() == true) { + string title = "Issue Search"; var providers = dlg.SelectedProviders.ToList(); var fileNames = GetFilesToSearch(dlg.Target).ToList(); - var monitor = SD.StatusBar.CreateProgressMonitor(); - var observable = ReactiveExtensions.CreateObservable( - (m, c) => SearchForIssuesAsync(fileNames, providers, c, m), - monitor); - SearchResultsPad.Instance.ShowSearchResults("Issue Search", observable); + if (dlg.FixIssues) { + int fixedIssueCount = 0; + IReadOnlyList remainingIssues = null; + AsynchronousWaitDialog.RunInCancellableWaitDialog( + title, null, + monitor => { + remainingIssues = FindAndFixIssues(fileNames, providers, monitor, out fixedIssueCount); + }); + string message = string.Format( + "{0} issues were fixed automatically." + + "{1} issues are remaining (no automatic fix available).", + fixedIssueCount, remainingIssues.Count); + SearchResultsPad.Instance.ShowSearchResults(title, remainingIssues); + MessageService.ShowMessage(message, title); + } else { + var monitor = SD.StatusBar.CreateProgressMonitor(); + var observable = ReactiveExtensions.CreateObservable( + (m, c) => SearchForIssuesAsync(fileNames, providers, c, m), + monitor); + SearchResultsPad.Instance.ShowSearchResults(title, observable); + } } } @@ -117,6 +138,82 @@ namespace CSharpBinding.Refactoring else return null; } + + IReadOnlyList FindAndFixIssues(List fileNames, List providers, IProgressMonitor progress, out int fixedIssueCount) + { + fixedIssueCount = 0; + List remainingIssues = new List(); + for (int i = 0; i < fileNames.Count; i++) { + remainingIssues.AddRange(FindAndFixIssues(fileNames[i], providers, progress.CancellationToken, ref fixedIssueCount)); + progress.Report((double)i / fileNames.Count); + } + return remainingIssues; + } + + IEnumerable FindAndFixIssues(FileName fileName, List providers, CancellationToken cancellationToken, ref int fixedIssueCount) + { + cancellationToken.ThrowIfCancellationRequested(); + var openedFile = SD.FileService.GetOpenedFile(fileName); + bool documentWasLoadedFromDisk = false; + IDocument document = null; + if (openedFile != null && openedFile.CurrentView != null) { + var provider = openedFile.CurrentView.GetService(); + if (provider != null) + document = provider.GetDocumentForFile(openedFile); + } + if (document == null) { + documentWasLoadedFromDisk = true; + document = new TextDocument(SD.FileService.GetFileContent(fileName)); + } + var parseInfo = SD.ParserService.Parse(fileName, document, cancellationToken: cancellationToken) as CSharpFullParseInformation; + if (parseInfo == null) + return Enumerable.Empty(); + + var compilation = SD.ParserService.GetCompilationForFile(fileName); + var resolver = parseInfo.GetResolver(compilation); + var context = new SDRefactoringContext(document, resolver, new TextLocation(0, 0), 0, 0, cancellationToken); + List allIssues = new List(); + bool documentWasChanged = false; + foreach (var provider in providers) { + cancellationToken.ThrowIfCancellationRequested(); + var issues = provider.GetIssues(context).ToList(); + // Fix issues, if possible: + if (issues.Any(i => i.Actions.Count > 0)) { + using (var script = context.StartScript()) { + foreach (var issue in issues) { + if (issue.Actions.Count > 0) { + fixedIssueCount++; + issue.Actions[0].Run(script); + } + } + } + documentWasChanged = true; + // Update parseInfo etc. now that we've modified the document + parseInfo = SD.ParserService.Parse(fileName, document, cancellationToken: cancellationToken) as CSharpFullParseInformation; + if (parseInfo == null) + return Enumerable.Empty(); + compilation = SD.ParserService.GetCompilationForFile(fileName); + resolver = parseInfo.GetResolver(compilation); + context = new SDRefactoringContext(document, resolver, new TextLocation(0, 0), 0, 0, cancellationToken); + // Find remaining issues: + allIssues.AddRange(provider.GetIssues(context)); + } else { + allIssues.AddRange(issues); + } + } + if (documentWasChanged && documentWasLoadedFromDisk) { + // Save changes back to disk + using (var writer = new StreamWriter(fileName, false, SD.FileService.DefaultFileEncoding)) { + document.WriteTextTo(writer); + } + } + if (allIssues.Count > 0) { + var highlighter = SD.EditorControlService.CreateHighlighter(document); + return allIssues.Select(issue => SearchResultMatch.Create(document, issue.Start, issue.End, highlighter)); + } else { + return Enumerable.Empty(); + } + } } public enum SearchForIssuesTarget diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesDialog.xaml.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesDialog.xaml.cs index 15f591e7a1..f6d4abe0d3 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesDialog.xaml.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesDialog.xaml.cs @@ -26,6 +26,7 @@ namespace CSharpBinding.Refactoring InitializeComponent(); FixCheckBox_Unchecked(null, null); treeView.Root = new RootTreeNode(IssueManager.IssueProviders); + searchInRBG.SelectedValue = SearchForIssuesTarget.WholeSolution; } public SearchForIssuesTarget Target { diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index bbd3a06b1a..1ed8bc559f 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -363,7 +363,7 @@ namespace ICSharpCode.AvalonEdit.AddIn // don't use TextEditor.Save here because that would touch the Modified flag, // but OpenedFile is already managing IsDirty using (StreamWriter writer = new StreamWriter(stream, primaryTextEditor.Encoding ?? Encoding.UTF8)) { - writer.Write(primaryTextEditor.Text); + primaryTextEditor.Document.WriteTextTo(writer); } } diff --git a/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs b/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs index 44a9caa807..025af9bf6b 100644 --- a/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs +++ b/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs @@ -54,9 +54,9 @@ namespace SearchAndReplace static ITextSource ReadFile(FileName fileName) { OpenedFile openedFile = SD.FileService.GetOpenedFile(fileName); - if (openedFile == null) + if (openedFile == null || openedFile.CurrentView == null) return null; - IFileDocumentProvider provider = FileService.GetOpenFile(fileName) as IFileDocumentProvider; + var provider = openedFile.CurrentView.GetService(); if (provider == null) return null; IDocument doc = provider.GetDocumentForFile(openedFile); diff --git a/src/Main/SharpDevelop/Workbench/FileService.cs b/src/Main/SharpDevelop/Workbench/FileService.cs index d1874c443d..f3a3dd633f 100644 --- a/src/Main/SharpDevelop/Workbench/FileService.cs +++ b/src/Main/SharpDevelop/Workbench/FileService.cs @@ -111,11 +111,13 @@ namespace ICSharpCode.SharpDevelop.Workbench delegate { OpenedFile file = this.GetOpenedFile(fileName); if (file != null) { - IFileDocumentProvider p = file.CurrentView as IFileDocumentProvider; - if (p != null) { - IDocument document = p.GetDocumentForFile(file); - if (document != null) { - return document.CreateSnapshot(); + if (file.CurrentView != null) { + IFileDocumentProvider provider = file.CurrentView.GetService(); + if (provider != null) { + IDocument document = provider.GetDocumentForFile(file); + if (document != null) { + return document.CreateSnapshot(); + } } }