#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.
 
 
 
 
 
 

534 lines
19 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.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.SharpDevelop;
using ICSharpCode.SharpDevelop.Bookmarks;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Editor.Search;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project;
namespace SearchAndReplace
{
/// <summary>
/// Provides all search actions: find next, find all, mark all, mark result
/// </summary>
public class SearchManager
{
static IEnumerable<FileName> GenerateFileList(SearchTarget target, string baseDirectory = null, string filter = "*.*", bool searchSubdirs = false)
{
List<FileName> files = new List<FileName>();
switch (target) {
case SearchTarget.CurrentDocument:
case SearchTarget.CurrentSelection:
ITextEditorProvider vc = WorkbenchSingleton.Workbench.ActiveViewContent as ITextEditorProvider;
if (vc != null)
files.Add(vc.TextEditor.FileName);
break;
case SearchTarget.AllOpenFiles:
foreach (ITextEditorProvider editor in WorkbenchSingleton.Workbench.ViewContentCollection.OfType<ITextEditorProvider>())
files.Add(editor.TextEditor.FileName);
break;
case SearchTarget.WholeProject:
if (ProjectService.CurrentProject == null)
break;
foreach (FileProjectItem item in ProjectService.CurrentProject.Items.OfType<FileProjectItem>())
files.Add(new FileName(item.FileName));
break;
case SearchTarget.WholeSolution:
if (ProjectService.OpenSolution == null)
break;
foreach (var item in ProjectService.OpenSolution.SolutionFolderContainers.Select(f => f.SolutionItems).SelectMany(si => si.Items))
files.Add(new FileName(Path.Combine(ProjectService.OpenSolution.Directory, item.Location)));
foreach (var item in ProjectService.OpenSolution.Projects.SelectMany(p => p.Items).OfType<FileProjectItem>())
files.Add(new FileName(item.FileName));
break;
case SearchTarget.Directory:
if (!Directory.Exists(baseDirectory))
break;
return FileUtility.LazySearchDirectory(baseDirectory, filter, searchSubdirs);
default:
throw new Exception("Invalid value for FileListType");
}
return files.Distinct();
}
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();
}
public static IObservable<SearchedFile> FindAll(IProgressMonitor progressMonitor, string pattern, bool ignoreCase, bool matchWholeWords, SearchMode mode,
SearchTarget target, string baseDirectory = null, string filter = "*.*", bool searchSubdirs = false, ISegment selection = null, bool useParallel = true)
{
currentSearchRegion = null;
var strategy = SearchStrategyFactory.Create(pattern, ignoreCase, matchWholeWords, mode);
ParseableFileContentFinder fileFinder = new ParseableFileContentFinder();
IEnumerable<FileName> fileList = GenerateFileList(target, baseDirectory, filter, searchSubdirs);
return new SearchRun(strategy, fileFinder, fileList, progressMonitor) { UseParallel = useParallel, Target = target, Selection = selection };
}
class SearchRun : IObservable<SearchedFile>, IDisposable
{
ISearchStrategy strategy;
ParseableFileContentFinder fileFinder;
IEnumerable<FileName> fileList;
IProgressMonitor monitor;
CancellationTokenSource cts;
public bool UseParallel { get; set; }
public SearchTarget Target { get; set; }
public ISegment Selection { get; set; }
public SearchRun(ISearchStrategy strategy, ParseableFileContentFinder 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)
{
if (UseParallel) {
LoggingService.Debug("Parallel FindAll starting");
var task = new System.Threading.Tasks.Task(
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();
});
task.Start();
} else {
var list = fileList.ToList();
monitor.CancellationToken.ThrowIfCancellationRequested();
cts.Token.ThrowIfCancellationRequested();
foreach (var file in list) {
//SearchFile(file, strategy, monitor.CancellationToken);
}
}
return this;
}
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) {
FileName file = files[i];
queue.Enqueue(System.Threading.Tasks.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)
{
ITextBuffer buffer = fileFinder.Create(fileName);
if (buffer == null)
return null;
ThrowIfCancellationRequested();
if (!MimeTypeDetection.FindMimeType(buffer).StartsWith("text/", StringComparison.Ordinal))
return null;
var source = DocumentUtilitites.GetTextSource(buffer);
TextDocument document = null;
DocumentHighlighter 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 TextDocument(source);
var highlighting = HighlightingManager.Instance.GetDefinitionByExtension(Path.GetExtension(fileName));
if (highlighting != null)
highlighter = new DocumentHighlighter(document, highlighting.MainRuleSet);
else
highlighter = null;
}
var start = document.GetLocation(result.Offset).ToLocation();
var end = document.GetLocation(result.Offset + result.Length).ToLocation();
var builder = SearchResultsPad.CreateInlineBuilder(start, end, document, highlighter);
results.Add(new SearchResultMatch(fileName, start, end, result.Offset, result.Length, builder));
}
if (results.Count > 0)
return new SearchedFile(fileName, results);
else
return null;
}
}
public static ITextEditor GetActiveTextEditor()
{
ITextEditorProvider provider = WorkbenchSingleton.Workbench.ActiveViewContent as ITextEditorProvider;
if (provider != null) {
return provider.TextEditor;
} else {
return null;
}
}
static SearchRegion currentSearchRegion;
public static SearchResultMatch FindNext(string pattern, bool ignoreCase, bool matchWholeWords, SearchMode mode,
SearchTarget target, string baseDirectory = null, string filter = "*.*", bool searchSubdirs = false)
{
if (string.IsNullOrEmpty(pattern))
return null;
var files = GenerateFileList(target, baseDirectory, filter, searchSubdirs).ToArray();
if (files.Length == 0)
return null;
if (currentSearchRegion == null || !currentSearchRegion.IsSameState(files, pattern, ignoreCase, matchWholeWords, mode,
target, baseDirectory, filter, searchSubdirs))
currentSearchRegion = SearchRegion.CreateSearchRegion(files, pattern, ignoreCase, matchWholeWords, mode,
target, baseDirectory, filter, searchSubdirs);
if (currentSearchRegion == null)
return null;
var result = currentSearchRegion.FindNext();
if (result == null)
currentSearchRegion = null;
return result;
}
class SearchRegion
{
FileName[] files;
AnchorSegment selection;
ISearchStrategy strategy;
public SearchResultMatch FindNext()
{
// Setup search inside current or first file.
ParseableFileContentFinder finder = new ParseableFileContentFinder();
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 (selection != null) {
searchOffset = Math.Max(selection.Offset, searchOffset);
length = 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];
ITextBuffer buffer = finder.Create(file);
if (buffer == null)
continue;
int length;
if (selection != null) {
searchOffset = selection.Offset;
length = selection.Length;
} else {
searchOffset = 0;
length = buffer.TextLength;
}
// try to find a result
result = Find(file, DocumentUtilitites.LoadReadOnlyDocumentFromBuffer(buffer), searchOffset, length);
i++;
}
return result;
}
SearchResultMatch Find(FileName file, IDocument document, int searchOffset, int length)
{
var result = strategy.FindNext(DocumentUtilitites.GetTextSource(document), searchOffset, length);
if (result != null) {
var start = document.OffsetToPosition(result.Offset);
var end = document.OffsetToPosition(result.EndOffset);
return new SearchResultMatch(file, start, end, result.Offset, result.Length, null);
}
return null;
}
int GetCurrentFileIndex()
{
var editor = GetActiveTextEditor();
if (editor == null)
return -1;
return files.FindIndex(file => editor.FileName.Equals(file));
}
public static SearchRegion CreateSearchRegion(FileName[] files, string pattern, bool ignoreCase, bool matchWholeWords, SearchMode mode, SearchTarget target, string baseDirectory, string filter, bool searchSubdirs)
{
ITextEditor editor = GetActiveTextEditor();
if (editor != null) {
var document = new TextDocument(DocumentUtilitites.GetTextSource(editor.Document));
AnchorSegment selection = null;
if (target == SearchTarget.CurrentSelection)
selection = new AnchorSegment(document, editor.SelectionStart, editor.SelectionLength);
return new SearchRegion(files, selection, pattern, ignoreCase, matchWholeWords, mode, target, baseDirectory, filter, searchSubdirs) { strategy = SearchStrategyFactory.Create(pattern, ignoreCase, matchWholeWords, mode) };
}
return null;
}
SearchRegion(FileName[] files, AnchorSegment selection, string pattern, bool ignoreCase, bool matchWholeWords, SearchMode mode, SearchTarget target, string baseDirectory, string filter, bool searchSubdirs)
{
if (files == null)
throw new ArgumentNullException("files");
this.files = files;
this.selection = selection;
this.pattern = pattern;
this.ignoreCase = ignoreCase;
this.matchWholeWords = matchWholeWords;
this.mode = mode;
this.target = target;
this.baseDirectory = baseDirectory;
this.filter = filter;
this.searchSubdirs = searchSubdirs;
}
string pattern;
bool ignoreCase;
bool matchWholeWords;
SearchMode mode;
SearchTarget target;
string baseDirectory;
string filter;
bool searchSubdirs;
public bool IsSameState(FileName[] files, string pattern, bool ignoreCase, bool matchWholeWords, SearchMode mode, SearchTarget target, string baseDirectory, string filter, bool searchSubdirs)
{
return this.files.SequenceEqual(files) &&
pattern == this.pattern &&
ignoreCase == this.ignoreCase &&
matchWholeWords == this.matchWholeWords &&
mode == this.mode &&
target == this.target &&
baseDirectory == this.baseDirectory &&
filter == this.filter &&
searchSubdirs == this.searchSubdirs;
}
}
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 void ReplaceAll(IObservable<SearchedFile> results, string replacement, CancellationToken ct)
{
int count = 0;
results.ObserveOnUIThread()
.Subscribe(
searchedFile => {
int difference = 0;
foreach (var match in searchedFile.Matches) {
ITextEditor textArea = OpenTextArea(match.FileName, false);
if (textArea != null) {
string newString = match.TransformReplacePattern(replacement);
textArea.Document.Replace(match.StartOffset + difference, match.Length, newString);
difference += newString.Length - match.Length;
count++;
}
}
},
error => MessageService.ShowException(error),
() => ShowReplaceDoneMessage(count)
);
}
static void MarkResult(SearchResultMatch result, bool switchToOpenedView = true)
{
ITextEditor textArea = OpenTextArea(result.FileName, switchToOpenedView);
if (textArea != null) {
if (switchToOpenedView)
textArea.Caret.Position = result.StartLocation;
foreach (var bookmark in BookmarkManager.GetBookmarks(result.FileName)) {
if (bookmark.CanToggle && bookmark.LineNumber == result.StartLocation.Line) {
// bookmark or breakpoint already exists at that line
return;
}
}
BookmarkManager.AddMark(new Bookmark(result.FileName, result.StartLocation));
}
}
static ITextEditor OpenTextArea(string fileName, bool switchToOpenedView = true)
{
ITextEditorProvider textEditorProvider;
if (fileName != null) {
textEditorProvider = FileService.OpenFile(fileName, switchToOpenedView) as ITextEditorProvider;
} else {
textEditorProvider = WorkbenchSingleton.Workbench.ActiveViewContent as ITextEditorProvider;
}
if (textEditorProvider != null) {
return textEditorProvider.TextEditor;
}
return null;
}
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}");
}
public static void SelectResult(SearchResultMatch result)
{
if (result == null) {
ShowNotFoundMessage();
return;
}
var editor = OpenTextArea(result.FileName, true);
var start = editor.Document.PositionToOffset(result.StartLocation.Line, result.StartLocation.Column);
var end = editor.Document.PositionToOffset(result.EndLocation.Line, result.EndLocation.Column);
if (editor != null) {
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 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));
}
}
}