// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team // // 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 System.Linq; using System.Threading; using System.Threading.Tasks; using ICSharpCode.Core; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Analysis; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Editor.Search; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Workbench; namespace ICSharpCode.SharpDevelop.Refactoring { /// /// Language-independent finds references implementation. /// This call will call into the individual language bindings to perform the job. /// public static class FindReferenceService { #region FindReferences static IEnumerable GetProjectsThatCouldReferenceEntity(ISymbol entity) { ISolution solution = ProjectService.OpenSolution; if (solution == null) yield break; foreach (IProject project in solution.Projects) { IProjectContent pc = project.ProjectContent; if (pc == null) continue; yield return project; } } static List PrepareSymbolSearch(ISymbol entity, CancellationToken cancellationToken, out double totalWorkAmount) { totalWorkAmount = 0; List symbolSearches = new List(); foreach (IProject project in GetProjectsThatCouldReferenceEntity(entity)) { cancellationToken.ThrowIfCancellationRequested(); ISymbolSearch symbolSearch = project.PrepareSymbolSearch(entity); if (symbolSearch != null) { symbolSearches.Add(symbolSearch); totalWorkAmount += symbolSearch.WorkAmount; } } if (totalWorkAmount < 1) totalWorkAmount = 1; return symbolSearches; } /// /// Finds all references to the specified entity. /// The results are reported using the callback. /// FindReferences may internally use parallelism, and may invoke the callback on multiple /// threads in parallel. /// public static async Task FindReferencesAsync(IEntity entity, IProgressMonitor progressMonitor, Action callback) { if (entity == null) throw new ArgumentNullException("entity"); if (progressMonitor == null) throw new ArgumentNullException("progressMonitor"); if (callback == null) throw new ArgumentNullException("callback"); SD.MainThread.VerifyAccess(); if (SD.ParserService.LoadSolutionProjectsThread.IsRunning) { progressMonitor.ShowingDialog = true; MessageService.ShowMessage("${res:SharpDevelop.Refactoring.LoadSolutionProjectsThreadRunning}"); progressMonitor.ShowingDialog = false; return; } double totalWorkAmount; List symbolSearches = PrepareSymbolSearch(entity, progressMonitor.CancellationToken, out totalWorkAmount); double workDone = 0; ParseableFileContentFinder parseableFileContentFinder = new ParseableFileContentFinder(); foreach (ISymbolSearch s in symbolSearches) { progressMonitor.CancellationToken.ThrowIfCancellationRequested(); using (var childProgressMonitor = progressMonitor.CreateSubTask(s.WorkAmount / totalWorkAmount)) { await s.FindReferencesAsync(new SymbolSearchArgs(childProgressMonitor, parseableFileContentFinder), callback); } workDone += s.WorkAmount; progressMonitor.Progress = workDone / totalWorkAmount; } } public static IObservable FindReferences(IEntity entity, IProgressMonitor progressMonitor) { return ReactiveExtensions.CreateObservable( (monitor, callback) => FindReferencesAsync(entity, monitor, callback), progressMonitor); } /// /// Finds references to a local variable. /// public static async Task FindLocalReferencesAsync(IVariable variable, IProgressMonitor progressMonitor) { if (variable == null) throw new ArgumentNullException("variable"); if (progressMonitor == null) throw new ArgumentNullException("progressMonitor"); var fileName = FileName.Create(variable.Region.FileName); List references = new List(); await SD.ParserService.FindLocalReferencesAsync( fileName, variable, r => { lock (references) references.Add(r); }, cancellationToken: progressMonitor.CancellationToken); return new SearchedFile(fileName, references); } public static IObservable FindLocalReferences(IVariable variable, IProgressMonitor progressMonitor) { return ReactiveExtensions.CreateObservable( (monitor, callback) => FindLocalReferencesAsync(variable, monitor).ContinueWith(t => callback(t.Result)), progressMonitor); } #endregion #region FindDerivedTypes /// /// Finds all types that are derived from the given base type. /// public static IList FindDerivedTypes(ITypeDefinition baseType, bool directDerivationOnly) { if (baseType == null) throw new ArgumentNullException("baseType"); baseType = baseType.GetDefinition(); // ensure we use the compound class List results = new List(); var solutionSnapshot = GetSolutionSnapshot(baseType.Compilation); foreach (IProject project in GetProjectsThatCouldReferenceEntity(baseType)) { var compilation = solutionSnapshot.GetCompilation(project); var importedBaseType = compilation.Import(baseType); if (importedBaseType == null) continue; foreach (ITypeDefinition typeDef in compilation.MainAssembly.GetAllTypeDefinitions()) { bool isDerived; if (directDerivationOnly) { isDerived = typeDef.DirectBaseTypes.Select(t => t.GetDefinition()).Contains(importedBaseType); } else { isDerived = typeDef.IsDerivedFrom(importedBaseType); } if (isDerived) results.Add(typeDef); } } return results; } static ISolutionSnapshotWithProjectMapping GetSolutionSnapshot(ICompilation compilation) { var snapshot = compilation.SolutionSnapshot as ISolutionSnapshotWithProjectMapping; return snapshot ?? SD.ParserService.GetCurrentSolutionSnapshot(); } /// /// Builds a graph of derived type definitions. /// public static TypeGraphNode BuildDerivedTypesGraph(ITypeDefinition baseType) { if (baseType == null) throw new ArgumentNullException("baseType"); var solutionSnapshot = GetSolutionSnapshot(baseType.Compilation); var assemblies = GetProjectsThatCouldReferenceEntity(baseType).Select(p => solutionSnapshot.GetCompilation(p).MainAssembly); var graph = new TypeGraph(assemblies); var node = graph.GetNode(baseType); if (node != null) { // only derived types were requested, so don't return the base types // (this helps the GC to collect the unused parts of the graph more quickly) node.BaseTypes.Clear(); return node; } else { return new TypeGraphNode(baseType); } } #endregion public static async Task RenameSymbolAsync(ISymbol symbol, string newName, IProgressMonitor progressMonitor, Action errorCallback) { if (symbol == null) throw new ArgumentNullException("symbol"); if (progressMonitor == null) throw new ArgumentNullException("progressMonitor"); SD.MainThread.VerifyAccess(); if (SD.ParserService.LoadSolutionProjectsThread.IsRunning) { progressMonitor.ShowingDialog = true; MessageService.ShowMessage("${res:SharpDevelop.Refactoring.LoadSolutionProjectsThreadRunning}"); progressMonitor.ShowingDialog = false; return; } double totalWorkAmount; List symbolSearches = PrepareSymbolSearch(symbol, progressMonitor.CancellationToken, out totalWorkAmount); double workDone = 0; ParseableFileContentFinder parseableFileContentFinder = new ParseableFileContentFinder(); var errors = new List(); var changes = new List(); foreach (ISymbolSearch s in symbolSearches) { progressMonitor.CancellationToken.ThrowIfCancellationRequested(); using (var childProgressMonitor = progressMonitor.CreateSubTask(s.WorkAmount / totalWorkAmount)) { var args = new SymbolRenameArgs(newName, childProgressMonitor, parseableFileContentFinder); args.ProvideHighlightedLine = false; await s.RenameAsync(args, file => changes.Add(file), error => errors.Add(error)); } workDone += s.WorkAmount; progressMonitor.Progress = workDone / totalWorkAmount; } if (errors.Count == 0) { foreach (var file in changes) { ApplyChanges(file); } } else { foreach (var error in errors) { errorCallback(error); } } } public static IObservable RenameSymbol(ISymbol symbol, string newName, IProgressMonitor progressMonitor) { return ReactiveExtensions.CreateObservable( (monitor, callback) => RenameSymbolAsync(symbol, newName, monitor, callback), progressMonitor); } static void ApplyChanges(PatchedFile file) { var openedFile = SD.FileService.GetOpenedFile(file.FileName); if (openedFile == null) { SD.FileService.OpenFile(file.FileName, false); openedFile = SD.FileService.GetOpenedFile(file.FileName); } if (openedFile != null) { var document = openedFile.GetModel(FileModels.TextDocument); file.Apply(document); } } } public class SymbolSearchArgs { public IProgressMonitor ProgressMonitor { get; private set; } public CancellationToken CancellationToken { get { return this.ProgressMonitor.CancellationToken; } } /// /// Specifies whether the symbol search should pass a HighlightedInlineBuilder /// for the matching line to the SearchResultMatch. /// The default value is true. /// public bool ProvideHighlightedLine { get; set; } public ParseableFileContentFinder ParseableFileContentFinder { get; private set; } public SymbolSearchArgs(IProgressMonitor progressMonitor, ParseableFileContentFinder parseableFileContentFinder) { if (progressMonitor == null) throw new ArgumentNullException("progressMonitor"); if (parseableFileContentFinder == null) throw new ArgumentNullException("parseableFileContentFinder"); this.ProgressMonitor = progressMonitor; this.ParseableFileContentFinder = parseableFileContentFinder; this.ProvideHighlightedLine = true; } } public class SymbolRenameArgs : SymbolSearchArgs { public SymbolRenameArgs(string newName, IProgressMonitor progressMonitor, ParseableFileContentFinder parseableFileContentFinder) : base(progressMonitor, parseableFileContentFinder) { if (newName == null) throw new ArgumentNullException("newName"); NewName = newName; } public string NewName { get; private set; } } public interface ISymbolSearch { double WorkAmount { get; } Task FindReferencesAsync(SymbolSearchArgs searchArguments, Action callback); Task RenameAsync(SymbolRenameArgs renameArguments, Action callback, Action errorCallback); } public sealed class CompositeSymbolSearch : ISymbolSearch { IEnumerable symbolSearches; CompositeSymbolSearch(params ISymbolSearch[] symbolSearches) { this.symbolSearches = symbolSearches; } public static ISymbolSearch Create(ISymbolSearch symbolSearch1, ISymbolSearch symbolSearch2) { if (symbolSearch1 == null) return symbolSearch2; if (symbolSearch2 == null) return symbolSearch1; return new CompositeSymbolSearch(symbolSearch1, symbolSearch2); } public double WorkAmount { get { return symbolSearches.Sum(s => s.WorkAmount); } } public Task FindReferencesAsync(SymbolSearchArgs searchArguments, Action callback) { return Task.WhenAll(symbolSearches.Select(s => s.FindReferencesAsync(searchArguments, callback))); } public Task RenameAsync(SymbolRenameArgs renameArguments, Action callback, Action errorCallback) { return Task.WhenAll(symbolSearches.Select(s => s.RenameAsync(renameArguments, callback, errorCallback))); } } }