#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

526 lines
17 KiB

// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.Core;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Editor.Bookmarks;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Editor.AvalonEdit;
using ICSharpCode.SharpDevelop.Editor.Bookmarks;
using ICSharpCode.SharpDevelop.Editor.Search;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project;
using ICSharpCode.SharpDevelop.Workbench;
namespace SearchAndReplace
{
/// <summary>
/// Provides all search actions: find next, parallel and sequential find all, mark all, mark result, interactive replace and replace all.
/// </summary>
public class SearchManager
{
#region FindAll
public static IObservable<SearchedFile> FindAllParallel(ISearchStrategy strategy, SearchLocation location, IProgressMonitor progressMonitor)
{
currentSearchRegion = null;
SearchableFileContentFinder fileFinder = new SearchableFileContentFinder();
return new SearchRun(strategy, fileFinder, location.GenerateFileList(), progressMonitor) { Target = location.Target, Selection = location.Selection };
}
public static IEnumerable<SearchedFile> FindAll(ISearchStrategy strategy, SearchLocation location, IProgressMonitor progressMonitor)
{
currentSearchRegion = null;
SearchableFileContentFinder fileFinder = new SearchableFileContentFinder();
return new SearchRun(strategy, fileFinder, location.GenerateFileList(), progressMonitor) { Target = location.Target, Selection = location.Selection }.GetResults();
}
class SearchableFileContentFinder
{
FileName[] viewContentFileNamesCollection = SD.MainThread.InvokeIfRequired(() => SD.FileService.OpenedFiles.Select(f => f.FileName).ToArray());
static ITextSource ReadFile(FileName fileName)
{
OpenedFile openedFile = SD.FileService.GetOpenedFile(fileName);
if (openedFile == null || openedFile.CurrentView == null)
return null;
var provider = openedFile.CurrentView.GetService<IFileDocumentProvider>();
if (provider == null)
return null;
IDocument doc = provider.GetDocumentForFile(openedFile);
if (doc == null)
return null;
return doc.CreateSnapshot();
}
public ITextSource Create(FileName fileName)
{
try {
foreach (FileName name in viewContentFileNamesCollection) {
if (FileUtility.IsEqualFileName(name, fileName)) {
ITextSource buffer = WorkbenchSingleton.SafeThreadFunction(ReadFile, fileName);
if (buffer != null)
return buffer;
}
}
using (Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) {
if (MimeTypeDetection.FindMimeType(stream).StartsWith("text/")) {
stream.Position = 0;
return new StringTextSource(ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(stream, SD.FileService.DefaultFileEncoding));
}
}
return null;
} catch (IOException) {
return null;
} catch (UnauthorizedAccessException) {
return null;
}
}
}
class SearchRun : IObservable<SearchedFile>, IDisposable
{
ISearchStrategy strategy;
SearchableFileContentFinder fileFinder;
IEnumerable<FileName> fileList;
IProgressMonitor monitor;
CancellationTokenSource cts;
public SearchTarget Target { get; set; }
public ISegment Selection { get; set; }
public SearchRun(ISearchStrategy strategy, SearchableFileContentFinder fileFinder, IEnumerable<FileName> fileList, IProgressMonitor monitor)
{
this.strategy = strategy;
this.fileFinder = fileFinder;
this.fileList = fileList;
this.monitor = monitor;
this.cts = new CancellationTokenSource();
}
public IDisposable Subscribe(IObserver<SearchedFile> observer)
{
LoggingService.Debug("Parallel FindAll starting");
var task = Task.Factory.StartNew(
delegate {
var list = fileList.ToList();
ThrowIfCancellationRequested();
SearchParallel(list, observer);
}, TaskCreationOptions.LongRunning);
task.ContinueWith(
t => {
LoggingService.Debug("Parallel FindAll finished " + (t.IsFaulted ? "with error" : "successfully"));
if (t.Exception != null)
observer.OnError(t.Exception);
else
observer.OnCompleted();
this.Dispose();
});
return this;
}
public IEnumerable<SearchedFile> GetResults()
{
var list = fileList.ToList();
foreach (var file in list) {
ThrowIfCancellationRequested();
var results = SearchFile(file);
if (results != null)
yield return results;
monitor.Progress += 1.0 / list.Count;
}
}
void SearchParallel(List<FileName> files, IObserver<SearchedFile> observer)
{
int taskCount = 2 * Environment.ProcessorCount;
Queue<Task<SearchedFile>> queue = new Queue<Task<SearchedFile>>(taskCount);
List<Exception> exceptions = new List<Exception>();
for (int i = 0; i < files.Count; i++) {
if (i >= taskCount) {
HandleResult(queue.Dequeue(), observer, exceptions, files);
}
if (exceptions.Count > 0) break;
FileName file = files[i];
queue.Enqueue(Task.Factory.StartNew(() => SearchFile(file)));
}
while (queue.Count > 0) {
HandleResult(queue.Dequeue(), observer, exceptions, files);
}
if (exceptions.Count != 0)
throw new AggregateException(exceptions);
}
void HandleResult(Task<SearchedFile> task, IObserver<SearchedFile> observer, List<Exception> exceptions, List<FileName> files)
{
SearchedFile result;
try {
result = task.Result;
} catch (AggregateException ex) {
exceptions.AddRange(ex.InnerExceptions);
return;
}
if (exceptions.Count == 0 && result != null)
observer.OnNext(result);
monitor.Progress += 1.0 / files.Count;
}
void ThrowIfCancellationRequested()
{
monitor.CancellationToken.ThrowIfCancellationRequested();
cts.Token.ThrowIfCancellationRequested();
}
public void Dispose()
{
cts.Cancel();
monitor.Dispose();
}
SearchedFile SearchFile(FileName fileName)
{
ITextSource source = fileFinder.Create(fileName);
if (source == null)
return null;
ThrowIfCancellationRequested();
ReadOnlyDocument document = null;
IHighlighter highlighter = null;
int offset = 0;
int length = source.TextLength;
if (Target == SearchTarget.CurrentSelection && Selection != null) {
offset = Selection.Offset;
length = Selection.Length;
}
List<SearchResultMatch> results = new List<SearchResultMatch>();
foreach (var result in strategy.FindAll(source, offset, length)) {
ThrowIfCancellationRequested();
if (document == null) {
document = new ReadOnlyDocument(source, fileName);
highlighter = SD.EditorControlService.CreateHighlighter(document);
}
var start = document.GetLocation(result.Offset);
var end = document.GetLocation(result.Offset + result.Length);
var builder = SearchResultsPad.CreateInlineBuilder(start, end, document, highlighter);
var defaultTextColor = highlighter != null ? highlighter.DefaultTextColor : null;
results.Add(new AvalonEditSearchResultMatch(fileName, start, end, result.Offset, result.Length, builder, defaultTextColor, result));
}
if (highlighter != null) {
highlighter.Dispose();
}
if (results.Count > 0)
return new SearchedFile(fileName, results);
else
return null;
}
}
#endregion
#region FindNext
static SearchRegion currentSearchRegion;
public static SearchResultMatch FindNext(ISearchStrategy strategy, SearchLocation location)
{
var files = location.GenerateFileList().ToArray();
if (files.Length == 0)
return null;
if (currentSearchRegion == null || !currentSearchRegion.IsSameState(files, strategy, location))
currentSearchRegion = SearchRegion.CreateSearchRegion(files, strategy, location);
if (currentSearchRegion == null)
return null;
var result = currentSearchRegion.FindNext();
if (result == null)
currentSearchRegion = null;
return result;
}
class SearchRegion
{
FileName[] files;
SearchLocation location;
ISearchStrategy strategy;
public SearchResultMatch FindNext()
{
// Setup search inside current or first file.
SearchableFileContentFinder finder = new SearchableFileContentFinder();
int index = GetCurrentFileIndex();
int i = 0;
int searchOffset = 0;
SearchResultMatch result = null;
if (index > -1) {
ITextEditor editor = GetActiveTextEditor();
if (editor.SelectionLength > 0)
searchOffset = editor.SelectionStart + editor.SelectionLength;
else
searchOffset = editor.Caret.Offset + 1;
var document = editor.Document;
int length;
// if (target == SearchTarget.CurrentSelection) selection will not be null
// hence use the selection as search region.
if (location.Selection != null) {
searchOffset = Math.Max(location.Selection.Offset, searchOffset);
length = location.Selection.EndOffset - searchOffset;
} else {
length = document.TextLength - searchOffset;
}
// try to find a result
if (length > 0 && (searchOffset + length) <= document.TextLength)
result = Find(editor.FileName, document, searchOffset, length);
}
// try the other files until we find something, or have processed all of them
while (result == null && i < files.Length) {
index = (index + 1) % files.Length;
FileName file = files[index];
ITextSource buffer = finder.Create(file);
if (buffer == null)
continue;
int length;
if (location.Selection != null) {
searchOffset = location.Selection.Offset;
length = location.Selection.Length;
} else {
searchOffset = 0;
length = buffer.TextLength;
}
// try to find a result
result = Find(file, new ReadOnlyDocument(buffer, file), searchOffset, length);
i++;
}
return result;
}
SearchResultMatch Find(FileName file, IDocument document, int searchOffset, int length)
{
var result = strategy.FindNext(document, searchOffset, length);
if (result != null) {
var start = document.GetLocation(result.Offset);
var end = document.GetLocation(result.EndOffset);
return new AvalonEditSearchResultMatch(file, start, end, result.Offset, result.Length, null, null, result);
}
return null;
}
int GetCurrentFileIndex()
{
var editor = GetActiveTextEditor();
if (editor == null)
return -1;
return Array.IndexOf(files, editor.FileName);
}
public static SearchRegion CreateSearchRegion(FileName[] files, ISearchStrategy strategy, SearchLocation location)
{
return new SearchRegion(files, strategy, location);
}
SearchRegion(FileName[] files, ISearchStrategy strategy, SearchLocation location)
{
if (files == null)
throw new ArgumentNullException("files");
this.files = files;
this.strategy = strategy;
this.location = location;
}
public bool IsSameState(FileName[] files, ISearchStrategy strategy, SearchLocation location)
{
return this.files.SequenceEqual(files) &&
this.strategy.Equals(strategy) &&
this.location.EqualsWithoutSelection(location);
}
}
#endregion
#region Mark & Replace(All)
public static void MarkAll(IObservable<SearchedFile> results)
{
int count = 0;
results.ObserveOnUIThread()
.Subscribe(
searchedFile => {
count++;
foreach (var m in searchedFile.Matches)
MarkResult(m, false);
},
error => MessageService.ShowException(error),
() => ShowMarkDoneMessage(count)
);
}
public static int ReplaceAll(IEnumerable<SearchedFile> results, string replacement, CancellationToken ct)
{
int count = 0;
foreach (var searchedFile in results) {
ct.ThrowIfCancellationRequested();
int difference = 0;
ITextEditor textArea = OpenTextArea(searchedFile.FileName, false);
if (textArea != null) {
using (textArea.Document.OpenUndoGroup()) {
foreach (var match in searchedFile.Matches) {
ct.ThrowIfCancellationRequested();
string newString = match.TransformReplacePattern(replacement);
textArea.Document.Replace(match.StartOffset + difference, match.Length, newString);
difference += newString.Length - match.Length;
count++;
}
}
}
}
return count;
}
public static void Replace(SearchResultMatch lastMatch, string replacement)
{
if (lastMatch == null)
return;
ITextEditor textArea = OpenTextArea(lastMatch.FileName);
if (textArea != null)
textArea.Document.Replace(lastMatch.StartOffset, lastMatch.Length, lastMatch.TransformReplacePattern(replacement));
}
static void MarkResult(SearchResultMatch result, bool switchToOpenedView = true)
{
ITextEditor textArea = OpenTextArea(result.FileName, switchToOpenedView);
if (textArea != null) {
if (switchToOpenedView)
textArea.Caret.Location = result.StartLocation;
foreach (var bookmark in SD.BookmarkManager.GetBookmarks(result.FileName)) {
if (bookmark.CanToggle && bookmark.LineNumber == result.StartLocation.Line) {
// bookmark or breakpoint already exists at that line
return;
}
}
SD.BookmarkManager.AddMark(new Bookmark { FileName = result.FileName, Location = result.StartLocation});
}
}
#endregion
#region TextEditor helpers
static ITextEditor OpenTextArea(string fileName, bool switchToOpenedView = true)
{
IViewContent viewContent;
if (fileName != null) {
viewContent = FileService.OpenFile(fileName, switchToOpenedView);
} else {
viewContent = WorkbenchSingleton.Workbench.ActiveViewContent;
}
if (viewContent != null) {
return viewContent.GetService<ITextEditor>();
}
return null;
}
public static ITextEditor GetActiveTextEditor()
{
return SD.GetActiveViewContentService<ITextEditor>();
}
public static ISegment GetActiveSelection(bool useAnchors)
{
ITextEditor editor = GetActiveTextEditor();
if (editor != null) {
if (useAnchors)
return new AnchorSegment((TextDocument)editor.Document.GetService(typeof(TextDocument)), editor.SelectionStart, editor.SelectionLength);
return new TextSegment { StartOffset = editor.SelectionStart, Length = editor.SelectionLength };
}
return null;
}
#endregion
#region Show Messages
public static void ShowReplaceDoneMessage(int count)
{
if (count == 0) {
ShowNotFoundMessage();
} else {
MessageService.ShowMessage(
StringParser.Parse("${res:ICSharpCode.TextEditor.Document.SearchReplaceManager.ReplaceAllDone}",
new StringTagPair("Count", count.ToString())),
"${res:Global.FinishedCaptionText}");
}
}
static void ShowMarkDoneMessage(int count)
{
if (count == 0) {
ShowNotFoundMessage();
} else {
MessageService.ShowMessage(
StringParser.Parse("${res:ICSharpCode.TextEditor.Document.SearchReplaceManager.MarkAllDone}",
new StringTagPair("Count", count.ToString())),
"${res:Global.FinishedCaptionText}");
}
}
static void ShowNotFoundMessage()
{
MessageService.ShowMessage("${res:Dialog.NewProject.SearchReplace.SearchStringNotFound}", "${res:Dialog.NewProject.SearchReplace.SearchStringNotFound.Title}");
}
#endregion
#region Result display helpers
public static void SelectResult(SearchResultMatch result)
{
if (result == null) {
ShowNotFoundMessage();
return;
}
var editor = OpenTextArea(result.FileName, true);
if (editor != null) {
var start = editor.Document.GetOffset(result.StartLocation.Line, result.StartLocation.Column);
var end = editor.Document.GetOffset(result.EndLocation.Line, result.EndLocation.Column);
editor.Caret.Offset = start;
editor.Select(start, end - start);
}
}
public static bool IsResultSelected(SearchResultMatch result)
{
if (result == null)
return false;
var editor = GetActiveTextEditor();
if (editor == null)
return false;
return result.FileName.Equals(editor.FileName)
&& result.StartOffset == editor.SelectionStart
&& result.Length == editor.SelectionLength;
}
public static void ShowSearchResults(string pattern, IObservable<SearchedFile> results)
{
string title = StringParser.Parse("${res:MainWindow.Windows.SearchResultPanel.OccurrencesOf}",
new StringTagPair("Pattern", pattern));
SearchResultsPad.Instance.ShowSearchResults(title, results);
SearchResultsPad.Instance.BringToFront();
}
#endregion
}
}