|
|
@ -24,6 +24,7 @@ using System.Runtime.InteropServices; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using ICSharpCode.NRefactory.Analysis; |
|
|
|
using CSharpBinding.Parser; |
|
|
|
using CSharpBinding.Parser; |
|
|
|
using ICSharpCode.AvalonEdit.Document; |
|
|
|
using ICSharpCode.AvalonEdit.Document; |
|
|
|
using ICSharpCode.AvalonEdit.Highlighting; |
|
|
|
using ICSharpCode.AvalonEdit.Highlighting; |
|
|
@ -51,27 +52,46 @@ namespace CSharpBinding |
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public class CSharpSymbolSearch : ISymbolSearch |
|
|
|
public class CSharpSymbolSearch : ISymbolSearch |
|
|
|
{ |
|
|
|
{ |
|
|
|
IProject project; |
|
|
|
readonly IProject project; |
|
|
|
ICompilation compilation; |
|
|
|
readonly ICompilation compilation; |
|
|
|
FindReferences fr = new FindReferences(); |
|
|
|
readonly FindReferences fr = new FindReferences(); |
|
|
|
IList<IFindReferenceSearchScope> searchScopes; |
|
|
|
readonly IList<IFindReferenceSearchScope> searchScopes; |
|
|
|
IList<string>[] interestingFileNames; |
|
|
|
readonly Dictionary<string, IList<IFindReferenceSearchScope>> searchScopesPerFile; |
|
|
|
int workAmount; |
|
|
|
readonly int workAmount; |
|
|
|
double workAmountInverse; |
|
|
|
readonly double workAmountInverse; |
|
|
|
|
|
|
|
|
|
|
|
public CSharpSymbolSearch(IProject project, ISymbol entity) |
|
|
|
public CSharpSymbolSearch(IProject project, ISymbol entity) |
|
|
|
{ |
|
|
|
{ |
|
|
|
this.project = project; |
|
|
|
this.project = project; |
|
|
|
searchScopes = fr.GetSearchScopes(entity); |
|
|
|
|
|
|
|
compilation = SD.ParserService.GetCompilation(project); |
|
|
|
compilation = SD.ParserService.GetCompilation(project); |
|
|
|
interestingFileNames = new IList<string>[searchScopes.Count]; |
|
|
|
var relatedSymbols = GetRelatedSymbols(entity); |
|
|
|
|
|
|
|
if ((relatedSymbols != null) && relatedSymbols.Any()) { |
|
|
|
|
|
|
|
searchScopes = relatedSymbols.SelectMany(e => fr.GetSearchScopes(e)).ToList(); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
searchScopes = fr.GetSearchScopes(entity); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
searchScopesPerFile = new Dictionary<string, IList<IFindReferenceSearchScope>>(); |
|
|
|
for (int i = 0; i < searchScopes.Count; i++) { |
|
|
|
for (int i = 0; i < searchScopes.Count; i++) { |
|
|
|
interestingFileNames[i] = fr.GetInterestingFiles(searchScopes[i], compilation).Select(f => f.FileName).ToList(); |
|
|
|
var thisSearchScope = searchScopes[i]; |
|
|
|
workAmount += interestingFileNames[i].Count; |
|
|
|
var interestingFiles = fr.GetInterestingFiles(thisSearchScope, compilation).Select(f => f.FileName); |
|
|
|
|
|
|
|
foreach (var file in interestingFiles) { |
|
|
|
|
|
|
|
if (!searchScopesPerFile.ContainsKey(file)) |
|
|
|
|
|
|
|
searchScopesPerFile[file] = new List<IFindReferenceSearchScope>(); |
|
|
|
|
|
|
|
searchScopesPerFile[file].Add(thisSearchScope); |
|
|
|
|
|
|
|
workAmount++; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
workAmountInverse = 1.0 / workAmount; |
|
|
|
workAmountInverse = 1.0 / workAmount; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IEnumerable<ISymbol> GetRelatedSymbols(ISymbol entity) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
TypeGraph typeGraph = new TypeGraph(new [] { compilation.MainAssembly }); |
|
|
|
|
|
|
|
var symbolCollector = new SymbolCollector(); |
|
|
|
|
|
|
|
return symbolCollector.GetRelatedSymbols(typeGraph, entity); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public double WorkAmount { |
|
|
|
public double WorkAmount { |
|
|
|
get { return workAmount; } |
|
|
|
get { return workAmount; } |
|
|
|
} |
|
|
|
} |
|
|
@ -83,38 +103,36 @@ namespace CSharpBinding |
|
|
|
var cancellationToken = args.ProgressMonitor.CancellationToken; |
|
|
|
var cancellationToken = args.ProgressMonitor.CancellationToken; |
|
|
|
return Task.Run( |
|
|
|
return Task.Run( |
|
|
|
() => { |
|
|
|
() => { |
|
|
|
for (int i = 0; i < searchScopes.Count; i++) { |
|
|
|
object progressLock = new object(); |
|
|
|
IFindReferenceSearchScope searchScope = searchScopes[i]; |
|
|
|
Parallel.ForEach( |
|
|
|
object progressLock = new object(); |
|
|
|
searchScopesPerFile.Keys, |
|
|
|
Parallel.ForEach( |
|
|
|
new ParallelOptions { |
|
|
|
interestingFileNames[i], |
|
|
|
MaxDegreeOfParallelism = Environment.ProcessorCount, |
|
|
|
new ParallelOptions { |
|
|
|
CancellationToken = cancellationToken |
|
|
|
MaxDegreeOfParallelism = Environment.ProcessorCount, |
|
|
|
}, |
|
|
|
CancellationToken = cancellationToken |
|
|
|
delegate (string fileName) { |
|
|
|
}, |
|
|
|
try { |
|
|
|
delegate (string fileName) { |
|
|
|
FindReferencesInFile(args, searchScopesPerFile[fileName], FileName.Create(fileName), callback, cancellationToken); |
|
|
|
try { |
|
|
|
} catch (OperationCanceledException) { |
|
|
|
FindReferencesInFile(args, searchScope, FileName.Create(fileName), callback, cancellationToken); |
|
|
|
throw; |
|
|
|
} catch (OperationCanceledException) { |
|
|
|
} catch (Exception ex) { |
|
|
|
throw; |
|
|
|
throw new ApplicationException("Error searching in file '" + fileName + "'", ex); |
|
|
|
} catch (Exception ex) { |
|
|
|
} |
|
|
|
throw new ApplicationException("Error searching in file '" + fileName + "'", ex); |
|
|
|
lock (progressLock) |
|
|
|
} |
|
|
|
args.ProgressMonitor.Progress += workAmountInverse; |
|
|
|
lock (progressLock) |
|
|
|
}); |
|
|
|
args.ProgressMonitor.Progress += workAmountInverse; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, cancellationToken |
|
|
|
}, cancellationToken |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void FindReferencesInFile(SymbolSearchArgs args, IFindReferenceSearchScope searchScope, FileName fileName, Action<SearchedFile> callback, CancellationToken cancellationToken) |
|
|
|
void FindReferencesInFile(SymbolSearchArgs args, IList<IFindReferenceSearchScope> searchScopeList, FileName fileName, Action<SearchedFile> callback, CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
{ |
|
|
|
ITextSource textSource = args.ParseableFileContentFinder.Create(fileName); |
|
|
|
ITextSource textSource = args.ParseableFileContentFinder.Create(fileName); |
|
|
|
if (textSource == null) |
|
|
|
if (textSource == null) |
|
|
|
return; |
|
|
|
return; |
|
|
|
if (searchScope.SearchTerm != null) { |
|
|
|
if (searchScopeList != null) { |
|
|
|
if (textSource.IndexOf(searchScope.SearchTerm, 0, textSource.TextLength, StringComparison.Ordinal) < 0) |
|
|
|
if (!searchScopeList.DistinctBy(scope => scope.SearchTerm ?? String.Empty).Any( |
|
|
|
|
|
|
|
scope => (scope.SearchTerm == null) || (textSource.IndexOf(scope.SearchTerm, 0, textSource.TextLength, StringComparison.Ordinal) >= 0))) |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -134,7 +152,7 @@ namespace CSharpBinding |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fr.FindReferencesInFile( |
|
|
|
fr.FindReferencesInFile( |
|
|
|
searchScope, unresolvedFile, parseInfo.SyntaxTree, compilation, |
|
|
|
searchScopeList, unresolvedFile, parseInfo.SyntaxTree, compilation, |
|
|
|
delegate (AstNode node, ResolveResult result) { |
|
|
|
delegate (AstNode node, ResolveResult result) { |
|
|
|
if (document == null) { |
|
|
|
if (document == null) { |
|
|
|
document = new ReadOnlyDocument(textSource, fileName); |
|
|
|
document = new ReadOnlyDocument(textSource, fileName); |
|
|
@ -154,8 +172,18 @@ namespace CSharpBinding |
|
|
|
if (highlighter != null) { |
|
|
|
if (highlighter != null) { |
|
|
|
highlighter.Dispose(); |
|
|
|
highlighter.Dispose(); |
|
|
|
} |
|
|
|
} |
|
|
|
if (results.Count > 0) |
|
|
|
if (results.Count > 0) { |
|
|
|
callback(new SearchedFile(fileName, results)); |
|
|
|
// Remove overlapping results
|
|
|
|
|
|
|
|
List<SearchResultMatch> fixedResults = new List<SearchResultMatch>(); |
|
|
|
|
|
|
|
int lastEndOffset = 0; |
|
|
|
|
|
|
|
foreach (var result in results.OrderBy(m => m.StartOffset)) { |
|
|
|
|
|
|
|
if (result.StartOffset >= lastEndOffset) { |
|
|
|
|
|
|
|
fixedResults.Add(result); |
|
|
|
|
|
|
|
lastEndOffset = result.EndOffset; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
callback(new SearchedFile(fileName, fixedResults)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public Task RenameAsync(SymbolRenameArgs args, Action<PatchedFile> callback, Action<Error> errorCallback) |
|
|
|
public Task RenameAsync(SymbolRenameArgs args, Action<PatchedFile> callback, Action<Error> errorCallback) |
|
|
@ -166,32 +194,30 @@ namespace CSharpBinding |
|
|
|
return Task.Run( |
|
|
|
return Task.Run( |
|
|
|
() => { |
|
|
|
() => { |
|
|
|
bool isNameValid = Mono.CSharp.Tokenizer.IsValidIdentifier(args.NewName); |
|
|
|
bool isNameValid = Mono.CSharp.Tokenizer.IsValidIdentifier(args.NewName); |
|
|
|
for (int i = 0; i < searchScopes.Count; i++) { |
|
|
|
object progressLock = new object(); |
|
|
|
IFindReferenceSearchScope searchScope = searchScopes[i]; |
|
|
|
Parallel.ForEach( |
|
|
|
object progressLock = new object(); |
|
|
|
searchScopesPerFile.Keys, |
|
|
|
Parallel.ForEach( |
|
|
|
new ParallelOptions { |
|
|
|
interestingFileNames[i], |
|
|
|
MaxDegreeOfParallelism = Environment.ProcessorCount, |
|
|
|
new ParallelOptions { |
|
|
|
CancellationToken = cancellationToken |
|
|
|
MaxDegreeOfParallelism = Environment.ProcessorCount, |
|
|
|
}, |
|
|
|
CancellationToken = cancellationToken |
|
|
|
delegate (string fileName) { |
|
|
|
}, |
|
|
|
RenameReferencesInFile(args, searchScopesPerFile[fileName], FileName.Create(fileName), callback, errorCallback, isNameValid, cancellationToken); |
|
|
|
delegate (string fileName) { |
|
|
|
lock (progressLock) |
|
|
|
RenameReferencesInFile(args, searchScope, FileName.Create(fileName), callback, errorCallback, isNameValid, cancellationToken); |
|
|
|
args.ProgressMonitor.Progress += workAmountInverse; |
|
|
|
lock (progressLock) |
|
|
|
}); |
|
|
|
args.ProgressMonitor.Progress += workAmountInverse; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, cancellationToken |
|
|
|
}, cancellationToken |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void RenameReferencesInFile(SymbolRenameArgs args, IFindReferenceSearchScope searchScope, FileName fileName, Action<PatchedFile> callback, Action<Error> errorCallback, bool isNameValid, CancellationToken cancellationToken) |
|
|
|
void RenameReferencesInFile(SymbolRenameArgs args, IList<IFindReferenceSearchScope> searchScopeList, FileName fileName, Action<PatchedFile> callback, Action<Error> errorCallback, bool isNameValid, CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
{ |
|
|
|
ITextSource textSource = args.ParseableFileContentFinder.Create(fileName); |
|
|
|
ITextSource textSource = args.ParseableFileContentFinder.Create(fileName); |
|
|
|
if (textSource == null) |
|
|
|
if (textSource == null) |
|
|
|
return; |
|
|
|
return; |
|
|
|
if (searchScope.SearchTerm != null) { |
|
|
|
if (searchScopeList != null) { |
|
|
|
if (textSource.IndexOf(searchScope.SearchTerm, 0, textSource.TextLength, StringComparison.Ordinal) < 0) |
|
|
|
if (!searchScopeList.DistinctBy(scope => scope.SearchTerm ?? String.Empty).Any( |
|
|
|
|
|
|
|
scope => (scope.SearchTerm == null) || (textSource.IndexOf(scope.SearchTerm, 0, textSource.TextLength, StringComparison.Ordinal) >= 0))) |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -213,7 +239,7 @@ namespace CSharpBinding |
|
|
|
CSharpAstResolver resolver = new CSharpAstResolver(compilation, parseInfo.SyntaxTree, unresolvedFile); |
|
|
|
CSharpAstResolver resolver = new CSharpAstResolver(compilation, parseInfo.SyntaxTree, unresolvedFile); |
|
|
|
|
|
|
|
|
|
|
|
fr.RenameReferencesInFile( |
|
|
|
fr.RenameReferencesInFile( |
|
|
|
new[] { searchScope }, args.NewName, resolver, |
|
|
|
searchScopeList, args.NewName, resolver, |
|
|
|
delegate (RenameCallbackArguments callbackArgs) { |
|
|
|
delegate (RenameCallbackArguments callbackArgs) { |
|
|
|
var node = callbackArgs.NodeToReplace; |
|
|
|
var node = callbackArgs.NodeToReplace; |
|
|
|
string newCode = callbackArgs.NewNode.ToString(); |
|
|
|
string newCode = callbackArgs.NewNode.ToString(); |
|
|
@ -249,10 +275,16 @@ namespace CSharpBinding |
|
|
|
} |
|
|
|
} |
|
|
|
IDocument changedDocument = new TextDocument(document); |
|
|
|
IDocument changedDocument = new TextDocument(document); |
|
|
|
var oldVersion = changedDocument.Version; |
|
|
|
var oldVersion = changedDocument.Version; |
|
|
|
|
|
|
|
List<SearchResultMatch> fixedResults = new List<SearchResultMatch>(); |
|
|
|
|
|
|
|
int lastStartOffset = changedDocument.TextLength + 1; |
|
|
|
foreach (var result in results.OrderByDescending(m => m.StartOffset)) { |
|
|
|
foreach (var result in results.OrderByDescending(m => m.StartOffset)) { |
|
|
|
changedDocument.Replace(result.StartOffset, result.Length, result.NewCode); |
|
|
|
if (result.EndOffset <= lastStartOffset) { |
|
|
|
|
|
|
|
changedDocument.Replace(result.StartOffset, result.Length, result.NewCode); |
|
|
|
|
|
|
|
fixedResults.Add(result); |
|
|
|
|
|
|
|
lastStartOffset = result.StartOffset; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
callback(new PatchedFile(fileName, results, oldVersion, changedDocument.Version)); |
|
|
|
callback(new PatchedFile(fileName, fixedResults, oldVersion, changedDocument.Version)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|