diff options
Diffstat (limited to 'main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad')
36 files changed, 3598 insertions, 0 deletions
diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AbstractGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AbstractGroupingProvider.cs new file mode 100644 index 0000000000..3f7cd390c7 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AbstractGroupingProvider.cs @@ -0,0 +1,115 @@ +// +// AbstractGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; + +namespace MonoDevelop.CodeIssues +{ + public abstract class AbstractGroupingProvider<T>: IGroupingProvider + { + /// <summary> + /// Associates a parent group and grouping key to a child group. + /// </summary> + readonly Dictionary<Tuple<IssueGroup, T>, IssueGroup> groups = new Dictionary<Tuple<IssueGroup, T>, IssueGroup> (); + + protected AbstractGroupingProvider() + { + Next = NullGroupingProvider.Instance; + } + + #region IGroupingProvider implementation + + protected abstract T GetGroupingKey (IssueSummary issue); + + protected abstract string GetGroupName (IssueSummary issue); + + public IssueGroup GetIssueGroup (IssueGroup parentGroup, IssueSummary issue) + { + IssueGroup group; + var providerCategory = GetGroupingKey (issue); + if (providerCategory == null) + return null; + var key = Tuple.Create (parentGroup, providerCategory); + if (!groups.TryGetValue (key, out group)) { + group = new IssueGroup (Next, GetGroupName(issue)); + groups.Add (key, group); + } + return group; + } + + public void Reset () + { + groups.Clear (); + Next.Reset (); + } + + IGroupingProvider next; + public IGroupingProvider Next + { + get { + return next; + } + set { + var eventArgs = new GroupingProviderEventArgs (this, next); + next = value; + foreach (var group in groups.Values) { + group.GroupingProvider = value; + } + OnNextChanged (eventArgs); + } + } + + protected virtual void OnNextChanged (GroupingProviderEventArgs eventArgs) + { + var handler = nextChanged; + if (handler != null) { + handler (this, eventArgs); + } + } + + event EventHandler<GroupingProviderEventArgs> nextChanged; + + event EventHandler<GroupingProviderEventArgs> IGroupingProvider.NextChanged + { + add { + nextChanged += value; + } + remove { + nextChanged -= value; + } + } + + public bool SupportsNext + { + get { + return true; + } + } + + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ActionSummary.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ActionSummary.cs new file mode 100644 index 0000000000..7a560e69fd --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ActionSummary.cs @@ -0,0 +1,65 @@ +// +// IGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using MonoDevelop.Ide.Editor; + +namespace MonoDevelop.CodeIssues +{ + public class ActionSummary + { + /// <summary> + /// Gets or sets the title. + /// </summary> + /// <value>The title.</value> + public string Title { get; set; } + + /// <summary> + /// Gets or sets the sibling key. + /// </summary> + /// <value>The sibling key.</value> + public object SiblingKey { get; set; } + + /// <summary> + /// Gets a value indicating whether this <see cref="MonoDevelop.CodeIssues.ActionSummary"/> is batchable. + /// </summary> + /// <value><c>true</c> if batchable; otherwise, <c>false</c>.</value> + public bool Batchable { get; set; } + + public DocumentRegion Region { get; set; } + + /// <summary> + /// Gets or sets the <see cref="IssueSummary"/> representing the source of this action. + /// </summary> + /// <value>The issue summary.</value> + public IssueSummary IssueSummary { get; set; } + + public override string ToString () + { + return string.Format ("[ActionSummary: Title={0}, Batchable={1}, Region={2}]", Title, Batchable, Region); + } + } + +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AnalysisState.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AnalysisState.cs new file mode 100644 index 0000000000..221e11d819 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AnalysisState.cs @@ -0,0 +1,37 @@ +// +// AnalysisState.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace MonoDevelop.CodeIssues +{ + public enum AnalysisState { + NeverStarted, + Running, + Completed, + Cancelled, + Error + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AnalysisStateChangeEventArgs.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AnalysisStateChangeEventArgs.cs new file mode 100644 index 0000000000..7531189f08 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/AnalysisStateChangeEventArgs.cs @@ -0,0 +1,50 @@ +// +// AnalysisStateChangeEventArgs.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public class AnalysisStateChangeEventArgs : EventArgs + { + public AnalysisStateChangeEventArgs (AnalysisState oldState, AnalysisState newState) + { + OldState = oldState; + NewState = newState; + } + + public AnalysisState OldState { + get; + private set; + } + + public AnalysisState NewState { + get; + private set; + } + } + +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/BatchFixer.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/BatchFixer.cs new file mode 100644 index 0000000000..c4c6491f05 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/BatchFixer.cs @@ -0,0 +1,141 @@ +// +// BatchFixer.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Linq; +using System.Collections.Generic; +using MonoDevelop.Ide; +using ICSharpCode.NRefactory.CSharp.Resolver; +using MonoDevelop.Core; +using System.Threading.Tasks; +using MonoDevelop.Refactoring; +using ICSharpCode.NRefactory.Refactoring; +using System.Threading; +using MonoDevelop.Projects; +using MonoDevelop.Ide.TypeSystem; +using ICSharpCode.NRefactory.CSharp; +using System.Text; +using MonoDevelop.Ide.Editor; +using MonoDevelop.Core.Text; + +namespace MonoDevelop.CodeIssues +{ + public class BatchFixer + { + readonly IActionMatcher matcher; + + readonly ProgressMonitor monitor; + + public BatchFixer (IActionMatcher matcher, ProgressMonitor monitor) + { + this.matcher = matcher; + this.monitor = monitor; + } + + /// <summary> + /// Tries to apply the actions passed in <paramref name="actions"/>. + /// </summary> + /// <param name="actions">The actions to apply.</param> + /// <returns>The fixed code actions.</returns> + public IEnumerable<ActionSummary> TryFixIssues (IEnumerable<ActionSummary> actions) + { + if (actions == null) + throw new ArgumentNullException ("actions"); + + // enumerate once + var actionSummaries = actions as IList<ActionSummary> ?? actions.ToList (); + var issueSummaries = actionSummaries.Select (action => action.IssueSummary).ToList (); + var files = issueSummaries.Select (issue => issue.File).Distinct ().ToList (); + monitor.BeginTask ("Applying fixes", files.Count); + + var appliedActions = new List<ActionSummary> (issueSummaries.Count); + Parallel.ForEach (files, file => { + monitor.Step (1); + + var fileSummaries = issueSummaries.Where (summary => summary.File == file); + var inspectorIds = new HashSet<string> (fileSummaries.Select (summary => summary.InspectorIdString)); + + bool isOpen; + var data = TextFileProvider.Instance.GetTextEditorData (file.FilePath, out isOpen); + IRefactoringContext refactoringContext; + var realActions = GetIssues (data, file, inspectorIds, out refactoringContext).SelectMany (issue => issue.Actions).ToList (); + if (realActions.Count == 0 || refactoringContext == null) + return; + + var fileActionSummaries = actionSummaries.Where (summary => summary.IssueSummary.File == file).ToList (); + var matches = matcher.Match (fileActionSummaries, realActions).ToList (); + + var appliedFixes = RefactoringService.ApplyFixes (matches.Select (match => match.Action), refactoringContext); + appliedActions.AddRange (matches.Where (match => appliedFixes.Contains (match.Action)).Select (match => match.Summary)); + + if (!isOpen) { + // If the file is open we leave it to the user to explicitly save the file + data.Save (); + } + }); + return appliedActions; + } + + static IList<CodeIssue> GetIssues (ITextDocument data, ProjectFile file, ISet<string> inspectorIds, out IRefactoringContext refactoringContext) + { + var issues = new List<CodeIssue> (); +// +// var document = TypeSystemService.ParseFile (file.Project, data); +// if (document == null) { + refactoringContext = null; +// return issues; +// } +// +// var content = TypeSystemService.GetProjectContext (file.Project); +// var compilation = content.AddOrUpdateFiles (document.ParsedFile).CreateCompilation (); +// var resolver = new CSharpAstResolver (compilation, document.GetAst<SyntaxTree> (), document.ParsedFile as ICSharpCode.NRefactory.CSharp.TypeSystem.CSharpUnresolvedFile); +// +// refactoringContext = document.CreateRefactoringContextWithEditor (data, resolver, CancellationToken.None); +// var context = refactoringContext; +// foreach (var provider in GetInspectors (data, inspectorIds)) { +// var severity = provider.GetSeverity (); +// if (severity == Severity.None || !provider.GetIsEnabled ()) +// continue; +// try { +// lock (issues) { +// issues.AddRange (provider.GetIssues (context, CancellationToken.None)); +// } +// } catch (Exception ex) { +// LoggingService.LogError ("Error while running code issue on: " + data.FileName, ex); +// } +// } + return issues; + } + + static IList<CodeIssueProvider> GetInspectors (IReadonlyTextDocument editor, ICollection<string> inspectorIds) + { + var inspectors = RefactoringService.GetInspectors (editor.MimeType).ToList (); + return inspectors + .Where (inspector => inspectorIds.Contains (inspector.IdString)) + .ToList (); + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CategoryGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CategoryGroupingProvider.cs new file mode 100644 index 0000000000..bb7094f20d --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CategoryGroupingProvider.cs @@ -0,0 +1,47 @@ +// +// ProjectGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace MonoDevelop.CodeIssues +{ + // TODO: should this be a threadsafe class? + // Our current usage is fine, but could be nice anyway (and might avoid future bugs) + [GroupingDescription("Category")] + public class CategoryGroupingProvider: AbstractGroupingProvider<string> + { + #region implemented abstract members of AbstractGroupingProvider + protected override string GetGroupingKey (IssueSummary issue) + { + return issue.ProviderCategory; + } + + protected override string GetGroupName (IssueSummary issue) + { + return issue.ProviderCategory; + } + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CodeAnalysisBatchRunner.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CodeAnalysisBatchRunner.cs new file mode 100644 index 0000000000..a2e9b91a5e --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CodeAnalysisBatchRunner.cs @@ -0,0 +1,163 @@ +// +// CodeIssueBatchRunner.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Linq; +using System.Threading; +using MonoDevelop.Ide; +using MonoDevelop.Projects; +using MonoDevelop.Ide.TypeSystem; +using System.Threading.Tasks; +using System.IO; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.Refactoring; +using MonoDevelop.Core; +using System.Collections.Concurrent; +using ICSharpCode.NRefactory.TypeSystem; +using MonoDevelop.Core.Instrumentation; +using MonoDevelop.Refactoring; +using System.Collections.Generic; +using MonoDevelop.Core; + +namespace MonoDevelop.CodeIssues +{ + public class CodeAnalysisBatchRunner + { + private readonly object _lock = new object (); + + private int workerCount; + + private readonly AnalysisJobQueue jobQueue = new AnalysisJobQueue (); + + public IJobContext QueueJob (IAnalysisJob job) + { + jobQueue.Add (job); + EnsureRunning (); + return new JobContext (job, jobQueue, this); + } + + private void EnsureRunning () + { + for (; Interlocked.Add (ref workerCount, 1) < Environment.ProcessorCount;) { + new Thread (() => { + try { + ProcessQueue (); + } + finally { + Interlocked.Add (ref workerCount, -1); + } + }).Start (); + } + } + + private void ProcessQueue () + { + while (true) { + try { + using (var slice = GetSlice ()) { + if (slice == null) + // TODO: Do something smarter if the queue is empty + return; + AnalyzeFile (slice, slice.GetJobs ().SelectMany (job => job.GetIssueProviders (slice.File))); + } + } catch (Exception e) { + LoggingService.LogError ("Unhandled exception", e); + MessageService.ShowException (e); + } + } + } + + private JobSlice GetSlice () + { + lock (_lock) { + return jobQueue.Dequeue (1).FirstOrDefault (); + } + } + + void AnalyzeFile (JobSlice item, IEnumerable<BaseCodeIssueProvider> codeIssueProviders) + { +// var file = item.File; +// +// if (file.BuildAction != BuildAction.Compile) +// return; +// +// if (!(file.Project is DotNetProject)) +// return; +// +// TextEditorData editor; +// try { +// editor = TextFileProvider.Instance.GetReadOnlyTextEditorData (file.FilePath); +// } catch (FileNotFoundException) { +// // Swallow exception and ignore this file +// return; +// } +// var document = TypeSystemService.ParseFile (file.Project, editor); +// if (document == null) +// return; +// +// var content = TypeSystemService.GetProjectContext (file.Project); +// var compilation = content.AddOrUpdateFiles (document.ParsedFile).CreateCompilation (); +// +// CSharpAstResolver resolver; +// using (var timer = ExtensionMethods.ResolveCounter.BeginTiming ()) { +// resolver = new CSharpAstResolver (compilation, document.GetAst<SyntaxTree> (), document.ParsedFile as ICSharpCode.NRefactory.CSharp.TypeSystem.CSharpUnresolvedFile); +// try { +// resolver.ApplyNavigator (new ExtensionMethods.ConstantModeResolveVisitorNavigator (ResolveVisitorNavigationMode.Resolve, null)); +// } catch (Exception e) { +// LoggingService.LogError ("Error while applying navigator", e); +// } +// } +// var context = document.CreateRefactoringContextWithEditor (editor, resolver, CancellationToken.None); +// +// foreach (var provider in codeIssueProviders) { +// if (item.CancellationToken.IsCancellationRequested) +// break; +// IList<IAnalysisJob> jobs; +// lock (_lock) +// jobs = item.GetJobs ().ToList (); +// var jobsForProvider = jobs.Where (j => j.GetIssueProviders (file).Contains (provider)).ToList(); +// try { +// var issues = provider.GetIssues (context, CancellationToken.None).ToList (); +// foreach (var job in jobsForProvider) { +// // Call AddResult even if issues.Count == 0, to enable a job implementation to keep +// // track of progress information. +// job.AddResult (file, provider, issues); +// } +// } catch (OperationCanceledException) { +// // The operation was cancelled, no-op as the user-visible parts are +// // handled elsewhere +// } catch (Exception e) { +// foreach (var job in jobsForProvider) { +// job.AddError (file, provider); +// } +// } +// } + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CodeIssuePad.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CodeIssuePad.cs new file mode 100644 index 0000000000..c875894512 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/CodeIssuePad.cs @@ -0,0 +1,534 @@ +// +// CodeIssuePad.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using MonoDevelop.Ide.Gui; +using Xwt; +using MonoDevelop.Ide; +using MonoDevelop.Projects; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using MonoDevelop.Refactoring;
+using Xwt.Drawing;
+using IconSize = Gtk.IconSize; +using MonoDevelop.Core;
+
+namespace MonoDevelop.CodeIssues +{ + public class CodeIssuePadControl : VBox + { + const int UpdatePeriod = 500; + const int BatchChoiceCount = 5; + + readonly TreeView view = new TreeView (); + readonly DataField<string> textField = new DataField<string> (); + readonly DataField<IIssueTreeNode> nodeField = new DataField<IIssueTreeNode> (); + readonly Button runButton = new Button ("Run"); + readonly Button cancelButton = new Button ("Cancel"); + + readonly IssueGroup rootGroup; + + readonly TreeStore store; + + readonly ISet<IIssueTreeNode> syncedNodes = new HashSet<IIssueTreeNode> (); + readonly Dictionary<IIssueTreeNode, TreePosition> nodePositions = new Dictionary<IIssueTreeNode, TreePosition> (); + + bool runPeriodicUpdate; + readonly object queueLock = new object (); + readonly Queue<IIssueTreeNode> updateQueue = new Queue<IIssueTreeNode> (); + + IJobContext currentJobContext; + + public IJobContext CurrentJobContext { + get { + return currentJobContext; + } + set { + currentJobContext = value; + bool working = currentJobContext != null; + runButton.Sensitive = !working; + cancelButton.Sensitive = working; + } + } + + static readonly Type[] groupingProviders = { + typeof(CategoryGroupingProvider), + typeof(ProviderGroupingProvider), + typeof(SeverityGroupingProvider), + typeof(ProjectGroupingProvider), + typeof(FileGroupingProvider) + }; + + public CodeIssuePadControl () + { + var buttonRow = new HBox();
+ runButton.Image = ImageService.GetIcon (Ide.Gui.Stock.Execute, IconSize.Menu); + runButton.Clicked += StartAnalyzation; + buttonRow.PackStart (runButton);
+
+ cancelButton.Image = ImageService.GetIcon (Ide.Gui.Stock.Stop, IconSize.Menu); + cancelButton.Clicked += StopAnalyzation; + cancelButton.Sensitive = false; + buttonRow.PackStart (cancelButton); + var groupingProvider = new CategoryGroupingProvider { + Next = new ProviderGroupingProvider() + }; + rootGroup = new IssueGroup (groupingProvider, "root group"); + var groupingProviderControl = new GroupingProviderChainControl (rootGroup, groupingProviders); + buttonRow.PackStart (groupingProviderControl); + + PackStart (buttonRow); + + store = new TreeStore (textField, nodeField); + view.DataSource = store; + view.HeadersVisible = false; + view.Columns.Add ("Name", textField); + view.SelectionMode = SelectionMode.Multiple; + + view.RowActivated += OnRowActivated; + view.RowExpanding += OnRowExpanding; + view.ButtonPressed += HandleButtonPressed; + view.ButtonReleased += HandleButtonReleased; + PackStart (view, true); + + IIssueTreeNode node = rootGroup; + node.ChildrenInvalidated += (sender, group) => { + Application.Invoke (delegate { + ClearSiblingNodes (store.GetFirstNode ()); + store.Clear (); + foreach(var child in ((IIssueTreeNode)rootGroup).Children) { + var navigator = store.AddNode (); + SetNode (navigator, child); + SyncNode (navigator); + } + }); + }; + node.ChildAdded += HandleRootChildAdded; + + IdeApp.Workspace.LastWorkspaceItemClosed += HandleLastWorkspaceItemClosed; + }
+
+ void HandleLastWorkspaceItemClosed (object sender, EventArgs e) + { + ClearState (); + } + + void ClearState () + { + store.Clear (); + rootGroup.ClearStatistics (); + rootGroup.EnableProcessing (); + + syncedNodes.Clear (); + nodePositions.Clear (); + lock (queueLock) { + updateQueue.Clear (); + } + } + + void StartPeriodicUpdate () + { + Debug.Assert (!runPeriodicUpdate); + runPeriodicUpdate = true; + Application.TimeoutInvoke (UpdatePeriod, RunPeriodicUpdate); + } + + void ProcessUpdateQueue () + { + IList<IIssueTreeNode> nodes; + lock (queueLock) { + nodes = new List<IIssueTreeNode> (updateQueue); + updateQueue.Clear (); + } + foreach (var node in nodes) { + TreePosition position; + if (!nodePositions.TryGetValue (node, out position)) { + // This might be an event for a group that has been invalidated and removed + continue; + } + var navigator = store.GetNavigatorAt (position); + if (!node.Visible) { + // Check above means node is always in nodePositions + nodePositions.Remove (node); + if (syncedNodes.Contains (node)) { + syncedNodes.Remove (node); + } + ClearChildNodes (navigator); + navigator.Remove (); + continue; + } + UpdateText (navigator, node); + if (!syncedNodes.Contains (node) && node.HasVisibleChildren) { + if (navigator.MoveToChild ()) { + navigator.MoveToParent (); + } + else { + AddDummyChild (navigator); + } + } + } + } + + bool RunPeriodicUpdate () + { + ProcessUpdateQueue (); + return runPeriodicUpdate; + } + + void EndPeriodicUpdate () + { + Debug.Assert (runPeriodicUpdate); + runPeriodicUpdate = false; + } + + void HandleRootChildAdded (object sender, IssueTreeNodeEventArgs e) + { + Application.Invoke (delegate { + Debug.Assert (e.Parent == rootGroup); + var navigator = store.AddNode (); + SetNode (navigator, e.Child); + SyncNode (navigator); + }); + } + + void StartAnalyzation (object sender, EventArgs e) + { + var solution = IdeApp.ProjectOperations.CurrentSelectedSolution; + if (solution == null) + return; + + ClearState (); + + var job = new SolutionAnalysisJob (solution); + job.CodeIssueAdded += HandleCodeIssueAdded; + job.Completed += delegate { + CurrentJobContext = null; + }; + CurrentJobContext = RefactoringService.QueueCodeIssueAnalysis (job, "Analyzing solution"); + StartPeriodicUpdate (); + } + + void HandleCodeIssueAdded (object sender, CodeIssueEventArgs e) + { + foreach (var issue in e.CodeIssues) { + var summary = IssueSummary.FromCodeIssue (e.File, e.Provider, issue); + rootGroup.AddIssue (summary); + } + } + + void StopAnalyzation (object sender, EventArgs e) + { + if (CurrentJobContext != null) { + CurrentJobContext.CancelJob (); + CurrentJobContext = null; + } + EndPeriodicUpdate (); + } + + void SetNode (TreeNavigator navigator, IIssueTreeNode node) + { + if (navigator == null) + throw new ArgumentNullException ("navigator"); + if (node == null) + throw new ArgumentNullException ("node"); + + navigator.SetValue (nodeField, node); + Debug.Assert (!nodePositions.ContainsKey (node)); + var position = navigator.CurrentPosition; + nodePositions.Add (node, position); + + node.ChildAdded += (sender, e) => { + Debug.Assert (e.Parent == node); + Application.Invoke (delegate { + var newNavigator = store.GetNavigatorAt (position); + newNavigator.AddChild (); + SetNode (newNavigator, e.Child); + SyncNode (newNavigator); + }); + }; + node.ChildrenInvalidated += (sender, e) => { + Application.Invoke (delegate { + SyncNode (store.GetNavigatorAt (position)); + }); + }; + node.TextChanged += (sender, e) => { + lock (queueLock) { + if (!updateQueue.Contains (e.Node)) { + updateQueue.Enqueue (e.Node); + } + } + }; + node.VisibleChanged += (sender, e) => { + lock (queueLock) { + if (!updateQueue.Contains (e.Node)) { + updateQueue.Enqueue (e.Node); + } + } + }; + } + + void ClearSiblingNodes (TreeNavigator navigator) + { + if (navigator.CurrentPosition == null) + return; + + do { + var node = navigator.GetValue (nodeField); + if (node != null) { + if (syncedNodes.Contains (node)) { + syncedNodes.Remove (node); + } + if (nodePositions.ContainsKey (node)) { + nodePositions.Remove (node); + } + } + ClearChildNodes (navigator); + } while (navigator.MoveNext ()); + } + + void ClearChildNodes (TreeNavigator navigator) + { + if (navigator.MoveToChild ()) { + ClearSiblingNodes (navigator); + navigator.MoveToParent (); + } + } + + void SyncNode (TreeNavigator navigator, bool forceExpansion = false) + { + var node = navigator.GetValue (nodeField); + UpdateText (navigator, node); + bool isExpanded = forceExpansion || view.IsRowExpanded (navigator.CurrentPosition); + ClearChildNodes (navigator); + syncedNodes.Remove (node); + navigator.RemoveChildren (); + if (!node.HasVisibleChildren) + return; + if (isExpanded) { + foreach (var childNode in node.Children.Where (child => child.Visible)) { + navigator.AddChild (); + SetNode (navigator, childNode); + SyncNode (navigator); + navigator.MoveToParent (); + } + } else { + AddDummyChild (navigator); + } + + if (isExpanded) { + syncedNodes.Add (node); + view.ExpandRow (navigator.CurrentPosition, false); + } + + } + + void UpdateText (TreeNavigator navigator, IIssueTreeNode node) + { + navigator.SetValue (textField, node.Text); + } + + void AddDummyChild (TreeNavigator navigator) + { + navigator.AddChild (); + navigator.SetValue (textField, "Loading..."); + navigator.MoveToParent (); + } + + EventHandler<IssueGroupEventArgs> GetChildrenInvalidatedHandler (TreePosition position) + { + return (sender, eventArgs) => { + Application.Invoke(delegate { + var expanded = view.IsRowExpanded (position); + var newNavigator = store.GetNavigatorAt (position); + newNavigator.RemoveChildren (); + SyncNode (newNavigator, expanded); + if (expanded) { + view.ExpandRow (position, false); + } + }); + }; + } + + void OnRowActivated (object sender, TreeViewRowEventArgs e) + { + var position = e.Position; + var node = store.GetNavigatorAt (position).GetValue (nodeField); + + var issueSummary = node as IssueSummary; + if (issueSummary != null) { + var region = issueSummary.Region; + IdeApp.Workbench.OpenDocument (region.FileName, region.BeginLine, region.BeginColumn); + } else { + if (!view.IsRowExpanded (position)) { + view.ExpandRow (position, false); + } else { + view.CollapseRow (position); + } + } + } + + void OnRowExpanding (object sender, TreeViewRowEventArgs e) + { + var navigator = store.GetNavigatorAt (e.Position); + var node = navigator.GetValue (nodeField); + if (!syncedNodes.Contains (node)) { + SyncNode (navigator, true); + } + } + + #region Button event handlers + // Event handling of right click on the TreeView is split in two parts + // This is because no single handler can support intuitive behavior regarding + // what happens to the selection when the right mouse button is pressed: + // if only a single row is selected: change the selection and then show menu + // if multiple rows are selected: show the menu directly + + void HandleButtonReleased (object sender, ButtonEventArgs e) + { + if (e.Button != PointerButton.Right || handledByPress) + return; + + var rows = view.SelectedRows; + if (rows.Length <= 1) { + // Single row or no row + ShowBatchFixContextMenu (e.X, e.Y, view.SelectedRows); + } + } + + bool handledByPress; + + void HandleButtonPressed (object sender, ButtonEventArgs e) + { + if (e.Button != PointerButton.Right) + return; + + var rows = view.SelectedRows; + if (rows.Length > 1) { + // this is a multiple selection + // waiting in this case means the selection disappears + ShowBatchFixContextMenu (e.X, e.Y, rows); + + // Don't let the selection be reset + e.Handled = true; + handledByPress = true; + } else { + handledByPress = false; + } + } + + #endregion + + void UpdateParents (TreeNavigator navigator) + { + do { + var node = navigator.GetValue (nodeField); + UpdateText (navigator, node); + } while (navigator.MoveToParent ()); + } + + void ShowBatchFixContextMenu (double x, double y, IEnumerable<TreePosition> rows) + { + var possibleFixes = rows + .Select (row => store.GetNavigatorAt (row).GetValue (nodeField)) + .Where (node1 => node1 != null) + .SelectMany (node2 => node2.AllChildren.Union (new [] { node2 })) + .Where (node3 => node3.Visible) + .OfType<IssueSummary> () + .Where (issue => issue.Actions.Any (a => a.Batchable)) + .Distinct() + .GroupBy(issue => issue.InspectorIdString) + .OrderBy (group => -group.Count ()); + + var groups = possibleFixes.Take (BatchChoiceCount).ToList (); + if (!groups.Any ()) + return; + + if (groups.Count == 1) { + CreateIssueMenu (groups.First ()).Popup (view, x, y); + } else { + var menu = new Menu (); + foreach (var g in groups) { + var menuItem = new MenuItem (g.First ().ProviderTitle); + menuItem.SubMenu = CreateIssueMenu (g); + menu.Items.Add (menuItem); + } + menu.Popup (view, x, y); + } + } + + Menu CreateIssueMenu (IEnumerable<IssueSummary> issues) + { + var allIssues = issues as IList<IssueSummary> ?? issues.ToList (); + var issueMenu = new Menu (); + + var actionGroups = allIssues + .SelectMany (issue => issue.Actions) + .GroupBy (action => action.SiblingKey); + foreach (var _actionGroup in actionGroups) { + var actionGroup = _actionGroup; + + var actionMenuItem = new MenuItem (actionGroup.First ().Title); + actionMenuItem.Clicked += delegate { + ThreadPool.QueueUserWorkItem (delegate { + try { + using (var monitor = IdeApp.Workbench.ProgressMonitors.GetStatusProgressMonitor ("Applying fixes", null, false)) { + var fixer = new BatchFixer (new ExactIssueMatcher (), monitor); + var appliedActions = fixer.TryFixIssues (actionGroup); + foreach (var action in appliedActions) { + ((IIssueTreeNode)action.IssueSummary).Visible = false; + } + } + Application.Invoke (delegate { + ProcessUpdateQueue (); + }); + } catch (Exception e) { + LoggingService.LogInternalError (e); + } + }); + }; + issueMenu.Items.Add (actionMenuItem); + } + return issueMenu; + } + + } + + public class CodeIssuePad : AbstractPadContent + { + CodeIssuePadControl issueControl; + + public override Gtk.Widget Control { + get { + if (issueControl == null) + issueControl = new CodeIssuePadControl (); + return (Gtk.Widget)Toolkit.CurrentEngine.GetNativeWidget (issueControl); + } + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ExactIssueMatcher.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ExactIssueMatcher.cs new file mode 100644 index 0000000000..3f1db8df72 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ExactIssueMatcher.cs @@ -0,0 +1,52 @@ +// +// ExactIssueMatcher.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Collections.Generic; +using MonoDevelop.CodeActions; +using System.Linq; + +namespace MonoDevelop.CodeIssues +{ + public class ExactIssueMatcher: IActionMatcher + { + #region IIssueMatcher implementation + + public IEnumerable<IssueMatch> Match (IList<ActionSummary> summaries, IList<CodeAction> realActions) + { + var summaryLookup = summaries.ToLookup (summary => summary.Region); + foreach (var action in realActions) { + if (summaryLookup.Contains (action.DocumentRegion)) { + yield return new IssueMatch { + Action = action, + Summary = summaryLookup[action.DocumentRegion].First () + }; + } + } + } + + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/FileGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/FileGroupingProvider.cs new file mode 100644 index 0000000000..aa1d64d7cf --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/FileGroupingProvider.cs @@ -0,0 +1,46 @@ +// +// FileGroupingProvider.cs +// +// Author: +// Marius Ungureanu <marius.ungureanu@xamarin.com> +// +// Copyright (c) 2013 Marius Ungureanu +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using MonoDevelop.Projects; +using MonoDevelop.Core; + +namespace MonoDevelop.CodeIssues +{ + [GroupingDescription("File")] + public class FileGroupingProvider : AbstractGroupingProvider<ProjectFile> + { + #region implemented abstract members of AbstractGroupingProvider + protected override ProjectFile GetGroupingKey (IssueSummary issue) + { + return issue.File; + } + protected override string GetGroupName (IssueSummary issue) + { + return issue.File.FilePath.ToRelative (issue.Project.BaseDirectory); + } + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingDescriptionAttribute.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingDescriptionAttribute.cs new file mode 100644 index 0000000000..720084149e --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingDescriptionAttribute.cs @@ -0,0 +1,40 @@ +// +// GroupingDescriptionAttribute.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public class GroupingDescriptionAttribute: Attribute + { + public string Title { get; private set; } + + public GroupingDescriptionAttribute (string title) + { + this.Title = title; + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingProviderChainControl.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingProviderChainControl.cs new file mode 100644 index 0000000000..3449d7b24b --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingProviderChainControl.cs @@ -0,0 +1,153 @@ +// +// GroupingProviderChainControl.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Linq; +using Xwt; +using System.Collections.Generic; +using MonoDevelop.Core; + +namespace MonoDevelop.CodeIssues +{ + public class GroupingProviderChainControl: HBox + { + readonly IList<Type> availableProviders; + + readonly IList<ComboBox> providerPickers = new List<ComboBox>(); + readonly IList<Label> labels = new List<Label>(); + + public GroupingProviderChainControl(IssueGroup rootGroup, IEnumerable<Type> providers) + { + this.RootGroupingProvider = new GroupingProvider (rootGroup); + availableProviders = providers.ToList(); + + BuildUi (); + } + + public IGroupingProvider RootGroupingProvider { + get; + private set; + } + + void BuildUi () + { + Clear (); + var label = new Label ("Group by:"); + labels.Add (label); + PackStart (label); + BuildProviderSelectors (RootGroupingProvider, RootGroupingProvider.Next); + } + + void BuildProviderSelectors (IGroupingProvider previousProvider, IGroupingProvider selectedProvider) + { + var combo = MakeSelector (selectedProvider); + combo.SelectionChanged += (sender, e) => { + var selectedType = combo.SelectedItem as Type; + if (selectedType == null) + return; + var newProvider = (IGroupingProvider)Activator.CreateInstance(selectedType); + + if (newProvider.SupportsNext && selectedProvider.SupportsNext) { + newProvider.Next = selectedProvider.Next; + } + previousProvider.Next = newProvider; + BuildUi (); + }; + providerPickers.Add (combo); + PackStart (combo); + + if (selectedProvider.SupportsNext) { + PackStart (new Label ("then by")); + BuildProviderSelectors (selectedProvider, selectedProvider.Next); + } + } + + ComboBox MakeSelector (IGroupingProvider selectedProvider) + { + var combo = new ComboBox (); + combo.Items.Add (typeof(NullGroupingProvider), "Nothing"); + combo.Items.Add (ItemSeparator.Instance); + foreach (var providerType in availableProviders) { + var metadata = (GroupingDescriptionAttribute)providerType.GetCustomAttributes (false).FirstOrDefault (attr => attr is GroupingDescriptionAttribute); + if (metadata == null) { + LoggingService.LogWarning ("Grouping provider '{0}' does not have a metadata attribute, ignoring provider.", providerType.FullName); + continue; + } + combo.Items.Add (providerType, metadata.Title); + } + if (selectedProvider != null) { + combo.SelectedItem = selectedProvider.GetType (); + } else { + combo.SelectedItem = typeof(NullGroupingProvider); + } + return combo; + } + + class GroupingProvider: IGroupingProvider + { + readonly IssueGroup rootGroup; + + public GroupingProvider (IssueGroup rootGroup) + { + this.rootGroup = rootGroup; + next = rootGroup.GroupingProvider; + } + + #region IGroupingProvider implementation + + public event EventHandler<GroupingProviderEventArgs> NextChanged; + + public IssueGroup GetIssueGroup (IssueGroup parent, IssueSummary issue) + { + throw new NotImplementedException (); + } + + public void Reset () + { + throw new NotImplementedException (); + } + + IGroupingProvider next; + public IGroupingProvider Next { + get { + return next; + } + set { + next = value; + rootGroup.GroupingProvider = value; + } + } + + public bool SupportsNext { + get { + return true; + } + } + #endregion + + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingProviderEventArgs.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingProviderEventArgs.cs new file mode 100644 index 0000000000..f9eee0fc87 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/GroupingProviderEventArgs.cs @@ -0,0 +1,52 @@ +// +// IGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public class GroupingProviderEventArgs : EventArgs + { + public GroupingProviderEventArgs(IGroupingProvider provider, IGroupingProvider oldNext) + { + GroupingProvider = provider; + OldNext = oldNext; + } + + /// <summary> + /// The <see cref="IGroupingProvider"/> whose Next reference has changed. + /// </summary> + /// <value>The grouping provider.</value> + public IGroupingProvider GroupingProvider { get; private set; } + + /// <summary> + /// The old value of the Next reference. + /// </summary> + /// <value>The old value.</value> + public IGroupingProvider OldNext { get; private set; } + } + +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IActionMatcher.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IActionMatcher.cs new file mode 100644 index 0000000000..8e732f596a --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IActionMatcher.cs @@ -0,0 +1,36 @@ +// +// IIssueMatcher.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Collections.Generic; +using MonoDevelop.CodeActions; + +namespace MonoDevelop.CodeIssues +{ + public interface IActionMatcher + { + IEnumerable<IssueMatch> Match (IList<ActionSummary> summaries, IList<CodeAction> realIssues); + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IGroupingProvider.cs new file mode 100644 index 0000000000..19b0001bf4 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IGroupingProvider.cs @@ -0,0 +1,67 @@ +// +// IGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public interface IGroupingProvider + { + /// <summary> + /// Gets the issue group for the <see cref="IssueGroup"/> specified in <paramref name="issue"/>. + /// </summary> + /// <returns>The issue group.</returns> + /// <param name="parentGroup">The parent group.</param> + /// <param name="issue">The <see cref="IssueSummary"/> to return a group for.</param> + IssueGroup GetIssueGroup(IssueGroup parentGroup, IssueSummary issue); + + /// <summary> + /// Removes the set of cached groups. + /// </summary> + void Reset (); + + /// <summary> + /// The <see cref="IGroupingProvider"/> to be applied after the current instance. Never returns null. + /// </summary> + /// <value>The next.</value> + /// <exception cref="InvalidOperationException"> + /// Thrown by both accessors if <see cref="SupportsNext"/> is false. + /// </exception> + IGroupingProvider Next { get; set; } + + /// <summary> + /// Occurs when <see cref="Next"/> changes. + /// </summary> + event EventHandler<GroupingProviderEventArgs> NextChanged; + + /// <summary> + /// Gets a value indicating whether this <see cref="MonoDevelop.CodeIssues.IGroupingProvider"/> + /// supports the usage of <see cref="Next"/> . + /// </summary> + /// <value>True if <see cref="Next"/> can be used.</value> + bool SupportsNext { get; } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IIssueSummarySink.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IIssueSummarySink.cs new file mode 100644 index 0000000000..af1cb51c0c --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IIssueSummarySink.cs @@ -0,0 +1,40 @@ +// +// IAnalysisResultSink.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public interface IIssueSummarySink + { + + /// <summary> + /// Adds a <see cref="IssueSummary"/> to the sink. + /// </summary> + /// <param name="issueSummary">The Issue summary.</param> + void AddIssue (IssueSummary issueSummary); + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IIssueTreeNode.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IIssueTreeNode.cs new file mode 100644 index 0000000000..68cf43ef50 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IIssueTreeNode.cs @@ -0,0 +1,87 @@ +// +// IIssueTreeNode.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections; +using System.Collections.Generic; + +namespace MonoDevelop.CodeIssues +{ + public interface IIssueTreeNode + { + /// <summary> + /// The text that should be displayed in the ui. + /// </summary> + /// <value>The text.</value> + string Text { get; } + + /// <summary> + /// The children of this instance. + /// </summary> + /// <value>The children.</value> + ICollection<IIssueTreeNode> Children { get; } + + /// <summary> + /// Indicates whether this instance has any children. + /// </summary> + /// <value><c>true</c> if this instance has children; otherwise, <c>false</c>.</value> + bool HasVisibleChildren { + get; + } + + /// <summary> + /// Indicates whether this node should be shown in the ui. + /// </summary> + /// <value><c>true</c> if the current instance should be shown; otherwise, <c>false</c>.</value> + bool Visible { get; set; } + + /// <summary> + /// Gets all children including nested children of this instance. + /// </summary> + /// <value>All children.</value> + ICollection<IIssueTreeNode> AllChildren { get; } + + /// <summary> + /// Occurs when children of this node are invalidated. + /// </summary> + event EventHandler<IssueGroupEventArgs> ChildrenInvalidated; + + /// <summary> + /// Occurs when children of this node are invalidated. + /// </summary> + event EventHandler<IssueTreeNodeEventArgs> ChildAdded; + + /// <summary> + /// Occurs when <see cref="Text"/> is updated. + /// </summary> + event EventHandler<IssueGroupEventArgs> TextChanged; + + /// <summary> + /// Occurs when <see cref="Visible"/> is updated. + /// </summary> + event EventHandler<IssueGroupEventArgs> VisibleChanged; + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueGroup.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueGroup.cs new file mode 100644 index 0000000000..10e261b7fc --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueGroup.cs @@ -0,0 +1,326 @@ +// +// IGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Linq; +using System.Collections.Generic; + +namespace MonoDevelop.CodeIssues +{ + /// <summary> + /// Represents the cached information saved for each issue detected in a source file. + /// </summary> + /// <remarks> + /// This class is thread safe. + /// </remarks> + public class IssueGroup : IIssueTreeNode, IIssueSummarySink + { + readonly object _lock = new object (); + bool processingEnabled; + /// <summary> + /// A list of groups produced by the <see cref="groupingProvider"/>. + /// </summary> + readonly ISet<IssueGroup> groups = new HashSet<IssueGroup>(); + readonly IList<IIssueTreeNode> children = new List<IIssueTreeNode>(); + readonly ISet<IssueSummary> allIssues = new HashSet<IssueSummary>(); + + /// <summary> + /// Initializes a new instance of the <see cref="MonoDevelop.CodeIssues.IssueGroup"/> class. + /// </summary> + /// <param name="nextProvider"> + /// The <see cref="IGroupingProvider"/> to use when grouping <see cref="IssueSummary"/> instances. + /// </param> + /// <param name="description">A string describing the contents of this group.</param> + public IssueGroup (IGroupingProvider nextProvider, string description) + { + groupingProvider = nextProvider; + Description = description; + processingEnabled = false; + } + + IGroupingProvider groupingProvider; + + public IGroupingProvider GroupingProvider { + get { + return groupingProvider; + } + set { + lock(_lock) { + processingEnabled = false; + groupingProvider = value; + groups.Clear (); + children.Clear (); + } + OnChildrenInvalidated (new IssueGroupEventArgs(this)); + } + } + + #region IIssueTreeNode implementation + + string IIssueTreeNode.Text { + get { + lock (_lock) { + return string.Format ("{0} ({1})", Description, allIssues.Count (issue => ((IIssueTreeNode)issue).Visible)); + } + } + } + + ICollection<IIssueTreeNode> IIssueTreeNode.Children { + get { + EnableProcessing (); + lock (_lock) { + return new List<IIssueTreeNode> (children); + } + } + } + + bool IIssueTreeNode.HasVisibleChildren { + get { + lock (_lock) { + return allIssues.Any (issue => ((IIssueTreeNode)issue).Visible); + } + } + } + + bool IIssueTreeNode.Visible { + get { + return ((IIssueTreeNode)this).HasVisibleChildren; + } + + set { + throw new InvalidOperationException ("Not supported"); + } + } + + ICollection<IIssueTreeNode> IIssueTreeNode.AllChildren { + get { + lock (_lock) { + return new List<IIssueTreeNode> (allIssues); + } + } + } + + event EventHandler<IssueGroupEventArgs> childrenInvalidated; + + event EventHandler<IssueGroupEventArgs> IIssueTreeNode.ChildrenInvalidated { + add { + childrenInvalidated += value; + } + remove { + childrenInvalidated -= value; + } + } + + protected virtual void OnChildrenInvalidated (IssueGroupEventArgs eventArgs) + { + var handler = childrenInvalidated; + if (handler != null) { + handler (this, eventArgs); + } + } + + event EventHandler<IssueTreeNodeEventArgs> childAdded; + + event EventHandler<IssueTreeNodeEventArgs> IIssueTreeNode.ChildAdded { + add { + childAdded += value; + } + remove { + childAdded -= value; + } + } + + protected virtual void OnChildAdded (IssueTreeNodeEventArgs eventArgs) + { + var handler = childAdded; + if (handler != null) { + handler (this, eventArgs); + } + } + + event EventHandler<IssueGroupEventArgs> textChanged; + + event EventHandler<IssueGroupEventArgs> IIssueTreeNode.TextChanged { + add { + textChanged += value; + } + remove { + textChanged -= value; + } + } + + protected virtual void OnTextChanged (IssueGroupEventArgs eventArgs) + { + var handler = textChanged; + if (handler != null) { + handler (this, eventArgs); + } + } + + event EventHandler<IssueGroupEventArgs> visibleChanged; + + event EventHandler<IssueGroupEventArgs> IIssueTreeNode.VisibleChanged { + add { + visibleChanged += value; + } + remove { + visibleChanged -= value; + } + } + + protected virtual void OnVisibleChanged (IssueGroupEventArgs eventArgs) + { + var handler = visibleChanged; + if (handler != null) { + handler (this, eventArgs); + } + } + + #endregion + + /// <summary> + /// Gets the description. + /// </summary> + /// <value>The description.</value> + public string Description { + get; + private set; + } + + /// <summary> + /// Gets a value indicating whether this instance issue count. + /// </summary> + /// <value><c>true</c> if this instance issue count; otherwise, <c>false</c>.</value> + public int IssueCount { + get; + private set; + } + + public void ClearStatistics () + { + lock (_lock) { + groups.Clear (); + children.Clear (); + allIssues.Clear (); + groupingProvider.Reset (); + IssueCount = 0; + processingEnabled = false; + } + } + + /// <summary> + /// Makes this instance start processing issues. + /// </summary> + public void EnableProcessing () + { + lock (_lock) { + if (!processingEnabled) { + processingEnabled = true; + + // Now process the existing issues + foreach (var issue in allIssues) { + IssueGroup group; + ProcessIssue (issue, out group); + + // TODO: Could this be run without holding the lock and is it worth it? + if (group != null) { + group.AddIssue (issue); + } + } + } + } + } + + #region IIssueSummarySink implementation + + public void AddIssue (IssueSummary issue) + { + IssueGroup group = null; + bool groupAdded = false; + bool issueAdded = false; + lock (_lock) { + if (!allIssues.Contains (issue)) { + IssueCount++; + allIssues.Add (issue); + ((IIssueTreeNode) issue).VisibleChanged += HandleVisibleChanged; + issueAdded = true; + } + if (processingEnabled) { + groupAdded = ProcessIssue (issue, out group); + } + } + if (issueAdded) { + OnTextChanged (new IssueGroupEventArgs (this)); + } + if (!processingEnabled) + return; + if (groupAdded) { + OnChildAdded (new IssueTreeNodeEventArgs (this, group)); + } else if (group == null) { + OnChildAdded (new IssueTreeNodeEventArgs (this, issue)); + } + if (group != null) { + group.AddIssue (issue); + } + + } + + void HandleVisibleChanged (object sender, IssueGroupEventArgs e) + { + lock (_lock) { + var visibleChildren = children.Any (child => child.Visible); + if ((e.Node.Visible && visibleChildren) || (!e.Node.Visible && !visibleChildren)) { + OnVisibleChanged (new IssueGroupEventArgs (this)); + } + OnTextChanged (new IssueGroupEventArgs (this)); + } + } + + #endregion + + bool ProcessIssue (IssueSummary issue, out IssueGroup group) + { + bool groupAdded = false; + group = null; + if (groupingProvider != null) { + group = groupingProvider.GetIssueGroup (this, issue); + } + if (group == null) { + children.Add (issue); + } else if (!groups.Contains (group)) { + groupAdded = true; + groups.Add (group); + children.Add (group); + } + return groupAdded; + } + + public override string ToString () + { + return string.Format ("[IssueGroup: Description={1}, IssueCount={2}, GroupingProvider={0}]", GroupingProvider, Description, IssueCount); + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueGroupEventArgs.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueGroupEventArgs.cs new file mode 100644 index 0000000000..42787d4560 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueGroupEventArgs.cs @@ -0,0 +1,40 @@ +// +// IssueGroupEventArgs.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public class IssueGroupEventArgs: EventArgs + { + public IssueGroupEventArgs (IIssueTreeNode node) + { + Node = node; + } + + public IIssueTreeNode Node { get; private set; } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueMatch.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueMatch.cs new file mode 100644 index 0000000000..fbdaafcd57 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueMatch.cs @@ -0,0 +1,38 @@ +// +// IIssueMatcher.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using MonoDevelop.CodeActions; + +namespace MonoDevelop.CodeIssues +{ + public class IssueMatch + { + public ActionSummary Summary { get; set; } + + public CodeAction Action { get; set; } + } + +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueSummary.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueSummary.cs new file mode 100644 index 0000000000..5e7c4d201c --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueSummary.cs @@ -0,0 +1,236 @@ +// +// IGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.Refactoring; +using MonoDevelop.Projects; +using System.Collections.Generic; +using System.IO; +using System; +using System.Linq; + +namespace MonoDevelop.CodeIssues +{ + public class IssueSummary: IIssueTreeNode + { + public static IssueSummary FromCodeIssue(ProjectFile file, BaseCodeIssueProvider provider, CodeIssue codeIssue) + { + var topLevelProvider = (provider as CodeIssueProvider) ?? provider.Parent; + if (topLevelProvider == null) + throw new ArgumentException ("must be a CodeIssueProvider or a BaseCodeIssueProvider with Parent != null", "provider"); + var issueSummary = new IssueSummary { + IssueDescription = codeIssue.Description, + Region = codeIssue.Region, + ProviderTitle = topLevelProvider.Title, + ProviderDescription = topLevelProvider.Description, + ProviderCategory = topLevelProvider.Category, + Severity = topLevelProvider.GetSeverity (), + IssueMarker = codeIssue.IssueMarker, + File = file, + Project = file.Project, + InspectorIdString = codeIssue.InspectorIdString + }; + issueSummary.Actions = codeIssue.Actions.Select (a => new ActionSummary { +// Batchable = a.SupportsBatchRunning, +// SiblingKey = a.SiblingKey, +// Title = a., +// Region = a.DocumentRegion, + IssueSummary = issueSummary + }).ToList (); + return issueSummary; + } + + #region IIssueTreeNode implementation + + string IIssueTreeNode.Text { + get { + string lineDescription; + if (Region.BeginLine == Region.EndLine) { + lineDescription = Region.BeginLine.ToString (); + } else { + lineDescription = string.Format ("{0}-{1}", Region.BeginLine, Region.EndLine); + } + var fileName = Path.GetFileName (File.Name); + return string.Format ("{0} [{1}:{2}]", IssueDescription, fileName, lineDescription); + } + } + + static readonly ICollection<IIssueTreeNode> emptyCollection = new IIssueTreeNode[0]; + + ICollection<IIssueTreeNode> IIssueTreeNode.Children { + get { + return emptyCollection; + } + } + + bool IIssueTreeNode.HasVisibleChildren { + get { + return false; + } + } + + bool visible = true; + bool IIssueTreeNode.Visible { + get { + return visible; + } + + set { + if (visible != value) { + visible = value; + OnVisibleChanged (new IssueGroupEventArgs (this)); + } + } + } + + ICollection<IIssueTreeNode> IIssueTreeNode.AllChildren { + get { + return emptyCollection; + } + } + + event EventHandler<IssueGroupEventArgs> visibleChanged; + event EventHandler<IssueGroupEventArgs> IIssueTreeNode.VisibleChanged { + add { + visibleChanged += value; + } + remove { + visibleChanged -= value; + } + } + + protected virtual void OnVisibleChanged (IssueGroupEventArgs eventArgs) + { + var handler = visibleChanged; + if (handler != null) { + handler (this, eventArgs); + } + } + + // no-op events, these never happen in this implementation + event EventHandler<IssueGroupEventArgs> IIssueTreeNode.ChildrenInvalidated { + add { + } + remove { + } + } + + event EventHandler<IssueTreeNodeEventArgs> IIssueTreeNode.ChildAdded { + add { + } + remove { + } + } + + event EventHandler<IssueGroupEventArgs> IIssueTreeNode.TextChanged { + add { + } + remove { + } + } + + #endregion + + /// <summary> + /// The description of the issue. + /// </summary> + public string IssueDescription { get; set; } + + /// <summary> + /// The region. + /// </summary> + public DomRegion Region { get; set; } + + /// <summary> + /// Gets or sets the category of the issue provider. + /// </summary> + public string ProviderCategory { get; set; } + + /// <summary> + /// Gets or sets the title of the issue provider. + /// </summary> + public string ProviderTitle { get; set; } + + /// <summary> + /// Gets or sets the description of the issue provider. + /// </summary> + public string ProviderDescription { get; set; } + + /// <summary> + /// Gets or sets the severity. + /// </summary> + public Severity Severity { get; set; } + + /// <summary> + /// Gets or sets a value indicating how this issue should be marked inside the text editor. + /// Note: There is only one code issue provider generated therfore providers need to be state less. + /// </summary> + public IssueMarker IssueMarker { get; set; } + + /// <summary> + /// Gets or sets the file that this issue was found in. + /// </summary> + /// <value>The file.</value> + public ProjectFile File { get; set; } + + /// <summary> + /// Gets or sets the project this issue was found in. + /// </summary> + /// <value>The project.</value> + public Project Project { get; set; } + + /// <summary> + /// Gets or sets the type of the inspector that was the source of this issue. + /// </summary> + /// <value>The type of the inspector.</value> + public string InspectorIdString { get; set; } + + IList<ActionSummary> actions; + + /// <summary> + /// Gets or sets the actions available to fix this issue. + /// </summary> + /// <value>The actions.</value> + public IList<ActionSummary> Actions { + get { + if (actions == null) { + Actions = new List<ActionSummary> (); + } + return actions; + } + set { + if (value == null) + throw new ArgumentNullException ("value"); + actions = value; + } + } + + public override string ToString () + { + return string.Format ("[IssueSummary: ProviderTitle={2}, Region={0}, ProviderCategory={1}]", Region, ProviderCategory, ProviderTitle); + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueTreeNodeEventArgs.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueTreeNodeEventArgs.cs new file mode 100644 index 0000000000..37d663f252 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/IssueTreeNodeEventArgs.cs @@ -0,0 +1,43 @@ +// +// IssueSummaryEventArgs.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public class IssueTreeNodeEventArgs: EventArgs + { + public IssueTreeNodeEventArgs (IIssueTreeNode parent, IIssueTreeNode child) + { + Parent = parent; + Child = child; + } + + public IIssueTreeNode Parent { get; private set; } + + public IIssueTreeNode Child { get; private set; } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/NullGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/NullGroupingProvider.cs new file mode 100644 index 0000000000..677d9989c6 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/NullGroupingProvider.cs @@ -0,0 +1,82 @@ +// +// NullGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public class NullGroupingProvider: IGroupingProvider + { + static readonly Lazy<NullGroupingProvider> instance = new Lazy<NullGroupingProvider>(); + public static IGroupingProvider Instance + { + get { + return instance.Value; + } + } + + #region IGroupingProvider implementation + + public IssueGroup GetIssueGroup (IssueGroup parent, IssueSummary issue) + { + return null; + } + + public void Reset () + { + // no-op + } + + public IGroupingProvider Next { + get { + throw new InvalidOperationException (); + } + set { + throw new InvalidOperationException (); + } + } + + event EventHandler<GroupingProviderEventArgs> nextChanged; + + event EventHandler<GroupingProviderEventArgs> IGroupingProvider.NextChanged + { + add { + nextChanged += value; + } + remove { + nextChanged -= value; + } + } + + public bool SupportsNext { + get { + return false; + } + } + + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ProjectGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ProjectGroupingProvider.cs new file mode 100644 index 0000000000..7ca19ba063 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ProjectGroupingProvider.cs @@ -0,0 +1,45 @@ +//
+// ProjectGroupingProvider.cs
+//
+// Author:
+// Marius Ungureanu <marius.ungureanu@xamarin.com>
+//
+// Copyright (c) 2013 Marius Ungureanu
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using MonoDevelop.Projects;
+
+namespace MonoDevelop.CodeIssues
+{
+ [GroupingDescription("Project")]
+ public class ProjectGroupingProvider : AbstractGroupingProvider<Project>
+ {
+ #region implemented abstract members of AbstractGroupingProvider
+ protected override Project GetGroupingKey (IssueSummary issue)
+ {
+ return issue.Project;
+ }
+ protected override string GetGroupName (IssueSummary issue)
+ {
+ return issue.Project.Name;
+ }
+ #endregion
+ }
+}
+
diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ProviderGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ProviderGroupingProvider.cs new file mode 100644 index 0000000000..7cdf46e461 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/ProviderGroupingProvider.cs @@ -0,0 +1,45 @@ +// +// ProviderGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace MonoDevelop.CodeIssues +{ + [GroupingDescription("CodeIssue")] + public class ProviderGroupingProvider: AbstractGroupingProvider<string> + { + #region implemented abstract members of AbstractGroupingProvider + protected override string GetGroupingKey (IssueSummary issue) + { + return issue.ProviderTitle; + } + + protected override string GetGroupName (IssueSummary issue) + { + return issue.ProviderTitle; + } + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/AnalysisJobQueue.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/AnalysisJobQueue.cs new file mode 100644 index 0000000000..cdb3182fbb --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/AnalysisJobQueue.cs @@ -0,0 +1,121 @@ +// +// AnalysisJobQueue.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Collections.Generic; +using System.Linq; + +namespace MonoDevelop.CodeIssues +{ + public class AnalysisJobQueue + { + readonly object _lock = new object(); + + /// <summary> + /// The list of items in the queue. + /// </summary> + readonly List<JobSlice> slices = new List<JobSlice>(); + + /// <summary> + /// Indicates whether queueItems is sorted. + /// </summary> + bool sorted; + + /// <summary> + /// Adds the specified job to the queue. + /// </summary> + /// <param name="job">The job.</param> + public void Add (IAnalysisJob job) + { + lock (_lock) { + var jobStatus = new JobStatus (job); + foreach (var file in job.GetFiles()) { + JobSlice slice = slices.FirstOrDefault (j => j.File == file); + if (slice == null) { + slice = new JobSlice (file); + slices.Add (slice); + } + jobStatus.AddSlice (slice); + slice.AddJob (job, jobStatus); + } + InvalidateSort (); + } + } + + /// <summary> + /// Remove the specified job from the queue. + /// </summary> + /// <param name="job">The job to remove.</param> + public void Remove (IAnalysisJob job) + { + lock (_lock) { + foreach (var file in job.GetFiles()) { + JobSlice queueItem = slices.FirstOrDefault (j => j.File == file); + if (queueItem == null) + // The file might have been processed already, carry on + continue; + queueItem.RemoveJob (job); + if (!queueItem.GetJobs ().Any ()) + slices.Remove (queueItem); + } + InvalidateSort (); + } + } + + /// <summary> + /// Dequeues a number of elements less than or equal to <paramref name="maxNumber"/>. + /// </summary> + /// <param name="maxNumber">The index.</param> + public IEnumerable<JobSlice> Dequeue (int maxNumber) + { + lock (_lock) { + EnsureSorted (); + var taken = slices.Take (maxNumber).ToList (); + foreach (var item in taken) + slices.Remove (item); + return taken; + } + } + + /// <summary> + /// Notifies the rest of the class that <see cref="slices"/> is no longer sorted. + /// </summary> + void InvalidateSort () + { + sorted = false; + } + + /// <summary> + /// Ensures that <see cref="slices"/> is sorted. + /// </summary> + void EnsureSorted () + { + if (!sorted) { + slices.Sort (); + sorted = true; + } + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/CodeIssueEventArgs.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/CodeIssueEventArgs.cs new file mode 100644 index 0000000000..53e3a29f8b --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/CodeIssueEventArgs.cs @@ -0,0 +1,70 @@ +// +// CodeIssueEventArgs.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using MonoDevelop.CodeIssues; +using System.Collections.Generic; +using System.Linq; +using MonoDevelop.Projects; + +namespace MonoDevelop.CodeIssues +{ + /// <summary> + /// Code issue event arguments. + /// </summary> + public class CodeIssueEventArgs : EventArgs + { + + /// <summary> + /// Initializes a new instance of the <see cref="CodeIssueEventArgs"/> class. + /// </summary> + /// <param name="codeIssues">The code issues.</param> + public CodeIssueEventArgs (ProjectFile file, BaseCodeIssueProvider provider, IEnumerable<CodeIssue> codeIssues) + { + File = file; + Provider = provider; + CodeIssues = codeIssues as IList<CodeIssue> ?? codeIssues.ToList (); + } + + /// <summary> + /// Gets the analyzed file. + /// </summary> + /// <value>The analyzed file.</value> + public ProjectFile File { get; private set; } + + /// <summary> + /// Gets the provider. + /// </summary> + /// <value>The code issue provider that provided the issues.</value> + public BaseCodeIssueProvider Provider { get; private set; } + + /// <summary> + /// Gets the code issues. + /// </summary> + /// <value>The new code issues.</value> + public IList<CodeIssue> CodeIssues { get; private set; } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/IAnalysisJob.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/IAnalysisJob.cs new file mode 100644 index 0000000000..0ab2553b4f --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/IAnalysisJob.cs @@ -0,0 +1,93 @@ +// +// IAnalysisJob.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Collections.Generic; +using MonoDevelop.Projects; +using System; + +namespace MonoDevelop.CodeIssues +{ + /// <summary> + /// Represents an analysis job. Implementations must be thread safe. + /// </summary> + public interface IAnalysisJob + { + /// <summary> + /// Gets the file names affected by this job. + /// </summary> + /// <remarks> + /// The return value of this method may not change after the initial invocation. + /// </remarks> + /// <returns>The file names.</returns> + IEnumerable<ProjectFile> GetFiles (); + + /// <summary> + /// Gets the issue providers for the file specified in <paramref name="file"/>. + /// </summary> + /// <returns>The issue providers to run on the specified file.</returns> + /// <param name="file">The file.</param> + IEnumerable<BaseCodeIssueProvider> GetIssueProviders (ProjectFile file); + + /// <summary> + /// Adds the results to this job. + /// </summary> + /// <param name="file">The file that the results apply to.</param> + /// <param name="provider">The provider that provided the issues.</param> + /// <param name="issues">The issues detected in the specified file.</param> + void AddResult (ProjectFile file, BaseCodeIssueProvider provider, IEnumerable<CodeIssue> issues); + + /// <summary> + /// Notifies the job that there was an error running the specified provider on the specified file. + /// </summary> + /// <param name="file">The file.</param> + /// <param name="provider">The provider.</param> + void AddError (ProjectFile file, BaseCodeIssueProvider provider); + + /// <summary> + /// Occurs when new code issues are added. + /// </summary> + event EventHandler<CodeIssueEventArgs> CodeIssueAdded; + + /// <summary> + /// Called when this job is cancelled to notify the instance about that change. + /// </summary> + /// <remarks> + /// The caller does NOT have to ensure that this is the last method called. + /// Specifically, <see cref="AddResult"/> can be called after this method has been invoked. + /// </remarks> + void NotifyCancelled (); + + /// <summary> + /// Notifies the job that all files have been processed. + /// </summary> + void SetCompleted (); + + /// <summary> + /// Occurs when the job is completed. + /// </summary> + event EventHandler<EventArgs> Completed; + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/IJobContext.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/IJobContext.cs new file mode 100644 index 0000000000..4027081b81 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/IJobContext.cs @@ -0,0 +1,35 @@ +// +// IJobContext.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace MonoDevelop.CodeIssues +{ + public interface IJobContext + { + IAnalysisJob Job { get; } + void CancelJob (); + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobContext.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobContext.cs new file mode 100644 index 0000000000..e15c83c88e --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobContext.cs @@ -0,0 +1,66 @@ +// +// JobContext.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; + +namespace MonoDevelop.CodeIssues +{ + public class JobContext : IJobContext + { + readonly IAnalysisJob job; + + readonly AnalysisJobQueue queue; + + readonly CodeAnalysisBatchRunner runner; + + public JobContext(IAnalysisJob job, AnalysisJobQueue queue, CodeAnalysisBatchRunner runner) + { + if (job == null) + throw new ArgumentNullException ("job"); + if (queue == null) + throw new ArgumentNullException ("queue"); + if (runner == null) + throw new ArgumentNullException ("runner"); + this.job = job; + this.queue = queue; + this.runner = runner; + } + + #region IJobContext implementation + public void CancelJob () + { + job.NotifyCancelled (); + queue.Remove (job); + } + + public IAnalysisJob Job { + get { + return job; + } + } + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobSlice.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobSlice.cs new file mode 100644 index 0000000000..7802e21ada --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobSlice.cs @@ -0,0 +1,162 @@ +// +// QueueItem.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using MonoDevelop.Projects; +using System.Threading; +using System.Linq; + +namespace MonoDevelop.CodeIssues +{ + /// <summary> + /// Represents a unit of analysis at the time of analysis. Essentially this + /// maps a file to the jobs that wants to run analysis on it so the runner + /// can parse the file once and then make progress on all the jobs. + /// This class is not thread safe. + /// </summary> + public class JobSlice : IComparable<JobSlice>, IDisposable + { + bool disposed; + readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); + + /// <summary> + /// The jobs to run on the file specified in <see cref="FileName"/>. + /// </summary> + readonly IList<IAnalysisJob> jobs = new List<IAnalysisJob>(); + + /// <summary> + /// The status to report to when this slice is complete. + /// </summary> + readonly IList<JobStatus> statuses = new List<JobStatus>(); + + /// <summary> + /// The name of a file to be analyzed. + /// </summary> + /// <value>The name of the file.</value> + public ProjectFile File { get; private set; } + + /// <summary> + /// Gets the cancellation token for this work unit. + /// </summary> + /// <value>A cancellation token.</value> + public CancellationToken CancellationToken { + get { + return tokenSource.Token; + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="MonoDevelop.CodeIssues.JobSlice"/> class. + /// </summary> + /// <param name="file">The file.</param> + public JobSlice (ProjectFile file) + { + File = file; + } + + ~JobSlice () + { + Dispose (false); + } + + /// <summary> + /// Adds a job to be run on this file. + /// </summary> + /// <param name="job">The job.</param> + /// <param name = "status">The status of the job.</param> + public void AddJob (IAnalysisJob job, JobStatus status) + { + if (disposed) + throw new ObjectDisposedException (GetType ().FullName); + + statuses.Add (status); + jobs.Add (job); + } + + /// <summary> + /// Gets the current jobs. + /// </summary> + /// <returns>The jobs.</returns> + public IEnumerable<IAnalysisJob> GetJobs () + { + if (disposed) + throw new ObjectDisposedException (GetType ().FullName); + + return new List<IAnalysisJob> (jobs); + } + + /// <summary> + /// Removes the specified job. + /// If the job is the last job in this instance it requests cancellation of it's CancellationToken. + /// </summary> + /// <param name="job">The job to remove.</param> + public void RemoveJob(IAnalysisJob job) + { + if (disposed) + throw new ObjectDisposedException (GetType ().FullName); + + jobs.Remove (job); + if (!jobs.Any ()) + tokenSource.Cancel (); + } + + void MarkAsComplete () + { + foreach (var status in statuses) { + status.MarkAsComplete (this); + } + } + + #region IComparable implementation + + public int CompareTo (JobSlice other) + { + return jobs.Count.CompareTo (other.jobs.Count); + } + + #endregion + + #region IDisposable implementation + + public void Dispose () + { + Dispose (true); + } + + protected virtual void Dispose (bool disposing) + { + if (disposed) + return; + + MarkAsComplete (); + + disposed = true; + } + + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobStatus.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobStatus.cs new file mode 100644 index 0000000000..02ca3eb1fd --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/JobStatus.cs @@ -0,0 +1,86 @@ +// +// JobStatus.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; + +namespace MonoDevelop.CodeIssues +{ + /// <summary> + /// Keeps track of the status of a possibly partially executed job. + /// </summary> + public class JobStatus + { + readonly object _lock = new object(); + + /// <summary> + /// The job. + /// </summary> + readonly IAnalysisJob job; + + /// <summary> + /// The slices that the job has been split into. + /// </summary> + readonly ISet<JobSlice> slices = new HashSet<JobSlice> (); + + /// <summary> + /// Initializes a new instance of the <see cref="MonoDevelop.CodeIssues.JobStatus"/> class. + /// </summary> + /// <param name="job">The job.</param> + public JobStatus (IAnalysisJob job) + { + if (job == null) + throw new ArgumentNullException ("job"); + + this.job = job; + } + + /// <summary> + /// Adds another slice. This method should not be called after <see cref="MarkAsComplete"/> has been called. + /// </summary> + /// <param name="slice">Slice.</param> + public void AddSlice (JobSlice slice) + { + lock (_lock) { + slices.Add (slice); + } + } + + /// <summary> + /// Marks a slice of the job as complete and marks the job as completed if all slices have been completed. + /// </summary> + /// <param name="slice">The completed slice.</param> + public void MarkAsComplete(JobSlice slice) + { + lock (_lock) { + slices.Remove (slice); + if (slices.Count == 0) { + job.SetCompleted (); + } + } + } + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/ProgressMonitorWrapperJob.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/ProgressMonitorWrapperJob.cs new file mode 100644 index 0000000000..b1eea307cf --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/ProgressMonitorWrapperJob.cs @@ -0,0 +1,126 @@ +// +// ProgressReportingWrapperJob.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Linq; +using MonoDevelop.Ide; +using MonoDevelop.Core; +using MonoDevelop.Projects; +using System.Collections.Generic; +using System; + +namespace MonoDevelop.CodeIssues +{ + public class ProgressMonitorWrapperJob : IAnalysisJob + { + readonly IAnalysisJob wrappedJob; + + ProgressMonitor monitor;
+
+ int reportingThinningFactor = 100;
+
+ int completedWork;
+
+ public ProgressMonitorWrapperJob (IAnalysisJob wrappedJob, string message) + { + this.wrappedJob = wrappedJob; + monitor = IdeApp.Workbench.ProgressMonitors.GetStatusProgressMonitor (message, null, false); + var work = wrappedJob.GetFiles ().Sum (f => wrappedJob.GetIssueProviders (f).Count ()); + + monitor.BeginTask (message, work); + } + + #region IAnalysisJob implementation + + public event EventHandler<CodeIssueEventArgs> CodeIssueAdded { + add { + wrappedJob.CodeIssueAdded += value; + } + remove { + wrappedJob.CodeIssueAdded -= value; + } + } + + public IEnumerable<ProjectFile> GetFiles () + { + return wrappedJob.GetFiles (); + } + + public IEnumerable<BaseCodeIssueProvider> GetIssueProviders (ProjectFile file) + { + return wrappedJob.GetIssueProviders (file); + } + + public void AddResult (ProjectFile file, BaseCodeIssueProvider provider, IEnumerable<CodeIssue> issues) + { + Step (); + wrappedJob.AddResult (file, provider, issues); + } + + public void AddError (ProjectFile file, BaseCodeIssueProvider provider) + { + Step (); + wrappedJob.AddError (file, provider); + } + + public event EventHandler<EventArgs> Completed { + add { + wrappedJob.Completed += value; + } + remove { + wrappedJob.Completed -= value; + } + } + + public void SetCompleted () + { + StopReporting (); + wrappedJob.SetCompleted (); + } + + void Step ()
+ {
+ completedWork++; + if (monitor != null && completedWork % reportingThinningFactor == 0) { + monitor.Step (reportingThinningFactor); + } + } + + void StopReporting () + { + if (monitor != null) { + monitor.Dispose (); + monitor = null; + } + } + + public void NotifyCancelled () + { + StopReporting (); + } + + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/SimpleAnalysisJob.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/SimpleAnalysisJob.cs new file mode 100644 index 0000000000..15878be01b --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/Runner/SimpleAnalysisJob.cs @@ -0,0 +1,142 @@ +// +// AbstractAnalysisJob.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using MonoDevelop.Projects; +using MonoDevelop.Refactoring; +using MonoDevelop.Ide; +using System.Linq; +using ICSharpCode.NRefactory.Refactoring; + +namespace MonoDevelop.CodeIssues +{ + /// <summary> + /// A simple analysis job. + /// </summary> + public class SimpleAnalysisJob : IAnalysisJob
+ {
+ object _lock = new object (); + + readonly IList<ProjectFile> files; + + /// <summary> + /// Initializes a new instance of the <see cref="MonoDevelop.CodeIssues.SimpleAnalysisJob"/> class. + /// </summary> + /// <param name="files">The files to analyze.</param> + public SimpleAnalysisJob (IList<ProjectFile> files) + { + if (files == null) + throw new ArgumentNullException ("files"); + this.files = files; + } + + public bool IsCompleted { + get; + private set; + } + + bool IsCancelled { + get; + set; + } + + #region IAnalysisJob implementation + + public event EventHandler<CodeIssueEventArgs> CodeIssueAdded; + + protected virtual void OnCodeIssueAdded (CodeIssueEventArgs args) + { + var handler = CodeIssueAdded; + if (handler != null) + handler(this, args); + } + + public IEnumerable<ProjectFile> GetFiles () + { + return files; + } + + public IEnumerable<BaseCodeIssueProvider> GetIssueProviders (ProjectFile file) + { + return RefactoringService.GetInspectors (DesktopService.GetMimeTypeForUri (file.Name)) + .Where (provider => { + var severity = provider.GetSeverity (); + if (severity == Severity.None || !provider.GetIsEnabled ()) + return false; + return true; + }); + } + + public void AddResult (ProjectFile file, BaseCodeIssueProvider provider, IEnumerable<CodeIssue> issues) + { + OnCodeIssueAdded (new CodeIssueEventArgs(file, provider, issues)); + } + + public void AddError (ProjectFile file, BaseCodeIssueProvider provider) + { + } + + public void NotifyCancelled () + {
+ lock (_lock) {
+ IsCancelled = true;
+ }
+ } + + event EventHandler<EventArgs> completed; + public event EventHandler<EventArgs> Completed { + add { + completed += value; + if (IsCompleted && !IsCancelled) + OnCompleted (new EventArgs()); + } + remove { + completed += value; + } + } + + protected virtual void OnCompleted (EventArgs e) + { + var handler = completed; + if (handler != null) + handler (this, e); + } + + public void SetCompleted ()
+ {
+ bool runEventHandler; + lock (_lock) {
+ IsCompleted = true;
+ runEventHandler = !IsCancelled && !IsCompleted;
+ } + if (runEventHandler) + OnCompleted (new EventArgs()); + } + + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/SeverityGroupingProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/SeverityGroupingProvider.cs new file mode 100644 index 0000000000..dc129bb35e --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/SeverityGroupingProvider.cs @@ -0,0 +1,45 @@ +// +// SeverityGroupingProvider.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using ICSharpCode.NRefactory.Refactoring; + +namespace MonoDevelop.CodeIssues +{ + [GroupingDescription("Severity")] + public class SeverityGroupingProvider : AbstractGroupingProvider<Severity> + { + #region implemented abstract members of AbstractGroupingProvider + protected override Severity GetGroupingKey (IssueSummary issue) + { + return issue.Severity; + } + protected override string GetGroupName (IssueSummary issue) + { + return issue.Severity.ToString (); + } + #endregion + } +} + diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/SolutionAnalysisJob.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/SolutionAnalysisJob.cs new file mode 100644 index 0000000000..785c037dcd --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/Pad/SolutionAnalysisJob.cs @@ -0,0 +1,72 @@ +// +// SolutionAnalysisJob.cs +// +// Author: +// Simon Lindgren <simon.n.lindgren@gmail.com> +// +// Copyright (c) 2013 Simon Lindgren +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using MonoDevelop.Projects; +using System.Linq; +using System.Collections.Generic; +using MonoDevelop.Ide; + +namespace MonoDevelop.CodeIssues +{ + public class SolutionAnalysisJob : SimpleAnalysisJob + { + public SolutionAnalysisJob (Solution solution) : base(GetApplicableFiles(solution)) + { + } + + static IList<ProjectFile> GetApplicableFiles (Solution solution) + { + var configurationSelector = IdeApp.Workspace.ActiveConfiguration; + var config = solution.GetConfiguration (configurationSelector); + return solution.GetAllProjects () + .Where (config.BuildEnabledForItem) + .SelectMany (p => p.Files) + .Where (f => f.BuildAction == BuildAction.Compile) + .Distinct (new FilePathComparer()) + .ToList (); + } + + class FilePathComparer : IEqualityComparer<ProjectFile> + { + #region IEqualityComparer implementation + + public bool Equals (ProjectFile x, ProjectFile y) + { + return x == y || + (x != null && + y != null && + x.Name == y.Name); + } + + public int GetHashCode (ProjectFile obj) + { + return obj == null ? -1 : obj.Name.GetHashCode (); + } + + #endregion + } + } +} + |